Add support for tagging operation

Change-Id: Ib2d806bef29f1a25535c35f3ee35f41d01e26aac
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/SshModule.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/SshModule.java
index c3363cb..5137cc5 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/SshModule.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/SshModule.java
@@ -21,5 +21,6 @@
   protected void configureCommands() {
     command(ShowSubscription.class);
     command(BranchManifest.class);
+    command(TagManifest.class);
   }
 }
diff --git a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifest.java b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifest.java
new file mode 100644
index 0000000..71e5aa6
--- /dev/null
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/TagManifest.java
@@ -0,0 +1,92 @@
+// 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.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.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;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@CommandMetaData(name = "tag", description = "tag all projects described in a manifest on the server")
+public class TagManifest extends SshCommand {
+
+  @Inject
+  private GitRepositoryManager gitRepoManager;
+
+  @Option(name = "-r", aliases = {"--manifest-repo"},
+          usage = "", required = true)
+  private String manifestRepo;
+
+  @Option(name = "-c", aliases = {"--manifest-commit-ish"},
+          usage = "", required = true)
+  private String manifestCommitish;
+
+  @Option(name = "-p", aliases = {"--manifest-path"},
+          usage = "", required = true)
+  private String manifestPath;
+
+  @Option(name = "-t", aliases = {"--new-tag"},
+      usage = "", required = true)
+  private String newTag;
+
+  @Override
+  protected void run() {
+    stdout.println("Tagging manifest:");
+    stdout.println(manifestRepo);
+    stdout.println(manifestCommitish);
+    stdout.println(manifestPath);
+
+
+    Project.NameKey p = new Project.NameKey(manifestRepo);
+    try {
+      Repository repo = gitRepoManager.openRepository(p);
+      ObjectId commitId = repo.resolve(manifestCommitish);
+      VersionedManifests vManifests = new VersionedManifests(manifestCommitish);
+      vManifests.load(repo, commitId);
+      CanonicalManifest manifests = new CanonicalManifest(vManifests);
+
+      Manifest manifest = manifests.getCanonicalManifest(manifestPath);
+
+      stdout.println("");
+      stdout.println("Tag '" + newTag +
+          "' will be created for the following projects:");
+      for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project proj :
+            manifest.getProject()) {
+        stdout.print(proj.getRevision());
+        stdout.print("\t");
+        stdout.println(proj.getName());
+      }
+
+      VersionedManifests.tagManifest(gitRepoManager, manifest, newTag);
+
+    } catch (IOException | ConfigInvalidException | ManifestReadException |
+        JAXBException | GitAPIException e) {
+      e.printStackTrace(stderr);
+    }
+  }
+}
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 91ffa7c..137724f 100644
--- a/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
+++ b/src/main/java/com/amd/gerrit/plugins/manifestsubscription/VersionedManifests.java
@@ -207,6 +207,39 @@
     saveFile(path, output.toByteArray());
   }
 
+  static void tagManifest(GitRepositoryManager gitRepoManager,
+                             Manifest manifest,
+                             final String tag)
+      throws GitAPIException, IOException {
+    Table<String, String, String> lookup = HashBasedTable.create();
+    String defaultRef = null;
+
+    if (manifest.getDefault() != null) {
+      defaultRef = manifest.getDefault().getRevision();
+    }
+
+    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 {
+        Project.NameKey p = new Project.NameKey(project.getName());
+        Repository db = gitRepoManager.openRepository(p);
+        Git git = new Git(db);
+
+        RevWalk walk = new RevWalk(db);
+        RevCommit commit = walk.parseCommit(db.resolve(hash));
+        git.tag().setName(tag).setObjectId(commit).setAnnotated(true).call();
+
+        git.close();
+        return true;
+      }
+    };
+
+    traverseManifestAndApplyOp(gitRepoManager, manifest.getProject(), defaultRef, op, lookup);
+  }
+
   static void branchManifest(GitRepositoryManager gitRepoManager,
                              Manifest manifest,
                              final String branch)
@@ -223,9 +256,7 @@
       public boolean apply(com.amd.gerrit.plugins.manifestsubscription.manifest.Project project,
                            String hash, String name,
                            GitRepositoryManager gitRepoManager) throws
-                                RefAlreadyExistsException,
-                                InvalidRefNameException, RefNotFoundException,
-                                GitAPIException, IOException {
+          GitAPIException, IOException {
         Project.NameKey p = new Project.NameKey(project.getName());
         Repository db = gitRepoManager.openRepository(p);
         Git git = new Git(db);
diff --git a/src/main/resources/Documentation/cmd-tag.md b/src/main/resources/Documentation/cmd-tag.md
new file mode 100644
index 0000000..50c04fb
--- /dev/null
+++ b/src/main/resources/Documentation/cmd-tag.md
@@ -0,0 +1,47 @@
+@PLUGIN@ tag
+==============
+
+NAME
+----
+@PLUGIN@ tag - Tag all projects on the server described by a manifest
+on the server.
+
+SYNOPSIS
+--------
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ tag
+  {-r/--manifest-repo <manifest repo>}
+  {-c/--manifest-commit-ish <manifest commit-ish>}
+  {-p/--manifest-path <manifest path>}
+  {-t/--new-tag <new tag name>}
+  [--help]
+```
+
+ACCESS
+------
+Caller must be a member of the privileged 'Administrators' group
+
+OPTIONS
+-------
+
+`-r/--manifest-repo <manifest repo>`
+`-c/--manifest-commit-ish <manifest commit-ish>`
+`-p/--manifest-path <manifest path>`
+: The manifest the taging operation is based on
+
+`-t/--new-tag <new tag name>`
+: The name of the tag that will be created on all projects in the manifest specified above
+
+EXAMPLES
+--------
+Tag all projects on the server described in the `default.xml` manifest in commit 5f51acb585b6a of repo `demo/build_manifest` to tag `releases/1.0.0`
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ tag -r demo/build_manifest -c 5f51acb585b6a -p default.xml -t releases/1.0.0
+
+```
+
+Tag all projects on the server described in the `default.xml` manifest in commit v0.9-15-g5f51acb of repo `demo/build_manifest` to tag `releases/1.0.0`
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ tag -r demo/build_manifest -c v0.9-15-g5f51acb -p default.xml -t releases/1.0.0
+
+```