Add option to create snapshot manifest from a branch point

Currently the snapshot manifests are stored like this:

refs/heads/m/master/some/manifest/default.xml
  a---b---c---d---e

and if you tag commit 'a', you can use git-describe to reference all
subsequent commits.

This plugin also let you branch a set of repositories described by a
manifest and create a new manifest to point to the same set of
repositories at a new branch.  So for example, you can tell the plugin
to branch at the snapshot manifest stored in commit 'c' and create a new
manifest back in the subscribing repository (master branch with path
release/1.0.xml, for example.)  When any of the projects in the
subscribed repositories change, a snapshot manifest will be created.
In previous implementation, the new snapshot manifest branch is created
from head of the master branch (assuming pointing to 'a'),

refs/heads/m/master/some/manifest/default.xml
  a---b---c---d---e
   \
    f
refs/heads/m/master/release/1.0.xml

With this commit, the user will have the option of pre-creating the
snapshot manifest of the newly created manifest at the point branching
was requested.

refs/heads/m/master/some/manifest/default.xml
  a---b---c---d---e
           \
            f
refs/heads/m/master/release/1.0.xml

This will allow a system-level versioning using git-describe.  For
example:
  a---b---c---d---e
  ^        \  ^
  v1.0      \ v1.2
             \
              f---g
                  ^
               v1.0-4-gc0ffee1

Change-Id: Ifba982d35c6f6af55200474d5ec090ba9758de36
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java
index 0f3ce1f..fd367a6 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java
@@ -14,6 +14,7 @@
 
 package com.amd.gerrit.plugins.manifestsubscription;
 
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -34,6 +35,9 @@
   @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
+  @Inject
+  private ChangeHooks changeHooks;
+
   @Option(name = "-r", aliases = {"--manifest-repo"},
           usage = "", required = true)
   private String manifestRepo;
@@ -69,6 +73,11 @@
       usage = "", required = false)
   private String newManifestPath;
 
+  @Option(name = "-cs", aliases = {"--create-snapshot-branch"},
+      depends = {"-nb", "-nr", "-np"},
+      usage = "", required = false)
+  private boolean createSnapShotBranch;
+
   @Override
   protected void run() {
     stdout.println("Branching manifest:");
@@ -79,10 +88,13 @@
     stdout.println(newManifestRepo);
     stdout.println(newManifestBranch);
     stdout.println(newManifestPath);
+    stdout.println("Create snapshot branch: " + createSnapShotBranch);
 
-    Utilities.branchManifest(gitRepoManager, metaDataUpdateFactory,
-        manifestRepo, manifestCommitish, manifestPath, newBranch,
+    Utilities.branchManifest(gitRepoManager, metaDataUpdateFactory, changeHooks,
+        manifestRepo, manifestCommitish, manifestPath,
+        newBranch,
         newManifestRepo, newManifestBranch, newManifestPath,
+        createSnapShotBranch,
         stdout, stderr,
         outputType==Utilities.OutputType.JSON);
 
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java
index 68a198a..aeb97f2 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java
@@ -14,6 +14,7 @@
 
 package com.amd.gerrit.plugins.manifestsubscription;
 
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -37,6 +38,9 @@
   @Inject
   private MetaDataUpdate.Server metaDataUpdateFactory;
 
+  @Inject
+  private ChangeHooks changeHooks;
+
   protected void doPost(HttpServletRequest req, HttpServletResponse res)
                                                             throws IOException {
 
@@ -49,6 +53,7 @@
       String newManifestRepo = null;
       String newManifestBranch= null;
       String newManifestPath = null;
+      boolean createSnapShotBranch = false;
 
       if (input.containsKey("new-manifest-repo")) {
         newManifestRepo = input.get("new-manifest-repo")[0];
@@ -56,16 +61,21 @@
         newManifestPath = input.get("new-manifest-path")[0];
       }
 
-      Utilities.branchManifest(gitRepoManager,
-                                metaDataUpdateFactory,
-                                input.get("manifest-repo")[0],
-                                input.get("manifest-commit-ish")[0],
-                                input.get("manifest-path")[0],
-                                input.get("new-branch")[0],
-                                newManifestRepo,
-                                newManifestBranch,
-                                newManifestPath,
-                                res.getWriter(), null, true);
+      if (input.containsKey("create-snapshot-branch")) {
+        createSnapShotBranch =
+            Boolean.parseBoolean(input.get("create-snapshot-branch")[0]);
+      }
+
+      Utilities.branchManifest(
+          gitRepoManager, metaDataUpdateFactory, changeHooks,
+          input.get("manifest-repo")[0],
+          input.get("manifest-commit-ish")[0],
+          input.get("manifest-path")[0],
+          input.get("new-branch")[0],
+          newManifestRepo,
+          newManifestBranch,
+          newManifestPath,
+          createSnapShotBranch, res.getWriter(), null, true);
 
     } else {
       res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java
index 0ecc2d2..4a51773 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/ManifestSubscription.java
@@ -20,7 +20,6 @@
 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.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -48,7 +47,7 @@
   private static final String KEY_BRANCH = "branch";
   private static final String KEY_STORE = "store";
 
-  private static final String STORE_BRANCH_PREFIX = "refs/heads/m/";
+  static final String STORE_BRANCH_PREFIX = "refs/heads/m/";
 
   private final String pluginName;
 
@@ -182,8 +181,9 @@
           }
 
           try {
-            updateManifest(store, STORE_BRANCH_PREFIX + storeBranch,
-                           manifest, manifestSrc, extraCommitMsg.toString());
+            Utilities.updateManifest(gitRepoManager, metaDataUpdateFactory,
+                changeHooks, store, STORE_BRANCH_PREFIX + storeBranch,
+                manifest, manifestSrc, extraCommitMsg.toString(), null);
           } catch (JAXBException | IOException e) {
             e.printStackTrace();
           }
@@ -454,61 +454,8 @@
   private void updateManifest(String projectName, String refName,
                               Manifest manifest, String manifestSrc)
       throws JAXBException, IOException {
-    updateManifest(projectName, refName, manifest, manifestSrc, "");
-  }
-
-  private void updateManifest(String projectName, String refName,
-                              Manifest manifest, String manifestSrc,
-                              String extraCommitMsg)
-      throws JAXBException, IOException {
-    Project.NameKey p = new Project.NameKey(projectName);
-    Repository repo = gitRepoManager.openRepository(p);
-    MetaDataUpdate update = metaDataUpdateFactory.create(p);
-    ObjectId commitId = repo.resolve(refName);
-    VersionedManifests vManifests = new VersionedManifests(refName);
-
-    //TODO find a better way to detect no branch
-    boolean refExists = true;
-    try {
-      vManifests.load(update, commitId);
-    } catch (Exception e) {
-      refExists = false;
-    }
-
-
-    RevCommit commit = null;
-    if (refExists) {
-      Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
-      entry.put("default.xml", manifest);
-      vManifests.setManifests(entry);
-      vManifests.setSrcManifestRepo(manifestSrc);
-      vManifests.setExtraCommitMsg(extraCommitMsg);
-      commit = 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);
-      commit = vManifests.commitToNewRef(update, refName);
-    }
-
-    // TODO this may be bug in the MetaDataUpdate or VersionedMetaData
-    // May be related:
-    // https://code.google.com/p/gerrit/issues/detail?id=2564
-    // https://gerrit-review.googlesource.com/55540
-    if (commit != null) {
-      changeHooks.doRefUpdatedHook(new Branch.NameKey(p, refName),
-                                    commit.getParent(0).getId(),
-                                    commit.getId(), null);
-    } else {
-      log.warn("Failing to commit manifest subscription update");
-    }
-
+    Utilities.updateManifest(gitRepoManager, metaDataUpdateFactory, changeHooks,
+        projectName, refName, manifest, manifestSrc, "", null);
   }
 
 }
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java
index 5c5ce00..08e9807 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java
@@ -14,8 +14,11 @@
 
 package com.amd.gerrit.plugins.manifestsubscription;
 
+import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Maps;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -26,11 +29,13 @@
 import com.amd.gerrit.plugins.manifestsubscription.manifest.Default;
 import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest;
 
+import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,6 +79,67 @@
     return false;
   }
 
+  static ObjectId updateManifest(GitRepositoryManager gitRepoManager,
+                             MetaDataUpdate.Server metaDataUpdateFactory,
+                             ChangeHooks changeHooks,
+                             String projectName, String refName,
+                             Manifest manifest, String manifestSrc,
+                             String extraCommitMsg,
+                             String defaultBranchBase)
+      throws JAXBException, IOException {
+    Project.NameKey p = new Project.NameKey(projectName);
+    Repository repo = gitRepoManager.openRepository(p);
+    MetaDataUpdate update = metaDataUpdateFactory.create(p);
+    ObjectId commitId = repo.resolve(refName);
+    VersionedManifests vManifests = new VersionedManifests(refName);
+
+    //TODO find a better way to detect no branch
+    boolean refExists = true;
+    try {
+      vManifests.load(update, commitId);
+    } catch (Exception e) {
+      refExists = false;
+    }
+
+    RevCommit commit = null;
+    if (refExists) {
+      Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
+      entry.put("default.xml", manifest);
+      vManifests.setManifests(entry);
+      vManifests.setSrcManifestRepo(manifestSrc);
+      vManifests.setExtraCommitMsg(extraCommitMsg);
+      commit = vManifests.commit(update);
+    } else {
+      if (defaultBranchBase == null) defaultBranchBase = "refs/heads/master";
+      vManifests = new VersionedManifests(defaultBranchBase);
+      ObjectId cid = repo.resolve(defaultBranchBase);
+      try {
+        vManifests.load(update, cid);
+      } catch (ConfigInvalidException e) {
+        e.printStackTrace();
+      }
+      Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
+      entry.put("default.xml", manifest);
+      vManifests.setManifests(entry);
+      commit = vManifests.commitToNewRef(update, refName);
+    }
+
+    // TODO this may be bug in the MetaDataUpdate or VersionedMetaData
+    // May be related:
+    // https://code.google.com/p/gerrit/issues/detail?id=2564
+    // https://gerrit-review.googlesource.com/55540
+    if (commit != null) {
+      changeHooks.doRefUpdatedHook(new Branch.NameKey(p, refName),
+                                    commit.getParent(0).getId(),
+                                    commit.getId(), null);
+      return commit.getId();
+    } else {
+      log.warn("Failing to commit manifest subscription update");
+    }
+
+    return null;
+  }
+
   public enum OutputType {
     TEXT,
     JSON
@@ -95,14 +161,15 @@
     return manifests.getCanonicalManifest(manifestPath);
   }
 
-  static Manifest createNewManifestFromBase
-                                    (GitRepositoryManager gitRepoManager,
-                                     MetaDataUpdate.Server metaDataUpdateFactory,
-                                     String manifestRepo,
-                                     String manifestBranch,
-                                     String manifestPath,
-                                     String newRef,
-                                     Manifest base)
+  static Manifest createNewManifestFromBase(
+      GitRepositoryManager gitRepoManager,
+       MetaDataUpdate.Server metaDataUpdateFactory,
+       ChangeHooks changeHooks,
+       String srcManifestRepo, String srcManifestCommitish,
+       String manifestRepo, String manifestBranch, String manifestPath,
+       String newRef,
+       boolean createSnapShotBranch,
+       Manifest base)
       throws JAXBException, IOException, ConfigInvalidException, GitAPIException {
 
     // Replace default ref with newly created branch or tag
@@ -141,6 +208,29 @@
 
     manifest.getDefault().setRevision(newRef);
 
+    if (createSnapShotBranch) {
+      // Create the snapshot branch and tag it
+      // branch name is by convention for the new manifest to be created below
+
+      // current jgit Repository.resolve doesn't seem to resolve short-name
+      // properly.  FIXME
+      String shortBranch = manifestBranch.replaceFirst("^refs/heads/(.*)", "$1");
+
+      ObjectId oid = Utilities.updateManifest(
+          gitRepoManager, metaDataUpdateFactory, changeHooks,
+          srcManifestRepo,
+          ManifestSubscription.STORE_BRANCH_PREFIX + shortBranch + "/" + manifestPath,
+          manifest, manifestRepo, "Manifest branched", srcManifestCommitish);
+
+//      try (Repository db = gitRepoManager.openRepository(new Project.NameKey(srcManifestRepo));
+//           Git git = new Git(db);
+//           RevWalk walk = new RevWalk(db)) {
+//        RevCommit commit = walk.parseCommit(oid);
+//        git.tag().setName(createSnapShotBranch)
+//            .setObjectId(commit).setAnnotated(true).call();
+//      }
+    }
+
     Project.NameKey p = new Project.NameKey(manifestRepo);
     Repository repo = gitRepoManager.openRepository(p);
     ObjectId commitId = repo.resolve(manifestBranch);
@@ -218,11 +308,13 @@
 
   static void branchManifest(GitRepositoryManager gitRepoManager,
                              MetaDataUpdate.Server metaDataUpdateFactory,
+                             ChangeHooks changeHooks,
                              String manifestRepo, String manifestCommitish,
                              String manifestPath, String newBranch,
                              String newManifestRepo,
                              String newManifestBranch,
                              String newManifestPath,
+                             boolean createSnapShotBranch,
                              Writer output, PrintWriter error, boolean inJSON) {
 
     Manifest manifest;
@@ -234,9 +326,11 @@
       if (newManifestBranch != null &&
           newManifestPath != null &&
           newManifestRepo != null) {
-        createNewManifestFromBase(gitRepoManager, metaDataUpdateFactory,
+        createNewManifestFromBase(
+            gitRepoManager, metaDataUpdateFactory, changeHooks,
+            manifestRepo, manifestCommitish,
             newManifestRepo, newManifestBranch, newManifestPath,
-            newBranch, manifest);
+            newBranch, createSnapShotBranch, manifest);
       }
 
     } catch (IOException | ConfigInvalidException | ManifestReadException |
diff --git a/src/main/resources/Documentation/cmd-branch.md b/src/main/resources/Documentation/cmd-branch.md
index a9ab868..d5c7899 100644
--- a/src/main/resources/Documentation/cmd-branch.md
+++ b/src/main/resources/Documentation/cmd-branch.md
@@ -16,8 +16,9 @@
   {-b/--new-branch <new branch name>}
   [-o/--output-type]
   [-nr/--new-manifest-repo <new manifest repo>]
-  [-nc/--new-manifest-branch <new manifest branch>]
+  [-nb/--new-manifest-branch <new manifest branch>]
   [-np/--new-manifest-path <new manifest path>]
+  [-cs/--create-snapeshot-branch]
   [--help]
 ```
 
@@ -41,19 +42,45 @@
 `-np/--new-manifest-path <new manifest path>`
 : (optional) A new manifest (to be created) that points to the new branch
 
+`-cs/--create-snapeshot-branch`
+: (optional, depends on -nr/-nb/-np) Create a new snapshot manifest branch for
+ the newly created manifest (as defined by -nr, -nb, -np) in the repository
+ defined by -r
+
 EXAMPLES
 --------
-Branch all projects on the server described in the `default.xml` manifest in commit 5f51acb585b6a of repo `demo/build_manifest` to branch `releases/1.0.0`
+Branch all projects on the server described in the `default.xml` manifest in 
+commit 5f51acb585b6a of repo `demo/build_manifest` to branch `releases/1.0.0`
 ```
 ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ branch -r demo/build_manifest -c 5f51acb585b6a -p default.xml -b releases/1.0.0
 
 ```
 
-Branch all projects on the server described in the `default.xml` manifest in commit v0.9-15-g5f51acb of repo `demo/build_manifest` to branch `releases/1.0.0`
-```
-ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ branch -r demo/build_manifest -c v0.9-15-g5f51acb -p default.xml -b releases/1.0.0
+Branch all projects on the server described in the `default.xml` manifest in 
+commit v0.9-15-g5f51acb of repo `demo/build_manifest` to branch `releases/1.0.0`
+and create a new manifest with path `releases/1.0.0.xml` in repository 
+`project/manifest` branch `master`.
 
 ```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ branch -r demo/build_manifest -c v0.9-15-g5f51acb -p default.xml -b releases/1.0.0 -nr project/manifest -nb master -np releases/1.0.0.xml -cs
+
+```
+
+In the following charts, commit 'c' is v0.9-15-g5f51acb. 
+
+With -cs:
+```
+ a---b---c---d---e master
+          \
+           f       m/master/releases/1.0.0.xml
+```
+
+Without -cs:
+```
+ a---b---c---d---e master
+  \
+   f               m/master/releases/1.0.0.xml
+```
 
 SEE ALSO
 --------
diff --git a/src/main/resources/Documentation/rest-api-branch.md b/src/main/resources/Documentation/rest-api-branch.md
index 7580424..5a9f2ae 100644
--- a/src/main/resources/Documentation/rest-api-branch.md
+++ b/src/main/resources/Documentation/rest-api-branch.md
@@ -14,6 +14,10 @@
 * manifest-commit-ish: commit-ish that points to the commit that contain the manifest (branch name, git describe string, etc.)
 * manifest-path: path to the manifest that defines the projects to be branched
 * new-branch: name of the branch to be created for each of the project defined in the manifest above
+* new-manifest-repo [optional]: 
+* new-manifest-branch [optional]: 
+* new-manifest-path [optional]: 
+* create-snapshot-branch [optional]: 
 
 #### Request
 ```