Add refs/for/<branch>%submit to auto-merge during push

Teams that want to use Gerrit's submit strategies to handle contention
on busy branches can use %submit to create a change and have it
immediately submitted, if the caller has Submit permission on
refs/for/<ref>. If the merge fails the change stays open.

Change-Id: I98bcefb51ea5363b011e3e57f89f567b73e16103
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-04-04 16:06:39 +02:00
parent 561ef62a92
commit bfa06218e4
9 changed files with 653 additions and 162 deletions

View File

@@ -106,6 +106,10 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
checkSubmitRule(rsrc);
change = submit(rsrc, caller);
if (change == null) {
throw new ResourceConflictException("change is "
+ status(dbProvider.get().changes().get(rsrc.getChange().getId())));
}
if (input.waitForMerge) {
mergeQueue.merge(change.getDest());
@@ -123,21 +127,7 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
case MERGED:
return new Output(Status.MERGED, change);
case NEW:
// If the merge was attempted and it failed the system usually
// writes a comment as a ChangeMessage and sets status to NEW.
// Find the relevant message and report that as the conflict.
final Timestamp before = rsrc.getChange().getLastUpdatedOn();
ChangeMessage msg = Iterables.getFirst(Iterables.filter(
Lists.reverse(dbProvider.get().changeMessages()
.byChange(change.getId())
.toList()),
new Predicate<ChangeMessage>() {
@Override
public boolean apply(ChangeMessage input) {
return input.getAuthor() == null
&& input.getWrittenOn().getTime() >= before.getTime();
}
}), null);
ChangeMessage msg = getConflictMessage(rsrc);
if (msg != null) {
throw new ResourceConflictException(msg.getMessage());
}
@@ -146,8 +136,30 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
}
}
private Change submit(RevisionResource rsrc, IdentifiedUser caller)
throws OrmException, ResourceConflictException {
/**
* If the merge was attempted and it failed the system usually writes a
* comment as a ChangeMessage and sets status to NEW. Find the relevant
* message and return it.
*/
public ChangeMessage getConflictMessage(RevisionResource rsrc)
throws OrmException {
final Timestamp before = rsrc.getChange().getLastUpdatedOn();
ChangeMessage msg = Iterables.getFirst(Iterables.filter(
Lists.reverse(dbProvider.get().changeMessages()
.byChange(rsrc.getChange().getId())
.toList()),
new Predicate<ChangeMessage>() {
@Override
public boolean apply(ChangeMessage input) {
return input.getAuthor() == null
&& input.getWrittenOn().getTime() >= before.getTime();
}
}), null);
return msg;
}
public Change submit(RevisionResource rsrc, IdentifiedUser caller)
throws OrmException {
final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
Change change = rsrc.getChange();
ReviewDb db = dbProvider.get();
@@ -169,8 +181,7 @@ public class Submit implements RestModifyView<RevisionResource, Input> {
}
});
if (change == null) {
throw new ResourceConflictException("change is "
+ status(db.changes().get(rsrc.getChange().getId())));
return null;
}
db.commit();
} finally {

View File

@@ -14,7 +14,7 @@
package com.google.gerrit.server.git;
enum CommitMergeStatus {
public enum CommitMergeStatus {
/** */
CLEAN_MERGE("Change has been successfully merged into the git repository."),

View File

@@ -59,6 +59,9 @@ import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.TrackingFooters;
@@ -87,6 +90,7 @@ import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -277,6 +281,8 @@ public class ReceiveCommits {
private Map<String, Ref> allRefs;
private final SubmoduleOp.Factory subOpFactory;
private final Provider<Submit> submitProvider;
private final MergeQueue mergeQueue;
private final List<CommitValidationMessage> messages = new ArrayList<CommitValidationMessage>();
private ListMultimap<Error, String> errors = LinkedListMultimap.create();
@@ -316,7 +322,9 @@ public class ReceiveCommits {
ReceiveConfig config,
@Assisted final ProjectControl projectControl,
@Assisted final Repository repo,
final SubmoduleOp.Factory subOpFactory) throws IOException {
final SubmoduleOp.Factory subOpFactory,
final Provider<Submit> submitProvider,
final MergeQueue mergeQueue) throws IOException {
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
this.db = db;
this.schemaFactory = schemaFactory;
@@ -351,6 +359,8 @@ public class ReceiveCommits {
this.rejectCommits = loadRejectCommitsMap();
this.subOpFactory = subOpFactory;
this.submitProvider = submitProvider;
this.mergeQueue = mergeQueue;
this.messageSender = new ReceivePackMessageSender();
@@ -1000,6 +1010,9 @@ public class ReceiveCommits {
@Option(name = "--draft", usage = "mark new/update changes as draft")
boolean draft;
@Option(name = "--submit", usage = "immediately submit the change")
boolean submit;
@Option(name = "-r", metaVar = "EMAIL", usage = "add reviewer to changes")
void reviewer(Account.Id id) {
reviewer.add(id);
@@ -1024,6 +1037,10 @@ public class ReceiveCommits {
return draft;
}
boolean isSubmit() {
return submit;
}
MailRecipients getMailRecipients() {
return new MailRecipients(reviewer, cc);
}
@@ -1120,6 +1137,16 @@ public class ReceiveCommits {
return;
}
if (magicBranch.isDraft() && magicBranch.isSubmit()) {
reject(cmd, "cannot submit draft");
return;
}
if (magicBranch.isSubmit() && !projectControl.controlForRef(
MagicBranch.NEW_CHANGE + ref).canSubmit()) {
reject(cmd, "submit not allowed");
}
// Validate that the new commits are connected with the target
// branch. If they aren't, we want to abort. We do this check by
// looking to see if we can compute a merge base between the new
@@ -1493,6 +1520,41 @@ public class ReceiveCommits {
return "send-email newchange";
}
}));
if (magicBranch != null && magicBranch.isSubmit()) {
submit(projectControl.controlFor(change), ps);
}
}
}
private void submit(ChangeControl changeCtl, PatchSet ps) throws OrmException {
Submit submit = submitProvider.get();
RevisionResource rsrc = new RevisionResource(new ChangeResource(changeCtl), ps);
Change c = submit.submit(rsrc, currentUser);
if (c == null) {
addError("Submitting change " + changeCtl.getChange().getChangeId()
+ " failed.");
} else {
addMessage("");
mergeQueue.merge(c.getDest());
c = db.changes().get(c.getId());
switch (c.getStatus()) {
case SUBMITTED:
addMessage("Change " + c.getChangeId() + " submitted.");
break;
case MERGED:
addMessage("Change " + c.getChangeId() + " merged.");
break;
case NEW:
ChangeMessage msg = submit.getConflictMessage(rsrc);
if (msg != null) {
addMessage("Change " + c.getChangeId() + ": " + msg.getMessage());
break;
}
default:
addMessage("change " + c.getChangeId() + " is "
+ c.getStatus().name().toLowerCase());
}
}
}
@@ -1865,6 +1927,11 @@ public class ReceiveCommits {
return "send-email newpatchset";
}
}));
if (magicBranch != null && magicBranch.isSubmit()) {
submit(changeCtl, newPatchSet);
}
return newPatchSet.getId();
}
}