aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJukka Jokiniva <jukka.jokiniva@qt.io>2019-01-04 11:44:07 +0200
committerJukka Jokiniva <jukka.jokiniva@qt.io>2019-04-10 06:43:24 +0000
commit58111cfb36971b3a86963c8d4814ffd151d90143 (patch)
tree1aa6c7d3e4e9679b9faa17f67a4f3204f8b21b50
parentbc4a6ad15999b520783630d5489829c82c52411c (diff)
Add ssh command to approve or fail a build
Fixes: QTBI-1546 Change-Id: I384c2c5c4d236d4b83add92b1cb74886427ec1eb Reviewed-by: Paul Wicking <paul.wicking@qt.io>
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java326
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java1
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java52
-rw-r--r--src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java24
-rw-r--r--src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java345
5 files changed, 748 insertions, 0 deletions
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java
new file mode 100644
index 0000000..3821966
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java
@@ -0,0 +1,326 @@
+//
+// Copyright (C) 2019 The Qt Company
+//
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.extensions.events.ChangeMerged;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.send.RevertedSender;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.NoSuchRefException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gerrit.sshd.CommandMetaData;
+
+import com.google.gwtorm.server.OrmException;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.kohsuke.args4j.Option;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Map.Entry;
+
+
+/**
+ * A command to report pass or fail status for builds. When a build receives
+ * pass status, the branch is updated with build ref and all open changes in
+ * the build are marked as merged. When a build receives fail status, all
+ * change in the build are marked as new and they need to be staged again.
+ *
+ * For example, how to approve a build
+ * $ ssh -p 29418 localhost gerrit-plugin-qt-workflow staging-approve -p project -b master -i 123 -r=pass
+ */
+@CommandMetaData(name="staging-approve", description="Report pass or fail status for builds. If passed changed are merged into target branch.")
+class QtCommandBuildApprove extends SshCommand {
+
+ @Inject
+ private PermissionBackend permissionBackend;
+
+ @Inject
+ private GitRepositoryManager gitManager;
+
+ @Inject
+ private Provider<ReviewDb> dbProvider;
+
+ @Inject
+ private MergedSender.Factory mergedSenderFactory;
+
+ @Inject
+ private RevertedSender.Factory revertedSenderFactory;
+
+ @Inject
+ private BatchUpdate.Factory updateFactory;
+
+ @Inject
+ private GitReferenceUpdated referenceUpdated;
+
+ @Inject
+ private ChangeMerged changeMerged;
+
+ @Inject
+ private QtUtil qtUtil;
+
+ @Inject
+ private QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Option(name = "--project", aliases = {"-p"},
+ required = true, usage = "project name")
+ private String project;
+
+ @Option(name = "--build-id", aliases = {"-i"},
+ required = true, usage = "build branch containing changes, e.g. refs/builds/123 or 123")
+ private String buildBranch;
+
+ @Option(name = "--result", aliases = {"-r"},
+ required = true, usage = "pass or fail")
+ private String result;
+
+ @Option(name = "--message", aliases = {"-m"}, metaVar ="-|MESSAGE",
+ usage = "message added to all changes")
+ private String message;
+
+ @Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH",
+ required = true, usage = "destination branch, e.g. refs/heads/master or just master")
+ private String destBranch;
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private Repository git;
+
+ /** Parameter value for pass result. */
+ private static final String PASS = "pass";
+ /** Parameter value for fail result. */
+ private static final String FAIL = "fail";
+ /** Parameter value for stdin message. */
+ private static final String STDIN_MESSAGE = "-";
+
+ private Project.NameKey projectKey;
+ private Branch.NameKey buildBranchKey;
+ private Branch.NameKey destBranchKey;
+ private Branch.NameKey stagingBranchKey;
+ private Branch.NameKey destBranchShortKey;
+
+ private List<Entry<ChangeData,RevCommit>> affectedChanges = null;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ logger.atInfo().log("qtcodereview: staging-approve -p %s -i %s -r %s -m %s -b %s",
+ project, buildBranch, result, message, destBranch);
+
+ readMessageParameter();
+
+ projectKey = QtUtil.getProjectKey(project);
+ buildBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_BUILDS, buildBranch);
+ destBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_HEADS, destBranch);
+ stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, destBranch);
+ destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, destBranch);
+
+ try {
+ git = gitManager.openRepository(projectKey);
+
+ // Check required permissions
+ permissionBackend.user(user).project(projectKey).ref(destBranchKey.get()).check(RefPermission.UPDATE);
+ permissionBackend.user(user).project(projectKey).ref(stagingBranchKey.get()).check(RefPermission.UPDATE);
+ permissionBackend.user(user).project(projectKey).ref(buildBranchKey.get()).check(RefPermission.READ);
+
+ if (git.resolve(destBranchKey.get()) == null) throw die("branch not found");
+ if (git.resolve(buildBranchKey.get()) == null) throw die("build not found");
+
+ // Initialize and populate open changes list.
+ affectedChanges = qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchKey);
+
+ // Notify user that build did not have any open changes. The build has already been approved.
+ if (affectedChanges.isEmpty()) {
+ logger.atInfo().log("qtcodereview: staging-approve build %s already in project %s branch %s",
+ buildBranch, projectKey, destBranchKey);
+ throw die("No open changes in the build branch");
+ }
+
+ if (result.toLowerCase().equals(PASS)) {
+ approveBuildChanges();
+ } else if (result.toLowerCase().equals(FAIL)) {
+ rejectBuildChanges();
+ } else {
+ throw die("result argument accepts only value pass or fail.");
+ }
+
+ } catch (AuthException e) {
+ throw die("not authorized");
+ } catch (PermissionBackendException e) {
+ throw die("git permission error");
+ } catch (RepositoryNotFoundException e) {
+ throw die("project not found");
+ } catch (IOException e) {
+ throw die(e.getMessage());
+ } catch (OrmException e) {
+ throw die("Failed to access database");
+ } catch (QtUtil.BranchNotFoundException e) {
+ throw die("invalid branch " + e.getMessage());
+ } catch (NoSuchRefException e) {
+ throw die("invalid reference " + e.getMessage());
+ } catch (UpdateException | RestApiException e) {
+ logger.atSevere().log("qtcodereview: staging-napprove failed to update change status %s", e);
+ throw die("Failed to update change status");
+ } catch (QtUtil.MergeConflictException e) {
+ String msg = String.format("Merge build %s to branch %s failed", buildBranch, destBranchKey);
+ logger.atSevere().log("qtcodereview: %s", msg);
+ throw die(String.format("Merge conflict! build branch %s into %s failed", buildBranch, destBranch));
+ } finally {
+ if (git != null) git.close();
+ }
+ }
+
+ private void approveBuildChanges() throws QtUtil.MergeConflictException, NoSuchRefException,
+ IOException, UpdateException, RestApiException,
+ OrmException {
+ if (message == null) message = String.format("Change merged into branch %s", destBranchKey);
+
+ ObjectId oldId = git.resolve(destBranchKey.get());
+
+ QtUtil.mergeBranches(user.asIdentifiedUser(), git, buildBranchKey, destBranchKey);
+
+ updateChanges(affectedChanges, Change.Status.MERGED, null,
+ message, ChangeMessagesUtil.TAG_MERGED, true);
+
+ logger.atInfo().log("qtcodereview: staging-approve build %s merged into branch %s",
+ buildBranch, destBranchKey);
+
+ ObjectId newId = git.resolve(destBranchKey.get());
+ // send ref updated event only if there are changes to build
+ if (!newId.equals(oldId)) {
+ referenceUpdated.fire(projectKey, destBranchKey.get(), oldId, newId, user.asIdentifiedUser().state());
+ }
+ }
+
+ private void rejectBuildChanges() throws QtUtil.MergeConflictException, UpdateException,
+ RestApiException, OrmException {
+ if (message == null) message = String.format("Change rejected for branch %s", destBranchKey);
+
+ updateChanges(affectedChanges, Change.Status.NEW, Change.Status.INTEGRATING,
+ message, ChangeMessagesUtil.TAG_REVERT, false);
+
+ // need to rebuild the staging ref because the reject changes need to be removed from there
+ qtUtil.rebuildStagingBranch(git, user.asIdentifiedUser(), projectKey, stagingBranchKey, destBranchShortKey);
+
+ logger.atInfo().log("qtcodereview: staging-approve build %s rejected for branch %s",
+ buildBranch, destBranchKey);
+ }
+
+ private void updateChanges(List<Entry<ChangeData,RevCommit>> list,
+ Change.Status status,
+ Change.Status oldStatus,
+ String changeMessage,
+ String tag,
+ Boolean passed)
+ throws UpdateException, RestApiException, OrmException {
+ // do the db update
+ QtChangeUpdateOp op = qtUpdateFactory.create(status, changeMessage, null, tag, null);
+ try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) {
+ for (Entry<ChangeData,RevCommit> item : list) {
+ Change change = item.getKey().change();
+ if (oldStatus == null || change.getStatus() == oldStatus) {
+ u.addOp(change.getId(), op);
+ }
+ }
+ u.execute();
+ }
+
+ // do rest
+ for (Entry<ChangeData,RevCommit> item : list) {
+ ChangeData cd = item.getKey();
+ Change change = cd.change();
+ if (passed) {
+ sendMergeEvent(cd);
+ logger.atInfo().log("qtcodereview: staging-approve change %s merged into %s",
+ change, destBranchKey);
+ } else {
+ logger.atInfo().log("qtcodereview: staging-approve change %s rejected for %s",
+ change, destBranchKey);
+ }
+
+ sendResultEmail(change.getId(), passed);
+ }
+ }
+
+ private void sendMergeEvent(ChangeData changeData) throws OrmException {
+ Timestamp ts = TimeUtil.nowTs();
+
+ try {
+ PatchSet ps = changeData.currentPatchSet();
+ changeMerged.fire(changeData.change(), ps, user.asIdentifiedUser().state(),
+ ps.getRevision().get(), ts);
+ } catch ( OrmException e) {
+ logger.atInfo().log("qtcodereview: staging-approve sending merge event failed for %s",
+ changeData.change());
+ }
+ }
+
+ private void readMessageParameter() throws UnloggedFailure {
+ if (message == null) return;
+
+ try {
+ // User will submit message through stdin.
+ if (message.equals(STDIN_MESSAGE)) {
+ // Clear stdin indicator.
+ message = "";
+
+ // Read message from stdin.
+ BufferedReader stdin = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ String line;
+ while ((line = stdin.readLine()) != null) {
+ message += line + "\n";
+ }
+ }
+ } catch (IOException e) {
+ throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e);
+ }
+ }
+
+ private void sendResultEmail(Change.Id changeId, Boolean passed) {
+ try {
+ if (passed) {
+ MergedSender mcm = mergedSenderFactory.create(projectKey, changeId);
+ mcm.send();
+ } else {
+ RevertedSender rcm = revertedSenderFactory.create(projectKey, changeId);
+ rcm.send();
+ }
+ } catch (Exception e) {
+ logger.atWarning().log("qtcodereview: staging-approve Cannot email notification for %s %s", changeId, e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java
index 11300c3..306a5cb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtSshModule.java
@@ -11,6 +11,7 @@ class QtSshModule extends PluginCommandModule {
@Override
protected void configureCommands() {
command(QtCommandPing.class);
+ command(QtCommandBuildApprove.class);
command(QtCommandNewBuild.class);
command(QtCommandListStaging.class);
command(QtCommandRebuildStaging.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java
index 8033d80..b2da710 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java
@@ -51,11 +51,13 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import java.io.IOException;
+import java.sql.Timestamp;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.TimeZone;
/**
@@ -475,4 +477,54 @@ public class QtUtil {
return revWalk.parseCommit(objInserter.insert(mergeCommit));
}
+ public static RefUpdate.Result mergeBranches(IdentifiedUser user,
+ Repository git,
+ final Branch.NameKey branch,
+ final Branch.NameKey destination)
+ throws NoSuchRefException, IOException, MergeConflictException {
+
+ ObjectId srcId = git.resolve(branch.get());
+ if (srcId == null) throw new NoSuchRefException("Invalid Revision: " + branch);
+
+ return mergeObjectToBranch(user, git, srcId, destination);
+ }
+
+ private static RefUpdate.Result mergeObjectToBranch(IdentifiedUser user,
+ Repository git,
+ ObjectId srcId,
+ final Branch.NameKey destination)
+ throws NoSuchRefException, IOException, MergeConflictException {
+
+ Ref destRef = git.getRefDatabase().getRef(destination.get());
+ if (destRef == null) throw new NoSuchRefException("No such branch: " + destination);
+
+ ObjectId destId = git.resolve(destination.get());
+ if (destId == null) throw new NoSuchRefException("Invalid Revision: " + destination);
+
+ RevWalk revWalk = new RevWalk(git);
+ try {
+
+ ObjectInserter objInserter = git.newObjectInserter();
+ RevCommit mergeTip = revWalk.lookupCommit(destId);
+ RevCommit toMerge = revWalk.lookupCommit(srcId);
+ PersonIdent committer = user.newCommitterIdent(new Timestamp(System.currentTimeMillis()), TimeZone.getDefault());
+
+ RevCommit mergeCommit = merge(committer,
+ git,
+ objInserter,
+ revWalk,
+ toMerge,
+ mergeTip,
+ false);
+ objInserter.flush();
+ logger.atInfo().log("qtcodereview: merge commit for %s added to %s", srcId, destination);
+
+ RefUpdate refUpdate = git.updateRef(destination.get());
+ refUpdate.setNewObjectId(mergeCommit);
+ return refUpdate.update();
+ } finally {
+ revWalk.dispose();
+ }
+ }
+
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java
index 5fbb78d..2f6d28b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCodeReviewIT.java
@@ -128,6 +128,30 @@ public class QtCodeReviewIT extends LightweightPluginDaemonTest {
resetEvents();
}
+ protected void QtApproveBuild(String branch, String buildId) throws Exception {
+ String commandStr;
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch " + branch;
+ commandStr += " --build-id "+ buildId;
+ commandStr += " --result pass";
+ commandStr += " --message thebuildpassed";
+ String resultStr = adminSshSession.exec(commandStr);
+ assertThat(adminSshSession.getError()).isNull();
+ }
+
+ protected void QtFailBuild(String branch, String buildId) throws Exception {
+ String commandStr;
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch " + branch;
+ commandStr += " --build-id "+ buildId;
+ commandStr += " --result fail";
+ commandStr += " --message thebuildfailed";
+ String resultStr = adminSshSession.exec(commandStr);
+ assertThat(adminSshSession.getError()).isNull();
+ }
+
protected PushOneCommit.Result pushCommit(String branch,
String message,
String file,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java
new file mode 100644
index 0000000..cb3e10a
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApproveIT.java
@@ -0,0 +1,345 @@
+// 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.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseSsh;
+
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.util.ArrayList;
+import java.io.StringBufferInputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+ name = "gerrit-plugin-qt-workflow",
+ sysModule = "com.googlesource.gerrit.plugins.qtcodereview.QtModule",
+ sshModule = "com.googlesource.gerrit.plugins.qtcodereview.QtSshModule"
+)
+
+@UseSsh
+public class QtCommandBuildApproveIT extends QtCodeReviewIT {
+
+ private final String MERGED_MSG = "thebuildwasapproved";
+ private final String FAILED_MSG = "thebuildfailed";
+
+ @Before
+ public void SetDefaultPermissions() throws Exception {
+ grant(project, "refs/heads/master", Permission.QT_STAGE, false, REGISTERED_USERS);
+ grant(project, "refs/staging/*", Permission.PUSH, false, adminGroupUuid());
+ grant(project, "refs/builds/*", Permission.CREATE, false, adminGroupUuid());
+ }
+
+ @Test
+ public void singleChange_New_Staged_Integrating_Merged() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-100");
+
+ RevCommit updatedHead = qtApproveBuild("master", "test-build-100", c, null);
+ }
+
+ @Test
+ public void multiChange_New_Staged_Integrating_Merged() throws Exception {
+ // Push 3 independent commits
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "file1", "content1");
+ testRepo.reset(initialHead);
+ PushOneCommit.Result c2 = pushCommit("master", "commitmsg2", "file2", "content2");
+ testRepo.reset(initialHead);
+ PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "file3", "content3");
+
+ approve(c1.getChangeId());
+ approve(c2.getChangeId());
+ approve(c3.getChangeId());
+ QtStage(c1);
+ QtStage(c2);
+ QtStage(c3);
+
+ QtNewBuild("master", "test-build-101");
+
+ RevCommit updatedHead = qtApproveBuild("master", "test-build-101", c3, null);
+ Change change = c1.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED);
+ change = c2.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED);
+ }
+
+ @Test
+ public void singleChange_New_Staged_Integrating_Fail() throws Exception {
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-200");
+
+ RevCommit updatedHead = qtFailBuild("master", "test-build-200", c, initialHead);
+ }
+
+ @Test
+ public void multiChange_New_Staged_Integrating_Failed() throws Exception {
+ // Push 3 independent commits
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "file1", "content1");
+ testRepo.reset(initialHead);
+ PushOneCommit.Result c2 = pushCommit("master", "commitmsg2", "file2", "content2");
+ testRepo.reset(initialHead);
+ PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "file3", "content3");
+
+ approve(c1.getChangeId());
+ approve(c2.getChangeId());
+ approve(c3.getChangeId());
+ QtStage(c1);
+ QtStage(c2);
+ QtStage(c3);
+
+ QtNewBuild("master", "test-build-201");
+
+ RevCommit updatedHead = qtFailBuild("master", "test-build-201", c3, initialHead);
+ Change change = c1.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
+ change = c2.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
+ }
+
+ @Test
+ public void errorApproveBuild_NoPermission() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-600");
+
+ String commandStr;
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch master";
+ commandStr += " --build-id test-build-600";
+ commandStr += " --result pass";
+ commandStr += " --message " + MERGED_MSG;
+ String resultStr = userSshSession.exec(commandStr);
+ assertThat(userSshSession.getError()).contains("not authorized");
+ }
+
+ @Test
+ public void errorApproveBuild_RepoNotFound() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-600");
+
+ String commandStr;
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project notarepo" ;
+ commandStr += " --branch master";
+ commandStr += " --build-id test-build-600";
+ commandStr += " --result pass";
+ commandStr += " --message " + MERGED_MSG;
+ String resultStr = adminSshSession.exec(commandStr);
+ assertThat(adminSshSession.getError()).contains("project not found");
+ }
+
+ @Test
+ public void errorApproveBuild_WrongParameter() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-601");
+
+ String resultStr = qtApproveBuildExpectFail("maybe", "master", "test-build-601");
+ assertThat(resultStr).contains("result argument accepts only value pass or fail");
+ }
+
+ @Test
+ public void errorApproveBuild_NoChanges() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-602");
+ RevCommit updatedHead = qtApproveBuild("master", "test-build-602", c, null);
+
+ String resultStr = qtApproveBuildExpectFail("pass", "master", "test-build-602");
+ assertThat(resultStr).contains("No open changes in the build branch");
+ }
+
+ @Test
+ public void errorApproveBuild_NonExistingBranch() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-603");
+
+ String resultStr = qtApproveBuildExpectFail("pass", "invalidbranch", "test-build-603");
+ assertThat(resultStr).contains("branch not found");
+ }
+
+ @Test
+ public void errorApproveBuild_NonExistingBuild() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-604");
+
+ String resultStr = qtApproveBuildExpectFail("pass", "master", "doesnotexist");
+ assertThat(resultStr).contains("build not found");
+ }
+
+ @Test
+ public void approveBuild_MultiLineMessage() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ QtStage(c);
+ QtNewBuild("master", "test-build-605");
+
+ String commandStr;
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch master";
+ commandStr += " --build-id test-build-605";
+ commandStr += " --result pass";
+ commandStr += " --message -";
+ String multiMessage = "the build\nwas\n\"approved\"\n";
+ StringBufferInputStream input = new StringBufferInputStream(multiMessage);
+
+ String resultStr = adminSshSession.exec(commandStr, input);
+ assertThat(resultStr).isEqualTo("");
+ assertThat(adminSshSession.getError()).isNull();
+
+ ArrayList<ChangeMessage> messages = new ArrayList(c.getChange().messages());
+ assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(multiMessage); // check last message
+ }
+
+ private RevCommit qtApproveBuild(String branch,
+ String buildId,
+ PushOneCommit.Result expectedContent,
+ RevCommit expectedHead)
+ throws Exception {
+ String stagingRef = R_STAGING + branch;
+ String branchRef = R_HEADS + branch;
+ String buildRef = R_BUILDS + buildId;
+
+ RevCommit stagingHeadOld = getRemoteHead(project, stagingRef);
+ RevCommit initialHead = getRemoteHead(project, branchRef);
+ String commandStr;
+
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch " + branch;
+ commandStr += " --build-id " + buildId;
+ commandStr += " --result pass";
+ commandStr += " --message " + MERGED_MSG;
+ String resultStr = adminSshSession.exec(commandStr);
+ assertThat(adminSshSession.getError()).isNull();
+
+ RevCommit buildHead = getRemoteHead(project, buildRef);
+ assertThat(buildHead.getId()).isNotNull(); // build ref is still there
+
+ RevCommit updatedHead = getRemoteHead(project, branchRef);
+ if (expectedContent != null && expectedHead == null) {
+ RevCommit commit = expectedContent.getCommit();
+ assertCherryPick(updatedHead, commit, getCurrentPatchSHA(expectedContent));
+ expectedHead = updatedHead;
+ } else {
+ assertThat(updatedHead).isEqualTo(expectedHead); // master is updated
+ }
+
+ RevCommit stagingHead = getRemoteHead(project, stagingRef);
+ assertThat(stagingHead).isEqualTo(stagingHeadOld); // staging remains the same
+
+ assertRefUpdatedEvents(branchRef, initialHead, expectedHead);
+ resetEvents();
+
+ Change change = expectedContent.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.MERGED);
+
+ ArrayList<ChangeMessage> messages = new ArrayList(expectedContent.getChange().messages());
+ assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(MERGED_MSG); // check last message
+
+ return updatedHead;
+ }
+
+ private RevCommit qtFailBuild(String branch,
+ String buildId,
+ PushOneCommit.Result c,
+ RevCommit expectedStagingHead)
+ throws Exception {
+ String stagingRef = R_STAGING + branch;
+ String branchRef = R_HEADS + branch;
+ String buildRef = R_BUILDS + buildId;
+ RevCommit stagingHeadOld = getRemoteHead(project, stagingRef);
+ RevCommit initialHead = getRemoteHead(project, branchRef);
+ String commandStr;
+
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch " + branch;
+ commandStr += " --build-id " + buildId;
+ commandStr += " --result fail";
+ commandStr += " --message " + FAILED_MSG;
+ String resultStr = adminSshSession.exec(commandStr);
+ assertThat(adminSshSession.getError()).isNull();
+
+ RevCommit updatedHead = getRemoteHead(project, branchRef);
+ assertThat(updatedHead.getId()).isEqualTo(initialHead.getId()); // master is not updated
+
+ RevCommit stagingHead = getRemoteHead(project, stagingRef);
+
+ if (c != null && expectedStagingHead == null) {
+ assertCherryPick(stagingHead, c.getCommit(), getCurrentPatchSHA(c));
+ expectedStagingHead = stagingHead;
+ }
+ assertThat(stagingHead).isEqualTo(expectedStagingHead); // staging is rebuild
+
+ RevCommit buildHead = getRemoteHead(project, buildRef);
+ assertThat(buildHead.getId()).isNotNull(); // build ref is still there
+
+ assertRefUpdatedEvents(stagingRef, stagingHeadOld, stagingHead); // staging is rebuild
+
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.NEW);
+
+ ArrayList<ChangeMessage> messages = new ArrayList(c.getChange().messages());
+ assertThat(messages.get(messages.size()-1).getMessage()).isEqualTo(FAILED_MSG); // check last message
+
+ return updatedHead;
+ }
+
+ private String qtApproveBuildExpectFail(String cmd,
+ String branch,
+ String buildId)
+ throws Exception {
+ String stagingRef = R_STAGING + branch;
+ String branchRef = R_HEADS + branch;
+ RevCommit initialHead = getRemoteHead(project, branchRef);
+ RevCommit stagingHeadOld = getRemoteHead(project, stagingRef);
+ String commandStr;
+
+ commandStr ="gerrit-plugin-qt-workflow staging-approve";
+ commandStr += " --project " + project.get();
+ commandStr += " --branch " + branch;
+ commandStr += " --build-id " + buildId;
+ commandStr += " --result " + cmd;
+ commandStr += " --message " + MERGED_MSG;
+ String resultStr = adminSshSession.exec(commandStr);
+
+ RevCommit updatedHead = getRemoteHead(project, branchRef);
+ if (updatedHead != null) assertThat(updatedHead).isEqualTo(initialHead); // master is not updated
+
+ RevCommit stagingHead = getRemoteHead(project, stagingRef);
+ if (stagingHead != null) assertThat(stagingHead).isEqualTo(stagingHeadOld); // staging is not updated
+
+ return adminSshSession.getError();
+ }
+
+}