Merge "Add backend functionality needed to import groups"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
index eb7d2e0..eef151a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/AddApprovalsStep.java
@@ -18,6 +18,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.errors.NoSuchAccountException;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -35,6 +36,9 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -46,6 +50,9 @@
AddApprovalsStep create(Change change, ChangeInfo changeInfo, boolean resume);
}
+ private static final Logger log = LoggerFactory
+ .getLogger(ReplayInlineCommentsStep.class);
+
private final AccountUtil accountUtil;
private final ChangeUpdate.Factory updateFactory;
private final ReviewDb db;
@@ -54,6 +61,7 @@
private final Change change;
private final ChangeInfo changeInfo;
private final boolean resume;
+ private final String pluginName;
@Inject
public AddApprovalsStep(AccountUtil accountUtil,
@@ -61,6 +69,7 @@
ReviewDb db,
IdentifiedUser.GenericFactory genericUserFactory,
ChangeControl.GenericFactory changeControlFactory,
+ @PluginName String pluginName,
@Assisted Change change,
@Assisted ChangeInfo changeInfo,
@Assisted boolean resume) {
@@ -72,6 +81,7 @@
this.change = change;
this.changeInfo = changeInfo;
this.resume = resume;
+ this.pluginName = pluginName;
}
void add(RemoteApi api) throws OrmException, NoSuchChangeException, IOException,
@@ -90,6 +100,15 @@
Account.Id user = accountUtil.resolveUser(api, a);
ChangeControl ctrl = control(change, a);
LabelType labelType = ctrl.getLabelTypes().byLabel(labelName);
+ if(labelType == null) {
+ log.warn(String.format("[%s] Label '%s' not found in target system."
+ + " This label was referenced by an approval provided from '%s'"
+ + " for change '%s'."
+ + " This approval will be skipped. In order to import this"
+ + " approval configure the missing label and resume the import."
+ , pluginName, labelName, a.username, changeInfo.id));
+ continue;
+ }
approvals.add(new PatchSetApproval(
new PatchSetApproval.Key(change.currentPatchSetId(), user,
labelType.getLabelId()), a.value.shortValue(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/AddHashtagsStep.java b/src/main/java/com/googlesource/gerrit/plugins/importer/AddHashtagsStep.java
index e40fc5e..f8aaef9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/AddHashtagsStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/AddHashtagsStep.java
@@ -14,13 +14,13 @@
package com.googlesource.gerrit.plugins.importer;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
-import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.HashtagsUtil;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
@@ -29,6 +29,9 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.util.HashSet;
@@ -38,51 +41,55 @@
AddHashtagsStep create(Change change, ChangeInfo changeInfo, boolean resume);
}
+ private static final Logger log = LoggerFactory
+ .getLogger(AddHashtagsStep.class);
+
private final HashtagsUtil hashtagsUtil;
- private final IdentifiedUser.GenericFactory genericUserFactory;
+ private final CurrentUser currentUser;
private final ChangeControl.GenericFactory changeControlFactory;
+ private final String pluginName;
private final Change change;
private final ChangeInfo changeInfo;
private final boolean resume;
@Inject
AddHashtagsStep(HashtagsUtil hashtagsUtil,
- IdentifiedUser.GenericFactory genericUserFactory,
+ CurrentUser currentUser,
ChangeControl.GenericFactory changeControlFactory,
+ @PluginName String pluginName,
@Assisted Change change,
@Assisted ChangeInfo changeInfo,
@Assisted boolean resume) {
this.hashtagsUtil = hashtagsUtil;
+ this.currentUser = currentUser;
+ this.changeControlFactory = changeControlFactory;
+ this.pluginName = pluginName;
this.change = change;
this.changeInfo = changeInfo;
- this.genericUserFactory = genericUserFactory;
- this.changeControlFactory = changeControlFactory;
this.resume = resume;
}
void add() throws IllegalArgumentException, AuthException, IOException,
ValidationException, OrmException, NoSuchChangeException {
- ChangeControl ctrl = control(change, changeInfo.owner);
+ ChangeControl ctrl = changeControlFactory.controlFor(change, currentUser);
- if (resume) {
+ try {
+ if (resume) {
+ HashtagsInput input = new HashtagsInput();
+ input.remove = ctrl.getNotes().load().getHashtags();
+ hashtagsUtil.setHashtags(ctrl, input, false, false);
+ }
+
HashtagsInput input = new HashtagsInput();
- input.remove = ctrl.getNotes().load().getHashtags();
+ input.add = new HashSet<>(changeInfo.hashtags);
hashtagsUtil.setHashtags(ctrl, input, false, false);
+ } catch (AuthException e) {
+ log.warn(String.format(
+ "[%s] Hashtags cannot be set on change %s because the importing"
+ + " user %s doesn't have permissions to edit hashtags"
+ + " (e.g. assign the 'Edit Hashtags' global capability"
+ + " and resume the import with the force option).",
+ pluginName, ChangeTriplet.format(change), currentUser.getUserName()));
}
-
- HashtagsInput input = new HashtagsInput();
- input.add = new HashSet<>(changeInfo.hashtags);
- hashtagsUtil.setHashtags(ctrl, input, false, false);
- }
-
- private ChangeControl control(Change change, AccountInfo acc)
- throws NoSuchChangeException {
- return control(change, new Account.Id(acc._accountId));
- }
-
- private ChangeControl control(Change change, Account.Id id)
- throws NoSuchChangeException {
- return changeControlFactory.controlFor(change,
- genericUserFactory.create(id));
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/CompleteProjectImport.java b/src/main/java/com/googlesource/gerrit/plugins/importer/CompleteProjectImport.java
index 7cb35ac..81363e7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/CompleteProjectImport.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/CompleteProjectImport.java
@@ -15,12 +15,21 @@
package com.googlesource.gerrit.plugins.importer;
import com.google.gerrit.extensions.annotations.PluginData;
+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.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
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.account.CapabilityControl;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.project.ProjectResource;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.importer.CompleteProjectImport.Input;
@@ -67,4 +76,57 @@
throw new ResourceConflictException("failed to lock project for delete");
}
}
+
+ public static class OnProjects implements
+ RestModifyView<ProjectResource, Input>, UiAction<ProjectResource> {
+ private final ProjectsCollection projectsCollection;
+ private final CompleteProjectImport completeProjectImport;
+ private final Provider<CurrentUser> currentUserProvider;
+ private final String pluginName;
+
+ @Inject
+ public OnProjects(ProjectsCollection projectsCollection,
+ CompleteProjectImport completeProjectImport,
+ Provider<CurrentUser> currentUserProvider,
+ @PluginName String pluginName) {
+ this.projectsCollection = projectsCollection;
+ this.completeProjectImport = completeProjectImport;
+ this.currentUserProvider = currentUserProvider;
+ this.pluginName = pluginName;
+ }
+
+ @Override
+ public Response<?> apply(ProjectResource rsrc, Input input)
+ throws ResourceNotFoundException, ResourceConflictException {
+ ImportProjectResource projectResource =
+ projectsCollection.parse(new ConfigResource(),
+ IdString.fromDecoded(rsrc.getName()));
+ return completeProjectImport.apply(projectResource, input);
+ }
+
+ @Override
+ public UiAction.Description getDescription(ProjectResource rsrc) {
+ UiAction.Description desc = new UiAction.Description()
+ .setLabel("Complete Import...")
+ .setTitle("Complete the project import."
+ + " After completion, resume is not possible anymore.");
+
+ try {
+ projectsCollection.parse(new ConfigResource(),
+ IdString.fromDecoded(rsrc.getName()));
+ desc.setVisible(canCompleteImport(rsrc));
+ } catch (ResourceNotFoundException e) {
+ desc.setVisible(false);
+ }
+
+ return desc;
+ }
+
+ private boolean canCompleteImport(ProjectResource rsrc) {
+ CapabilityControl ctl = currentUserProvider.get().getCapabilities();
+ return ctl.canAdministrateServer()
+ || (ctl.canPerform(pluginName + "-" + ImportCapability.ID)
+ && rsrc.getControl().isOwner());
+ }
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
index 7a5bdfc..7b6edf0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/HttpModule.java
@@ -29,6 +29,8 @@
.toInstance(new JavaScriptPlugin("resume-copy-project.js"));
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new JavaScriptPlugin("resume-project-import.js"));
+ DynamicSet.bind(binder(), WebUiPlugin.class)
+ .toInstance(new JavaScriptPlugin("complete-project-import.js"));
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new GwtPlugin("importer"));
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 76fefdd..74b8eb0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ImportProject.java
@@ -97,6 +97,7 @@
private final Project.NameKey targetProject;
private Project.NameKey srcProject;
private Project.NameKey parent;
+ private boolean force;
private Writer err;
@@ -146,9 +147,10 @@
}
}
- public Response<String> resume(String user, String pass, File importStatus)
- throws RestApiException, OrmException, IOException, ValidationException,
- GitAPIException, NoSuchChangeException, NoSuchAccountException {
+ public Response<String> resume(String user, String pass, boolean force,
+ File importStatus) throws RestApiException, OrmException, IOException,
+ ValidationException, GitAPIException, NoSuchChangeException,
+ NoSuchAccountException {
LockFile lockFile = lockForImport();
try {
ImportProjectInfo info = ImportJson.parse(importStatus);
@@ -160,6 +162,8 @@
input.name = info.name;
input.parent = info.parent;
+ this.force = force;
+
return apply(lockFile, input, info);
} finally {
lockFile.unlock();
@@ -189,7 +193,7 @@
gitFetchStep.fetch(input.user, input.pass, repo, pm);
configProjectStep.configure(targetProject, parent, pm);
replayChangesFactory.create(input.from, input.user, input.pass, repo,
- srcProject, targetProject, resume, pm).replay();
+ srcProject, targetProject, force, resume, pm).replay();
} finally {
repo.close();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ListImportedProjects.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ListImportedProjects.java
index 7ba24db..1ac50be 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ListImportedProjects.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ListImportedProjects.java
@@ -16,6 +16,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
+import com.google.common.io.Files;
import com.google.gerrit.extensions.annotations.PluginData;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -26,8 +27,10 @@
import org.kohsuke.args4j.Option;
import java.io.File;
-import java.io.FilenameFilter;
import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Locale;
import java.util.Map;
@Singleton
@@ -59,14 +62,16 @@
return importedProjects;
}
- private File[] listImportFiles() {
- match = Strings.nullToEmpty(match);
-
- return lockRoot.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return !name.endsWith(".lock") && name.toLowerCase().contains(match);
+ private Collection<File> listImportFiles() {
+ match = Strings.nullToEmpty(match).toLowerCase(Locale.ENGLISH);
+ Collection<File> importFiles = new HashSet<>();
+ for (File f : Files.fileTreeTraverser().preOrderTraversal(lockRoot)) {
+ if (f.isFile()
+ && !f.getName().endsWith(".lock")
+ && f.getName().toLowerCase(Locale.ENGLISH).contains(match)) {
+ importFiles.add(f);
}
- });
+ }
+ return importFiles;
}
}
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 d703aa8..0a121be 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/Module.java
@@ -52,6 +52,7 @@
put(PROJECT_KIND, "copy").to(CopyProject.class);
put(PROJECT_KIND, "copy.resume").to(ResumeCopyProject.class);
put(PROJECT_KIND, "import.resume").to(ResumeProjectImport.OnProjects.class);
+ post(PROJECT_KIND, "delete").to(CompleteProjectImport.OnProjects.class);
child(CONFIG_KIND, "groups").to(GroupsCollection.class);
}
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 6acc499..55de14d 100755
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/RemoteApi.java
@@ -84,6 +84,7 @@
public GroupInfo getGroup(String groupName) throws IOException,
BadRequestException {
+ groupName = encode(groupName);
String endPoint = "/groups/" + groupName + "/detail";
try (RestResponse r = checkedGet(endPoint)) {
return newGson().fromJson(r.getReader(),
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 e32d1ec..bffae8c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ReplayChangesStep.java
@@ -53,7 +53,8 @@
Repository repo,
@Assisted("srcProject") Project.NameKey srcProject,
@Assisted("targetProject") Project.NameKey targetProject,
- boolean resume,
+ @Assisted("force") boolean force,
+ @Assisted("resume") boolean resume,
ProgressMonitor pm);
}
@@ -72,6 +73,7 @@
private final Repository repo;
private final Project.NameKey srcProject;
private final Project.NameKey targetProject;
+ private final boolean force;
private final boolean resume;
private final ProgressMonitor pm;
@@ -93,7 +95,8 @@
@Assisted Repository repo,
@Assisted("srcProject") Project.NameKey srcProject,
@Assisted("targetProject") Project.NameKey targetProject,
- @Assisted boolean resume,
+ @Assisted("force") boolean force,
+ @Assisted("resume") boolean resume,
@Assisted ProgressMonitor pm) {
this.replayRevisionsFactory = replayRevisionsFactory;
this.replayInlineCommentsFactory = replayInlineCommentsFactory;
@@ -110,6 +113,7 @@
this.repo = repo;
this.srcProject = srcProject;
this.targetProject = targetProject;
+ this.force = force;
this.resume = resume;
this.pm = pm;
}
@@ -147,7 +151,7 @@
change = createChange(c);
} else {
resumeChange = true;
- if (change.getLastUpdatedOn().equals(c.updated)) {
+ if (!force && change.getLastUpdatedOn().equals(c.updated)) {
// change was not modified since last import
return;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectCommand.java b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectCommand.java
index 4aa3d05..5dfb115 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectCommand.java
@@ -35,6 +35,9 @@
usage = "password of remote user")
private String pass;
+ @Option(name = "--force", usage = "Whether the resume should be done forcefully.")
+ private boolean force;
+
@Option(name = "--quiet", usage = "suppress progress messages")
private boolean quiet;
@@ -58,6 +61,7 @@
ResumeProjectImport.Input input = new ResumeProjectImport.Input();
input.user = user;
input.pass = pass;
+ input.force = force;
resume.apply(rsrc, input);
} catch (RestApiException e) {
throw die(e.getMessage());
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 521be04..ad172d4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/ResumeProjectImport.java
@@ -50,6 +50,7 @@
public static class Input {
public String user;
public String pass;
+ public boolean force;
}
private final ImportProject.Factory importProjectFactory;
@@ -74,7 +75,8 @@
ImportProject importer = importProjectFactory.create(rsrc.getName());
importer.setErr(err);
- return importer.resume(input.user, input.pass, rsrc.getImportStatus());
+ return importer.resume(input.user, input.pass, input.force,
+ rsrc.getImportStatus());
}
public static class OnProjects implements
diff --git a/src/main/java/com/googlesource/gerrit/plugins/importer/client/ProjectImportsScreen.java b/src/main/java/com/googlesource/gerrit/plugins/importer/client/ProjectImportsScreen.java
index d031708..874bb35 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/importer/client/ProjectImportsScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/importer/client/ProjectImportsScreen.java
@@ -21,11 +21,8 @@
import com.google.gerrit.plugin.client.Plugin;
import com.google.gerrit.plugin.client.rpc.RestApi;
import com.google.gerrit.plugin.client.screen.Screen;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
-import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.Label;
diff --git a/src/main/resources/Documentation/cmd-resume-project.md b/src/main/resources/Documentation/cmd-resume-project.md
index 8221c5f..f5c7ebd 100644
--- a/src/main/resources/Documentation/cmd-resume-project.md
+++ b/src/main/resources/Documentation/cmd-resume-project.md
@@ -11,6 +11,7 @@
ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ resume-project \
--user <USER> | -u <USER> \
--pass - | <PASS> \
+ [--force] \
[--quiet] \
<NAME>
```
@@ -38,6 +39,11 @@
`--user`
: User on remote system.
+`--force`
+: Whether the resume should be done forcefully. On resume with force
+ changes that have the same last modified timestamp in the source
+ and target system are resumed, otherwise they will be skipped.
+
`--quiet`
: Suppress progress messages.
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index a582d1d..5655414 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -41,9 +41,6 @@
Lists the imported projects.
-As result a map is returned that maps the project name to
-[ImportProjectInfo](#import-project-info) entity.
-
Caller must be a member of a group that is granted the 'Import'
capability (provided by this plugin) or the 'Administrate Server'
capability.
@@ -58,6 +55,9 @@
GET /config/server/@PLUGIN@~projects/?match=my HTTP/1.0
```
+As result a map is returned that maps the project name to
+[ImportProjectInfo](#import-project-info) entity.
+
#### Response
```
@@ -272,6 +272,9 @@
* _user_: User on remote system.
* _pass_: Password of remote user.
+* _force_: Whether the resume should be done forcefully. On resume with
+force changes that have the same last modified timestamp in the source
+and target system are resumed, otherwise they will be skipped.
SEE ALSO
diff --git a/src/main/resources/Documentation/rest-api-projects.md b/src/main/resources/Documentation/rest-api-projects.md
index ebc443f..252b33d 100644
--- a/src/main/resources/Documentation/rest-api-projects.md
+++ b/src/main/resources/Documentation/rest-api-projects.md
@@ -73,6 +73,25 @@
}
```
+### <a id="complete-project-import"> Complete Project Import
+_POST /projects/[\{project-name\}](../../../Documentation/rest-api-projects.html#project-name)/@PLUGIN@~delete)_
+
+Mark a project import as completed.
+
+Once a project import is completed it cannot be resumed any more.
+
+#### Request
+
+```
+ POST /projects/myProject/@PLUGIN@~delete HTTP/1.0
+```
+
+#### Response
+
+```
+ HTTP/1.1 204 No Content
+```
+
<a id="json-entities">JSON Entities
-----------------------------------
diff --git a/src/main/resources/static/complete-project-import.js b/src/main/resources/static/complete-project-import.js
new file mode 100644
index 0000000..059dc46
--- /dev/null
+++ b/src/main/resources/static/complete-project-import.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 onCompleteProjectImport(c) {
+ var b = c.button('Complete',
+ {onclick: function(){
+ c.call(
+ {},
+ function(r) {
+ c.hide();
+ window.alert('Import for project "'
+ + c.project
+ + '" was completed."'),
+ Gerrit.go('/admin/projects/' + c.project);
+ });
+ }});
+ c.popup(c.div(
+ c.msg('Complete import for project "'
+ + c.project
+ + '" ?'),
+ c.br(),
+ c.br(),
+ b));
+ }
+ self.onAction('project', 'delete', onCompleteProjectImport);
+ });
diff --git a/src/main/resources/static/resume-project-import.js b/src/main/resources/static/resume-project-import.js
index eaf752d..fdca63c 100644
--- a/src/main/resources/static/resume-project-import.js
+++ b/src/main/resources/static/resume-project-import.js
@@ -17,10 +17,11 @@
var u = c.textfield();
var p = c.textfield();
p.type = 'password';
+ var f = c.checkbox();
var b = c.button('Resume',
{onclick: function(){
c.call(
- {user: u.value, pass: p.value},
+ {user: u.value, pass: p.value, force: f.checked},
function(r) {
c.hide();
window.alert('Import for project "'
@@ -38,6 +39,8 @@
c.br(),
c.prependLabel('Password:', p),
c.br(),
+ c.label(f, 'Force Resume'),
+ c.br(),
c.br(),
b));
}