Enhance review ssh command to abandon/restore patchsets.
The review command could only do approvals, verifies, comments, and submits. It now can also do abandons and restores like the WUI. Also refactor the abandon and restore logic out of the AbandonChange and RestoreChange classes and into the ChangeUtil class since it is used by both the WUI and SSH now. Bug: issue 674 Change-Id: I7ac24a0ff47f3edaebb6fcad8ad3127f370e5672
This commit is contained in:
parent
78ea8756d3
commit
30f823c1f8
@ -8,8 +8,8 @@ gerrit review - Verify, approve and/or submit one or more patch sets
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'ssh' -p <port> <host> 'gerrit approve' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
|
||||
'ssh' -p <port> <host> 'gerrit review' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
|
||||
'ssh' -p <port> <host> 'gerrit approve' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--abandon] [\--restore] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
|
||||
'ssh' -p <port> <host> 'gerrit review' [\--project <PROJECT>] [\--message <MESSAGE>] [\--verified <N>] [\--code-review <N>] [\--abandon] [\--restore] [\--submit] {COMMIT | CHANGEID,PATCHSET}...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -54,9 +54,18 @@ OPTIONS
|
||||
differs per site, check the output of \--help, or contact
|
||||
your site administrator for further details.
|
||||
|
||||
\--abandon::
|
||||
Abandon the specified patch set(s).
|
||||
(option is mutually exclusive with --submit and --restore)
|
||||
|
||||
\--restore::
|
||||
Restore the specified abandonned patch set(s).
|
||||
(option is mutually exclusive with --abandon)
|
||||
|
||||
\--submit::
|
||||
-s::
|
||||
Submit the specified patch set(s) for merging.
|
||||
(option is mutually exclusive with --abandon)
|
||||
|
||||
ACCESS
|
||||
------
|
||||
|
@ -19,9 +19,7 @@ import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
@ -30,14 +28,10 @@ import com.google.gerrit.server.mail.EmailException;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.client.AtomicUpdate;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
class AbandonChange extends Handler<ChangeDetail> {
|
||||
@ -78,60 +72,15 @@ class AbandonChange extends Handler<ChangeDetail> {
|
||||
@Override
|
||||
public ChangeDetail call() throws NoSuchChangeException, OrmException,
|
||||
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
|
||||
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final ChangeControl control = changeControlFactory.validateFor(changeId);
|
||||
if (!control.canAbandon()) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
Change change = control.getChange();
|
||||
final PatchSet patch = db.patchSets().get(patchSetId);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
final ChangeMessage cmsg =
|
||||
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
|
||||
.messageUUID(db)), currentUser.getAccountId());
|
||||
final StringBuilder msgBuf =
|
||||
new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
|
||||
if (message != null && message.length() > 0) {
|
||||
msgBuf.append("\n\n");
|
||||
msgBuf.append(message);
|
||||
}
|
||||
cmsg.setMessage(msgBuf.toString());
|
||||
|
||||
change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus().isOpen()
|
||||
&& change.currentPatchSetId().equals(patchSetId)) {
|
||||
change.setStatus(Change.Status.ABANDONED);
|
||||
ChangeUtil.updated(change);
|
||||
return change;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (change != null) {
|
||||
db.changeMessages().insert(Collections.singleton(cmsg));
|
||||
|
||||
final List<PatchSetApproval> approvals =
|
||||
db.patchSetApprovals().byChange(changeId).toList();
|
||||
for (PatchSetApproval a : approvals) {
|
||||
a.cache(change);
|
||||
}
|
||||
db.patchSetApprovals().update(approvals);
|
||||
|
||||
// Email the reviewers
|
||||
final AbandonedSender cm = abandonedSenderFactory.create(change);
|
||||
cm.setFrom(currentUser.getAccountId());
|
||||
cm.setChangeMessage(cmsg);
|
||||
cm.send();
|
||||
}
|
||||
|
||||
hooks.doChangeAbandonedHook(change, currentUser.getAccount(), message);
|
||||
ChangeUtil.abandon(patchSetId, currentUser, message, db,
|
||||
abandonedSenderFactory, hooks);
|
||||
|
||||
return changeDetailFactory.create(changeId).call();
|
||||
}
|
||||
|
@ -73,59 +73,15 @@ class RestoreChange extends Handler<ChangeDetail> {
|
||||
@Override
|
||||
public ChangeDetail call() throws NoSuchChangeException, OrmException,
|
||||
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
|
||||
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final ChangeControl control = changeControlFactory.validateFor(changeId);
|
||||
if (!control.canRestore()) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
final PatchSet patch = db.patchSets().get(patchSetId);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
final ChangeMessage cmsg =
|
||||
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
|
||||
.messageUUID(db)), currentUser.getAccountId());
|
||||
final StringBuilder msgBuf =
|
||||
new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
|
||||
if (message != null && message.length() > 0) {
|
||||
msgBuf.append("\n\n");
|
||||
msgBuf.append(message);
|
||||
}
|
||||
cmsg.setMessage(msgBuf.toString());
|
||||
|
||||
Change change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus() == Change.Status.ABANDONED
|
||||
&& change.currentPatchSetId().equals(patchSetId)) {
|
||||
change.setStatus(Change.Status.NEW);
|
||||
ChangeUtil.updated(change);
|
||||
return change;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (change != null) {
|
||||
db.changeMessages().insert(Collections.singleton(cmsg));
|
||||
|
||||
final List<PatchSetApproval> approvals =
|
||||
db.patchSetApprovals().byChange(changeId).toList();
|
||||
for (PatchSetApproval a : approvals) {
|
||||
a.cache(change);
|
||||
}
|
||||
db.patchSetApprovals().update(approvals);
|
||||
|
||||
// Email the reviewers
|
||||
final AbandonedSender cm = abandonedSenderFactory.create(change);
|
||||
cm.setFrom(currentUser.getAccountId());
|
||||
cm.setChangeMessage(cmsg);
|
||||
cm.send();
|
||||
}
|
||||
|
||||
hooks.doChangeRestoreHook(change, currentUser.getAccount(), message);
|
||||
ChangeUtil.restore(patchSetId, currentUser, message, db,
|
||||
abandonedSenderFactory, hooks);
|
||||
|
||||
return changeDetailFactory.create(changeId).call();
|
||||
}
|
||||
|
@ -16,19 +16,28 @@ package com.google.gerrit.server;
|
||||
|
||||
import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT;
|
||||
|
||||
import com.google.gerrit.common.ChangeHookRunner;
|
||||
import com.google.gerrit.common.data.ChangeDetail;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.TrackingId;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.TrackingFooter;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.mail.AbandonedSender;
|
||||
import com.google.gerrit.server.mail.EmailException;
|
||||
import com.google.gwtorm.client.AtomicUpdate;
|
||||
import com.google.gwtorm.client.OrmConcurrencyException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
|
||||
|
||||
import org.eclipse.jgit.revwalk.FooterLine;
|
||||
import org.eclipse.jgit.util.Base64;
|
||||
import org.eclipse.jgit.util.NB;
|
||||
@ -178,6 +187,119 @@ public class ChangeUtil {
|
||||
return new PatchSetApproval(akey, (short) 1);
|
||||
}
|
||||
|
||||
public static void abandon(final PatchSet.Id patchSetId,
|
||||
final IdentifiedUser user, final String message, final ReviewDb db,
|
||||
final AbandonedSender.Factory abandonedSenderFactory,
|
||||
final ChangeHookRunner hooks) throws NoSuchChangeException,
|
||||
EmailException, OrmException {
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final PatchSet patch = db.patchSets().get(patchSetId);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
final ChangeMessage cmsg =
|
||||
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
|
||||
.messageUUID(db)), user.getAccountId());
|
||||
final StringBuilder msgBuf =
|
||||
new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
|
||||
if (message != null && message.length() > 0) {
|
||||
msgBuf.append("\n\n");
|
||||
msgBuf.append(message);
|
||||
}
|
||||
cmsg.setMessage(msgBuf.toString());
|
||||
|
||||
final Change change = db.changes().atomicUpdate(changeId,
|
||||
new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus().isOpen()
|
||||
&& change.currentPatchSetId().equals(patchSetId)) {
|
||||
change.setStatus(Change.Status.ABANDONED);
|
||||
ChangeUtil.updated(change);
|
||||
return change;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (change != null) {
|
||||
db.changeMessages().insert(Collections.singleton(cmsg));
|
||||
|
||||
final List<PatchSetApproval> approvals =
|
||||
db.patchSetApprovals().byChange(changeId).toList();
|
||||
for (PatchSetApproval a : approvals) {
|
||||
a.cache(change);
|
||||
}
|
||||
db.patchSetApprovals().update(approvals);
|
||||
|
||||
// Email the reviewers
|
||||
final AbandonedSender cm = abandonedSenderFactory.create(change);
|
||||
cm.setFrom(user.getAccountId());
|
||||
cm.setChangeMessage(cmsg);
|
||||
cm.send();
|
||||
}
|
||||
|
||||
hooks.doChangeAbandonedHook(change, user.getAccount(), message);
|
||||
}
|
||||
|
||||
public static void restore(final PatchSet.Id patchSetId,
|
||||
final IdentifiedUser user, final String message, final ReviewDb db,
|
||||
final AbandonedSender.Factory abandonedSenderFactory,
|
||||
final ChangeHookRunner hooks) throws NoSuchChangeException,
|
||||
EmailException, OrmException {
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final PatchSet patch = db.patchSets().get(patchSetId);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
final ChangeMessage cmsg =
|
||||
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
|
||||
.messageUUID(db)), user.getAccountId());
|
||||
final StringBuilder msgBuf =
|
||||
new StringBuilder("Patch Set " + patchSetId.get() + ": Restored");
|
||||
if (message != null && message.length() > 0) {
|
||||
msgBuf.append("\n\n");
|
||||
msgBuf.append(message);
|
||||
}
|
||||
cmsg.setMessage(msgBuf.toString());
|
||||
|
||||
Change change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change change) {
|
||||
if (change.getStatus() == Change.Status.ABANDONED
|
||||
&& change.currentPatchSetId().equals(patchSetId)) {
|
||||
change.setStatus(Change.Status.NEW);
|
||||
ChangeUtil.updated(change);
|
||||
return change;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (change != null) {
|
||||
db.changeMessages().insert(Collections.singleton(cmsg));
|
||||
|
||||
final List<PatchSetApproval> approvals =
|
||||
db.patchSetApprovals().byChange(changeId).toList();
|
||||
for (PatchSetApproval a : approvals) {
|
||||
a.cache(change);
|
||||
}
|
||||
db.patchSetApprovals().update(approvals);
|
||||
|
||||
// Email the reviewers
|
||||
final AbandonedSender cm = abandonedSenderFactory.create(change);
|
||||
cm.setFrom(user.getAccountId());
|
||||
cm.setChangeMessage(cmsg);
|
||||
cm.send();
|
||||
}
|
||||
|
||||
hooks.doChangeRestoreHook(change, user.getAccount(), message);
|
||||
}
|
||||
|
||||
public static String sortKey(long lastUpdated, int id){
|
||||
// The encoding uses minutes since Wed Oct 1 00:00:00 2008 UTC.
|
||||
// We overrun approximately 4,085 years later, so ~6093.
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.ChangeHookRunner;
|
||||
import com.google.gerrit.common.data.ApprovalType;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.reviewdb.ApprovalCategory;
|
||||
@ -30,6 +31,8 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.MergeOp;
|
||||
import com.google.gerrit.server.git.MergeOp.Factory;
|
||||
import com.google.gerrit.server.git.MergeQueue;
|
||||
import com.google.gerrit.server.mail.AbandonedSender;
|
||||
import com.google.gerrit.server.mail.EmailException;
|
||||
import com.google.gerrit.server.patch.PublishComments;
|
||||
import com.google.gerrit.server.project.CanSubmitResult;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
@ -88,6 +91,12 @@ public class ReviewCommand extends BaseCommand {
|
||||
@Option(name = "--message", aliases = "-m", usage = "cover message to publish on change", metaVar = "MESSAGE")
|
||||
private String changeComment;
|
||||
|
||||
@Option(name = "--abandon", usage = "abandon the patch set")
|
||||
private boolean abandonChange;
|
||||
|
||||
@Option(name = "--restore", usage = "restore an abandoned the patch set")
|
||||
private boolean restoreChange;
|
||||
|
||||
@Option(name = "--submit", aliases = "-s", usage = "submit the patch set")
|
||||
private boolean submitChange;
|
||||
|
||||
@ -110,13 +119,19 @@ public class ReviewCommand extends BaseCommand {
|
||||
private ChangeControl.Factory changeControlFactory;
|
||||
|
||||
@Inject
|
||||
private FunctionState.Factory functionStateFactory;
|
||||
private AbandonedSender.Factory abandonedSenderFactory;
|
||||
|
||||
private List<ApproveOption> optionList;
|
||||
@Inject
|
||||
private FunctionState.Factory functionStateFactory;
|
||||
|
||||
@Inject
|
||||
private PublishComments.Factory publishCommentsFactory;
|
||||
|
||||
@Inject
|
||||
private ChangeHookRunner hooks;
|
||||
|
||||
private List<ApproveOption> optionList;
|
||||
|
||||
private Set<PatchSet.Id> toSubmit = new HashSet<PatchSet.Id>();
|
||||
|
||||
@Override
|
||||
@ -126,6 +141,14 @@ public class ReviewCommand extends BaseCommand {
|
||||
public void run() throws Failure {
|
||||
initOptionList();
|
||||
parseCommandLine();
|
||||
if (abandonChange) {
|
||||
if (restoreChange) {
|
||||
throw error("abandon and restore actions are mutually exclusive");
|
||||
}
|
||||
if (submitChange) {
|
||||
throw error("abandon and submit actions are mutually exclusive");
|
||||
}
|
||||
}
|
||||
|
||||
boolean ok = true;
|
||||
for (final PatchSet.Id patchSetId : patchSetIds) {
|
||||
@ -182,11 +205,11 @@ public class ReviewCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
private void approveOne(final PatchSet.Id patchSetId)
|
||||
throws NoSuchChangeException, UnloggedFailure, OrmException {
|
||||
throws NoSuchChangeException, UnloggedFailure, OrmException,
|
||||
EmailException {
|
||||
|
||||
final Change.Id changeId = patchSetId.getParentKey();
|
||||
final ChangeControl changeControl =
|
||||
changeControlFactory.validateFor(changeId);
|
||||
ChangeControl changeControl = changeControlFactory.validateFor(changeId);
|
||||
|
||||
if (changeComment == null) {
|
||||
changeComment = "";
|
||||
@ -203,6 +226,27 @@ public class ReviewCommand extends BaseCommand {
|
||||
|
||||
publishCommentsFactory.create(patchSetId, changeComment, aps).call();
|
||||
|
||||
if (abandonChange) {
|
||||
if (changeControl.canAbandon()) {
|
||||
ChangeUtil.abandon(patchSetId, currentUser, changeComment, db,
|
||||
abandonedSenderFactory, hooks);
|
||||
} else {
|
||||
throw error("Not permitted to abandon change");
|
||||
}
|
||||
}
|
||||
|
||||
if (restoreChange) {
|
||||
if (changeControl.canRestore()) {
|
||||
ChangeUtil.restore(patchSetId, currentUser, changeComment, db,
|
||||
abandonedSenderFactory, hooks);
|
||||
} else {
|
||||
throw error("Not permitted to restore change");
|
||||
}
|
||||
if (submitChange) {
|
||||
changeControl = changeControlFactory.validateFor(changeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (submitChange) {
|
||||
CanSubmitResult result =
|
||||
changeControl.canSubmit(patchSetId, db, approvalTypes,
|
||||
|
Loading…
x
Reference in New Issue
Block a user