Add support to create manifest after branch/tag operation

Change-Id: I353beb5aaeb503e76cda44206fe5ad48609c7b58
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 b508589..0f3ce1f 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestCommand.java
@@ -14,22 +14,15 @@
 
 package com.amd.gerrit.plugins.manifestsubscription;
 
-import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
-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.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.inject.Inject;
-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.kohsuke.args4j.Option;
 
-import javax.xml.bind.JAXBException;
-import java.io.IOException;
+import org.kohsuke.args4j.Option;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @CommandMetaData(name = "branch", description = "branch all projects described in a manifest on the server")
@@ -38,6 +31,9 @@
   @Inject
   private GitRepositoryManager gitRepoManager;
 
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
   @Option(name = "-r", aliases = {"--manifest-repo"},
           usage = "", required = true)
   private String manifestRepo;
@@ -58,6 +54,21 @@
       usage = "", required = false)
   private Utilities.OutputType outputType;
 
+  @Option(name = "-nr", aliases = {"--new-manifest-repo"},
+      depends = {"-nb", "-np"},
+      usage = "", required = false)
+  private String newManifestRepo;
+
+  @Option(name = "-nb", aliases = {"--new-manifest-branch"},
+      depends = {"-nr", "-np"},
+      usage = "", required = false)
+  private String newManifestBranch;
+
+  @Option(name = "-np", aliases = {"--new-manifest-path"},
+      depends = {"-nb", "-nr"},
+      usage = "", required = false)
+  private String newManifestPath;
+
   @Override
   protected void run() {
     stdout.println("Branching manifest:");
@@ -65,8 +76,14 @@
     stdout.println(manifestCommitish);
     stdout.println(manifestPath);
 
-    Utilities.branchManifest(gitRepoManager, manifestRepo, manifestCommitish,
-        manifestPath, newBranch, stdout, stderr,
+    stdout.println(newManifestRepo);
+    stdout.println(newManifestBranch);
+    stdout.println(newManifestPath);
+
+    Utilities.branchManifest(gitRepoManager, metaDataUpdateFactory,
+        manifestRepo, manifestCommitish, manifestPath, newBranch,
+        newManifestRepo, newManifestBranch, newManifestPath,
+        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 4245db1..68a198a 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/BranchManifestServlet.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -33,36 +34,41 @@
   @Inject
   private GitRepositoryManager gitRepoManager;
 
+  @Inject
+  private MetaDataUpdate.Server metaDataUpdateFactory;
+
   protected void doPost(HttpServletRequest req, HttpServletResponse res)
                                                             throws IOException {
 
     Map<String, String[]> input = req.getParameterMap();
 
-    if (inputValid(req)) {
+    if (Utilities.httpInputValid(req)) {
       res.setContentType("application/json");
       res.setCharacterEncoding("UTF-8");
 
+      String newManifestRepo = null;
+      String newManifestBranch= null;
+      String newManifestPath = null;
+
+      if (input.containsKey("new-manifest-repo")) {
+        newManifestRepo = input.get("new-manifest-repo")[0];
+        newManifestBranch = input.get("new-manifest-branch")[0];
+        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);
 
     } else {
       res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
     }
   }
-
-  private boolean inputValid(HttpServletRequest request) {
-    Map<String, String[]> input = request.getParameterMap();
-    if(input.containsKey("manifest-repo") &&
-        input.containsKey("manifest-commit-ish") &&
-        input.containsKey("manifest-path") &&
-        input.containsKey("new-branch")) {
-      return true;
-    }
-
-    return false;
-  }
 }
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifestServlet.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifestServlet.java
index 434d3c0..d8e4433 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifestServlet.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifestServlet.java
@@ -38,7 +38,17 @@
     res.setCharacterEncoding("UTF-8");
     Map<String, String[]> input = req.getParameterMap();
 
-    if (inputValid(req)) {
+    String newManifestRepo = null;
+    String newManifestBranch= null;
+    String newManifestPath = null;
+
+    if (input.containsKey("new-manifest-repo")) {
+      newManifestRepo = input.get("new-manifest-repo")[0];
+      newManifestBranch = input.get("new-manifest-branch")[0];
+      newManifestPath = input.get("new-manifest-path")[0];
+    }
+
+    if (Utilities.httpInputValid(req)) {
       res.setContentType("application/json");
       res.setCharacterEncoding("UTF-8");
 
@@ -53,17 +63,4 @@
       res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
     }
   }
-
-  private boolean inputValid(HttpServletRequest request) {
-    Map<String, String[]> input = request.getParameterMap();
-    if(input.containsKey("manifest-repo") &&
-        input.containsKey("manifest-commit-ish") &&
-        input.containsKey("manifest-path") &&
-        input.containsKey("new-tag")) {
-      return true;
-    }
-
-    return false;
-  }
-
 }
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 8a8a2d8..fb6608b 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/Utilities.java
@@ -18,16 +18,21 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
+import com.amd.gerrit.plugins.manifestsubscription.manifest.Default;
 import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest;
 
 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -35,15 +40,40 @@
 import java.util.Map;
 import java.util.Set;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.xml.bind.JAXBException;
 
 public class Utilities {
+  private static final Logger log =
+      LoggerFactory.getLogger(Utilities.class);
   private static Gson gson = new GsonBuilder()
       .generateNonExecutableJson()
       .setPrettyPrinting()
       .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
       .create();
 
+  static boolean httpInputValid(HttpServletRequest request) {
+    Map<String, String[]> input = request.getParameterMap();
+    if (input.containsKey("manifest-repo") &&
+        input.containsKey("manifest-commit-ish") &&
+        input.containsKey("manifest-path") &&
+        (input.containsKey("new-branch") || input.containsKey("new-tag")) ) {
+
+      // these inputs are related.  Either they are all present or all absent
+      if ((input.containsKey("new-manifest-repo") &&
+          input.containsKey("new-manifest-branch") &&
+          input.containsKey("new-manifest-path")) ||
+          (!input.containsKey("new-manifest-repo") &&
+              !input.containsKey("new-manifest-branch") &&
+              !input.containsKey("new-manifest-path"))) {
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   public enum OutputType {
     TEXT,
     JSON
@@ -56,7 +86,6 @@
                                                                  JAXBException {
 
     Project.NameKey p = new Project.NameKey(manifestRepo);
-
     Repository repo = gitRepoManager.openRepository(p);
     ObjectId commitId = repo.resolve(manifestCommitish);
     VersionedManifests vManifests = new VersionedManifests(manifestCommitish);
@@ -66,6 +95,89 @@
     return manifests.getCanonicalManifest(manifestPath);
   }
 
+  static Manifest createNewManifestFromBase
+                                    (GitRepositoryManager gitRepoManager,
+                                     MetaDataUpdate.Server metaDataUpdateFactory,
+                                     String manifestRepo,
+                                     String manifestBranch,
+                                     String manifestPath,
+                                     String newRef,
+                                     Manifest base)
+      throws JAXBException, IOException, ConfigInvalidException, GitAPIException {
+
+    // Replace default ref with newly created branch or tag
+    Manifest manifest = (Manifest) base.clone();
+    final String defaultRef;
+    if (manifest.getDefault() != null) {
+      defaultRef = manifest.getDefault().getRevision();
+    } else {
+      defaultRef = null;
+    }
+
+    if (manifest.getDefault() != null) {
+      ManifestOp op = new ManifestOp() {
+        @Override
+        public boolean apply(com.amd.gerrit.plugins.manifestsubscription.manifest.Project project,
+                             String hash, String name,
+                             GitRepositoryManager gitRepoManager) throws
+            GitAPIException, IOException {
+
+          //This is assuming newRef points to the existing hash
+          if (project.getRevision() != null && project.getRevision().equals(hash)) {
+            project.setRevision(null);
+          } else {
+            project.setRevision(defaultRef);
+          }
+
+          return true;
+        }
+      };
+
+      VersionedManifests.traverseManifestAndApplyOp(gitRepoManager,
+          manifest.getProject(), defaultRef, op, null);
+    } else {
+      manifest.setDefault(new Default());
+    }
+
+    manifest.getDefault().setRevision(newRef);
+
+    Project.NameKey p = new Project.NameKey(manifestRepo);
+    Repository repo = gitRepoManager.openRepository(p);
+    ObjectId commitId = repo.resolve(manifestBranch);
+    VersionedManifests vManifests;
+    MetaDataUpdate update = metaDataUpdateFactory.create(p);
+    if (commitId == null) {
+      // TODO remove assumption that master branch always exists
+      vManifests = new VersionedManifests("master");
+      vManifests.load(update);
+    } else {
+      vManifests = new VersionedManifests(manifestBranch);
+      vManifests.load(repo, commitId);
+    }
+
+
+    Map<String, Manifest> entry = Maps.newHashMapWithExpectedSize(1);
+    entry.put(manifestPath, manifest);
+    vManifests.setManifests(entry);
+
+    RevCommit commit;
+    if (commitId == null) {
+      commit = vManifests.commitToNewRef(update, manifestBranch);
+    } else {
+      commit = vManifests.commit(update);
+    }
+
+    //TODO
+    //if (commit != null) {
+    //  changeHooks.doRefUpdatedHook(new Branch.NameKey(p, refName),
+    //      commit.getParent(0).getId(),
+    //      commit.getId(), null);
+    //} else {
+    //  log.warn("Failing to create new manifest");
+    //}
+    return manifest;
+  }
+
   private static void outputError(Writer output, PrintWriter error,
                                   boolean inJSON, Exception e) {
     if (inJSON) {
@@ -105,8 +217,12 @@
   }
 
   static void branchManifest(GitRepositoryManager gitRepoManager,
+                             MetaDataUpdate.Server metaDataUpdateFactory,
                              String manifestRepo, String manifestCommitish,
                              String manifestPath, String newBranch,
+                             String newManifestRepo,
+                             String newManifestBranch,
+                             String newManifestPath,
                              Writer output, PrintWriter error, boolean inJSON) {
 
     Manifest manifest;
@@ -115,6 +231,14 @@
                               manifestCommitish, manifestPath);
       VersionedManifests.branchManifest(gitRepoManager, manifest, newBranch);
 
+      if (newManifestBranch != null &&
+          newManifestPath != null &&
+          newManifestRepo != null) {
+        createNewManifestFromBase(gitRepoManager, metaDataUpdateFactory,
+            newManifestRepo, newManifestBranch, newManifestPath,
+            newBranch, manifest);
+      }
+
     } catch (IOException | ConfigInvalidException | ManifestReadException |
         JAXBException | GitAPIException e) {
       outputError(output, error, inJSON, e);
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 3e1f8d3..cc3dbb1 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
@@ -31,6 +31,8 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
@@ -42,6 +44,8 @@
 import java.util.*;
 
 public class VersionedManifests extends VersionedMetaData implements ManifestProvider {
+  private static final Logger log =
+      LoggerFactory.getLogger(VersionedManifests.class);
   private String refName;
   private Unmarshaller manifestUnmarshaller;
   private Marshaller manifestMarshaller;
@@ -306,7 +310,7 @@
     traverseManifestAndApplyOp(gitRepoManager, manifest.getProject(), defaultRef, op, lookup);
   }
 
-  private static void traverseManifestAndApplyOp(
+  static void traverseManifestAndApplyOp(
       GitRepositoryManager gitRepoManager,
       List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects,
       String defaultRef,
@@ -324,7 +328,11 @@
       ref = (ref == null) ? defaultRef : ref;
 
       if (ref != null) {
-        hash = lookup.get(projectName, ref);
+        if (lookup != null) {
+          hash = lookup.get(projectName, ref);
+        } else {
+          hash = null;
+        }
 
         if (hash == null) {
           p = new Project.NameKey(projectName);
@@ -339,7 +347,7 @@
         }
 
         if (hash != null) {
-          lookup.put(projectName, ref, hash);
+          if (lookup != null) lookup.put(projectName, ref, hash);
           op.apply(project, hash, ref, gitRepoManager);
         }
       }
diff --git a/src/main/resources/Documentation/cmd-branch.md b/src/main/resources/Documentation/cmd-branch.md
index acf913b..d22bbda 100644
--- a/src/main/resources/Documentation/cmd-branch.md
+++ b/src/main/resources/Documentation/cmd-branch.md
@@ -14,6 +14,10 @@
   {-c/--manifest-commit-ish <manifest commit-ish>}
   {-p/--manifest-path <manifest path>}
   {-b/--new-branch <new branch name>}
+  [-o/--output-type]
+  [-nr/--new-manifest-repo <new manifest repo>]
+  [-nc/--new-manifest-branch <new manifest branch>]
+  [-np/--new-manifest-path <new manifest path>]
   [--help]
 ```
 
@@ -32,6 +36,11 @@
 `-b/--new-branch <new branch name>`
 : The name of the branch that will be created on all projects in the manifest specified above
 
+`-nr/--new-manifest-repo <new manifest repo>`
+`-nb/--new-manifest-branch <new manifest branch>`
+`-np/--new-manifest-path <new manifest path>`
+: (optional) A new manifest (to be created) that points to the new branch
+
 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`