aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJukka Jokiniva <jukka.jokiniva@qt.io>2018-12-28 10:37:40 +0200
committerJukka Jokiniva <jukka.jokiniva@qt.io>2019-01-18 08:29:03 +0000
commit3019b115f8fbee9d82e9e76132ef8b4c35944e14 (patch)
treeaf67f058ab99760bd9d7f2b9f5f8160036d4af80
parentb793cd0db4164d0ea390eae68e1e4af1d42e53c5 (diff)
Add ReOpen for deferred changes
Adds ReOpen button to UI and corresponding REST API. Change-Id: I4e79b4d8dc65993bf7491a09ffa85d7448176f27 Reviewed-by: Jukka Jokiniva <jukka.jokiniva@qt.io> Reviewed-by: Kari Oikarinen <kari.oikarinen@qt.io>
-rw-r--r--qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html4
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java1
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpen.java131
-rw-r--r--src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpenIT.java123
4 files changed, 259 insertions, 0 deletions
diff --git a/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html b/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
index f077159..03480b0 100644
--- a/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
+++ b/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
@@ -16,6 +16,10 @@
'gerrit-plugin-qt-workflow~defer' : {
header: 'Defer the change?',
action_name: 'defer'
+ },
+ 'gerrit-plugin-qt-workflow~reopen' : {
+ header: 'Reopen the change?',
+ action_name: 'reopen'
}
};
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
index 4b478b5..46ae162 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
@@ -31,6 +31,7 @@ public class QtModule extends FactoryModule {
protected void configure() {
post(CHANGE_KIND, "abandon").to(QtAbandon.class);
post(CHANGE_KIND, "defer").to(QtDefer.class);
+ post(CHANGE_KIND, "reopen").to(QtReOpen.class);
}
}
);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpen.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpen.java
new file mode 100644
index 0000000..778d3dc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpen.java
@@ -0,0 +1,131 @@
+//
+// Copyright (C) 2019 The Qt Company
+//
+
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import com.google.common.base.Strings;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.api.changes.RestoreInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+
+@Singleton
+class QtReOpen extends RetryingRestModifyView<ChangeResource, RestoreInput, ChangeInfo>
+ implements UiAction<ChangeResource> {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeJson.Factory json;
+ private final PatchSetUtil psUtil;
+ private final ProjectCache projectCache;
+ private final QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Inject
+ QtReOpen(Provider<ReviewDb> dbProvider,
+ ChangeJson.Factory json,
+ PatchSetUtil psUtil,
+ RetryHelper retryHelper,
+ ProjectCache projectCache,
+ QtChangeUpdateOp.Factory qtUpdateFactory) {
+ super(retryHelper);
+ this.dbProvider = dbProvider;
+ this.json = json;
+ this.psUtil = psUtil;
+ this.projectCache = projectCache;
+ this.qtUpdateFactory = qtUpdateFactory;
+ }
+
+
+ @Override
+ protected ChangeInfo applyImpl(BatchUpdate.Factory updateFactory,
+ ChangeResource rsrc,
+ RestoreInput input)
+ throws RestApiException, UpdateException,
+ OrmException, PermissionBackendException,
+ IOException {
+ Change change = rsrc.getChange();
+ logger.atInfo().log("qtcodereview: reopen %s", change);
+
+ // Not allowed to restore if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes());
+
+ // Use same permission as Restore. Note that Abandon permission grants the
+ // Restore if the user also has push permission on the change’s destination ref.
+ rsrc.permissions().database(dbProvider).check(ChangePermission.RESTORE);
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
+
+ if (change.getStatus() != Change.Status.DEFERRED) {
+ logger.atSevere().log("qtcodereview: reopen %s status wrong %s", change, change.getStatus());
+ throw new ResourceConflictException("change is " + ChangeUtil.status(change));
+ }
+
+ QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.NEW, "Reopened", input.message, null);
+ try (BatchUpdate u = updateFactory.create(dbProvider.get(), change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
+ u.addOp(rsrc.getId(), op).execute();
+ }
+
+ change = op.getChange();
+ logger.atInfo().log("qtcodereview: reopened %s", change);
+ return json.noOptions().format(change);
+ }
+
+ @Override
+ public UiAction.Description getDescription(ChangeResource rsrc) {
+ UiAction.Description description = new UiAction.Description()
+ .setLabel("Reopen")
+ .setTitle("Reopen the change")
+ .setVisible(false);
+
+ Change change = rsrc.getChange();
+ if (change.getStatus() != Change.Status.DEFERRED) {
+ return description;
+ }
+
+ try {
+ if (!projectCache.checkedGet(rsrc.getProject()).statePermitsWrite()) {
+ return description;
+ }
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log("Failed to check if project state permits write: %s", rsrc.getProject());
+ return description;
+ }
+
+ try {
+ if (psUtil.isPatchSetLocked(rsrc.getNotes())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ logger.atSevere().withCause(e).log("Failed to check if the current patch set of change %s is locked", change.getId());
+ return description;
+ }
+
+ boolean visible = rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.RESTORE);
+ return description.setVisible(visible);
+ }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpenIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpenIT.java
new file mode 100644
index 0000000..6ed9091
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtReOpenIT.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2019 The Qt Company
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseSsh;
+
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.RestoreInput;
+import com.google.gerrit.reviewdb.client.Change;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.http.HttpStatus;
+
+@TestPlugin(
+ name = "gerrit-plugin-qt-workflow",
+ sysModule = "com.googlesource.gerrit.plugins.qtcodereview.QtModule",
+ sshModule = "com.googlesource.gerrit.plugins.qtcodereview.QtSshModule"
+)
+
+@UseSsh
+public class QtReOpenIT extends QtCodeReviewIT {
+
+ @Before
+ public void SetDefaultPermissions() throws Exception {
+ grant(project, "refs/heads/master", Permission.ABANDON, false, REGISTERED_USERS);
+ }
+
+ @Test
+ public void singleChange_New_Defer_ReOpen() throws Exception {
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+
+ QtDefer(c);
+ RevCommit updatedHead = qtReOpen(c, initialHead);
+ }
+
+ @Test
+ public void singleChange_Defer_ReOpen_With_Input_Messsage() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ String changeId = c.getChangeId();
+
+ QtDefer(c);
+
+ RestoreInput restoreInput = new RestoreInput();
+ restoreInput.message = "myrestorenote";
+
+ String url = "/changes/" + changeId + "/gerrit-plugin-qt-workflow~reopen";
+ RestResponse response = adminRestSession.post(url, restoreInput);
+ response.assertOK();
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
+ }
+
+ @Test
+ public void errorReOpen_No_Permission() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ QtDefer(c);
+
+ deny(project, "refs/heads/master", Permission.ABANDON, REGISTERED_USERS);
+ RestResponse response = qtReOpenExpectFail(c, HttpStatus.SC_FORBIDDEN);
+ assertThat(response.getEntityContent()).isEqualTo("restore not permitted");
+ grant(project, "refs/heads/master", Permission.ABANDON, false, REGISTERED_USERS);
+ }
+
+ @Test
+ public void errorReOpen_Wrong_Status() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ RestResponse response = qtReOpenExpectFail(c, HttpStatus.SC_CONFLICT);
+ assertThat(response.getEntityContent()).isEqualTo("change is new");
+ }
+
+ private RevCommit qtReOpen(PushOneCommit.Result c,
+ RevCommit initialHead)
+ throws Exception {
+ String masterRef = R_HEADS + "master";
+ String stagingRef = R_STAGING + "master";
+
+ RestResponse response = call_REST_API_ReOpen(c.getChangeId());
+ response.assertOK();
+
+ RevCommit masterHead = getRemoteHead(project, masterRef);
+ assertThat(masterHead.getId()).isEqualTo(initialHead.getId()); // master is not updated
+
+ RevCommit stagingHead = getRemoteHead(project, stagingRef);
+ if (stagingHead != null) assertThat(stagingHead.getId()).isEqualTo(initialHead.getId()); // staging is not updated
+
+ assertRefUpdatedEvents(masterRef); // no events
+ assertRefUpdatedEvents(stagingRef); // no events
+
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
+
+ return masterHead;
+ }
+
+ private RestResponse qtReOpenExpectFail(PushOneCommit.Result c,
+ int expectedStatus)
+ throws Exception {
+ RestResponse response = call_REST_API_ReOpen(c.getChangeId());
+ response.assertStatus(expectedStatus);
+ return response;
+ }
+
+ private RestResponse call_REST_API_ReOpen(String changeId) throws Exception {
+ String url = "/changes/" + changeId + "/gerrit-plugin-qt-workflow~reopen";
+ RestResponse response = userRestSession.post(url);
+ return response;
+ }
+
+}