Add new extension point for clone commands

The clone commands can be retrieved via REST from the
/config/server/info endpoint.

Bug: Issue 2208
Change-Id: I1a6bcc8eeea38ca30061bb380266e3b5cf7d3515
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index a86c1fd..5a5028d 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1767,15 +1767,17 @@
 [[download-commands]]
 == Download Commands
 
-Gerrit offers commands for downloading changes using different
-download schemes (e.g. for downloading via different network
-protocols). Plugins can contribute download schemes and download
-commands by implementing
-`com.google.gerrit.extensions.config.DownloadScheme` and
-`com.google.gerrit.extensions.config.DownloadCommand`.
+Gerrit offers commands for downloading changes and cloning projects
+using different download schemes (e.g. for downloading via different
+network protocols). Plugins can contribute download schemes, download
+commands and clone commands by implementing
+`com.google.gerrit.extensions.config.DownloadScheme`,
+`com.google.gerrit.extensions.config.DownloadCommand` and
+`com.google.gerrit.extensions.config.CloneCommand`.
 
-The download schemes and download commands which are used most often
-are provided by the Gerrit core plugin `download-commands`.
+The download schemes, download commands and clone commands which are
+used most often are provided by the Gerrit core plugin
+`download-commands`.
 
 [[included-in]]
 == Included In
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index cf49b3a..ece7450 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -68,6 +68,10 @@
             "Format Patch": "git fetch http://gerrithost:8080/${project} ${ref} \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
             "Pull": "git pull http://gerrithost:8080/${project} ${ref}",
             "Cherry Pick": "git fetch http://gerrithost:8080/${project} ${ref} \u0026\u0026 git cherry-pick FETCH_HEAD"
+          },
+          "clone_commands": {
+            "Clone": "git clone http://gerrithost:8080/${project}"
+            "Clone with commit-msg hook": "git clone http://gerrithost:8080/${project} \u0026\u0026 scp -p -P 29418 jdoe@gerrithost:hooks/commit-msg ${project}/.git/hooks/"
           }
         },
         "http": {
@@ -79,6 +83,10 @@
             "Format Patch": "git fetch http://jdoe@gerrithost:8080/${project} ${ref} \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
             "Pull": "git pull http://jdoe@gerrithost:8080/${project} ${ref}",
             "Cherry Pick": "git fetch http://jdoe@gerrithost:8080/${project} ${ref} \u0026\u0026 git cherry-pick FETCH_HEAD"
+          },
+          "clone_commands": {
+            "Clone": "git clone http://jdoe@gerrithost:8080/${project}",
+            "Clone with commit-msg hook": "git clone http://jdoe@gerrithost:8080/${project} \u0026\u0026 scp -p -P 29418 jdoe@gerrithost:hooks/commit-msg ${project}/.git/hooks/"
           }
         },
         "ssh": {
@@ -90,6 +98,10 @@
             "Format Patch": "git fetch ssh://jdoe@gerrithost:29418/${project} ${ref} \u0026\u0026 git format-patch -1 --stdout FETCH_HEAD",
             "Pull": "git pull ssh://jdoe@gerrithost:29418/${project} ${ref}",
             "Cherry Pick": "git fetch ssh://jdoe@gerrithost:29418/${project} ${ref} \u0026\u0026 git cherry-pick FETCH_HEAD"
+          },
+          "clone_commands": {
+            "Clone": "git clone ssh://jdoe@gerrithost:29418/${project}",
+            "Clone with commit-msg hook": "git clone ssh://jdoe@gerrithost:29418/${project} \u0026\u0026 scp -p -P 29418 jdoe@gerrithost:hooks/commit-msg ${project}/.git/hooks/"
           }
         }
       ],
@@ -1092,6 +1104,13 @@
 
 Empty, if accessed anonymously and the download scheme requires
 authentication.
+|`clone_commands`    ||
+Clone commands as a map which maps the command name to the clone
+command. In the clone command '${project}' is used as
+placeholder for the project name.
+
+Empty, if accessed anonymously and the download scheme requires
+authentication.
 |=================================
 
 [[entries-info]]
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CloneCommand.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CloneCommand.java
new file mode 100644
index 0000000..f773380
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/config/CloneCommand.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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.google.gerrit.extensions.config;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+@ExtensionPoint
+public abstract class CloneCommand {
+  /**
+   * Returns the clone command for the given download scheme and project.
+   *
+   * @param scheme the download scheme for which the command should be returned
+   * @param project the name of the project for which the clone command
+   *        should be returned
+   * @return the clone command
+   */
+  public abstract String getCommand(DownloadScheme scheme, String project);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 78802c9..a6acb99 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.audit.AuditModule;
 import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.config.CloneCommand;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.config.ExternalIncludedIn;
@@ -276,6 +277,7 @@
     DynamicSet.setOf(binder(), MessageOfTheDay.class);
     DynamicMap.mapOf(binder(), DownloadScheme.class);
     DynamicMap.mapOf(binder(), DownloadCommand.class);
+    DynamicMap.mapOf(binder(), CloneCommand.class);
     DynamicMap.mapOf(binder(), ExternalIncludedIn.class);
     DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
     DynamicSet.setOf(binder(), PatchSetWebLink.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 4791725..c79bb5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GitWebType;
+import com.google.gerrit.extensions.config.CloneCommand;
 import com.google.gerrit.extensions.config.DownloadCommand;
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -45,6 +46,7 @@
   private final Realm realm;
   private final DynamicMap<DownloadScheme> downloadSchemes;
   private final DynamicMap<DownloadCommand> downloadCommands;
+  private final DynamicMap<CloneCommand> cloneCommands;
   private final GetArchive.AllowedFormats archiveFormats;
   private final AllProjectsName allProjectsName;
   private final AllUsersName allUsersName;
@@ -58,6 +60,7 @@
       Realm realm,
       DynamicMap<DownloadScheme> downloadSchemes,
       DynamicMap<DownloadCommand> downloadCommands,
+      DynamicMap<CloneCommand> cloneCommands,
       GetArchive.AllowedFormats archiveFormats,
       AllProjectsName allProjectsName,
       AllUsersName allUsersName,
@@ -68,6 +71,7 @@
     this.realm = realm;
     this.downloadSchemes = downloadSchemes;
     this.downloadCommands = downloadCommands;
+    this.cloneCommands = cloneCommands;
     this.archiveFormats = archiveFormats;
     this.allProjectsName = allProjectsName;
     this.allUsersName = allUsersName;
@@ -82,7 +86,8 @@
     info.change = getChangeInfo(config);
     info.contactStore = getContactStoreInfo();
     info.download =
-        getDownloadInfo(downloadSchemes, downloadCommands, archiveFormats);
+        getDownloadInfo(downloadSchemes, downloadCommands, cloneCommands,
+            archiveFormats);
     info.gerrit = getGerritInfo(config, allProjectsName, allUsersName);
     info.gitWeb = getGitWebInfo(gitWebConfig);
     info.suggest = getSuggestInfo(config);
@@ -155,8 +160,10 @@
     return contactStore;
   }
 
-  private DownloadInfo getDownloadInfo(DynamicMap<DownloadScheme> downloadSchemes,
+  private DownloadInfo getDownloadInfo(
+      DynamicMap<DownloadScheme> downloadSchemes,
       DynamicMap<DownloadCommand> downloadCommands,
+      DynamicMap<CloneCommand> cloneCommands,
       GetArchive.AllowedFormats archiveFormats) {
     DownloadInfo info = new DownloadInfo();
     info.schemes = new HashMap<>();
@@ -164,7 +171,7 @@
       DownloadScheme scheme = e.getProvider().get();
       if (scheme.isEnabled() && scheme.getUrl("${project}") != null) {
         info.schemes.put(e.getExportName(),
-            getDownloadSchemeInfo(scheme, downloadCommands));
+            getDownloadSchemeInfo(scheme, downloadCommands, cloneCommands));
       }
     }
     info.archives = Lists.newArrayList(Iterables.transform(
@@ -179,7 +186,8 @@
   }
 
   private DownloadSchemeInfo getDownloadSchemeInfo(DownloadScheme scheme,
-      DynamicMap<DownloadCommand> downloadCommands) {
+      DynamicMap<DownloadCommand> downloadCommands,
+      DynamicMap<CloneCommand> cloneCommands) {
     DownloadSchemeInfo info = new DownloadSchemeInfo();
     info.url = scheme.getUrl("${project}");
     info.isAuthRequired = toBoolean(scheme.isAuthRequired());
@@ -195,6 +203,16 @@
       }
     }
 
+    info.cloneCommands = new HashMap<>();
+    for (DynamicMap.Entry<CloneCommand> e : cloneCommands) {
+      String commandName = e.getExportName();
+      CloneCommand command = e.getProvider().get();
+      String c = command.getCommand(scheme, "${project}");
+      if (c != null) {
+        info.cloneCommands.put(commandName, c);
+      }
+    }
+
     return info;
   }
 
@@ -282,6 +300,7 @@
     public Boolean isAuthRequired;
     public Boolean isAuthSupported;
     public Map<String, String> commands;
+    public Map<String, String> cloneCommands;
   }
 
   public static class GerritInfo {