Merge "SSH command list-projects for listing project imports"
diff --git a/BUCK b/BUCK
index 8f8013f..fb8a3f0 100644
--- a/BUCK
+++ b/BUCK
@@ -1,14 +1,13 @@
 include_defs('//bucklets/gerrit_plugin.bucklet')
 
-# TODO: support standalone Buck build with 2.11
-#if STANDALONE_MODE:
-#  HTTP_LIB = '//lib/http:http_lib'
-#  GSON = '//lib/gson:gson'
-#else:
-#  HTTP_LIB = '//plugins/importer/lib/http:http_lib'
-#  GSON = '//plugins/importer/lib/gson:gson'
-HTTP_LIB = '//plugins/importer/lib/http:http_lib'
-GSON = '//plugins/importer/lib/gson:gson'
+if STANDALONE_MODE:
+  HTTP_LIB = '//lib/http:http_lib'
+  GSON = '//lib/gson:gson'
+  LOG4J = '//lib/log:log4j'
+else:
+  HTTP_LIB = '//plugins/importer/lib/http:http_lib'
+  GSON = '//plugins/importer/lib/gson:gson'
+  LOG4J = '//plugins/importer/lib/log:log4j'
 
 gerrit_plugin(
   name = 'importer',
@@ -20,14 +19,14 @@
     'Gerrit-ApiVersion: 2.11-SNAPSHOT',
     'Gerrit-Module: com.googlesource.gerrit.plugins.importer.Module',
     'Gerrit-SshModule: com.googlesource.gerrit.plugins.importer.SshModule',
+    'Gerrit-HttpModule: com.googlesource.gerrit.plugins.importer.HttpModule',
   ],
   deps = [
     HTTP_LIB,
     GSON,
   ],
   provided_deps = [
-    '//lib/commons:validator',
-    '//lib/log:log4j'
+    LOG4J,
   ],
 )
 
diff --git a/lib/http/BUCK b/lib/http/BUCK
index 0b9d990..4de1df9 100644
--- a/lib/http/BUCK
+++ b/lib/http/BUCK
@@ -1,15 +1,12 @@
 include_defs('//bucklets/gerrit_plugin.bucklet')
 include_defs('//bucklets/maven_jar.bucklet')
 
-# TODO: support standalone Buck build with 2.11
-#if STANDALONE_MODE:
-#  COMMONS = '//lib/commons:commons_lib'
-#  LOG = '//lib/log:jcl-over-slf4j'
-#else:
-#  COMMONS = '//plugins/importer/lib/commons:commons_lib'
-#  LOG = '//plugins/importer/lib/log:jcl-over-slf4j'
-COMMONS = '//plugins/importer/lib/commons:commons_lib'
-LOG = '//plugins/importer/lib/log:jcl-over-slf4j'
+if STANDALONE_MODE:
+  COMMONS = '//lib/commons:commons_lib'
+  LOG = '//lib/log:jcl-over-slf4j'
+else:
+  COMMONS = '//plugins/importer/lib/commons:commons_lib'
+  LOG = '//plugins/importer/lib/log:jcl-over-slf4j'
 
 java_library(
   name = 'http_lib',
diff --git a/lib/log/BUCK b/lib/log/BUCK
index fc994d6..17e606b 100644
--- a/lib/log/BUCK
+++ b/lib/log/BUCK
@@ -6,3 +6,11 @@
   sha1 = '56003dcd0a31deea6391b9e2ef2f2dc90b205a92',
   license = 'slf4j',
 )
+
+maven_jar(
+  name = 'log4j',
+  id = 'log4j:log4j:1.2.17',
+  sha1 = '5af35056b4d257e4b64b9e8069c0746e8b08629f',
+  license = 'Apache2.0',
+  exclude = ['META-INF/LICENSE', 'META-INF/NOTICE'],
+)
diff --git a/pom.xml b/pom.xml
index 0870deb..2698efb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,7 @@
               <Gerrit-PluginName>importer</Gerrit-PluginName>
               <Gerrit-Module>com.googlesource.gerrit.plugins.importer.Module</Gerrit-Module>
               <Gerrit-SshModule>com.googlesource.gerrit.plugins.importer.SshModule</Gerrit-SshModule>
+              <Gerrit-HttpModule>com.googlesource.gerrit.plugins.importer.HttpModule</Gerrit-HttpModule>
 
               <Implementation-Vendor>Gerrit Code Review</Implementation-Vendor>
               <Implementation-URL>http://code.google.com/p/gerrit/</Implementation-URL>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java
new file mode 100644
index 0000000..eba963c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProject.java
@@ -0,0 +1,105 @@
+// 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.googlesource.gerrit.plugins.importer;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import com.googlesource.gerrit.plugins.importer.CopyProject.Input;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+
+import java.io.IOException;
+
+@Singleton
+@RequiresCapability(CopyProjectCapability.ID)
+class CopyProject implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
+  public static class Input {
+    public String name;
+  }
+
+  private final ImportProject.Factory importProjectFactory;
+  private final String canonicalWebUrl;
+  private final Provider<CurrentUser> currentUserProvider;
+  private final String pluginName;
+
+  @Inject
+  CopyProject(
+      ImportProject.Factory importProjectFactory,
+      @CanonicalWebUrl String canonicalWebUrl,
+      Provider<CurrentUser> currentUserProvider,
+      @PluginName String pluginName) {
+    this.importProjectFactory = importProjectFactory;
+    this.canonicalWebUrl = canonicalWebUrl;
+    this.currentUserProvider = currentUserProvider;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public Response<String> apply(ProjectResource rsrc, Input input)
+      throws RestApiException, OrmException, IOException, ValidationException,
+      GitAPIException, NoSuchChangeException, NoSuchAccountException {
+    if (Strings.isNullOrEmpty(input.name)) {
+      throw new BadRequestException("name is required");
+    }
+
+    AccountState s = ((IdentifiedUser) currentUserProvider.get()).state();
+
+    ImportProject.Input in = new ImportProject.Input();
+    in.from = canonicalWebUrl;
+    in.name = rsrc.getName();
+    in.user = s.getUserName();
+    in.pass = s.getPassword(s.getUserName());
+
+    return importProjectFactory.create(new Project.NameKey(input.name))
+        .apply(new ConfigResource(), in);
+  }
+
+  @Override
+  public UiAction.Description getDescription(ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Copy...")
+        .setTitle(String.format("Copy project %s", rsrc.getName()))
+        .setVisible(canCopy(rsrc));
+  }
+
+  private boolean canCopy(ProjectResource rsrc) {
+    CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+    return ctl.canAdministrateServer()
+        || ctl.canPerform(pluginName + "-" + CopyProjectCapability.ID);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProjectCapability.java b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProjectCapability.java
new file mode 100644
index 0000000..fe75fa6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/CopyProjectCapability.java
@@ -0,0 +1,26 @@
+// 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.googlesource.gerrit.plugins.importer;
+
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+
+public class CopyProjectCapability extends CapabilityDefinition {
+  public final static String ID = "copyProject";
+
+  @Override
+  public String getDescription() {
+    return "Copy Project";
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
new file mode 100644
index 0000000..ee62329
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
@@ -0,0 +1,32 @@
+// 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.googlesource.gerrit.plugins.importer;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.gerrit.httpd.plugins.HttpPluginModule;
+
+public class HttpModule extends HttpPluginModule {
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("copy-project.js"));
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("resume-copy-project.js"));
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("resume-project-import.js"));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java
index 4cd58dd..34786d3 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpResponse.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.Preconditions;
 
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -24,12 +25,12 @@
 import java.io.Reader;
 import java.nio.ByteBuffer;
 
-public class HttpResponse {
+public class HttpResponse implements AutoCloseable {
 
-  protected org.apache.http.HttpResponse response;
+  protected CloseableHttpResponse response;
   protected Reader reader;
 
-  HttpResponse(org.apache.http.HttpResponse response) {
+  HttpResponse(CloseableHttpResponse response) {
     this.response = response;
   }
 
@@ -40,10 +41,15 @@
     return reader;
   }
 
-  public void consume() throws IllegalStateException, IOException {
-    Reader reader = getReader();
-    if (reader != null) {
-      while (reader.read() != -1);
+  @Override
+  public void close() throws IOException {
+    try {
+      Reader reader = getReader();
+      if (reader != null) {
+        while (reader.read() != -1);
+      }
+    } finally {
+      response.close();
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java
index 31329b3..cf49557 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpSession.java
@@ -18,11 +18,11 @@
 
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.conn.ssl.X509HostnameVerifier;
 import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 
 import java.io.IOException;
@@ -43,7 +43,7 @@
   protected final String url;
   private final String user;
   private final String pass;
-  private HttpClient client;
+  private CloseableHttpClient client;
 
   public HttpSession(String url, String user, String pass) {
     this.url = CharMatcher.is('/').trimTrailingFrom(url);
@@ -56,7 +56,7 @@
     return new HttpResponse(getClient().execute(get));
   }
 
-  protected HttpClient getClient() throws IOException {
+  protected CloseableHttpClient getClient() throws IOException {
     if (client == null) {
       URI uri = URI.create(url);
       BasicCredentialsProvider creds = new BasicCredentialsProvider();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportJson.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportJson.java
index b92db2b..f5cd59a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportJson.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportJson.java
@@ -59,6 +59,7 @@
     if (info == null) {
       info = new ImportProjectInfo();
       info.from = input.from;
+      info.name = input.name;
       info.parent = input.parent;
       info.imports = new ArrayList<>();
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLog.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLog.java
index bf1d1e9..9a1ef18 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLog.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLog.java
@@ -27,6 +27,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.apache.log4j.AsyncAppender;
 import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
@@ -40,7 +41,8 @@
   public static String ACCOUNT_ID = "accountId";
   public static String USER_NAME = "userName";
   public static String FROM = "from";
-  public static String PROJECT_NAME = "projectName";
+  public static String SRC_PROJECT_NAME = "srcProjectName";
+  public static String TARGET_PROJECT_NAME = "targetProjectName";
   public static String ERROR = "error";
 
   private final SystemLog systemLog;
@@ -57,12 +59,13 @@
     this.auditService = auditService;
   }
 
-  public void onImport(IdentifiedUser user, Project.NameKey project, String from) {
-    onImport(user, project, from, null);
+  public void onImport(IdentifiedUser user, Project.NameKey srcProject,
+      Project.NameKey targetProject, String from) {
+    onImport(user, srcProject, targetProject, from, null);
   }
 
-  public void onImport(IdentifiedUser user, Project.NameKey project,
-      String from, Exception ex) {
+  public void onImport(IdentifiedUser user, Project.NameKey srcProject,
+      Project.NameKey targetProject, String from, Exception ex) {
     long ts = TimeUtil.nowMs();
     LoggingEvent event = new LoggingEvent( //
         Logger.class.getName(), // fqnOfCategoryClass
@@ -84,7 +87,8 @@
     event.setProperty(ACCOUNT_ID, user.getAccountId().toString());
     event.setProperty(USER_NAME, user.getUserName());
     event.setProperty(FROM, from);
-    event.setProperty(PROJECT_NAME, project.get());
+    event.setProperty(SRC_PROJECT_NAME, srcProject.get());
+    event.setProperty(TARGET_PROJECT_NAME, targetProject.get());
 
     if (ex != null) {
       event.setProperty(ERROR, ex.toString());
@@ -92,7 +96,7 @@
 
     log.callAppenders(event);
 
-    audit(user, ts, project, from, ex);
+    audit(user, ts, srcProject, from, ex);
   }
 
   private void audit(IdentifiedUser user, long ts, Project.NameKey project,
@@ -121,9 +125,11 @@
   public void start() {
     if (!started) {
       Logger importLogger = LogManager.getLogger(IMPORT_LOG_NAME);
-      importLogger.removeAllAppenders();
-      importLogger.addAppender(systemLog.createAsyncAppender(
-          importLogger.getName(), new ImportLogLayout()));
+      String loggerName = importLogger.getName();
+      AsyncAppender asyncAppender = systemLog.createAsyncAppender(
+          loggerName, new ImportLogLayout());
+      importLogger.removeAppender(loggerName);
+      importLogger.addAppender(asyncAppender);
       importLogger.setAdditivity(false);
       started = true;
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLogLayout.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLogLayout.java
index 8dc1bf0..cc29340 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLogLayout.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportLogLayout.java
@@ -43,12 +43,12 @@
    *
    * A successful project import will result in a log entry like this:
    *   [2015-03-05 09:13:28,912 +0100] INFO 1000000 admin OK \
-   *   https://some-gerrit-server:8080 myProject
+   *   https://some-gerrit-server:8080 srcName targetName
    *
    * The log entry for a failed project import will look like this:
    *   [2015-03-05 12:14:30,180 +0100] ERROR 1000000 admin FAIL \
-   *   https://some-gerrit-server:8080 myProject com.google.gwtorm.server.OrmException: \
-   *   Failed to access the database
+   *   https://some-gerrit-server:8080 srcName targetName \
+   *   com.google.gwtorm.server.OrmException: Failed to access the database
    */
   @Override
   public String format(LoggingEvent event) {
@@ -70,7 +70,8 @@
     buf.append(event.getMessage());
 
     req(ImportLog.FROM, buf, event);
-    req(ImportLog.PROJECT_NAME, buf, event);
+    req(ImportLog.SRC_PROJECT_NAME, buf, event);
+    req(ImportLog.TARGET_PROJECT_NAME, buf, event);
     opt(ImportLog.ERROR, buf, event);
 
     buf.append('\n');
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java
index 95303da..76fefdd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java
@@ -59,6 +59,7 @@
 class ImportProject implements RestModifyView<ConfigResource, Input> {
   public static class Input {
     public String from;
+    public String name;
     public String user;
     public String pass;
     public String parent;
@@ -77,7 +78,7 @@
   }
 
   interface Factory {
-    ImportProject create(Project.NameKey project);
+    ImportProject create(Project.NameKey targetProject);
   }
 
   private static Logger log = LoggerFactory.getLogger(ImportProject.class);
@@ -93,7 +94,8 @@
   private final ImportLog importLog;
   private final File lockRoot;
 
-  private final Project.NameKey project;
+  private final Project.NameKey targetProject;
+  private Project.NameKey srcProject;
   private Project.NameKey parent;
 
   private Writer err;
@@ -110,7 +112,7 @@
       ImportJson importJson,
       ImportLog importLog,
       @PluginData File data,
-      @Assisted Project.NameKey project) {
+      @Assisted Project.NameKey targetProject) {
     this.projectCache = projectCache;
     this.openRepoStep = openRepoStep;
     this.configRepoStep = configRepoStep;
@@ -121,7 +123,7 @@
     this.importJson = importJson;
     this.importLog = importLog;
     this.lockRoot = data;
-    this.project = project;
+    this.targetProject = targetProject;
   }
 
   void setErr(Writer err) {
@@ -155,6 +157,7 @@
       input.user = user;
       input.pass = pass;
       input.from = info.from;
+      input.name = info.name;
       input.parent = info.parent;
 
       return apply(lockFile, input, info);
@@ -173,33 +176,33 @@
     ProgressMonitor pm = err != null ? new TextProgressMonitor(err) :
         NullProgressMonitor.INSTANCE;
 
-    checkProjectInSource(input, pm);
-
     try {
+      srcProject = !Strings.isNullOrEmpty(input.name)
+          ? new Project.NameKey(input.name) : targetProject;
+      checkProjectInSource(input, pm);
       setParentProjectName(input, pm);
       checkPreconditions(pm);
-      Repository repo = openRepoStep.open(project, resume, pm);
+      Repository repo = openRepoStep.open(targetProject, resume, pm);
       try {
         ImportJson.persist(lockFile, importJson.format(input, info), pm);
-        configRepoStep.configure(repo, project, input.from, pm);
+        configRepoStep.configure(repo, srcProject, input.from, pm);
         gitFetchStep.fetch(input.user, input.pass, repo, pm);
-        configProjectStep.configure(project, parent, pm);
+        configProjectStep.configure(targetProject, parent, pm);
         replayChangesFactory.create(input.from, input.user, input.pass, repo,
-            project, resume, pm)
-            .replay();
+            srcProject, targetProject, resume, pm).replay();
       } finally {
         repo.close();
       }
-      importLog.onImport((IdentifiedUser) currentUser.get(), project,
-          input.from);
+      importLog.onImport((IdentifiedUser) currentUser.get(), srcProject,
+          targetProject, input.from);
     } catch (BadRequestException e) {
       throw e;
     } catch (Exception e) {
-      importLog.onImport((IdentifiedUser) currentUser.get(), project,
-          input.from, e);
+      importLog.onImport((IdentifiedUser) currentUser.get(), srcProject,
+          targetProject, input.from, e);
       log.error(format("Unable to transfer project '%s' from"
           + " source gerrit host '%s'.",
-          project.get(), input.from), e);
+          srcProject.get(), input.from), e);
       throw e;
     }
 
@@ -209,7 +212,7 @@
   private void checkProjectInSource(Input input, ProgressMonitor pm)
       throws IOException, BadRequestException {
     pm.beginTask("Check source project", 1);
-    new RemoteApi(input.from, input.user, input.pass).getProject(project.get());
+    new RemoteApi(input.from, input.user, input.pass).getProject(srcProject.get());
     updateAndEnd(pm);
   }
 
@@ -217,12 +220,12 @@
       throws IOException, BadRequestException {
     pm.beginTask("Set parent project", 1);
     if (parent == null) {
-      if (input.parent != null) {
+      if (!Strings.isNullOrEmpty(input.parent)) {
         parent = new Project.NameKey(input.parent);
       } else {
         parent = new Project.NameKey(
             new RemoteApi(input.from, input.user, input.pass)
-                .getProject(project.get()).parent);
+                .getProject(srcProject.get()).parent);
       }
     }
     updateAndEnd(pm);
@@ -239,7 +242,7 @@
   }
 
   private LockFile lockForImport() throws ResourceConflictException {
-    File importStatus = new File(lockRoot, project.get());
+    File importStatus = new File(lockRoot, targetProject.get());
     LockFile lockFile = new LockFile(importStatus, FS.DETECTED);
     try {
       if (lockFile.lock()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProjectInfo.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProjectInfo.java
index 92364e4..f35ca7b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProjectInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProjectInfo.java
@@ -18,6 +18,7 @@
 
 public class ImportProjectInfo {
   public String from;
+  public String name;
   public String parent;
   public List<ImportInfo> imports;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java b/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java
index fb66805..c45d18a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.importer;
 
 import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
 import static com.googlesource.gerrit.plugins.importer.ImportProjectResource.IMPORT_PROJECT_KIND;
 
 import com.google.gerrit.extensions.annotations.Exports;
@@ -31,6 +32,9 @@
     bind(CapabilityDefinition.class)
         .annotatedWith(Exports.named(ImportCapability.ID))
         .to(ImportCapability.class);
+    bind(CapabilityDefinition.class)
+        .annotatedWith(Exports.named(CopyProjectCapability.ID))
+        .to(CopyProjectCapability.class);
     install(new RestApiModule() {
       @Override
       protected void configure() {
@@ -38,6 +42,10 @@
         child(CONFIG_KIND, "projects").to(ProjectsCollection.class);
         get(IMPORT_PROJECT_KIND).to(GetImportedProject.class);
         put(IMPORT_PROJECT_KIND, "resume").to(ResumeProjectImport.class);
+
+        put(PROJECT_KIND, "copy").to(CopyProject.class);
+        put(PROJECT_KIND, "copy.resume").to(ResumeCopyProject.class);
+        put(PROJECT_KIND, "import.resume").to(ResumeProjectImport.OnProjects.class);
       }
     });
     bind(LifecycleListener.class)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ProjectCommand.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ProjectCommand.java
index aca1b61..92ae1d2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ProjectCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ProjectCommand.java
@@ -45,6 +45,11 @@
       usage = "URL of the remote system from where the project should be imported")
   private String url;
 
+  @Option(name = "--name", required = false, metaVar = "NAME",
+      usage = "name of project in source system (if not specified it is"
+          + " assumed to be the same name as in the target system)")
+  private String name;
+
   @Option(name = "--user", aliases = {"-u"}, required = true, metaVar = "NAME",
       usage = "user on remote system")
   private String user;
@@ -61,7 +66,7 @@
   private boolean quiet;
 
   @Argument(index = 0, required = true, metaVar = "NAME",
-      usage = "name of the project to be imported")
+      usage = "name of the project in target system")
   private String project;
 
   @Inject
@@ -73,6 +78,7 @@
       NoSuchAccountException {
     ImportProject.Input input = new ImportProject.Input();
     input.from = url;
+    input.name = name;
     input.user = user;
     input.pass = readPassword();
     if (!Strings.isNullOrEmpty(parent)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java b/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
index d48ca5d..967069e 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
@@ -42,9 +42,10 @@
   public ProjectInfo getProject(String projectName) throws IOException,
       BadRequestException {
     String endPoint = "/projects/" + projectName;
-    RestResponse r = checkedGet(endPoint);
-    return newGson().fromJson(r.getReader(),
-        new TypeToken<ProjectInfo>() {}.getType());
+    try (RestResponse r = checkedGet(endPoint)) {
+      return newGson().fromJson(r.getReader(),
+          new TypeToken<ProjectInfo>() {}.getType());
+    }
   }
 
   public List<ChangeInfo> queryChanges(String projectName) throws IOException,
@@ -59,10 +60,12 @@
                 ListChangesOption.CURRENT_REVISION,
                 ListChangesOption.ALL_REVISIONS,
                 ListChangesOption.ALL_COMMITS)));
-    RestResponse r = checkedGet(endPoint);
-    List<ChangeInfo> result =
-        newGson().fromJson(r.getReader(),
+
+    List<ChangeInfo> result;
+    try (RestResponse r = checkedGet(endPoint)) {
+      result = newGson().fromJson(r.getReader(),
             new TypeToken<List<ChangeInfo>>() {}.getType());
+    }
 
     for (ChangeInfo c : result) {
       for (Map.Entry<String, RevisionInfo> e : c.revisions.entrySet()) {
@@ -76,10 +79,11 @@
   public Iterable<CommentInfo> getComments(int changeId, String rev)
       throws IOException, BadRequestException {
     String endPoint = "/changes/" + changeId + "/revisions/" + rev + "/comments";
-    RestResponse r = checkedGet(endPoint);
-    Map<String, List<CommentInfo>> result =
-        newGson().fromJson(r.getReader(),
-            new TypeToken<Map<String, List<CommentInfo>>>() {}.getType());
+    Map<String, List<CommentInfo>> result;
+    try (RestResponse r = checkedGet(endPoint)) {
+      result = newGson().fromJson(r.getReader(),
+              new TypeToken<Map<String, List<CommentInfo>>>() {}.getType());
+    }
     for (Map.Entry<String, List<CommentInfo>> e : result.entrySet()) {
       for (CommentInfo i : e.getValue()) {
         i.path = e.getKey();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
index 42e1e43..6fbd8a0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
@@ -50,7 +50,8 @@
         @Assisted("user") String user,
         @Assisted("password") String password,
         Repository repo,
-        Project.NameKey name,
+        @Assisted("srcProject") Project.NameKey srcProject,
+        @Assisted("targetProject") Project.NameKey targetProject,
         boolean resume,
         ProgressMonitor pm);
   }
@@ -68,7 +69,8 @@
   private final String fromGerrit;
   private final RemoteApi api;
   private final Repository repo;
-  private final Project.NameKey name;
+  private final Project.NameKey srcProject;
+  private final Project.NameKey targetProject;
   private final boolean resume;
   private final ProgressMonitor pm;
 
@@ -88,7 +90,8 @@
       @Assisted("user") String user,
       @Assisted("password") String password,
       @Assisted Repository repo,
-      @Assisted Project.NameKey name,
+      @Assisted("srcProject") Project.NameKey srcProject,
+      @Assisted("targetProject") Project.NameKey targetProject,
       @Assisted boolean resume,
       @Assisted ProgressMonitor pm) {
     this.replayRevisionsFactory = replayRevisionsFactory;
@@ -104,15 +107,16 @@
     this.fromGerrit = fromGerrit;
     this.api = new RemoteApi(fromGerrit, user, password);
     this.repo = repo;
-    this.name = name;
-    this.pm = pm;
+    this.srcProject = srcProject;
+    this.targetProject = targetProject;
     this.resume = resume;
+    this.pm = pm;
   }
 
   void replay() throws IOException, OrmException,
       NoSuchAccountException, NoSuchChangeException, RestApiException,
       ValidationException {
-    List<ChangeInfo> changes = api.queryChanges(name.get());
+    List<ChangeInfo> changes = api.queryChanges(srcProject.get());
 
     pm.beginTask("Replay Changes", changes.size());
     RevWalk rw = new RevWalk(repo);
@@ -159,7 +163,7 @@
   private Change findChange(ChangeInfo c) throws OrmException {
     List<Change> changes = ChangeData.asChanges(
         queryProvider.get().byBranchKey(
-            new Branch.NameKey(name, fullName(c.branch)),
+            new Branch.NameKey(targetProject, fullName(c.branch)),
             new Change.Key(c.changeId)));
     if (changes.isEmpty()) {
       return null;
@@ -175,8 +179,7 @@
 
     Change change =
         new Change(new Change.Key(c.changeId), changeId, accountUtil.resolveUser(c.owner),
-            new Branch.NameKey(new Project.NameKey(c.project),
-            fullName(c.branch)), c.created);
+            new Branch.NameKey(targetProject, fullName(c.branch)), c.created);
     change.setStatus(Change.Status.forChangeStatus(c.status));
     change.setTopic(c.topic);
     change.setLastUpdatedOn(c.updated);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java b/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java
index 512db6e..24f6831 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RestResponse.java
@@ -16,13 +16,15 @@
 
 import static com.google.gerrit.httpd.restapi.RestApiServlet.JSON_MAGIC;
 
+import org.apache.http.client.methods.CloseableHttpResponse;
+
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
 
 public class RestResponse extends HttpResponse {
 
-  RestResponse(org.apache.http.HttpResponse response) {
+  RestResponse(CloseableHttpResponse response) {
     super(response);
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java
new file mode 100644
index 0000000..00e22e1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeCopyProject.java
@@ -0,0 +1,124 @@
+// 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.googlesource.gerrit.plugins.importer;
+
+import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import com.googlesource.gerrit.plugins.importer.CopyProject.Input;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+
+import java.io.IOException;
+
+@Singleton
+@RequiresCapability(CopyProjectCapability.ID)
+class ResumeCopyProject implements RestModifyView<ProjectResource, Input>,
+    UiAction<ProjectResource> {
+  private final ResumeProjectImport resumeProjectImport;
+  private final ProjectsCollection projectsCollection;
+  private final Provider<CurrentUser> currentUserProvider;
+  private final String pluginName;
+  private final String canonicalWebUrl;
+  private final ProjectCache projectCache;
+
+  @Inject
+  ResumeCopyProject(
+      ResumeProjectImport resumeProjectImport,
+      ProjectsCollection projectsCollection,
+      Provider<CurrentUser> currentUserProvider,
+      @PluginName String pluginName,
+      @CanonicalWebUrl String canonicalWebUrl,
+      ProjectCache projectCache) {
+    this.resumeProjectImport = resumeProjectImport;
+    this.projectsCollection = projectsCollection;
+    this.currentUserProvider = currentUserProvider;
+    this.pluginName = pluginName;
+    this.canonicalWebUrl = canonicalWebUrl;
+    this.projectCache = projectCache;
+  }
+
+  @Override
+  public Response<String> apply(ProjectResource rsrc, Input input)
+      throws RestApiException, IOException, OrmException, ValidationException,
+      GitAPIException, NoSuchChangeException, NoSuchAccountException {
+    AccountState s = ((IdentifiedUser) currentUserProvider.get()).state();
+
+    ResumeProjectImport.Input in = new ResumeProjectImport.Input();
+    in.user = s.getUserName();
+    in.pass = s.getPassword(s.getUserName());
+
+    ImportProjectResource projectResource =
+        projectsCollection.parse(new ConfigResource(),
+            IdString.fromDecoded(rsrc.getName()));
+    return resumeProjectImport.apply(projectResource, in);
+  }
+
+  @Override
+  public UiAction.Description getDescription(
+      ProjectResource rsrc) {
+    return new UiAction.Description()
+        .setLabel("Resume Copy...")
+        .setTitle(String.format("Resume copy for project %s", rsrc.getName()))
+        .setVisible(canResumeCopy(rsrc) && isCopied(rsrc));
+  }
+
+  private boolean canResumeCopy(ProjectResource rsrc) {
+    CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+    return ctl.canAdministrateServer()
+        || (ctl.canPerform(pluginName + "-" + CopyProjectCapability.ID)
+            && rsrc.getControl().isOwner());
+  }
+
+  private boolean isCopied(ProjectResource rsrc) {
+    try {
+      ImportProjectResource projectResource =
+          projectsCollection.parse(new ConfigResource(),
+              IdString.fromDecoded(rsrc.getName()));
+      ImportProjectInfo info = projectResource.getInfo();
+      if (!canonicalWebUrl.equals(info.from)) {
+        // no copy, but an import from another system
+        return false;
+      }
+
+      // check that source project still exists
+      return projectCache.get(new Project.NameKey(info.name)) != null;
+    } catch (ResourceNotFoundException | IOException e) {
+      return false;
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java
index 40abb1e..31e8671 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java
@@ -16,15 +16,25 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import com.googlesource.gerrit.plugins.importer.ResumeProjectImport.Input;
@@ -61,4 +71,70 @@
     return importProjectFactory.create(rsrc.getName())
         .resume(input.user, input.pass, rsrc.getImportStatus());
   }
+
+  public static class OnProjects implements
+      RestModifyView<ProjectResource, Input>, UiAction<ProjectResource> {
+    private final ProjectsCollection projectsCollection;
+    private final ResumeProjectImport resumeProjectImport;
+    private final Provider<CurrentUser> currentUserProvider;
+    private final String pluginName;
+    private final String canonicalWebUrl;
+
+    @Inject
+    public OnProjects(
+        ProjectsCollection projectsCollection,
+        ResumeProjectImport resumeProjectImport,
+        Provider<CurrentUser> currentUserProvider,
+        @PluginName String pluginName,
+        @CanonicalWebUrl String canonicalWebUrl) {
+      this.projectsCollection = projectsCollection;
+      this.resumeProjectImport = resumeProjectImport;
+      this.currentUserProvider = currentUserProvider;
+      this.pluginName = pluginName;
+      this.canonicalWebUrl = canonicalWebUrl;
+    }
+
+    @Override
+    public Response<String> apply(ProjectResource rsrc, Input input)
+        throws RestApiException, IOException, OrmException,
+        ValidationException, GitAPIException, NoSuchChangeException,
+        NoSuchAccountException {
+      ImportProjectResource projectResource =
+          projectsCollection.parse(new ConfigResource(),
+              IdString.fromDecoded(rsrc.getName()));
+      return resumeProjectImport.apply(projectResource, input);
+    }
+
+    @Override
+    public UiAction.Description getDescription(ProjectResource rsrc) {
+      return new UiAction.Description()
+          .setLabel("Resume Import...")
+          .setTitle(String.format("Resume import for project %s", rsrc.getName()))
+          .setVisible(canResumeImport(rsrc) && isImported(rsrc));
+    }
+
+    private boolean canResumeImport(ProjectResource rsrc) {
+      CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+      return ctl.canAdministrateServer()
+          || (ctl.canPerform(pluginName + "-" + ImportCapability.ID)
+              && rsrc.getControl().isOwner());
+    }
+
+    private boolean isImported(ProjectResource rsrc) {
+      try {
+        ImportProjectResource projectResource =
+            projectsCollection.parse(new ConfigResource(),
+                IdString.fromDecoded(rsrc.getName()));
+        ImportProjectInfo info = projectResource.getInfo();
+        if (canonicalWebUrl.equals(info.from)) {
+          // no import, but a copy within the same system
+          return false;
+        }
+
+        return true;
+      } catch (ResourceNotFoundException | IOException e) {
+        return false;
+      }
+    }
+  }
 }
diff --git a/src/main/resources/Documentation/cmd-project.md b/src/main/resources/Documentation/cmd-project.md
index e02cbcd..87b1363 100644
--- a/src/main/resources/Documentation/cmd-project.md
+++ b/src/main/resources/Documentation/cmd-project.md
@@ -10,6 +10,7 @@
 ```
 ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ project \
   --from <URL> | -f <URL> \
+  [--name <NAME>] \
   --user <USER> | -u <USER> \
   --pass - | <PASS> \
   [--parent <NAME>] \
@@ -37,6 +38,10 @@
 `--from`
 :	URL of the remote system from where the project should be imported.
 
+`--name`
+:	Name of the project in the source system.
+	If not specified it is assumed to be the same name as in the target system.
+
 `--pass`
 :	Password of remote user.
 
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index 5893086..74b99de 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -69,6 +69,7 @@
   {
     "myProject": {
       "from": "http://localhost:8081/",
+      "name": "myProject",
       "imports": [
         {
           "timestamp": "2015-03-11 09:14:21.748000000",
@@ -82,8 +83,9 @@
         }
       ]
     },
-    "myOtherProject": {
+    "myProject2": {
       "from": "http://localhost:8081/",
+      "name": "projectToBeRenamed",
       "imports": [
         {
           "timestamp": "2015-03-11 09:16:04.511000000",
@@ -128,6 +130,7 @@
   )]}'
   {
     "from": "http://localhost:8081/",
+    "name": "myProject",
     "imports": [
       {
         "timestamp": "2015-03-11 09:14:21.748000000",
@@ -188,6 +191,7 @@
 
 * _from_: URL of the remote system from where the project should be
 imported.
+* _name_: Name of the project in the source system.
 * _parent_: (Optional) Name of the parent project in the target system.
 * _imports_: List of past imports as [ImportInfo](#import-info)
 entities.
@@ -199,6 +203,9 @@
 
 * _from_: URL of the remote system from where the project should be
 imported.
+* _name_: (Optional) Name of the project in the source system.
+If not specified it is assumed to be the same name as in the target
+system.
 * _user_: User on remote system.
 * _pass_: Password of remote user.
 * _parent_: (Optional) Name of the parent project in the target system.
diff --git a/src/main/resources/Documentation/rest-api-projects.md b/src/main/resources/Documentation/rest-api-projects.md
new file mode 100644
index 0000000..ebc443f
--- /dev/null
+++ b/src/main/resources/Documentation/rest-api-projects.md
@@ -0,0 +1,95 @@
+@PLUGIN@ - /projects/ REST API
+==============================
+
+This page describes the REST endpoints that are added by the @PLUGIN@
+plugin.
+
+Please also take note of the general information on the
+[REST API](../../../Documentation/rest-api.html).
+
+<a id="importer-endpoints"> Importer Endpoints
+----------------------------------------------
+
+### <a id="copy-project"> Copy Project
+_PUT /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/@PLUGIN@~copy_
+
+Copies a project.
+
+Information about the copy target must be provided in the request body
+as a [CopyProjectInput](#copy-project-input) entity.
+
+Caller must be a member of a group that is granted the 'CopyProject'
+capability (provided by this plugin) or the 'Administrate Server'
+capability.
+
+#### Request
+
+```
+  PUT /projects/myProject/@PLUGIN@~copy HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "name": "myProjectCopy"
+  }
+```
+
+### <a id="resume-copy-project"> Resume Copy Project
+_PUT /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/@PLUGIN@~copy.resume_
+
+Resumes copying to a project from the original copy source.
+
+Caller must be a member of a group that is granted the 'CopyProject'
+capability (provided by this plugin) or the 'Administrate Server'
+capability.
+
+#### Request
+
+```
+  PUT /projects/myProjectCopy/@PLUGIN@~copy.resume HTTP/1.0
+```
+
+### <a id="resume-project-import"> Resume Project Import
+_PUT /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/@PLUGIN@~import.resume_
+
+Resumes importing to a project from the original copy source.
+
+Information about the import resume must be provided in the request
+body as a [ImportResumeInput](rest-api-config.html#import-resume-input)
+entity.
+
+Caller must be a member of a group that is granted the 'ImportProject'
+capability (provided by this plugin) or the 'Administrate Server'
+capability.
+
+#### Request
+
+```
+  PUT /projects/myProjectCopy/@PLUGIN@~import.resume HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "user": "myUser",
+    "pass": "myPassword"
+  }
+```
+
+
+<a id="json-entities">JSON Entities
+-----------------------------------
+
+### <a id="copy-project-input"></a>CopyProjectInput
+
+The `CopyProjectInput` entity contains information about a the copy
+target.
+
+* _name_: The target project name.
+
+
+SEE ALSO
+--------
+
+* [Config related REST endpoints](../../../Documentation/rest-api-projects.html)
+
+GERRIT
+------
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/src/main/resources/static/copy-project.js b/src/main/resources/static/copy-project.js
new file mode 100644
index 0000000..a7bc91d
--- /dev/null
+++ b/src/main/resources/static/copy-project.js
@@ -0,0 +1,42 @@
+// 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.
+
+Gerrit.install(function(self) {
+    function onCopyProject(c) {
+      var t = c.textfield();
+      var b = c.button('Copy',
+        {onclick: function(){
+          c.call(
+            {name: t.value},
+            function(r) {
+              c.hide();
+              window.alert('The project: "'
+                + c.project
+                + '" was copied to "'
+                + t.value
+                + '".'),
+              Gerrit.go('/admin/projects/' + t.value);
+            });
+        }});
+      c.popup(c.div(
+        c.msg('Copy project "'
+          + c.project
+          + '" to '),
+        t,
+        c.br(),
+        c.br(),
+        b));
+    }
+    self.onAction('project', 'copy', onCopyProject);
+  });
diff --git a/src/main/resources/static/resume-copy-project.js b/src/main/resources/static/resume-copy-project.js
new file mode 100644
index 0000000..9d61631
--- /dev/null
+++ b/src/main/resources/static/resume-copy-project.js
@@ -0,0 +1,38 @@
+// 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.
+
+Gerrit.install(function(self) {
+    function onResumeCopyProject(c) {
+      var b = c.button('Resume',
+        {onclick: function(){
+          c.call(
+            {},
+            function(r) {
+              c.hide();
+              window.alert('Copy for project "'
+                + c.project
+                + '" was resumed."'),
+              Gerrit.go('/admin/projects/' + c.project);
+            });
+        }});
+      c.popup(c.div(
+        c.msg('Resume copy for project "'
+          + c.project
+          + '" ?'),
+        c.br(),
+        c.br(),
+        b));
+    }
+    self.onAction('project', 'copy.resume', onResumeCopyProject);
+  });
diff --git a/src/main/resources/static/resume-project-import.js b/src/main/resources/static/resume-project-import.js
new file mode 100644
index 0000000..eaf752d
--- /dev/null
+++ b/src/main/resources/static/resume-project-import.js
@@ -0,0 +1,45 @@
+// 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.
+
+Gerrit.install(function(self) {
+    function onResumeProjectImport(c) {
+      var u = c.textfield();
+      var p = c.textfield();
+      p.type = 'password';
+      var b = c.button('Resume',
+        {onclick: function(){
+          c.call(
+            {user: u.value, pass: p.value},
+            function(r) {
+              c.hide();
+              window.alert('Import for project "'
+                + c.project
+                + '" was resumed."'),
+              Gerrit.go('/admin/projects/' + c.project);
+            });
+        }});
+      c.popup(c.div(
+        c.msg('Resume import for project "'
+          + c.project
+          + '" ?'),
+        c.br(),
+        c.prependLabel('User:', u),
+        c.br(),
+        c.prependLabel('Password:', p),
+        c.br(),
+        c.br(),
+        b));
+    }
+    self.onAction('project', 'import.resume', onResumeProjectImport);
+  });