list = query.byLegacyChangeId(id);
+ if (list.isEmpty()) throw die("change not found");
+ if (list.size() > 1) throw die("multiple changes found");
+ ChangeData change = list.get(0);
+
+ Project.NameKey projectKey = QtUtil.getProjectKey(project);
+ QtChangeUpdateOp op = qtUpdateFactory.create(to, null, null, null, null, null);
+ try (BatchUpdate u = updateFactory.create(projectKey, user, TimeUtil.nowTs())) {
+ Change c = change.change();
+ if (c.getStatus() == from) {
+ u.addOp(c.getId(), op);
+ u.execute();
+ } else {
+ throw die("change status was not " + fromStr);
}
+ }
+ logger.atInfo().log("admin change-status done");
+ } catch (NumberFormatException e) {
+ throw die("change-id not numeric");
+ } catch (UpdateException | RestApiException e) {
+ logger.atSevere().log("admin change-status error %s", e.getMessage());
+ throw die("Database update failed");
}
-
+ }
+
+ private Change.Status toStatus(String str) {
+ switch (str) {
+ case "new":
+ return Change.Status.NEW;
+ case "staged":
+ return Change.Status.STAGED;
+ case "integrating":
+ return Change.Status.INTEGRATING;
+ case "merged":
+ return Change.Status.MERGED;
+ case "abandoned":
+ return Change.Status.ABANDONED;
+ case "deferred":
+ return Change.Status.DEFERRED;
+ default:
+ return null;
+ }
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java
index dab69b1..793e6f1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandBuildApprove.java
@@ -1,393 +1,405 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2021-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.api.changes.NotifyHandling;
-import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
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.ChangeUtil;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.extensions.events.ChangeMerged;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.send.MergedSender;
import com.google.gerrit.server.notedb.ChangeNotes;
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.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-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.Arrays;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map.Entry;
import java.util.Map;
-
+import java.util.Map.Entry;
+import java.util.concurrent.locks.ReentrantLock;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.kohsuke.args4j.Option;
/**
- * 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.
+ * 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
+ * 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.")
+@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 dbProvider;
-
- @Inject
- private MergedSender.Factory mergedSenderFactory;
-
- @Inject
- QtBuildFailedSender.Factory qtBuildFailedSenderFactory;
-
- @Inject
- private BatchUpdate.Factory updateFactory;
-
- @Inject
- private PatchSetInserter.Factory patchSetInserterFactory;
-
- @Inject
- private GitReferenceUpdated referenceUpdated;
-
- @Inject
- private ChangeMerged changeMerged;
+ @Inject private PermissionBackend permissionBackend;
- @Inject
- private QtUtil qtUtil;
+ @Inject private GitRepositoryManager gitManager;
- @Inject
- private QtChangeUpdateOp.Factory qtUpdateFactory;
+ @Inject private BatchUpdate.Factory updateFactory;
- @Option(name = "--project", aliases = {"-p"},
- required = true, usage = "project name")
- private String project;
+ @Inject private PatchSetInserter.Factory patchSetInserterFactory;
- @Option(name = "--build-id", aliases = {"-i"},
- required = true, usage = "build branch containing changes, e.g. refs/builds/123 or 123")
- private String buildBranch;
+ @Inject private GitReferenceUpdated referenceUpdated;
- @Option(name = "--result", aliases = {"-r"},
- required = true, usage = "pass or fail")
- private String result;
+ @Inject private ChangeMerged changeMerged;
- @Option(name = "--message", aliases = {"-m"}, metaVar ="-|MESSAGE",
- usage = "message added to all changes")
- private String message;
+ @Inject private QtUtil qtUtil;
- @Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH",
- required = true, usage = "destination branch, e.g. refs/heads/master or just master")
- private String destBranch;
+ @Inject private QtEmailSender qtEmailSender;
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ @Inject private QtChangeUpdateOp.Factory qtUpdateFactory;
- private Repository git;
+ private final ReentrantLock buildApproveLock = new ReentrantLock();
- /** 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 = "-";
+ @Option(
+ name = "--project",
+ aliases = {"-p"},
+ required = true,
+ usage = "project name")
+ private String project;
- private Project.NameKey projectKey;
- private Branch.NameKey buildBranchKey;
- private Branch.NameKey destBranchKey;
- private Branch.NameKey stagingBranchKey;
- private Branch.NameKey destBranchShortKey;
+ @Option(
+ name = "--build-id",
+ aliases = {"-i"},
+ required = true,
+ usage = "build branch containing changes, e.g. refs/builds/123 or 123")
+ private String buildBranch;
- private List> affectedChanges = null;
+ @Option(
+ name = "--result",
+ aliases = {"-r"},
+ required = true,
+ usage = "pass or fail")
+ private String result;
- @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);
+ @Option(
+ name = "--message",
+ aliases = {"-m"},
+ metaVar = "-|MESSAGE",
+ usage = "message added to all changes")
+ private String message;
- readMessageParameter();
+ @Option(
+ name = "--branch",
+ aliases = {"-b"},
+ metaVar = "BRANCH",
+ required = true,
+ usage = "destination branch, e.g. refs/heads/master or just master")
+ private String destBranch;
- 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);
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- try {
- git = gitManager.openRepository(projectKey);
+ private Repository git;
- // 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);
+ /** 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 = "-";
- if (git.resolve(destBranchKey.get()) == null) throw die("branch not found");
- if (git.resolve(buildBranchKey.get()) == null) throw die("build not found");
+ private Project.NameKey projectKey;
+ private BranchNameKey buildBranchKey;
+ private BranchNameKey destBranchKey;
+ private BranchNameKey stagingBranchKey;
+ private BranchNameKey destBranchShortKey;
- // Initialize and populate open changes list.
- affectedChanges = qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchKey);
+ private List> affectedChanges = null;
- // 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 | ConfigInvalidException 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();
- }
+ @Override
+ protected void run() throws UnloggedFailure {
+ buildApproveLock.lock(); // block processing of parallel requests
+ try {
+ runBuildApprove();
+ } finally {
+ buildApproveLock.unlock();
}
-
- private void approveBuildChanges() throws QtUtil.MergeConflictException, NoSuchRefException,
- IOException, UpdateException, RestApiException,
- OrmException, ConfigInvalidException {
- if (message == null) message = String.format("Change merged into branch %s", destBranchKey);
-
- ObjectId oldId = git.resolve(destBranchKey.get());
-
- Result result = QtUtil.mergeBranches(user.asIdentifiedUser(), git, buildBranchKey, destBranchKey);
-
- if (result != Result.FAST_FORWARD) {
- message = "Branch update failed, changed back to NEW. Either the destination branch was changed externally, or this is an issue in the Qt plugin.";
- rejectBuildChanges();
- return;
- }
-
- 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 runBuildApprove() throws UnloggedFailure {
+ logger.atInfo().log(
+ "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.branch())
+ .check(RefPermission.UPDATE);
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(stagingBranchKey.branch())
+ .check(RefPermission.UPDATE);
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(buildBranchKey.branch())
+ .check(RefPermission.READ);
+
+ if (git.resolve(destBranchKey.branch()) == null) throw die("branch not found");
+ if (git.resolve(buildBranchKey.branch()) == null) throw die("build not found");
+
+ 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 (QtUtil.BranchNotFoundException e) {
+ throw die("invalid branch " + e.getMessage());
+ } catch (UpdateException | RestApiException | ConfigInvalidException e) {
+ logger.atSevere().log("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.shortName());
+ logger.atSevere().log("%s", msg);
+ throw die(
+ String.format(
+ "Merge conflict! build branch '%s' into '%s' failed", buildBranch, destBranch));
+ } finally {
+ if (git != null) git.close();
}
-
- private void rejectBuildChanges() throws QtUtil.MergeConflictException, UpdateException,
- RestApiException, OrmException, IOException,
- ConfigInvalidException {
- 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 approveBuildChanges()
+ throws QtUtil.MergeConflictException, IOException, UpdateException, UnloggedFailure,
+ RestApiException, ConfigInvalidException, QtUtil.BranchNotFoundException {
+ if (message == null)
+ message = String.format("Change merged into branch '%s'", destBranchKey.shortName());
+
+ ObjectId oldId = git.resolve(destBranchKey.branch());
+
+ try {
+ affectedChanges =
+ qtUtil.mergeIntegrationToBranch(
+ user.asIdentifiedUser(),
+ git,
+ projectKey,
+ buildBranchKey,
+ destBranchKey,
+ "Merge integration " + buildBranch);
+ } catch (NoSuchRefException e) {
+ message = "Gerrit plugin internal error. Please contact Gerrit Admin.";
+ logger.atInfo().log(e.getMessage());
+ rejectBuildChanges();
+ return;
+ } catch (QtUtil.MergeConflictException e) {
+ message =
+ "Unable to merge this integration because another integration parallel to this one "
+ + "successfully merged first and created a conflict in one of the tested changes.\n"
+ + "Please review, resolve conflicts if necessary, and restage.";
+ logger.atInfo().log(e.getMessage());
+ rejectBuildChanges();
+ return;
}
- private void updateChanges(List> list,
- Change.Status status,
- Change.Status oldStatus,
- String changeMessage,
- String tag,
- Boolean passed)
- throws UpdateException, RestApiException, OrmException,
- IOException, ConfigInvalidException {
-
- List> emailingList = new ArrayList>();
-
- // do the db update
- QtChangeUpdateOp op = qtUpdateFactory.create(status, oldStatus, changeMessage, null, tag, null);
- try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) {
- for (Entry item : list) {
- ChangeData cd = item.getKey();
- Change change = cd.change();
- if ((oldStatus == null || change.getStatus() == oldStatus)
- && change.getStatus() != Change.Status.MERGED) {
- if (status == Change.Status.MERGED) {
- ObjectId obj = git.resolve(cd.currentPatchSet().getRevision().get());
- CodeReviewCommit currCommit = new CodeReviewCommit(obj);
- currCommit.setPatchsetId(cd.currentPatchSet().getId());
- CodeReviewCommit newCommit = new CodeReviewCommit(item.getValue());
- Change.Id changeId = insertPatchSet(u, git, cd.notes(), newCommit);
- if (!changeId.equals(cd.getId())) {
- logger.atWarning().log("staging-approve wrong changeId for new patchSet %s != %s",
- changeId, cd.getId());
- }
- u.addOp(changeId, qtUpdateFactory.create(status,
- oldStatus,
- changeMessage,
- null,
- tag,
- currCommit));
- } else {
- u.addOp(change.getId(), op);
- }
- emailingList.add(item);
- }
- }
- u.execute();
- }
-
- // do rest
- for (Entry item : emailingList) {
- ChangeData cd = item.getKey();
- Change change = cd.change();
- if (passed) {
- sendMergeEvent(cd);
- sendMergedEmail(change.getId());
- logger.atInfo().log("qtcodereview: staging-approve change %s merged into %s",
- change, destBranchKey);
- } else {
- sendBuildFailedEmail(change.getId());
- logger.atInfo().log("qtcodereview: staging-approve change %s rejected for %s",
- change, destBranchKey);
- }
- }
-
+ updateChanges(
+ affectedChanges,
+ Change.Status.MERGED,
+ Change.Status.INTEGRATING,
+ message,
+ ChangeMessagesUtil.TAG_MERGED,
+ true);
+
+ logger.atInfo().log(
+ "build '%s' merged into branch '%s'", buildBranch, destBranchKey.shortName());
+
+ // need to rebuild the staging ref to include recently merged changes
+ qtUtil.rebuildStagingBranch(
+ git, user.asIdentifiedUser(), projectKey, stagingBranchKey, destBranchShortKey);
+
+ ObjectId newId = git.resolve(destBranchKey.branch());
+ // send ref updated event only if there are changes to build
+ if (!newId.equals(oldId)) {
+ referenceUpdated.fire(
+ projectKey, destBranchKey.branch(), oldId, newId, user.asIdentifiedUser().state());
}
- private Change.Id insertPatchSet(BatchUpdate bu,
- Repository git,
- ChangeNotes destNotes,
- CodeReviewCommit cherryPickCommit)
- throws IOException, OrmException, BadRequestException, ConfigInvalidException {
- Change destChange = destNotes.getChange();
- PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
- PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
- inserter.setNotify(NotifyHandling.NONE)
- .setAllowClosed(true);
- // .setCopyApprovals(true) doesn't work, so copying done in QtChangeUpdateOp
- bu.addOp(destChange.getId(), inserter);
- return destChange.getId();
- }
+ // return merged sha1 to the caller
+ stdout.println(newId.name());
+ }
- private void sendMergeEvent(ChangeData changeData) throws OrmException {
- Timestamp ts = TimeUtil.nowTs();
+ private void rejectBuildChanges()
+ throws QtUtil.MergeConflictException, UpdateException, RestApiException, IOException,
+ ConfigInvalidException, QtUtil.BranchNotFoundException, UnloggedFailure {
+ if (message == null)
+ message = String.format("Change rejected for branch '%s'", destBranchKey.shortName());
- 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());
- }
+ 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(
+ "build '%s' already in project '%s' branch '%s'",
+ buildBranch, projectKey, destBranchKey.shortName());
+ throw die("No open changes in the build branch");
}
- 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";
- }
+ updateChanges(
+ affectedChanges,
+ Change.Status.NEW,
+ Change.Status.INTEGRATING,
+ message,
+ ChangeMessagesUtil.TAG_REVERT,
+ false);
+
+ logger.atInfo().log(
+ "build '%s' rejected for branch '%s'", buildBranch, destBranchKey.shortName());
+ }
+
+ private void updateChanges(
+ List> list,
+ Change.Status newStatus,
+ Change.Status oldStatus,
+ String changeMessage,
+ String tag,
+ Boolean passed)
+ throws UpdateException, RestApiException, IOException, ConfigInvalidException {
+
+ List> emailingList =
+ new ArrayList>();
+
+ // do the db update
+ QtChangeUpdateOp op =
+ qtUpdateFactory.create(newStatus, oldStatus, changeMessage, null, tag, null);
+ try (BatchUpdate u = updateFactory.create(projectKey, user, TimeUtil.nowTs())) {
+ for (Entry item : list) {
+ ChangeData cd = item.getKey();
+ Change change = cd.change();
+ if (change.getStatus() == oldStatus) {
+ if (newStatus == Change.Status.MERGED) {
+ ObjectId obj = git.resolve(cd.currentPatchSet().commitId().name());
+ CodeReviewCommit currCommit = new CodeReviewCommit(obj);
+ currCommit.setPatchsetId(cd.currentPatchSet().id());
+ CodeReviewCommit newCommit = new CodeReviewCommit(item.getValue());
+ Change.Id changeId = insertPatchSet(u, git, cd.notes(), newCommit);
+ if (!changeId.equals(cd.getId())) {
+ logger.atWarning().log(
+ "wrong changeId for new patchSet %s != %s", changeId, cd.getId());
}
- } catch (IOException e) {
- throw new UnloggedFailure(1, "fatal: " + e.getMessage(), e);
+ u.addOp(
+ changeId,
+ qtUpdateFactory.create(newStatus, oldStatus, changeMessage, null, tag, currCommit));
+ } else {
+ u.addOp(change.getId(), op);
+ }
+ emailingList.add(item);
}
+ }
+ u.execute();
}
- private void sendMergedEmail(Change.Id changeId) {
- try {
- MergedSender mcm = mergedSenderFactory.create(projectKey, changeId);
- mcm.setFrom(user.getAccountId());
- mcm.send();
- } catch (Exception e) {
- logger.atWarning().log("qtcodereview: staging-approve Merged notification not sent for %s %s", changeId, e);
- }
+ // do rest
+ for (Entry item : emailingList) {
+ ChangeData cd = item.getKey();
+ Change change = cd.change();
+ if (passed) {
+ qtUtil.postChangeIntegrationPassEvent(change);
+ sendMergeEvent(cd);
+ qtEmailSender.sendMergedEmail(projectKey, change, user.getAccountId());
+ logger.atInfo().log(
+ " change %s merged into '%s'", change.getId(), destBranchKey.shortName());
+ } else {
+ qtUtil.postChangeIntegrationFailEvent(change);
+ qtEmailSender.sendBuildFailedEmail(projectKey, change, user.getAccountId(), message);
+ logger.atInfo().log(
+ " change %s rejected for '%s'", change.getId(), destBranchKey.shortName());
+ }
}
-
- private void sendBuildFailedEmail(Change.Id changeId) {
- try {
- QtBuildFailedSender cm = qtBuildFailedSenderFactory.create(projectKey, changeId);
- cm.setFrom(user.getAccountId());
- cm.setChangeMessage(message, TimeUtil.nowTs());
- cm.send();
- } catch (Exception e) {
- logger.atWarning().log("qtcodereview: staging-approve Build Failed not sent notification for %s %s", changeId, e);
+ }
+
+ private Change.Id insertPatchSet(
+ BatchUpdate bu, Repository git, ChangeNotes destNotes, CodeReviewCommit cherryPickCommit)
+ throws IOException, BadRequestException, ConfigInvalidException {
+ Change destChange = destNotes.getChange();
+ PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, destChange.currentPatchSetId());
+ PatchSetInserter inserter = patchSetInserterFactory.create(destNotes, psId, cherryPickCommit);
+ inserter.setSendEmail(false).setAllowClosed(true);
+ // .setCopyApprovals(true) doesn't work, so copying done in QtChangeUpdateOp
+ bu.addOp(destChange.getId(), inserter);
+ return destChange.getId();
+ }
+
+ private void sendMergeEvent(ChangeData changeData) {
+ Timestamp ts = TimeUtil.nowTs();
+
+ PatchSet ps = changeData.currentPatchSet();
+ changeMerged.fire(changeData, ps, user.asIdentifiedUser().state(), ps.commitId().name(), ts);
+ }
+
+ 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);
}
-
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandListStaging.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandListStaging.java
index 5ae07ed..801b6fb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandListStaging.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandListStaging.java
@@ -1,124 +1,130 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2019-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.CommandMetaData;
-
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import com.google.gwtorm.server.OrmException;
-
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.kohsuke.args4j.Option;
-
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map.Entry;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.kohsuke.args4j.Option;
-@CommandMetaData(name = "staging-ls", description = "List all the changes that have been applied to the staging or build ref that are not in the destination branch yet.")
+@CommandMetaData(
+ name = "staging-ls",
+ description =
+ "List all the changes that have been applied to the staging or build ref that are not in"
+ + " the destination branch yet.")
class QtCommandListStaging extends SshCommand {
- @Inject
- private PermissionBackend permissionBackend;
-
- @Inject
- private GitRepositoryManager gitManager;
-
- @Inject
- private ReviewDb db;
-
- @Inject
- private QtUtil qtUtil;
-
- @Option(name = "--project", aliases = {"-p"},
- required = true, usage = "project name")
- private String project;
-
- @Option(name = "--branch", aliases = {"-b"},
- required = true, usage = "any ref, e.g. refs/staging/master or refs/builds/my_build")
- private String branch;
-
- @Option(name = "--destination", aliases = {"-d"},
- required = true, usage = "destination branch filter, e.g. refs/heads/master or just master")
- private String destination;
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private Repository git;
-
-
- @Override
- protected void run() throws UnloggedFailure {
- logger.atInfo().log("qtcodereview: staging-ls -p %s -b %s", project, branch);
-
- final PrintWriter stdout = toPrintWriter(out);
-
- Project.NameKey projectKey = new Project.NameKey(project);
- Branch.NameKey aBranchKey = new Branch.NameKey(projectKey, branch);
- Branch.NameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, destination);
-
- try {
- git = gitManager.openRepository(projectKey);
-
- permissionBackend.user(user).project(projectKey).ref(aBranchKey.get()).check(RefPermission.READ);
- permissionBackend.user(user).project(projectKey).ref(destBranchShortKey.get()).check(RefPermission.READ);
-
- if (git.resolve(aBranchKey.get()) == null) {
- throw die("branch ref not found");
- }
-
- final List> open = qtUtil.listChangesNotMerged(git, aBranchKey, destBranchShortKey);
-
- for (Entry item : open) {
- final Change change = item.getKey().change();
- final Change.Status status = change.getStatus();
-
- if (status == Change.Status.STAGED || status == Change.Status.INTEGRATING) {
- final RevCommit commit = item.getValue();
- stdout.println(commit.name() + " " + change.currentPatchSetId() + " " + change.getSubject());
- }
- }
-
- logger.atInfo().log("qtcodereview: staging-ls done");
- } catch (AuthException e) {
- logger.atSevere().log("qtcodereview: staging-ls Authentication failed to access repository: %s", e);
- throw die("not authorized");
- } catch (PermissionBackendException e) {
- logger.atSevere().log("qtcodereview: staging-ls permission error %s", e);
- } catch (RepositoryNotFoundException e) {
- logger.atSevere().log("qtcodereview: staging-ls repository not found: %s", e);
- throw die("project not found");
- } catch (QtUtil.BranchNotFoundException e) {
- throw die("invalid branch " + e.getMessage());
- } catch (IOException e) {
- logger.atSevere().log("qtcodereview: staging-ls IOException %s", e);
- throw die(e.getMessage());
- } catch (OrmException e) {
- logger.atSevere().log("qtcodereview: staging-ls cannot access Gerrit database %s", e);
- throw die("cannot access Gerrit database");
- } finally {
- stdout.flush();
- if (git != null) {
- git.close();
- }
+ @Inject private PermissionBackend permissionBackend;
+
+ @Inject private GitRepositoryManager gitManager;
+
+ @Inject private QtUtil qtUtil;
+
+ @Option(
+ name = "--project",
+ aliases = {"-p"},
+ required = true,
+ usage = "project name")
+ private String project;
+
+ @Option(
+ name = "--branch",
+ aliases = {"-b"},
+ required = true,
+ usage = "any ref, e.g. refs/staging/master or refs/builds/my_build")
+ private String branch;
+
+ @Option(
+ name = "--destination",
+ aliases = {"-d"},
+ required = true,
+ usage = "destination branch filter, e.g. refs/heads/master or just master")
+ private String destination;
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private Repository git;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ logger.atInfo().log("staging-ls -p %s -b %s", project, branch);
+
+ final PrintWriter stdout = toPrintWriter(out);
+
+ Project.NameKey projectKey = Project.nameKey(project);
+ BranchNameKey aBranchKey = BranchNameKey.create(projectKey, branch);
+ BranchNameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, destination);
+
+ try {
+ git = gitManager.openRepository(projectKey);
+
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(aBranchKey.branch())
+ .check(RefPermission.READ);
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(destBranchShortKey.branch())
+ .check(RefPermission.READ);
+
+ if (git.resolve(aBranchKey.branch()) == null) {
+ throw die("branch ref not found");
+ }
+
+ final List> open =
+ qtUtil.listChangesNotMerged(git, aBranchKey, destBranchShortKey);
+
+ for (Entry item : open) {
+ final Change change = item.getKey().change();
+ final Change.Status status = change.getStatus();
+
+ if (status == Change.Status.STAGED || status == Change.Status.INTEGRATING) {
+ final RevCommit commit = item.getValue();
+ stdout.println(
+ commit.name() + " " + change.currentPatchSetId() + " " + change.getSubject());
}
+ }
+
+ logger.atInfo().log("staging-ls done");
+ } catch (AuthException e) {
+ logger.atSevere().log("staging-ls Authentication failed to access repository: %s", e);
+ throw die("not authorized");
+ } catch (PermissionBackendException e) {
+ logger.atSevere().log("staging-ls permission error %s", e);
+ } catch (RepositoryNotFoundException e) {
+ logger.atSevere().log("staging-ls repository not found: %s", e);
+ throw die("project not found");
+ } catch (QtUtil.BranchNotFoundException e) {
+ throw die("invalid branch " + e.getMessage());
+ } catch (IOException e) {
+ logger.atSevere().log("staging-ls IOException %s", e);
+ throw die(e.getMessage());
+ } finally {
+ stdout.flush();
+ if (git != null) {
+ git.close();
+ }
}
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandNewBuild.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandNewBuild.java
index 266f830..39d37f1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandNewBuild.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandNewBuild.java
@@ -1,180 +1,198 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2019-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
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.InternalUser;
import com.google.gerrit.server.git.GitRepositoryManager;
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.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-
-import org.eclipse.jgit.errors.MissingObjectException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map.Entry;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
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.IOException;
-import java.util.List;
-import java.util.Map.Entry;
-
-@CommandMetaData(name="staging-new-build", description="Create unique build branch of the current staging branch and change the gerrit status of the changes to INTEGRATING.")
+@CommandMetaData(
+ name = "staging-new-build",
+ description =
+ "Create unique build branch of the current staging branch and change the gerrit status of"
+ + " the changes to INTEGRATING.")
class QtCommandNewBuild extends SshCommand {
- @Inject
- private PermissionBackend permissionBackend;
-
- @Inject
- private GitRepositoryManager gitManager;
-
- @Inject
- private Provider dbProvider;
-
- @Inject
- private BatchUpdate.Factory updateFactory;
-
- @Inject
- private QtUtil qtUtil;
-
- @Inject
- private QtChangeUpdateOp.Factory qtUpdateFactory;
-
- @Option(name = "--project", aliases = {"-p"},
- required = true, usage = "project name")
- private String project;
-
- @Option(name = "--staging-branch", aliases = {"-s"},
- required = true, usage = "branch name, e.g. refs/staging/master or just master")
- private String stagingBranch;
-
- @Option(name = "--build-id", aliases = {"-i"},
- required = true, usage = "build id, e.g. refs/builds/my_build or just my_build")
- private String build;
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private Repository git;
-
-
- @Override
- protected void run() throws UnloggedFailure {
-
- logger.atInfo().log("qtcodereview: staging-new-build -p %s -s %s -i %s", project, stagingBranch, build);
-
- try {
- Project.NameKey projectKey = new Project.NameKey(project);
- git = gitManager.openRepository(projectKey);
-
- Branch.NameKey buildBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_BUILDS, build);
- Branch.NameKey stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, stagingBranch);
- Branch.NameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_STAGING, stagingBranch);
- Branch.NameKey destinationKey = QtUtil.getNameKeyLong(project, QtUtil.R_HEADS, stagingBranch);
-
- // Check required permissions
- permissionBackend.user(user).project(projectKey).ref(destinationKey.get()).check(RefPermission.UPDATE);
- permissionBackend.user(user).project(projectKey).ref(buildBranchKey.get()).check(RefPermission.CREATE);
-
- if (QtUtil.branchExists(git, buildBranchKey) == true) {
- logger.atSevere().log("qtcodereview: staging-new-build Target build %s already exists", buildBranchKey);
- throw die("Target build already exists!");
- }
-
- if (QtUtil.branchExists(git, stagingBranchKey) == false) {
- logger.atSevere().log("qtcodereview: staging-new-build staging ref %s not found", stagingBranchKey);
- throw die("Staging ref not found!");
- }
-
- // Create build reference.
- Result result = qtUtil.createBuildRef(git, user.asIdentifiedUser(),
- projectKey, stagingBranchKey, buildBranchKey);
- String message = String.format("Added to build %s for %s", build, destinationKey);
+ @Inject private PermissionBackend permissionBackend;
+
+ @Inject private GitRepositoryManager gitManager;
+
+ @Inject private BatchUpdate.Factory updateFactory;
+
+ @Inject private QtUtil qtUtil;
+
+ @Inject private QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Option(
+ name = "--project",
+ aliases = {"-p"},
+ required = true,
+ usage = "project name")
+ private String project;
+
+ @Option(
+ name = "--staging-branch",
+ aliases = {"-s"},
+ required = true,
+ usage = "branch name, e.g. refs/staging/master or just master")
+ private String stagingBranch;
+
+ @Option(
+ name = "--build-id",
+ aliases = {"-i"},
+ required = true,
+ usage = "build id, e.g. refs/builds/my_build or just my_build")
+ private String build;
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private Repository git;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+
+ logger.atInfo().log("staging-new-build -p %s -s %s -i %s", project, stagingBranch, build);
+
+ try {
+ Project.NameKey projectKey = Project.nameKey(project);
+ git = gitManager.openRepository(projectKey);
+
+ BranchNameKey buildBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_BUILDS, build);
+ BranchNameKey stagingBranchKey =
+ QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, stagingBranch);
+ BranchNameKey destBranchShortKey =
+ QtUtil.getNameKeyShort(project, QtUtil.R_STAGING, stagingBranch);
+ BranchNameKey destinationKey = QtUtil.getNameKeyLong(project, QtUtil.R_HEADS, stagingBranch);
+
+ // Check required permissions
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(destinationKey.branch())
+ .check(RefPermission.UPDATE);
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(buildBranchKey.branch())
+ .check(RefPermission.CREATE);
+
+ if (QtUtil.branchExists(git, buildBranchKey) == true) {
+ logger.atSevere().log(
+ "staging-new-build Target build '%s' already exists", buildBranchKey.branch());
+ throw die("Target build already exists!");
+ }
+
+ if (QtUtil.branchExists(git, stagingBranchKey) == false) {
+ logger.atSevere().log(
+ "staging-new-build staging ref '%s' not found", stagingBranchKey.branch());
+ throw die("Staging ref not found!");
+ }
+
+ // Create build reference.
+ Result result =
+ qtUtil.createBuildRef(
+ git, user.asIdentifiedUser(), projectKey, stagingBranchKey, buildBranchKey);
+ String message =
+ String.format("Added to build '%s' for '%s'", build, destinationKey.shortName());
+
+ if (result != Result.NEW && result != Result.FAST_FORWARD) {
+ logger.atSevere().log(
+ "staging-new-build failed to create new build ref '%s' result %s",
+ buildBranchKey.branch(), result);
+ throw new UnloggedFailure(1, "fatal: failed to create new build ref: " + result);
+ } else {
+ // list the changes in staging branch but missing from the destination branch
+ List> openChanges =
+ qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchShortKey);
+
+ // Make sure that there are changes in the staging branch.
+ if (openChanges.isEmpty()) {
+ logger.atSevere().log(
+ "staging-new-build No changes in staging branch %s.", stagingBranchKey.branch());
+ throw die("No changes in staging branch. Not creating a build reference");
+ }
- if (result != Result.NEW && result != Result.FAST_FORWARD) {
- logger.atSevere().log("qtcodereview: staging-new-build failed to create new build ref %s result %s",
- buildBranchKey, result);
- throw new UnloggedFailure(1, "fatal: failed to create new build ref: " + result);
+ QtChangeUpdateOp op =
+ qtUpdateFactory.create(
+ Change.Status.INTEGRATING,
+ Change.Status.STAGED,
+ message,
+ null,
+ QtUtil.TAG_CI,
+ null);
+ try (BatchUpdate u = updateFactory.create(projectKey, user, TimeUtil.nowTs())) {
+ for (Entry item : openChanges) {
+ Change change = item.getKey().change();
+ if (change.getStatus() == Change.Status.STAGED) {
+ logger.atInfo().log(
+ "staging-new-build inserted change %s,%s into build '%s' for '%s'",
+ change.getId(), change.getKey(), build, destinationKey.shortName());
+ u.addOp(change.getId(), op);
} else {
- // list the changes in staging branch but missing from the destination branch
- List> openChanges = qtUtil.listChangesNotMerged(git, buildBranchKey, destBranchShortKey);
-
- // Make sure that there are changes in the staging branch.
- if (openChanges.isEmpty()) {
- logger.atSevere().log("qtcodereview: staging-new-build No changes in staging branch %s.", stagingBranchKey);
- throw die("No changes in staging branch. Not creating a build reference");
- }
-
- QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.INTEGRATING, Change.Status.STAGED, message, null, QtUtil.TAG_CI, null);
- try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) {
- for (Entry item: openChanges) {
- Change change = item.getKey().change();
- if (change.getStatus() == Change.Status.STAGED) {
- logger.atInfo().log("qtcodereview: staging-new-build inserted change %s (%s) into build %s for %s",
- change, item.getValue().toString(), build, destinationKey);
- u.addOp(change.getId(), op);
- } else {
- logger.atInfo().log("qtcodereview: staging-new-build change %s (%s) is included in build %s for %s",
- change, item.getValue().toString(), build, destinationKey);
- }
- }
- u.execute();
- }
- }
-
- logger.atInfo().log("qtcodereview: staging-new-build build %s for %s created", build, destBranchShortKey);
-
- } catch (AuthException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Authentication failed to access repository: %s", e);
- throw die("Authentication failed to access repository");
- } catch (PermissionBackendException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Not enough permissions to access repository %s", e);
- throw die("Not enough permissions to access repository");
- } catch (RepositoryNotFoundException e) {
- throw die("project not found");
- } catch (IOException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Failed to access repository %s", e);
- throw die("Failed to access repository");
- } catch (OrmException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Failed to access database %s", e);
- throw die("Failed to access database");
- } catch (QtUtil.BranchNotFoundException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Failed to access build or staging ref %s", e);
- throw die("Failed to access build or staging ref");
- } catch (NoSuchRefException e) {
- logger.atSevere().log("qtcodereview: staging-new-build Invalid branch name %s", e);
- throw die("Invalid branch name");
- } catch (UpdateException | RestApiException e) {
- logger.atSevere().log("qtcodereview: staging-new-build failed to update change status %s", e);
- throw die("Failed to update change status");
- } finally {
- if (git != null) {
- git.close();
+ logger.atInfo().log(
+ "staging-new-build change %s, %s is included in build '%s' for '%s'",
+ change.getId(), change.getKey(), build, destinationKey.shortName());
}
+ }
+ u.execute();
}
+ }
+
+ // reset staging ref back to branch head
+ result = QtUtil.createStagingBranch(git, destBranchShortKey);
+
+ logger.atInfo().log(
+ "staging-new-build build '%s' for '%s' created", build, destBranchShortKey.shortName());
+
+ } catch (AuthException e) {
+ logger.atSevere().log("staging-new-build Authentication failed to access repository: %s", e);
+ throw die("Authentication failed to access repository");
+ } catch (PermissionBackendException e) {
+ logger.atSevere().log("staging-new-build Not enough permissions to access repository %s", e);
+ throw die("Not enough permissions to access repository");
+ } catch (RepositoryNotFoundException e) {
+ throw die("project not found");
+ } catch (IOException e) {
+ logger.atSevere().log("staging-new-build Failed to access repository %s", e);
+ throw die("Failed to access repository");
+ } catch (QtUtil.BranchNotFoundException e) {
+ logger.atSevere().log("staging-new-build Failed to access build or staging ref %s", e);
+ throw die("Failed to access build or staging ref");
+ } catch (NoSuchRefException e) {
+ logger.atSevere().log("staging-new-build Invalid branch name %s", e);
+ throw die("Invalid branch name");
+ } catch (UpdateException | RestApiException e) {
+ logger.atSevere().log("staging-new-build failed to update change status %s", e);
+ throw die("Failed to update change status");
+ } finally {
+ if (git != null) {
+ git.close();
+ }
}
-
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandPing.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandPing.java
index 6d9dea0..a11d2d1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandPing.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandPing.java
@@ -4,17 +4,18 @@
package com.googlesource.gerrit.plugins.qtcodereview;
-import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
-@CommandMetaData(name="ping", description="Ping the SSH Command interface")
+@CommandMetaData(name = "ping", description = "Ping the SSH Command interface")
class QtCommandPing extends SshCommand {
- @Override
- protected void run() {
- stdout.print(String.format("Pong\n username=%s\n name=%s\n email=%s\n",
- user.asIdentifiedUser().getUserName(),
- user.asIdentifiedUser().getName(),
- user.asIdentifiedUser().getNameEmail()));
- }
+ @Override
+ protected void run() {
+ stdout.print(
+ String.format(
+ "Pong\n username=%s\n name=%s\n email=%s\n",
+ user.asIdentifiedUser().getUserName(),
+ user.asIdentifiedUser().getName(),
+ user.asIdentifiedUser().getNameEmail()));
+ }
}
-
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandRebuildStaging.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandRebuildStaging.java
index 32b1af9..ced9d3c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandRebuildStaging.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandRebuildStaging.java
@@ -1,101 +1,96 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2019-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.CommandMetaData;
-
+import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import com.google.inject.Provider;
-
+import java.io.IOException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
-
-
-@CommandMetaData(name="staging-rebuild", description="Rebuild a staging branch.")
+@CommandMetaData(name = "staging-rebuild", description = "Rebuild a staging branch.")
class QtCommandRebuildStaging extends SshCommand {
- @Inject
- private PermissionBackend permissionBackend;
-
- @Inject
- private GitRepositoryManager gitManager;
-
- @Inject
- private ReviewDb db;
-
- @Inject
- private BatchUpdate.Factory updateFactory;
-
- @Inject
- private QtUtil qtUtil;
-
- @Option(name = "--project", aliases = {"-p"},
- required = true, usage = "project name")
- private String project;
-
- @Option(name = "--branch", aliases = {"-b"},
- required = true, usage = "branch name, e.g. refs/heads/master or just master")
- private String branch;
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private Repository git;
-
-
- @Override
- protected void run() throws UnloggedFailure {
- logger.atInfo().log("qtcodereview: staging-rebuild -p %s -b %s", project, branch);
-
- Branch.NameKey stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, branch);
- Branch.NameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, branch);
-
- try {
- Project.NameKey projectKey = new Project.NameKey(project);
- git = gitManager.openRepository(projectKey);
-
- permissionBackend.user(user).project(projectKey).ref(destBranchShortKey.get()).check(RefPermission.UPDATE);
-
- if (git.resolve(stagingBranchKey.get()) == null) throw die("branch staging ref not found");
-
- qtUtil.rebuildStagingBranch(git, user.asIdentifiedUser(), projectKey, stagingBranchKey, destBranchShortKey);
-
- logger.atInfo().log("qtcodereview: staging-rebuild done for %s", stagingBranchKey);
- } catch (AuthException e) {
- logger.atSevere().log("qtcodereview: staging-rebuild Authentication failed to access repository: %s", e);
- throw die("not authorized");
- } catch (PermissionBackendException e) {
- logger.atSevere().log("qtcodereview: staging-rebuild permission error %s", e);
- } catch (RepositoryNotFoundException e) {
- logger.atSevere().log("qtcodereview: staging-rebuild repository not found: %s", e);
- throw die("project not found");
- } catch (IOException e) {
- logger.atSevere().log("qtcodereview: staging-rebuild IOException %s", e);
- throw die(e.getMessage());
- } catch (QtUtil.MergeConflictException e) {
- logger.atSevere().log("qtcodereview: staging-rebuild error %s", e);
- throw die("staging rebuild failed, merge conflict");
- } finally {
- if (git != null) {
- git.close();
- }
- }
-
+ @Inject private PermissionBackend permissionBackend;
+
+ @Inject private GitRepositoryManager gitManager;
+
+ @Inject private BatchUpdate.Factory updateFactory;
+
+ @Inject private QtUtil qtUtil;
+
+ @Option(
+ name = "--project",
+ aliases = {"-p"},
+ required = true,
+ usage = "project name")
+ private String project;
+
+ @Option(
+ name = "--branch",
+ aliases = {"-b"},
+ required = true,
+ usage = "branch name, e.g. refs/heads/master or just master")
+ private String branch;
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private Repository git;
+
+ @Override
+ protected void run() throws UnloggedFailure {
+ logger.atInfo().log("staging-rebuild -p %s -b %s", project, branch);
+
+ BranchNameKey stagingBranchKey = QtUtil.getNameKeyLong(project, QtUtil.R_STAGING, branch);
+ BranchNameKey destBranchShortKey = QtUtil.getNameKeyShort(project, QtUtil.R_HEADS, branch);
+
+ try {
+ Project.NameKey projectKey = Project.nameKey(project);
+ git = gitManager.openRepository(projectKey);
+
+ permissionBackend
+ .user(user)
+ .project(projectKey)
+ .ref(destBranchShortKey.branch())
+ .check(RefPermission.UPDATE);
+
+ if (git.resolve(stagingBranchKey.branch()) == null) throw die("branch staging ref not found");
+
+ qtUtil.rebuildStagingBranch(
+ git, user.asIdentifiedUser(), projectKey, stagingBranchKey, destBranchShortKey);
+
+ logger.atInfo().log("staging-rebuild done for %s", stagingBranchKey.shortName());
+ } catch (AuthException e) {
+ logger.atSevere().log("staging-rebuild Authentication failed to access repository: %s", e);
+ throw die("not authorized");
+ } catch (PermissionBackendException e) {
+ logger.atSevere().log("staging-rebuild permission error %s", e);
+ } catch (RepositoryNotFoundException e) {
+ logger.atSevere().log("staging-rebuild repository not found: %s", e);
+ throw die("project not found");
+ } catch (IOException e) {
+ logger.atSevere().log("staging-rebuild IOException %s", e);
+ throw die(e.getMessage());
+ } catch (QtUtil.MergeConflictException e) {
+ logger.atSevere().log("staging-rebuild error %s", e);
+ throw die("staging rebuild failed, merge conflict");
+ } finally {
+ if (git != null) {
+ git.close();
+ }
}
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandStage.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandStage.java
index 5a851c4..5f0598d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandStage.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommandStage.java
@@ -1,76 +1,71 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2019-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
import com.google.common.flogger.FluentLogger;
-
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.restapi.change.ChangesCollection;
import com.google.gerrit.server.restapi.change.Revisions;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.commands.PatchSetParser;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.util.HashSet;
import java.util.Set;
import org.kohsuke.args4j.Argument;
-@CommandMetaData(name="stage", description="Stage a change.")
+@CommandMetaData(name = "stage", description = "Stage a change.")
class QtCommandStage extends SshCommand {
- @Inject private QtStage qtStage;
- @Inject private ChangesCollection changes;
- @Inject private PatchSetParser psParser;
- @Inject private Revisions revisions;
+ @Inject private QtStage qtStage;
+ @Inject private ChangesCollection changes;
+ @Inject private PatchSetParser psParser;
+ @Inject private Revisions revisions;
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final Set patchSets = new HashSet<>();
+ private final Set patchSets = new HashSet<>();
- @Argument(
- index = 0,
- required = true,
- multiValued = true,
- metaVar = "{COMMIT | CHANGE,PATCHSET}",
- usage = "list of commits or patch sets to stage")
- void addPatchSetId(String token) {
- try {
- PatchSet ps = psParser.parsePatchSet(token, null, null);
- patchSets.add(ps);
- } catch (UnloggedFailure e) {
- throw new IllegalArgumentException(e.getMessage(), e);
- } catch (OrmException e) {
- throw new IllegalArgumentException("database error", e);
- }
+ @Argument(
+ index = 0,
+ required = true,
+ multiValued = true,
+ metaVar = "{COMMIT | CHANGE,PATCHSET}",
+ usage = "list of commits or patch sets to stage")
+ void addPatchSetId(String token) {
+ try {
+ PatchSet ps = psParser.parsePatchSet(token, null, null);
+ patchSets.add(ps);
+ } catch (UnloggedFailure e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
}
+ }
- @Override
- protected void run() throws UnloggedFailure {
- boolean ok = true;
+ @Override
+ protected void run() throws UnloggedFailure {
+ boolean ok = true;
- for (PatchSet patchSet : patchSets) {
- try {
- logger.atInfo().log("qtcodereview: ssh command stage %s", patchSet.getId());
- ChangeResource c = changes.parse(patchSet.getId().getParentKey());
- IdString id = IdString.fromDecoded(patchSet.getRevision().get());
- RevisionResource r = revisions.parse(c, id);
- qtStage.apply(r, new SubmitInput());
- } catch (Exception e) {
- ok = false;
- writeError("error", e.getMessage() + "\n");
- }
- }
-
- if (!ok) {
- throw die("one or more stages failed; review output above");
- }
+ for (PatchSet patchSet : patchSets) {
+ try {
+ logger.atInfo().log("ssh command stage %s", patchSet.id());
+ ChangeResource c = changes.parse(patchSet.id().changeId());
+ IdString id = IdString.fromDecoded(patchSet.commitId().name());
+ RevisionResource r = revisions.parse(c, id);
+ qtStage.apply(r, new SubmitInput());
+ } catch (Exception e) {
+ ok = false;
+ writeError("error", e.getMessage() + "\n");
+ }
}
+ if (!ok) {
+ throw die("one or more stages failed; review output above");
+ }
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtDefer.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtDefer.java
index 03452de..87ed166 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtDefer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtDefer.java
@@ -1,6 +1,7 @@
//
-// Copyright (C) 2019 The Qt Company
-// Modified from https://gerrit.googlesource.com/gerrit/+/refs/heads/stable-2.16/java/com/google/gerrit/server/restapi/change/Abandon.java
+// Copyright (C) 2019-22 The Qt Company
+// Modified from
+// https://gerrit.googlesource.com/gerrit/+/refs/heads/stable-2.16/java/com/google/gerrit/server/restapi/change/Abandon.java
//
// Copyright (C) 2012 The Android Open Source Project
//
@@ -18,15 +19,16 @@
package com.googlesource.gerrit.plugins.qtcodereview;
-import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.AbandonInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+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.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.PatchSetUtil;
@@ -35,95 +37,91 @@
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
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 QtDefer extends RetryingRestModifyView
- implements UiAction {
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private final Provider dbProvider;
- private final ChangeJson.Factory json;
- private final PatchSetUtil psUtil;
- private final QtChangeUpdateOp.Factory qtUpdateFactory;
-
- @Inject
- QtDefer(Provider dbProvider,
- ChangeJson.Factory json,
- RetryHelper retryHelper,
- PatchSetUtil psUtil,
- QtChangeUpdateOp.Factory qtUpdateFactory) {
- super(retryHelper);
- this.dbProvider = dbProvider;
- this.json = json;
- this.psUtil = psUtil;
- this.qtUpdateFactory = qtUpdateFactory;
+public class QtDefer
+ implements RestModifyView, UiAction {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final BatchUpdate.Factory updateFactory;
+ private final ChangeJson.Factory json;
+ private final PatchSetUtil psUtil;
+ private final QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Inject
+ QtDefer(
+ BatchUpdate.Factory updateFactory,
+ ChangeJson.Factory json,
+ PatchSetUtil psUtil,
+ QtChangeUpdateOp.Factory qtUpdateFactory) {
+ this.updateFactory = updateFactory;
+ this.json = json;
+ this.psUtil = psUtil;
+ this.qtUpdateFactory = qtUpdateFactory;
+ }
+
+ @Override
+ public Response apply(ChangeResource rsrc, AbandonInput input)
+ throws RestApiException, UpdateException, PermissionBackendException, IOException {
+ Change change = rsrc.getChange();
+ logger.atInfo().log("defer %s", rsrc.getChange().toString());
+
+ // Not allowed to defer if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes());
+
+ // Defer uses same permission as abandon
+ rsrc.permissions().check(ChangePermission.ABANDON);
+
+ if (change.getStatus() != Change.Status.NEW && change.getStatus() != Change.Status.ABANDONED) {
+ logger.atSevere().log("defer: change %s status wrong %s", change.getId(), change.getStatus());
+ throw new ResourceConflictException("change is " + ChangeUtil.status(change));
}
- @Override
- protected ChangeInfo applyImpl(BatchUpdate.Factory updateFactory,
- ChangeResource rsrc,
- AbandonInput input)
- throws RestApiException, UpdateException,
- OrmException, PermissionBackendException,
- IOException {
- Change change = rsrc.getChange();
- logger.atInfo().log("qtcodereview: defer %s", rsrc.getChange().toString());
-
- // Not allowed to defer if the current patch set is locked.
- psUtil.checkPatchSetNotLocked(rsrc.getNotes());
-
- // Defer uses same permission as abandon
- rsrc.permissions().database(dbProvider).check(ChangePermission.ABANDON);
+ QtChangeUpdateOp op =
+ qtUpdateFactory.create(
+ Change.Status.DEFERRED,
+ null,
+ "Deferred",
+ input.message,
+ ChangeMessagesUtil.TAG_ABANDON,
+ null);
+ try (BatchUpdate u =
+ updateFactory.create(change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
+ u.addOp(rsrc.getId(), op).execute();
+ }
- if (change.getStatus() != Change.Status.NEW && change.getStatus() != Change.Status.ABANDONED) {
- logger.atSevere().log("qtcodereview: defer: change %s status wrong %s", change, change.getStatus());
- throw new ResourceConflictException("change is " + ChangeUtil.status(change));
- }
+ change = op.getChange();
+ logger.atInfo().log("deferred %s,%s", change.getId(), change.getKey());
- QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.DEFERRED, null, "Deferred", input.message, ChangeMessagesUtil.TAG_ABANDON, null);
- try (BatchUpdate u = updateFactory.create(dbProvider.get(), change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
- u.addOp(rsrc.getId(), op).execute();
- }
+ return Response.ok(json.noOptions().format(change));
+ }
- change = op.getChange();
- logger.atInfo().log("qtcodereview: deferred %s", change);
+ @Override
+ public UiAction.Description getDescription(ChangeResource rsrc) {
+ UiAction.Description description =
+ new UiAction.Description().setLabel("Defer").setTitle("Defer the change").setVisible(false);
- return json.noOptions().format(change);
+ Change change = rsrc.getChange();
+ if (change.getStatus() != Change.Status.NEW && change.getStatus() != Change.Status.ABANDONED) {
+ return description;
}
- @Override
- public UiAction.Description getDescription(ChangeResource rsrc) {
- UiAction.Description description = new UiAction.Description()
- .setLabel("Defer")
- .setTitle("Defer the change")
- .setVisible(false);
-
- Change change = rsrc.getChange();
- if (change.getStatus() != Change.Status.NEW && change.getStatus() != Change.Status.ABANDONED) {
- 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;
- }
-
- return description.setVisible(rsrc.permissions().testOrFalse(ChangePermission.ABANDON));
+ try {
+ if (psUtil.isPatchSetLocked(rsrc.getNotes())) {
+ return description;
+ }
+ } catch (StorageException e) {
+ logger.atSevere().withCause(e).log(
+ "Failed to check if the current patch set of change %s is locked", change.getId());
+ return description;
}
+ return description.setVisible(rsrc.permissions().testOrFalse(ChangePermission.ABANDON));
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtEmailSender.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtEmailSender.java
new file mode 100644
index 0000000..af0cee5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtEmailSender.java
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2021 The Qt Company
+//
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Optional;
+
+@Singleton
+public class QtEmailSender {
+
+ @Inject private MergedSender.Factory mergedSenderFactory;
+
+ @Inject private QtBuildFailedSender.Factory qtBuildFailedSenderFactory;
+
+ @Inject private MessageIdGenerator messageIdGenerator;
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ public void sendMergedEmail(Project.NameKey projectKey, Change change, Account.Id fromAccount) {
+ try {
+ MergedSender mcm = mergedSenderFactory.create(projectKey, change.getId(), Optional.empty());
+ mcm.setFrom(fromAccount);
+ mcm.setMessageId(messageIdGenerator.fromChangeUpdate(projectKey, change.currentPatchSetId()));
+ mcm.send();
+ } catch (Exception e) {
+ logger.atWarning().log("Merged notification not sent for %s %s", change.getId(), e);
+ }
+ }
+
+ public void sendBuildFailedEmail(
+ Project.NameKey projectKey, Change change, Account.Id fromAccount, String message) {
+ try {
+ QtBuildFailedSender cm = qtBuildFailedSenderFactory.create(projectKey, change.getId());
+ cm.setFrom(fromAccount);
+ cm.setMessageId(messageIdGenerator.fromChangeUpdate(projectKey, change.currentPatchSetId()));
+ cm.setChangeMessage(message, TimeUtil.nowTs());
+ cm.send();
+ } catch (Exception e) {
+ logger.atWarning().log("Build Failed not sent notification for %s %s", change.getId(), e);
+ }
+ }
+}
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 c264e3a..5b17e51 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2019 The Qt Company
+// Copyright (C) 2020-22 The Qt Company
//
package com.googlesource.gerrit.plugins.qtcodereview;
@@ -7,39 +7,48 @@
import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND;
import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
-import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.events.EventTypes;
import com.google.gerrit.server.git.ChangeMessageModifier;
-
-import com.google.inject.Inject;
-import com.google.inject.AbstractModule;
-
-public class QtModule extends FactoryModule {
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- @Override
- protected void configure() {
-
- factory(QtBuildFailedSender.Factory.class);
- factory(QtChangeUpdateOp.Factory.class);
- DynamicSet.bind(binder(), ChangeMessageModifier.class).to(QtChangeMessageModifier.class);
-
- install(
- new RestApiModule() {
- @Override
- protected void configure() {
- post(CHANGE_KIND, "abandon").to(QtAbandon.class);
- post(CHANGE_KIND, "defer").to(QtDefer.class);
- post(CHANGE_KIND, "reopen").to(QtReOpen.class);
- post(REVISION_KIND, "stage").to(QtStage.class);
- post(REVISION_KIND, "unstage").to(QtUnStage.class);
- }
- }
- );
-
- }
-
+import com.google.gerrit.server.mail.send.MailSoyTemplateProvider;
+
+public class QtModule extends FactoryModule {
+
+ static {
+ EventTypes.register(QtChangeStagedEvent.TYPE, QtChangeStagedEvent.class);
+ EventTypes.register(QtChangeUnStagedEvent.TYPE, QtChangeUnStagedEvent.class);
+ EventTypes.register(QtChangePreCheckEvent.TYPE, QtChangePreCheckEvent.class);
+ }
+
+ @Override
+ protected void configure() {
+
+ // Plugin settings
+ bind(ProjectConfigEntry.class)
+ .annotatedWith(Exports.named("showReviewedOnFooter"))
+ .toInstance(new ProjectConfigEntry("Show 'Reviewed-on' footer in commit messages", false));
+
+ factory(QtBuildFailedSender.Factory.class);
+ factory(QtChangeUpdateOp.Factory.class);
+ DynamicSet.bind(binder(), ChangeMessageModifier.class).to(QtChangeMessageModifier.class);
+ DynamicSet.bind(binder(), MailSoyTemplateProvider.class)
+ .to(QtBuildFailedEmailTemplateRegister.class);
+
+ install(
+ new RestApiModule() {
+ @Override
+ protected void configure() {
+ post(CHANGE_KIND, "abandon").to(QtAbandon.class);
+ post(CHANGE_KIND, "defer").to(QtDefer.class);
+ post(CHANGE_KIND, "reopen").to(QtReOpen.class);
+ post(REVISION_KIND, "stage").to(QtStage.class);
+ post(REVISION_KIND, "unstage").to(QtUnStage.class);
+ post(REVISION_KIND, "precheck").to(QtPreCheck.class);
+ }
+ });
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtPreCheck.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtPreCheck.java
new file mode 100644
index 0000000..0a89a40
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtPreCheck.java
@@ -0,0 +1,194 @@
+//
+// Copyright (C) 2021-22 The Qt Company
+//
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.common.InputWithMessage;
+import com.google.gerrit.extensions.restapi.AuthException;
+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.change.RevisionResource;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.permissions.LabelPermission;
+import com.google.gerrit.server.permissions.LabelPermission.ForUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+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.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.qtcodereview.QtPrecheckMessage;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+
+@Singleton
+public class QtPreCheck
+ implements RestModifyView, UiAction {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final String DEFAULT_TOOLTIP = "Trigger a precheck integration";
+ private static final String DEFAULT_TOOLTIP_DISABLED = "Precheck disabled for this project";
+ private static final String LABEL_CODE_REVIEW = "Code-Review";
+ private static final short LABEL_CODE_REVIEW_VALUE = 2;
+
+ public static class Output {
+ transient Change change;
+
+ private Output(Change c) {
+ change = c;
+ }
+ }
+
+ private final PermissionBackend permissionBackend;
+ private final BatchUpdate.Factory updateFactory;
+ private final QtUtil qtUtil;
+ private final String label;
+ private final ParameterizedString titlePattern;
+ private final ParameterizedString titlePatternDisabled;
+
+ @Inject private PluginConfigFactory pluginCfg;
+
+ @Inject private QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Inject
+ QtPreCheck(
+ PermissionBackend permissionBackend,
+ @GerritServerConfig Config cfg,
+ BatchUpdate.Factory updateFactory,
+ QtUtil qtUtil) {
+
+ this.permissionBackend = permissionBackend;
+ this.updateFactory = updateFactory;
+ this.qtUtil = qtUtil;
+ this.label = "PreCheck";
+ this.titlePattern =
+ new ParameterizedString(
+ MoreObjects.firstNonNull(
+ cfg.getString("precheck", null, "precheckTooltip"), DEFAULT_TOOLTIP));
+ this.titlePatternDisabled =
+ new ParameterizedString(
+ MoreObjects.firstNonNull(
+ cfg.getString("precheck", null, "precheckTooltip"), DEFAULT_TOOLTIP_DISABLED));
+ }
+
+ @Override
+ public Response