Implement essential subscriptions functions

* Look for projects with monitor enabled on plugin load
* Keep track of manifest repos that has subscription enabled
* Keep track of projects defined in manifests being monitored
* Update when project.config is changed
* Update when manifest changes
* Update on project changes (for projects in manifests being monitored)
* Repo where generated manifests are stored will not be monitored

Change-Id: I63c749487f52c58d20ce8004bdb892ae4b78afd6
diff --git a/README.md b/README.md
new file mode 120000
index 0000000..ac8bf0b
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+src/main/resources/Documentation/about.md
\ No newline at end of file
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java
new file mode 100644
index 0000000..0376815
--- /dev/null
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java
@@ -0,0 +1,447 @@
+// Copyright (C) 2015 Advanced Micro Devices, Inc.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.amd.gerrit.plugins.manifestsubscription;
+
+import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest;
+import com.google.common.collect.*;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.bind.JAXBException;
+import java.io.IOException;
+import java.util.*;
+
+public class ManifestSubscription implements
+    GitReferenceUpdatedListener, LifecycleListener {
+  private static final Logger log =
+      LoggerFactory.getLogger(ManifestSubscription.class);
+
+  private static final String KEY_BRANCH = "branch";
+  private static final String KEY_STORE = "store";
+
+  private static final String STORE_BRANCH_PREFIX = "refs/heads/m/";
+
+  private final String pluginName;
+
+  private final MetaDataUpdate.Server metaDataUpdateFactory;
+  private final GitRepositoryManager gitRepoManager;
+  private final ProjectCache projectCache;
+
+  /**
+   * source project lookup
+   * manifest source project name, plugin config
+   **/
+  private Map<String, PluginProjectConfig> enabledManifestSource = Maps.newHashMap();
+
+  /**
+   * manifest store lookup
+   * repo name, branch (branchPath), Manifest
+   */
+  private Table<String, String, Manifest> manifestStores = HashBasedTable.create();
+
+  /**
+   * lookup
+   * subscribed project name and branch, manifest dest store, <manifest dest branch Project>
+   **/
+  private Table<ProjectBranchKey, String, Map<String, Set<
+      com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>> subscribedRepos = HashBasedTable.create();
+
+  public Set<String> getEnabledManifestSource() {
+    return ImmutableSet.copyOf(enabledManifestSource.keySet());
+  }
+
+  public Set<ProjectBranchKey> getSubscribedProjects() {
+    return ImmutableSet.copyOf(subscribedRepos.rowKeySet());
+  }
+
+  @Override
+  public void start() {
+    ProjectConfig config;
+    for (Project.NameKey p : projectCache.all()) {
+      //TODO parallelize parsing/load up?
+      try {
+        config = ProjectConfig.read(metaDataUpdateFactory.create(p));
+        loadStoreFromProjectConfig(p.toString(), config);
+
+      } catch (IOException | ConfigInvalidException | JAXBException e) {
+        log.error(e.toString());
+        e.printStackTrace();
+      }
+    }
+  }
+
+  @Override
+  public void stop() {
+
+  }
+
+  @Inject
+  ManifestSubscription(MetaDataUpdate.Server metaDataUpdateFactory,
+                       GitRepositoryManager gitRepoManager,
+                       @PluginName String pluginName,
+                       ProjectCache projectCache) {
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.gitRepoManager = gitRepoManager;
+    this.pluginName = pluginName;
+    this.projectCache = projectCache;
+  }
+
+  @Override
+  public void onGitReferenceUpdated(Event event) {
+    String projectName = event.getProjectName();
+    String refName = event.getRefName();
+    String branchName = refName.startsWith("refs/heads/") ?
+        refName.substring(11) : "";
+    ProjectBranchKey pbKey = new ProjectBranchKey(projectName, branchName);
+
+    if ("refs/meta/config".equals(refName)) {
+      // possible change in enabled repos
+      processProjectConfigChange(event);
+    } else if (enabledManifestSource.containsKey(projectName) &&
+        enabledManifestSource.get(projectName)
+            .getBranches().contains(branchName)) {
+      processManifestChange(event, projectName, branchName);
+
+    } else if (subscribedRepos.containsRow(pbKey)) {
+      // Manifest store and branch
+      Map<String, Map<String, Set<
+          com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>>
+          destinations = subscribedRepos.row(pbKey);
+
+      for (String store : destinations.keySet()) {
+        for (String storeBranch : destinations.get(store).keySet()) {
+          Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> ps
+              = destinations.get(store).get(storeBranch);
+
+          Manifest manifest = manifestStores.get(store, storeBranch);
+          // these are project from the above manifest previously
+          // cached in the lookup table
+          for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project
+              updateProject : ps) {
+            updateProject.setRevision(event.getNewObjectId());
+          }
+
+          try {
+            updateManifest(store, STORE_BRANCH_PREFIX + storeBranch, manifest);
+          } catch (JAXBException | IOException e) {
+            e.printStackTrace();
+          }
+
+        }
+      }
+
+      //updates in subscribed repos
+    }
+
+
+  }
+
+  private void updateProjectRev(String projectName, String branch, String rev,
+                                List<com.amd.gerrit.plugins.
+                                    manifestsubscription.manifest.Project> projects) {
+    //TODO optimize to not have to iterate through manifest?
+    for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project project : projects) {
+      if (Objects.equals(projectName, project.getName()) &&
+          Objects.equals(branch, project.getUpstream())) {
+        project.setRevision(rev);
+      }
+
+      if (project.getProject().size() > 0) {
+        updateProjectRev(projectName, branch, rev, project.getProject());
+      }
+    }
+
+  }
+
+  private void processManifestChange(Event event,
+                                     String projectName, String branchName) {
+    try {
+      VersionedManifests versionedManifests = parseManifests(event);
+      processManifestChange(versionedManifests, projectName, branchName);
+    } catch (JAXBException | IOException | ConfigInvalidException e) {
+      e.printStackTrace();
+    }
+
+  }
+
+  private void processManifestChange(VersionedManifests versionedManifests,
+                                     String projectName, String branchName) {
+    //possible manifest update in subscribing repos
+    //TODO Fix, right now update all manifest every time
+    //TODO even when only one of the manifest has changed
+
+    try {
+      if (versionedManifests != null) {
+        CanonicalManifest cManifest = new CanonicalManifest(versionedManifests);
+        Set<String> manifests = versionedManifests.getManifestPaths();
+        Manifest manifest;
+        String store = enabledManifestSource.get(projectName).getStore();
+        Table<String, String, String> lookup = HashBasedTable.create();
+
+        // Remove old manifest from subscription if destination store and branch
+        // matches manifest source being updated
+        // TODO again, this assume 1-1 map between store and manifest store
+        Map<String,
+            Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>> branchPaths;
+        for (Table.Cell<ProjectBranchKey, String,
+            Map<String,
+                Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>> cell :
+            subscribedRepos.cellSet()) {
+          if (store.equals(cell.getColumnKey())) {
+            branchPaths = cell.getValue();
+
+            Iterator<String> iter = branchPaths.keySet().iterator();
+            String branchPath;
+            while (iter.hasNext()) {
+              branchPath = iter.next();
+              if (branchPath.startsWith(branchName)) {
+                iter.remove();
+                manifestStores.remove(store, branchPath);
+              }
+            }
+          }
+        }
+
+        //TODO need to make sure remote is pointing to this server?
+        //TODO this may be impossible
+        //TODO only monitor projects without 'remote' attribute / only using default?
+
+        for (String path : manifests) {
+          String bp = branchName + "/" + path;
+          try {
+            manifest = cManifest.getCanonicalManifest(path);
+
+            VersionedManifests.affixManifest(gitRepoManager, manifest, lookup);
+
+            watchCanonicalManifest(manifest, store, bp);
+            //save manifest
+            //TODO added the m/ to the ref to to work around LOCK_FAILURE error of creating master/bla/bla
+            //TODO (because default master ref already exists) better solution?
+            updateManifest(store, STORE_BRANCH_PREFIX + bp, manifest);
+
+          } catch (ManifestReadException e) {
+            e.printStackTrace();
+          }
+
+        }
+
+      }
+    } catch (JAXBException | IOException e) {
+      e.printStackTrace();
+    }
+
+  }
+
+  private void watchCanonicalManifest(Manifest manifest, String store,
+                                      String branchPath) {
+    String defaultBranch;
+    if (manifest.getDefault() != null &&
+        manifest.getDefault().getRevision() != null) {
+      defaultBranch = manifest.getDefault().getRevision();
+    } else {
+      defaultBranch = "";
+    }
+
+    manifestStores.put(store, branchPath, manifest);
+
+    List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects
+        = manifest.getProject();
+    watchProjectsInCanonicalManifest(store, branchPath, defaultBranch, projects);
+  }
+
+  private void watchProjectsInCanonicalManifest(String store, String branchPath,
+                                                String defaultBranch,
+                                                List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects) {
+    ProjectBranchKey pbKey;
+    for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project project : projects) {
+      if (manifestStores.containsRow(project.getName()) &&
+          !manifestStores.row(project.getName()).isEmpty()) {
+        // Skip if it's one of the repo for storing
+        // manifest to avoid infinite loop
+        // This is a bit too general, but it's done to avoid the complexity
+        // of actually tracing out the loop
+        // i.e. manifest1->store2 --> manifest2->store1
+        continue;
+      }
+
+      // Make sure revision is branch ref w/o refs/heads
+      String branch = project.getRevision() == null ?
+          defaultBranch : (project.getUpstream() != null ?
+          project.getUpstream() : project.getRevision());
+      pbKey = new ProjectBranchKey(project.getName(),
+          Repository.shortenRefName(branch));
+
+
+      //TODO only update manifests that changed
+      if (!subscribedRepos.contains(pbKey, store)) {
+        subscribedRepos.put(pbKey, store,
+            Maps.<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>newHashMap());
+      }
+
+
+      Map<String,
+          Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>> ps;
+      ps = subscribedRepos.get(pbKey, store);
+      if (!ps.containsKey(branchPath)) {
+        ps.put(branchPath,
+            Sets.<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>newHashSet());
+      }
+      ps.get(branchPath).add(project);
+
+      if (project.getProject().size() > 0) {
+        watchProjectsInCanonicalManifest(store, branchPath, defaultBranch,
+            project.getProject());
+      }
+    }
+  }
+
+  private void processProjectConfigChange(Event event) {
+    Project.NameKey p = new Project.NameKey(event.getProjectName());
+
+    //TODO test two separate project configured to the same store
+    try {
+      ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
+      ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
+
+      //TODO selectively update changes instead of complete reset
+      if (oldCfg != null) {
+        String oldStore =
+            oldCfg.getPluginConfig(pluginName).getString(KEY_STORE);
+
+        if (oldStore != null && !oldStore.isEmpty()) {
+          //TODO FIX assume unique store for each manifest source (1-1 map)
+          manifestStores.row(oldStore).clear();
+          enabledManifestSource.remove(event.getProjectName());
+
+          Iterator<Table.Cell<ProjectBranchKey, String, Map<String,
+              Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>>> iter =
+              subscribedRepos.cellSet().iterator();
+          while (iter.hasNext()) {
+            if (oldStore.equals(iter.next().getColumnKey())) {
+              iter.remove();
+            }
+          }
+        }
+      }
+
+      if (newCfg != null) {
+        loadStoreFromProjectConfig(event.getProjectName(), newCfg);
+
+      }
+    } catch (IOException | ConfigInvalidException | JAXBException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void loadStoreFromProjectConfig(String projectName,
+                                          ProjectConfig config)
+      throws JAXBException, IOException, ConfigInvalidException {
+    String newStore =
+        config.getPluginConfig(pluginName).getString(KEY_STORE);
+
+    if (newStore != null) {
+      newStore = newStore.trim();
+      if (!newStore.isEmpty()) {
+        Set<String> branches = Sets.newHashSet(
+            config.getPluginConfig(pluginName)
+                .getStringList(KEY_BRANCH));
+
+        if (branches.size() > 0) {
+          PluginProjectConfig ppc = new PluginProjectConfig(newStore, branches);
+
+          enabledManifestSource.put(projectName, ppc);
+          Project.NameKey nameKey = new Project.NameKey(projectName);
+          VersionedManifests versionedManifests;
+          for (String branch : branches) {
+            versionedManifests = parseManifests(nameKey, branch);
+            processManifestChange(versionedManifests, projectName, branch);
+          }
+        }
+      }
+    }
+  }
+
+  private ProjectConfig parseConfig(Project.NameKey p, String idStr)
+      throws IOException, ConfigInvalidException {
+    ObjectId id = ObjectId.fromString(idStr);
+    if (ObjectId.zeroId().equals(id)) {
+      return null;
+    }
+    return ProjectConfig.read(metaDataUpdateFactory.create(p), id);
+  }
+
+  private VersionedManifests parseManifests(Event event)
+      throws JAXBException, IOException, ConfigInvalidException {
+    Project.NameKey p = new Project.NameKey(event.getProjectName());
+    return parseManifests(p, event.getRefName());
+  }
+
+  private VersionedManifests parseManifests(Project.NameKey p, String refName)
+      throws IOException, JAXBException, ConfigInvalidException {
+
+    MetaDataUpdate update = metaDataUpdateFactory.create(p);
+    VersionedManifests vManifests = new VersionedManifests(refName);
+    vManifests.load(update);
+
+    return vManifests;
+  }
+
+  private void updateManifest(String projectName, String refName,
+                              Manifest manifest)
+      throws JAXBException, IOException {
+    Project.NameKey p = new Project.NameKey(projectName);
+    MetaDataUpdate update = metaDataUpdateFactory.create(p);
+    VersionedManifests vManifests = new VersionedManifests(refName);
+
+    //TODO find a better way to detect no branch
+    boolean refExists = true;
+    try {
+      vManifests.load(update);
+    } catch (Exception e) {
+      refExists = false;
+    }
+
+    if (refExists) {
+      Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
+      entry.put("default.xml", manifest);
+      vManifests.setManifests(entry);
+      vManifests.commit(update);
+    } else {
+      vManifests = new VersionedManifests("master");
+      try {
+        vManifests.load(update);
+      } catch (ConfigInvalidException e) {
+        e.printStackTrace();
+      }
+      Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
+      entry.put("default.xml", manifest);
+      vManifests.setManifests(entry);
+      vManifests.commitToNewRef(update, refName);
+    }
+  }
+
+}
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Module.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Module.java
index e73bdf2..ff8c3b0 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Module.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Module.java
@@ -14,11 +14,22 @@
 
 package com.amd.gerrit.plugins.manifestsubscription;
 
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
 
 class Module extends AbstractModule {
+
   @Override
   protected void configure() {
-    // TODO
+
+    bind(ManifestSubscription.class).in(Scopes.SINGLETON);
+
+    DynamicSet.bind(binder(), LifecycleListener.class)
+        .to(ManifestSubscription.class);
+    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
+        .to(ManifestSubscription.class);
   }
 }
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/PluginProjectConfig.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/PluginProjectConfig.java
new file mode 100644
index 0000000..bad5797
--- /dev/null
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/PluginProjectConfig.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2015 Advanced Micro Devices, Inc.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.amd.gerrit.plugins.manifestsubscription;
+
+import java.util.Set;
+
+public class PluginProjectConfig {
+  private String store;
+  private Set<String> branches;
+
+  public PluginProjectConfig(String store, Set<String> branches) {
+    this.store = store;
+    this.branches = branches;
+  }
+
+  public String getStore() {
+    return store;
+  }
+
+  public void setStore(String store) {
+    this.store = store;
+  }
+
+  public Set<String> getBranches() {
+    return branches;
+  }
+
+  public void setBranches(Set<String> branches) {
+    this.branches = branches;
+  }
+}
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ProjectBranchKey.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ProjectBranchKey.java
new file mode 100644
index 0000000..13734c6
--- /dev/null
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ProjectBranchKey.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 Advanced Micro Devices, Inc.  All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.amd.gerrit.plugins.manifestsubscription;
+
+import java.util.Objects;
+
+public class ProjectBranchKey {
+  private String project;
+  private String branch;
+
+  public ProjectBranchKey(String project, String branch) {
+    this.project = project;
+    this.branch = branch;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ProjectBranchKey that = (ProjectBranchKey) o;
+    return Objects.equals(project, that.project) &&
+        Objects.equals(branch, that.branch);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(project, branch);
+  }
+
+  @Override
+  public String toString() {
+    return com.google.common.base.Objects.toStringHelper(this)
+        .add("project", project)
+        .add("branch", branch)
+        .toString();
+  }
+}
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
index 5cf3466..b7ea26e 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
@@ -39,6 +39,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class VersionedManifests extends VersionedMetaData implements ManifestProvider {
   private String refName;
@@ -50,6 +51,10 @@
     this.manifests = manifests;
   }
 
+  public Set<String> getManifestPaths() {
+    return manifests.keySet();
+  }
+
   public Map<String, Manifest> getManifests() {
     return Collections.unmodifiableMap(manifests);
   }
@@ -88,6 +93,11 @@
     Manifest manifest;
 
     RevWalk rw = new RevWalk(reader);
+
+    // This happens when someone configured a invalid branch name
+    if (getRevision() == null) {
+      throw new ConfigInvalidException(refName);
+    }
     RevCommit r = rw.parseCommit(getRevision());
     TreeWalk treewalk = new TreeWalk(reader);
     treewalk.addTree(r.getTree());
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..ac28edd
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,65 @@
+This plugin allows users to monitor git-repo manifests in manifest repositories
+and generate rev-specific manifests (similar to "repo manifest -o") and store
+them to a separate git repository (configurable.)
+
+> **Warning: this plugin is currently under development and is targeting 2.9.1**
+
+The rev-specific manifest of each source manifest is stored in its own branch
+with the following naming convention:
+m/\<source manifest branch\>/\<source manifest path\>
+
+The name of the rev-specific manifest is default.xml
+
+For example, if the source manifests are
+branch: master
+file: default.xml
+file: dev.xml
+file: upstream-mirror.xml
+
+There will be three *branches* in the destination repository with these names:
+m/master/default.xml
+m/master/dev.xml
+m/master/upstream-mirror.xml
+
+If a tag is placed at the start of one of these branches, "git describe" can be
+used to provide system-level version metadata.  One can also use "git bisect" on
+the branches to identify system-level regression.
+
+* Enabled by project.config in the manifest project to be monitored
+* Manifest class is generated from 'repo help manifest' DTD using JAXB
+* \<include\>s are expanded similar to "repo manifest -o" (only works for
+manifests in the same source manifest repository reference by relative path)
+* Supports \<remove-project\>
+* Circular update loop is prevented by not monitoring any of the rev-specific
+manifest repositories
+* Projects in the manifests are assumed to be on the server.  (There is no check
+currently so it's a potential source of error.)
+
+
+```
+xjc -dtd -d gen -p com.amd.gerrit.plugins.manifestsubscription.manifest manifest.dtd
+```
+
+Manifest represent raw XML
+CanonicalManifest resolve <include> and <remove-project>
+
+* TODO: keep parsed manifest in memory for quick lookup
+* TODO: monitor all manifest branch if no branch is specified
+* TODO: ssh command to check what is being monitored
+* TODO: stright mode, only monitor projects using default (no remote)
+* TODO: split mode (store all manifests in same branch strcuture as source instead of flattening the underlying file strcuture)
+* TODO: not monitor non-local projects
+
+* TODO: make sure no circular dependencies (project with manifest subscription is not in the
+manifest being monitored.)
+* TODO: support changes in manifest
+
+* resolve relative path in include
+* supports include tag in manifest
+
+* TODO: check project.config on newkly created project
+
+* TODO: include an external manifest DTD or XML schema/XSD
+* generates/xjc classes from DTD/XSD at build time
+* TODO add test verify include manifest have original project
+* TODO sub dir include manifest have same level include working
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index bde3084..78c3ce4 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -1 +1,11 @@
-TODO: config documentation
+In the manifest project's project.config in refs/meta/config, set the following:
+
+```
+[plugin "manifest-subscription"]
+  store = "repo/name/on/the/server"
+  branch = "branch-being-monitored-in-this-repo"
+  branch = "another-branch-being-monitored-in-this-repo"
+  branch = "master"
+```
+
+There should be only one value for store.  More than one value for branch.
\ No newline at end of file
diff --git a/src/test/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifestsTest.java b/src/test/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifestsTest.java
index dff6e8d..2799435 100644
--- a/src/test/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifestsTest.java
+++ b/src/test/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifestsTest.java
@@ -46,6 +46,19 @@
     db = createBareRepository();
     util = new TestRepository<>(db);
   }
+  @Test
+  public void testVersionedManifestsReadFromNonManifestGit() throws Exception {
+    RevCommit rev = util.commit(util.tree(
+        util.file("nonxml.txt",
+            util.blob(IOUtils.toByteArray(
+                getClass().getResourceAsStream("/nonxml.txt"))))
+    ));
+
+    VersionedManifests versionedManifests =
+        new VersionedManifests("master");
+
+    versionedManifests.load(db, rev);
+  }
 
   @Test
   public void testVersionedManifestsReadFromGit() throws Exception {