Merge changes from topics 'mergeop', 'mergeop-prep'
* changes: Rewrite MergeOp to use BatchUpdate BatchUpdate: Eagerly load ChangeNotes MergeOp: Record when submit rules were bypassed BatchUpdate: Always bump lastUpdatedOn BatchUpdate: Don't update change if there was nothing to do
This commit is contained in:
@@ -33,6 +33,9 @@ public class SubmitRecord {
|
||||
/** The change has been closed. */
|
||||
CLOSED,
|
||||
|
||||
/** The change was submitted bypassing submit rules. */
|
||||
FORCED,
|
||||
|
||||
/**
|
||||
* An internal server error occurred preventing computation.
|
||||
* <p>
|
||||
|
||||
@@ -118,7 +118,7 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||
public boolean updateChange(ChangeContext ctx) throws OrmException,
|
||||
ResourceConflictException {
|
||||
change = ctx.getChange();
|
||||
PatchSet.Id psId = change.currentPatchSetId();
|
||||
@@ -137,6 +137,7 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
|
||||
update.setStatus(change.getStatus());
|
||||
message = newMessage(ctx.getDb());
|
||||
cmUtil.addChangeMessage(ctx.getDb(), update, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ChangeMessage newMessage(ReviewDb db) throws OrmException {
|
||||
|
||||
@@ -287,7 +287,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException, IOException {
|
||||
public boolean updateChange(ChangeContext ctx) throws OrmException, IOException {
|
||||
change = ctx.getChange(); // Use defensive copy created by ChangeControl.
|
||||
ReviewDb db = ctx.getDb();
|
||||
ChangeControl ctl = ctx.getControl();
|
||||
@@ -330,6 +330,7 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
||||
changeMessage.setMessage(message);
|
||||
cmUtil.addChangeMessage(db, update, changeMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -104,7 +104,7 @@ public class CreateDraftComment implements RestModifyView<RevisionResource, Draf
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws ResourceNotFoundException, OrmException {
|
||||
PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
|
||||
if (ps == null) {
|
||||
@@ -126,6 +126,7 @@ public class CreateDraftComment implements RestModifyView<RevisionResource, Draf
|
||||
comment, patchListCache, ctx.getChange(), ps);
|
||||
plcUtil.insertComments(
|
||||
ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(comment));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class DeleteDraftChangeOp extends BatchUpdate.Op {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws RestApiException, OrmException {
|
||||
checkState(ctx.getOrder() == BatchUpdate.Order.DB_BEFORE_REPO,
|
||||
"must use DeleteDraftChangeOp with DB_BEFORE_REPO");
|
||||
@@ -100,6 +100,7 @@ class DeleteDraftChangeOp extends BatchUpdate.Op {
|
||||
db.changeMessages().delete(db.changeMessages().byChange(id));
|
||||
starredChangesUtil.unstarAll(id);
|
||||
ctx.deleteChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,12 +86,12 @@ public class DeleteDraftComment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws ResourceNotFoundException, OrmException {
|
||||
Optional<PatchLineComment> maybeComment =
|
||||
plcUtil.get(ctx.getDb(), ctx.getNotes(), key);
|
||||
if (!maybeComment.isPresent()) {
|
||||
return; // Nothing to do.
|
||||
return false; // Nothing to do.
|
||||
}
|
||||
PatchSet.Id psId = key.getParentKey().getParentKey();
|
||||
PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
|
||||
@@ -102,6 +102,7 @@ public class DeleteDraftComment
|
||||
setCommentRevId(c, patchListCache, ctx.getChange(), ps);
|
||||
plcUtil.deleteComments(
|
||||
ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(c));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.PatchSetUtil;
|
||||
import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
@@ -100,11 +99,11 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws RestApiException, OrmException, IOException {
|
||||
patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
|
||||
if (patchSet == null) {
|
||||
return; // Nothing to do.
|
||||
return false; // Nothing to do.
|
||||
}
|
||||
if (!patchSet.isDraft()) {
|
||||
throw new ResourceConflictException("Patch set is not a draft");
|
||||
@@ -118,6 +117,7 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
||||
|
||||
deleteDraftPatchSet(patchSet, ctx);
|
||||
deleteOrUpdateDraftChange(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,7 +155,6 @@ public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Inp
|
||||
if (c.currentPatchSetId().equals(psId)) {
|
||||
c.setCurrentPatchSet(previousPatchSetInfo(ctx));
|
||||
}
|
||||
ChangeUtil.updated(c);
|
||||
ctx.saveChange();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public class DeleteVote implements RestModifyView<VoteResource, Input> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws OrmException, AuthException, ResourceNotFoundException {
|
||||
IdentifiedUser user = ctx.getUser().asIdentifiedUser();
|
||||
Change change = ctx.getChange();
|
||||
@@ -135,6 +135,7 @@ public class DeleteVote implements RestModifyView<VoteResource, Input> {
|
||||
cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId),
|
||||
changeMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ public class PatchSetInserter extends BatchUpdate.Op {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||
public boolean updateChange(ChangeContext ctx) throws OrmException,
|
||||
InvalidChangeOperationException, IOException {
|
||||
ChangeControl ctl = ctx.getControl();
|
||||
|
||||
@@ -241,7 +241,6 @@ public class PatchSetInserter extends BatchUpdate.Op {
|
||||
change.setStatus(Change.Status.NEW);
|
||||
}
|
||||
change.setCurrentPatchSet(patchSetInfo);
|
||||
ChangeUtil.updated(change);
|
||||
ctx.saveChange();
|
||||
if (copyApprovals) {
|
||||
approvalCopier.copy(db, ctl, patchSet);
|
||||
@@ -249,6 +248,7 @@ public class PatchSetInserter extends BatchUpdate.Op {
|
||||
if (changeMessage != null) {
|
||||
cmUtil.addChangeMessage(db, update, changeMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -354,7 +354,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws OrmException, ResourceConflictException {
|
||||
user = ctx.getUser().asIdentifiedUser();
|
||||
change = ctx.getChange();
|
||||
@@ -369,6 +369,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
||||
if (dirty) {
|
||||
ctx.saveChange();
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -168,7 +168,7 @@ public class PublishDraftPatchSet implements RestModifyView<RevisionResource, In
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws RestApiException, OrmException, IOException {
|
||||
if (!ctx.getControl().canPublish(ctx.getDb())) {
|
||||
throw new AuthException("Cannot publish this draft patch set");
|
||||
@@ -182,6 +182,7 @@ public class PublishDraftPatchSet implements RestModifyView<RevisionResource, In
|
||||
saveChange(ctx);
|
||||
savePatchSet(ctx);
|
||||
addReviewers(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveChange(ChangeContext ctx) {
|
||||
@@ -191,7 +192,6 @@ public class PublishDraftPatchSet implements RestModifyView<RevisionResource, In
|
||||
if (wasDraftChange) {
|
||||
change.setStatus(Change.Status.NEW);
|
||||
update.setStatus(change.getStatus());
|
||||
ChangeUtil.updated(change);
|
||||
ctx.saveChange();
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,6 @@ public class PublishDraftPatchSet implements RestModifyView<RevisionResource, In
|
||||
patchSet.setDraft(false);
|
||||
// Force ETag invalidation if not done already
|
||||
if (!wasDraftChange) {
|
||||
ChangeUtil.updated(change);
|
||||
ctx.saveChange();
|
||||
}
|
||||
ctx.getDb().patchSets().update(Collections.singleton(patchSet));
|
||||
|
||||
@@ -109,7 +109,7 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws ResourceNotFoundException, OrmException {
|
||||
Optional<PatchLineComment> maybeComment =
|
||||
plcUtil.get(ctx.getDb(), ctx.getNotes(), key);
|
||||
@@ -152,6 +152,7 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
|
||||
plcUtil.updateComments(ctx.getDb(), update,
|
||||
Collections.singleton(update(comment, in)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,13 +98,13 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException {
|
||||
public boolean updateChange(ChangeContext ctx) throws OrmException {
|
||||
change = ctx.getChange();
|
||||
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
|
||||
newTopicName = Strings.nullToEmpty(input.topic);
|
||||
oldTopicName = Strings.nullToEmpty(change.getTopic());
|
||||
if (oldTopicName.equals(newTopicName)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
String summary;
|
||||
if (oldTopicName.isEmpty()) {
|
||||
@@ -117,7 +117,6 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
|
||||
}
|
||||
change.setTopic(Strings.emptyToNull(newTopicName));
|
||||
update.setTopic(change.getTopic());
|
||||
ChangeUtil.updated(change);
|
||||
ctx.saveChange();
|
||||
|
||||
ChangeMessage cmsg = new ChangeMessage(
|
||||
@@ -128,6 +127,7 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
|
||||
change.currentPatchSetId());
|
||||
cmsg.setMessage(summary);
|
||||
cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -152,10 +152,11 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws OrmException, InvalidChangeOperationException, IOException {
|
||||
patchSetInserter.updateChange(ctx);
|
||||
boolean ret = patchSetInserter.updateChange(ctx);
|
||||
rebasedPatchSet = patchSetInserter.getPatchSet();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -108,7 +108,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||
public boolean updateChange(ChangeContext ctx) throws OrmException,
|
||||
ResourceConflictException {
|
||||
caller = ctx.getUser().asIdentifiedUser();
|
||||
change = ctx.getChange();
|
||||
@@ -125,6 +125,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
|
||||
|
||||
message = newMessage(ctx.getDb());
|
||||
cmUtil.addChangeMessage(ctx.getDb(), update, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ChangeMessage newMessage(ReviewDb db) throws OrmException {
|
||||
|
||||
@@ -73,12 +73,12 @@ public class SetHashtagsOp extends BatchUpdate.Op {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx)
|
||||
public boolean updateChange(ChangeContext ctx)
|
||||
throws AuthException, BadRequestException, OrmException, IOException {
|
||||
if (input == null
|
||||
|| (input.add == null && input.remove == null)) {
|
||||
updatedHashtags = ImmutableSortedSet.of();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!ctx.getControl().canEditHashtags()) {
|
||||
throw new AuthException("Editing hashtags not permitted");
|
||||
@@ -112,6 +112,7 @@ public class SetHashtagsOp extends BatchUpdate.Op {
|
||||
}
|
||||
|
||||
updatedHashtags = ImmutableSortedSet.copyOf(updated);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.google.gerrit.extensions.api.changes.SubmitInput;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.extensions.webui.UiAction;
|
||||
@@ -157,9 +158,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
||||
|
||||
@Override
|
||||
public Output apply(RevisionResource rsrc, SubmitInput input)
|
||||
throws AuthException, ResourceConflictException,
|
||||
RepositoryNotFoundException, IOException, OrmException,
|
||||
UnprocessableEntityException {
|
||||
throws RestApiException, RepositoryNotFoundException, IOException,
|
||||
OrmException {
|
||||
input.onBehalfOf = Strings.emptyToNull(input.onBehalfOf);
|
||||
if (input.onBehalfOf != null) {
|
||||
rsrc = onBehalfOf(rsrc, input);
|
||||
@@ -430,9 +430,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
||||
|
||||
@Override
|
||||
public ChangeInfo apply(ChangeResource rsrc, SubmitInput input)
|
||||
throws AuthException, ResourceConflictException,
|
||||
RepositoryNotFoundException, IOException, OrmException,
|
||||
UnprocessableEntityException {
|
||||
throws RestApiException, RepositoryNotFoundException, IOException,
|
||||
OrmException {
|
||||
PatchSet ps = dbProvider.get().patchSets()
|
||||
.get(rsrc.getChange().currentPatchSetId());
|
||||
if (ps == null) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
||||
import com.google.gerrit.server.index.ChangeIndexer;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||
import com.google.gerrit.server.notedb.NotesMigration;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.google.inject.assistedinject.AssistedInject;
|
||||
@@ -221,7 +222,8 @@ public class BatchUpdate implements AutoCloseable {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void updateChange(ChangeContext ctx) throws Exception {
|
||||
public boolean updateChange(ChangeContext ctx) throws Exception {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(dborowitz): Support async operations?
|
||||
@@ -380,6 +382,7 @@ public class BatchUpdate implements AutoCloseable {
|
||||
private final ChangeControl.GenericFactory changeControlFactory;
|
||||
private final ChangeUpdate.Factory changeUpdateFactory;
|
||||
private final GitReferenceUpdated gitRefUpdated;
|
||||
private final NotesMigration notesMigration;
|
||||
|
||||
private final Project.NameKey project;
|
||||
private final CurrentUser user;
|
||||
@@ -405,6 +408,7 @@ public class BatchUpdate implements AutoCloseable {
|
||||
ChangeControl.GenericFactory changeControlFactory,
|
||||
ChangeUpdate.Factory changeUpdateFactory,
|
||||
GitReferenceUpdated gitRefUpdated,
|
||||
NotesMigration notesMigration,
|
||||
@GerritPersonIdent PersonIdent serverIdent,
|
||||
@Assisted ReviewDb db,
|
||||
@Assisted Project.NameKey project,
|
||||
@@ -416,6 +420,7 @@ public class BatchUpdate implements AutoCloseable {
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.changeUpdateFactory = changeUpdateFactory;
|
||||
this.gitRefUpdated = gitRefUpdated;
|
||||
this.notesMigration = notesMigration;
|
||||
this.project = project;
|
||||
this.user = user;
|
||||
this.when = when;
|
||||
@@ -542,11 +547,13 @@ public class BatchUpdate implements AutoCloseable {
|
||||
Change.Id id = e.getKey();
|
||||
db.changes().beginTransaction(id);
|
||||
ChangeContext ctx;
|
||||
boolean dirty = false;
|
||||
try {
|
||||
ctx = newChangeContext(id);
|
||||
for (Op op : e.getValue()) {
|
||||
op.updateChange(ctx);
|
||||
dirty |= op.updateChange(ctx);
|
||||
}
|
||||
ctx.getChange().setLastUpdatedOn(ctx.getWhen());
|
||||
Iterable<Change> changes = Collections.singleton(ctx.getChange());
|
||||
if (newChanges.containsKey(id)) {
|
||||
db.changes().insert(changes);
|
||||
@@ -555,26 +562,30 @@ public class BatchUpdate implements AutoCloseable {
|
||||
} else if (ctx.deleted) {
|
||||
db.changes().delete(changes);
|
||||
}
|
||||
db.commit();
|
||||
if (dirty) {
|
||||
db.commit();
|
||||
}
|
||||
} finally {
|
||||
db.rollback();
|
||||
}
|
||||
|
||||
BatchMetaDataUpdate bmdu = null;
|
||||
for (ChangeUpdate u : ctx.updates.values()) {
|
||||
if (bmdu == null) {
|
||||
bmdu = u.openUpdate();
|
||||
if (dirty) {
|
||||
BatchMetaDataUpdate bmdu = null;
|
||||
for (ChangeUpdate u : ctx.updates.values()) {
|
||||
if (bmdu == null) {
|
||||
bmdu = u.openUpdate();
|
||||
}
|
||||
u.writeCommit(bmdu);
|
||||
}
|
||||
if (bmdu != null) {
|
||||
bmdu.commit();
|
||||
}
|
||||
u.writeCommit(bmdu);
|
||||
}
|
||||
if (bmdu != null) {
|
||||
bmdu.commit();
|
||||
}
|
||||
|
||||
if (ctx.deleted) {
|
||||
indexFutures.add(indexer.deleteAsync(id));
|
||||
} else {
|
||||
indexFutures.add(indexer.indexAsync(id));
|
||||
if (ctx.deleted) {
|
||||
indexFutures.add(indexer.deleteAsync(id));
|
||||
} else {
|
||||
indexFutures.add(indexer.indexAsync(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -591,8 +602,12 @@ public class BatchUpdate implements AutoCloseable {
|
||||
// Pass in preloaded change to controlFor, to avoid:
|
||||
// - reading from a db that does not belong to this update
|
||||
// - attempting to read a change that doesn't exist yet
|
||||
return new ChangeContext(
|
||||
ChangeContext ctx = new ChangeContext(
|
||||
changeControlFactory.controlFor(c, user), new BatchUpdateReviewDb(db));
|
||||
if (notesMigration.readChanges()) {
|
||||
ctx.getNotes().load();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private void executePostOps() throws Exception {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class EmailMerge implements Runnable, RequestContext {
|
||||
this.submitter = submitter;
|
||||
}
|
||||
|
||||
void sendAsync() {
|
||||
public void sendAsync() {
|
||||
sendEmailsExecutor.submit(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
|
||||
package com.google.gerrit.server.git;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
@@ -35,40 +35,29 @@ import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.gerrit.common.ChangeHooks;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.common.data.SubmitRecord;
|
||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
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.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.LabelId;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ApprovalsUtil;
|
||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
||||
import com.google.gerrit.server.git.strategy.CommitMergeStatus;
|
||||
import com.google.gerrit.server.git.strategy.SubmitStrategy;
|
||||
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
|
||||
import com.google.gerrit.server.git.strategy.SubmitStrategyListener;
|
||||
import com.google.gerrit.server.git.validators.MergeValidationException;
|
||||
import com.google.gerrit.server.git.validators.MergeValidators;
|
||||
import com.google.gerrit.server.index.ChangeIndexer;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
@@ -84,7 +73,6 @@ import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
@@ -105,6 +93,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -127,12 +116,14 @@ import java.util.Set;
|
||||
public class MergeOp implements AutoCloseable {
|
||||
private static final Logger log = LoggerFactory.getLogger(MergeOp.class);
|
||||
|
||||
private static class OpenRepo {
|
||||
private class OpenRepo {
|
||||
final Repository repo;
|
||||
final CodeReviewRevWalk rw;
|
||||
final RevFlag canMergeFlag;
|
||||
final ObjectInserter ins;
|
||||
|
||||
ProjectState project;
|
||||
BatchUpdate update;
|
||||
|
||||
private final Map<Branch.NameKey, OpenBranch> branches;
|
||||
|
||||
@@ -162,7 +153,18 @@ public class MergeOp implements AutoCloseable {
|
||||
return project.getProject().getNameKey();
|
||||
}
|
||||
|
||||
BatchUpdate getUpdate() {
|
||||
if (update == null) {
|
||||
update = batchUpdateFactory.create(db, getProjectName(), caller, ts);
|
||||
update.setRepository(repo, rw, ins);
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (update != null) {
|
||||
update.close();
|
||||
}
|
||||
ins.close();
|
||||
rw.close();
|
||||
repo.close();
|
||||
@@ -192,22 +194,39 @@ public class MergeOp implements AutoCloseable {
|
||||
throw new IntegrationException("Cannot open branch " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
CodeReviewCommit getCurrentTip() {
|
||||
return mergeTip != null ? mergeTip.getCurrentTip() : oldTip;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommitStatus {
|
||||
private final Map<Change.Id, CodeReviewCommit> commits = new HashMap<>();
|
||||
private final Multimap<Change.Id, String> problems =
|
||||
MultimapBuilder.treeKeys(
|
||||
private final ImmutableMap<Change.Id, ChangeData> changes;
|
||||
private final ImmutableSetMultimap<Branch.NameKey, Change.Id> byBranch;
|
||||
private final Map<Change.Id, CodeReviewCommit> commits;
|
||||
private final Multimap<Change.Id, String> problems;
|
||||
|
||||
private CommitStatus(ChangeSet cs) throws OrmException {
|
||||
changes = cs.changesById();
|
||||
ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> bb =
|
||||
ImmutableSetMultimap.builder();
|
||||
for (ChangeData cd : cs.changes()) {
|
||||
bb.put(cd.change().getDest(), cd.getId());
|
||||
}
|
||||
byBranch = bb.build();
|
||||
commits = new HashMap<>();
|
||||
problems = MultimapBuilder.treeKeys(
|
||||
Ordering.natural().onResultOf(new Function<Change.Id, Integer>() {
|
||||
@Override
|
||||
public Integer apply(Change.Id in) {
|
||||
return in.get();
|
||||
}
|
||||
})).arrayListValues(1).build();
|
||||
}
|
||||
|
||||
public ImmutableSet<Change.Id> getChangeIds() {
|
||||
return changes.keySet();
|
||||
}
|
||||
|
||||
public ImmutableSet<Change.Id> getChangeIds(Branch.NameKey branch) {
|
||||
return byBranch.get(branch);
|
||||
}
|
||||
|
||||
public CodeReviewCommit get(Change.Id changeId) {
|
||||
return commits.get(changeId);
|
||||
@@ -232,36 +251,70 @@ public class MergeOp implements AutoCloseable {
|
||||
problems.put(id, msg);
|
||||
}
|
||||
|
||||
boolean isOk() {
|
||||
public boolean isOk() {
|
||||
return problems.isEmpty();
|
||||
}
|
||||
|
||||
ImmutableMultimap<Change.Id, String> getProblems() {
|
||||
public ImmutableMultimap<Change.Id, String> getProblems() {
|
||||
return ImmutableMultimap.copyOf(problems);
|
||||
}
|
||||
|
||||
public List<SubmitRecord> getSubmitRecords(Change.Id id) {
|
||||
// Use the cached submit records from the original ChangeData in the input
|
||||
// ChangeSet, which were checked earlier in the integrate process. Even in
|
||||
// the case of a race where the submit records may have changed, it makes
|
||||
// more sense to store the original results of the submit rule evaluator
|
||||
// than to fail at this point.
|
||||
//
|
||||
// However, do NOT expose that ChangeData directly, as it is way out of
|
||||
// date by this point.
|
||||
ChangeData cd = checkNotNull(changes.get(id), "ChangeData for %s", id);
|
||||
return checkNotNull(cd.getSubmitRecords(),
|
||||
"getSubmitRecord only valid after submit rules are evalutated");
|
||||
}
|
||||
|
||||
public void maybeFailVerbose() throws ResourceConflictException {
|
||||
if (isOk()) {
|
||||
return;
|
||||
}
|
||||
String msg = "Failed to submit " + changes.size() + " change"
|
||||
+ (changes.size() > 1 ? "s" : "")
|
||||
+ " due to the following problems:\n";
|
||||
List<String> ps = new ArrayList<>(problems.keySet().size());
|
||||
for (Change.Id id : problems.keySet()) {
|
||||
ps.add("Change " + id + ": " + Joiner.on("; ").join(problems.get(id)));
|
||||
}
|
||||
throw new ResourceConflictException(msg + Joiner.on('\n').join(ps));
|
||||
}
|
||||
|
||||
public void maybeFail(String msgPrefix) throws ResourceConflictException {
|
||||
if (isOk()) {
|
||||
return;
|
||||
}
|
||||
StringBuilder msg = new StringBuilder(msgPrefix).append(" of change");
|
||||
Set<Change.Id> ids = problems.keySet();
|
||||
if (ids.size() == 1) {
|
||||
msg.append(" ").append(ids.iterator().next());
|
||||
} else {
|
||||
msg.append("s ").append(Joiner.on(", ").join(ids));
|
||||
}
|
||||
throw new ResourceConflictException(msg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private final AccountCache accountCache;
|
||||
private final ApprovalsUtil approvalsUtil;
|
||||
private final ChangeControl.GenericFactory changeControlFactory;
|
||||
private final ChangeHooks hooks;
|
||||
private final ChangeIndexer indexer;
|
||||
private final ChangeMessagesUtil cmUtil;
|
||||
private final ChangeUpdate.Factory updateFactory;
|
||||
private final GitReferenceUpdated gitRefUpdated;
|
||||
private final ChangeUpdate.Factory changeUpdateFactory;
|
||||
private final BatchUpdate.Factory batchUpdateFactory;
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final IdentifiedUser.GenericFactory identifiedUserFactory;
|
||||
private final LabelNormalizer labelNormalizer;
|
||||
private final EmailMerge.Factory mergedSenderFactory;
|
||||
private final MergeSuperSet mergeSuperSet;
|
||||
private final MergeValidators.Factory mergeValidatorsFactory;
|
||||
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||
private final ProjectCache projectCache;
|
||||
private final InternalChangeQuery internalChangeQuery;
|
||||
private final SubmitStrategyFactory submitStrategyFactory;
|
||||
private final Provider<SubmoduleOp> subOpProvider;
|
||||
private final TagCache tagCache;
|
||||
private final CommitStatus commits;
|
||||
|
||||
private final Map<Project.NameKey, OpenRepo> openRepos;
|
||||
|
||||
@@ -279,52 +332,38 @@ public class MergeOp implements AutoCloseable {
|
||||
private String submissionId;
|
||||
private IdentifiedUser caller;
|
||||
|
||||
private CommitStatus commits;
|
||||
private ReviewDb db;
|
||||
|
||||
@Inject
|
||||
MergeOp(AccountCache accountCache,
|
||||
ApprovalsUtil approvalsUtil,
|
||||
ChangeControl.GenericFactory changeControlFactory,
|
||||
ChangeHooks hooks,
|
||||
MergeOp(ChangeControl.GenericFactory changeControlFactory,
|
||||
ChangeIndexer indexer,
|
||||
ChangeMessagesUtil cmUtil,
|
||||
ChangeUpdate.Factory updateFactory,
|
||||
GitReferenceUpdated gitRefUpdated,
|
||||
ChangeUpdate.Factory changeUpdateFactory,
|
||||
BatchUpdate.Factory batchUpdateFactory,
|
||||
GitRepositoryManager repoManager,
|
||||
IdentifiedUser.GenericFactory identifiedUserFactory,
|
||||
LabelNormalizer labelNormalizer,
|
||||
EmailMerge.Factory mergedSenderFactory,
|
||||
MergeSuperSet mergeSuperSet,
|
||||
MergeValidators.Factory mergeValidatorsFactory,
|
||||
PatchSetInfoFactory patchSetInfoFactory,
|
||||
ProjectCache projectCache,
|
||||
InternalChangeQuery internalChangeQuery,
|
||||
SubmitStrategyFactory submitStrategyFactory,
|
||||
Provider<SubmoduleOp> subOpProvider,
|
||||
TagCache tagCache) {
|
||||
this.accountCache = accountCache;
|
||||
this.approvalsUtil = approvalsUtil;
|
||||
Provider<SubmoduleOp> subOpProvider) {
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.hooks = hooks;
|
||||
this.indexer = indexer;
|
||||
this.cmUtil = cmUtil;
|
||||
this.updateFactory = updateFactory;
|
||||
this.gitRefUpdated = gitRefUpdated;
|
||||
this.changeUpdateFactory = changeUpdateFactory;
|
||||
this.batchUpdateFactory = batchUpdateFactory;
|
||||
this.repoManager = repoManager;
|
||||
this.identifiedUserFactory = identifiedUserFactory;
|
||||
this.labelNormalizer = labelNormalizer;
|
||||
this.mergedSenderFactory = mergedSenderFactory;
|
||||
this.mergeSuperSet = mergeSuperSet;
|
||||
this.mergeValidatorsFactory = mergeValidatorsFactory;
|
||||
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||
this.projectCache = projectCache;
|
||||
this.internalChangeQuery = internalChangeQuery;
|
||||
this.submitStrategyFactory = submitStrategyFactory;
|
||||
this.subOpProvider = subOpProvider;
|
||||
this.tagCache = tagCache;
|
||||
|
||||
openRepos = new HashMap<>();
|
||||
commits = new CommitStatus();
|
||||
}
|
||||
|
||||
private OpenRepo getRepo(Project.NameKey project) {
|
||||
@@ -377,11 +416,7 @@ public class MergeOp implements AutoCloseable {
|
||||
throw new ResourceConflictException(
|
||||
"missing current patch set for change " + cd.getId());
|
||||
}
|
||||
List<SubmitRecord> results = cd.getSubmitRecords();
|
||||
if (results == null) {
|
||||
results = new SubmitRuleEvaluator(cd).evaluate();
|
||||
cd.setSubmitRecords(results);
|
||||
}
|
||||
List<SubmitRecord> results = getSubmitRecords(cd);
|
||||
if (findOkRecord(results).isPresent()) {
|
||||
// Rules supplied a valid solution.
|
||||
return;
|
||||
@@ -418,6 +453,16 @@ public class MergeOp implements AutoCloseable {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private static List<SubmitRecord> getSubmitRecords(ChangeData cd)
|
||||
throws OrmException {
|
||||
List<SubmitRecord> results = cd.getSubmitRecords();
|
||||
if (results == null) {
|
||||
results = new SubmitRuleEvaluator(cd).evaluate();
|
||||
cd.setSubmitRecords(results);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static String describeLabels(ChangeData cd,
|
||||
List<SubmitRecord.Label> labels) throws OrmException {
|
||||
List<String> labelResults = new ArrayList<>();
|
||||
@@ -470,6 +515,22 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private void bypassSubmitRules(ChangeSet cs) {
|
||||
for (ChangeData cd : cs.changes()) {
|
||||
List<SubmitRecord> records;
|
||||
try {
|
||||
records = new ArrayList<>(getSubmitRecords(cd));
|
||||
} catch (OrmException e) {
|
||||
log.warn("Error checking submit rules for change " + cd.getId(), e);
|
||||
records = new ArrayList<>(1);
|
||||
}
|
||||
SubmitRecord forced = new SubmitRecord();
|
||||
forced.status = SubmitRecord.Status.FORCED;
|
||||
records.add(forced);
|
||||
cd.setSubmitRecords(records);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSubmissionId(Change change) {
|
||||
Hasher h = Hashing.sha1().newHasher();
|
||||
h.putLong(Thread.currentThread().getId())
|
||||
@@ -480,19 +541,23 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
|
||||
public void merge(ReviewDb db, Change change, IdentifiedUser caller,
|
||||
boolean checkSubmitRules) throws OrmException, ResourceConflictException {
|
||||
boolean checkSubmitRules) throws OrmException, RestApiException {
|
||||
this.caller = caller;
|
||||
updateSubmissionId(change);
|
||||
this.db = db;
|
||||
logDebug("Beginning integration of {}", change);
|
||||
try {
|
||||
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change);
|
||||
this.commits = new CommitStatus(cs);
|
||||
reloadChanges(cs);
|
||||
logDebug("Calculated to merge {}", cs);
|
||||
if (checkSubmitRules) {
|
||||
logDebug("Checking submit rules and state");
|
||||
checkSubmitRulesAndState(cs);
|
||||
failFast(cs); // Done checks that don't involve opening repo.
|
||||
} else {
|
||||
logDebug("Bypassing submit rules");
|
||||
bypassSubmitRules(cs);
|
||||
}
|
||||
try {
|
||||
integrateIntoHistory(cs);
|
||||
@@ -528,7 +593,7 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
|
||||
private void integrateIntoHistory(ChangeSet cs)
|
||||
throws IntegrationException, ResourceConflictException {
|
||||
throws IntegrationException, RestApiException {
|
||||
logDebug("Beginning merge attempt on {}", cs);
|
||||
Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>();
|
||||
logDebug("Perform the merges");
|
||||
@@ -544,74 +609,60 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
failFast(cs); // Done checks that don't involve running submit strategies.
|
||||
|
||||
Map<Branch.NameKey, SubmitStrategy> strategies =
|
||||
Maps.newHashMapWithExpectedSize(branches.size());
|
||||
List<SubmitStrategy> strategies = new ArrayList<>(branches.size());
|
||||
for (Branch.NameKey branch : branches) {
|
||||
OpenRepo or = getRepo(branch.getParentKey());
|
||||
OpenBranch ob = or.getBranch(branch);
|
||||
BranchBatch submitting = toSubmit.get(branch);
|
||||
SubmitStrategy strategy = createStrategy(or, branch,
|
||||
Set<CodeReviewCommit> commitsToSubmit = commits(submitting.changes());
|
||||
ob.mergeTip = new MergeTip(ob.oldTip, commitsToSubmit);
|
||||
SubmitStrategy strategy = createStrategy(or, ob.mergeTip, branch,
|
||||
submitting.submitType(), ob.oldTip);
|
||||
strategies.put(branch, strategy);
|
||||
ob.mergeTip = preMerge(strategy, submitting.changes(), ob.oldTip);
|
||||
}
|
||||
checkMergeStrategyResults(cs, strategies, toSubmit);
|
||||
for (Project.NameKey project : br.keySet()) {
|
||||
getRepo(project).ins.flush();
|
||||
strategies.add(strategy);
|
||||
strategy.addOps(or.getUpdate(), commitsToSubmit);
|
||||
}
|
||||
|
||||
Set<Branch.NameKey> done =
|
||||
Sets.newHashSetWithExpectedSize(cbb.keySet().size());
|
||||
logDebug("Write out the new branch tips");
|
||||
BatchUpdate.execute(
|
||||
batchUpdates(projects),
|
||||
new SubmitStrategyListener(strategies, commits));
|
||||
|
||||
SubmoduleOp subOp = subOpProvider.get();
|
||||
for (Project.NameKey project : br.keySet()) {
|
||||
OpenRepo or = getRepo(project);
|
||||
for (Branch.NameKey branch : br.get(project)) {
|
||||
OpenBranch ob = or.getBranch(branch);
|
||||
boolean updated = updateBranch(or, branch);
|
||||
|
||||
BranchBatch submitting = toSubmit.get(branch);
|
||||
updateChangeStatus(ob, submitting.changes());
|
||||
updateSubmoduleSubscriptions(ob, subOp);
|
||||
if (updated) {
|
||||
fireRefUpdated(ob);
|
||||
}
|
||||
done.add(branch);
|
||||
}
|
||||
for (Branch.NameKey branch : branches) {
|
||||
OpenBranch ob = getRepo(branch.getParentKey()).getBranch(branch);
|
||||
updateSubmoduleSubscriptions(ob, subOp);
|
||||
}
|
||||
|
||||
updateSuperProjects(subOp, br.values());
|
||||
checkState(done.equals(cbb.keySet()), "programmer error: did not process"
|
||||
+ " all branches in input set.\nExpected: %s\nActual: %s",
|
||||
done, cbb.keySet());
|
||||
} catch (OrmException e) {
|
||||
throw new IntegrationException("Cannot query the database", e);
|
||||
} catch (IOException e) {
|
||||
throw new IntegrationException("Cannot query the database", e);
|
||||
} catch (UpdateException | OrmException e) {
|
||||
throw new IntegrationException("Error submitting changes", e);
|
||||
}
|
||||
}
|
||||
|
||||
private MergeTip preMerge(SubmitStrategy strategy,
|
||||
List<ChangeData> submitted, CodeReviewCommit branchTip)
|
||||
throws IntegrationException, OrmException {
|
||||
logDebug("Running submit strategy {} for {} commits {}",
|
||||
strategy.getClass().getSimpleName(), submitted.size(), submitted);
|
||||
List<CodeReviewCommit> toMerge = new ArrayList<>(submitted.size());
|
||||
for (ChangeData cd : submitted) {
|
||||
private List<BatchUpdate> batchUpdates(Collection<Project.NameKey> projects) {
|
||||
List<BatchUpdate> updates = new ArrayList<>(projects.size());
|
||||
for (Project.NameKey project : projects) {
|
||||
updates.add(getRepo(project).getUpdate());
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
private Set<CodeReviewCommit> commits(List<ChangeData> cds) throws OrmException {
|
||||
LinkedHashSet<CodeReviewCommit> result =
|
||||
Sets.newLinkedHashSetWithExpectedSize(cds.size());
|
||||
for (ChangeData cd : cds) {
|
||||
CodeReviewCommit commit = commits.get(cd.getId());
|
||||
checkState(commit != null,
|
||||
"commit for %s not found by validateChangeList", cd.change().getId());
|
||||
toMerge.add(commit);
|
||||
result.add(commit);
|
||||
}
|
||||
return strategy.run(branchTip, toMerge);
|
||||
return result;
|
||||
}
|
||||
|
||||
private SubmitStrategy createStrategy(OpenRepo or,
|
||||
Branch.NameKey destBranch, SubmitType submitType,
|
||||
MergeTip mergeTip, Branch.NameKey destBranch, SubmitType submitType,
|
||||
CodeReviewCommit branchTip) throws IntegrationException {
|
||||
return submitStrategyFactory.create(submitType, db, or.repo, or.rw, or.ins,
|
||||
or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller,
|
||||
commits);
|
||||
mergeTip, commits, submissionId);
|
||||
}
|
||||
|
||||
private Set<RevCommit> getAlreadyAccepted(OpenRepo or,
|
||||
@@ -783,239 +834,6 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateBranch(OpenRepo or, Branch.NameKey destBranch)
|
||||
throws IntegrationException {
|
||||
OpenBranch ob = or.getBranch(destBranch);
|
||||
CodeReviewCommit currentTip = ob.getCurrentTip();
|
||||
if (Objects.equals(ob.oldTip, currentTip)) {
|
||||
if (currentTip != null) {
|
||||
logDebug("Branch already at merge tip {}, no update to perform",
|
||||
currentTip.name());
|
||||
} else {
|
||||
logDebug("Both branch and merge tip are nonexistent, no update");
|
||||
}
|
||||
return false;
|
||||
} else if (currentTip == null) {
|
||||
logDebug("No merge tip, no update to perform");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RefNames.REFS_CONFIG.equals(ob.update.getName())) {
|
||||
logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
|
||||
try {
|
||||
ProjectConfig cfg = new ProjectConfig(or.getProjectName());
|
||||
cfg.load(or.repo, currentTip);
|
||||
} catch (Exception e) {
|
||||
throw new IntegrationException("Submit would store invalid"
|
||||
+ " project configuration " + currentTip.name() + " for "
|
||||
+ or.getProjectName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
ob.update.setRefLogIdent(
|
||||
identifiedUserFactory.create(caller.getAccountId()).newRefLogIdent());
|
||||
ob.update.setForceUpdate(false);
|
||||
ob.update.setNewObjectId(currentTip);
|
||||
ob.update.setRefLogMessage("merged", true);
|
||||
try {
|
||||
RefUpdate.Result result = ob.update.update(or.rw);
|
||||
logDebug("Update of {}: {}..{} returned status {}",
|
||||
ob.update.getName(), ob.update.getOldObjectId(),
|
||||
ob.update.getNewObjectId(), result);
|
||||
switch (result) {
|
||||
case NEW:
|
||||
case FAST_FORWARD:
|
||||
if (ob.update.getResult() == RefUpdate.Result.FAST_FORWARD) {
|
||||
tagCache.updateFastForward(destBranch.getParentKey(),
|
||||
ob.update.getName(),
|
||||
ob.update.getOldObjectId(),
|
||||
currentTip);
|
||||
}
|
||||
|
||||
if (RefNames.REFS_CONFIG.equals(ob.update.getName())) {
|
||||
Project p = or.project.getProject();
|
||||
projectCache.evict(p);
|
||||
or.project = projectCache.get(p.getNameKey());
|
||||
repoManager.setProjectDescription(
|
||||
p.getNameKey(), p.getDescription());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case LOCK_FAILURE:
|
||||
throw new IntegrationException(
|
||||
"Failed to lock " + ob.update.getName());
|
||||
default:
|
||||
throw new IOException(
|
||||
ob.update.getResult().name() + '\n' + ob.update);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IntegrationException("Cannot update " + ob.update.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireRefUpdated(OpenBranch ob) {
|
||||
logDebug("Firing ref updated hooks for {}", ob.name);
|
||||
gitRefUpdated.fire(ob.name.getParentKey(), ob.update);
|
||||
hooks.doRefUpdatedHook(ob.name, ob.update,
|
||||
getAccount(ob.mergeTip.getCurrentTip()));
|
||||
}
|
||||
|
||||
private Account getAccount(CodeReviewCommit codeReviewCommit) {
|
||||
Account account = null;
|
||||
PatchSetApproval submitter = approvalsUtil.getSubmitter(
|
||||
db, codeReviewCommit.notes(), codeReviewCommit.getPatchsetId());
|
||||
if (submitter != null) {
|
||||
account = accountCache.get(submitter.getAccountId()).getAccount();
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
private String getByAccountName(CodeReviewCommit codeReviewCommit) {
|
||||
Account account = getAccount(codeReviewCommit);
|
||||
if (account != null && account.getFullName() != null) {
|
||||
return " by " + account.getFullName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private Iterable<ChangeData> flattenBatches(Collection<BranchBatch> batches) {
|
||||
return FluentIterable.from(batches)
|
||||
.transformAndConcat(new Function<BranchBatch, List<ChangeData>>() {
|
||||
@Override
|
||||
public List<ChangeData> apply(BranchBatch batch) {
|
||||
return batch.changes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkMergeStrategyResults(ChangeSet cs,
|
||||
Map<Branch.NameKey, SubmitStrategy> strategies,
|
||||
Map<Branch.NameKey, BranchBatch> batches)
|
||||
throws ResourceConflictException, IntegrationException {
|
||||
checkCommitStatus(batches);
|
||||
failFast(cs);
|
||||
findUnmergedChanges(strategies, batches);
|
||||
failFast(cs);
|
||||
}
|
||||
|
||||
private void checkCommitStatus(Map<Branch.NameKey, BranchBatch> batches) {
|
||||
for (ChangeData cd : flattenBatches(batches.values())) {
|
||||
Change.Id id = cd.getId();
|
||||
CodeReviewCommit commit = commits.get(id);
|
||||
CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
|
||||
if (s == null) {
|
||||
commits.problem(id,
|
||||
"internal error: change not processed by merge strategy");
|
||||
continue;
|
||||
}
|
||||
switch (s) {
|
||||
case CLEAN_MERGE:
|
||||
case CLEAN_REBASE:
|
||||
case CLEAN_PICK:
|
||||
case ALREADY_MERGED:
|
||||
break; // Merge strategy accepted this change.
|
||||
|
||||
case PATH_CONFLICT:
|
||||
case REBASE_MERGE_CONFLICT:
|
||||
case MANUAL_RECURSIVE_MERGE:
|
||||
case CANNOT_CHERRY_PICK_ROOT:
|
||||
case NOT_FAST_FORWARD:
|
||||
// TODO(dborowitz): Reformat these messages to be more appropriate for
|
||||
// short problem descriptions.
|
||||
commits.problem(id,
|
||||
CharMatcher.is('\n').collapseFrom(s.getMessage(), ' '));
|
||||
break;
|
||||
|
||||
case MISSING_DEPENDENCY:
|
||||
commits.problem(id, "depends on change that was not submitted");
|
||||
break;
|
||||
|
||||
default:
|
||||
commits.problem(id, "unspecified merge failure: " + s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findUnmergedChanges(
|
||||
Map<Branch.NameKey, SubmitStrategy> strategies,
|
||||
Map<Branch.NameKey, BranchBatch> batches) throws IntegrationException {
|
||||
checkState(strategies.keySet().equals(batches.keySet()));
|
||||
for (Branch.NameKey branch : strategies.keySet()) {
|
||||
SubmitStrategy strategy = strategies.get(branch);
|
||||
BranchBatch batch = batches.get(branch);
|
||||
if (batch.submitType() == SubmitType.CHERRY_PICK) {
|
||||
// Might have picked a subset of changes, can't do this sanity check.
|
||||
continue;
|
||||
}
|
||||
OpenBranch ob = getRepo(branch.getParentKey()).branches.get(branch);
|
||||
Set<Change.Id> unmerged = strategy.args.mergeUtil.findUnmergedChanges(
|
||||
batch.changes(), strategy.args.rw, strategy.args.canMergeFlag,
|
||||
ob.oldTip, ob.mergeTip.getCurrentTip());
|
||||
for (Change.Id id : unmerged) {
|
||||
commits.problem(id,
|
||||
"internal error: change not reachable from new branch tip");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChangeStatus(OpenBranch ob, List<ChangeData> submitted)
|
||||
throws ResourceConflictException {
|
||||
List<Change.Id> problemChanges = new ArrayList<>(submitted.size());
|
||||
logDebug("Updating change status for {} changes", submitted.size());
|
||||
|
||||
for (ChangeData cd : submitted) {
|
||||
Change.Id id = cd.getId();
|
||||
try {
|
||||
Change c = cd.change();
|
||||
CodeReviewCommit commit = commits.get(id);
|
||||
CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
|
||||
logDebug("Status of change {} ({}) on {}: {}", id, commit.name(),
|
||||
c.getDest(), s);
|
||||
checkState(s != null,
|
||||
"status not set for change %s; expected to previously fail fast",
|
||||
id);
|
||||
setApproval(cd);
|
||||
|
||||
ObjectId mergeResultRev = ob.mergeTip != null
|
||||
? ob.mergeTip.getMergeResults().get(commit) : null;
|
||||
String txt = s.getMessage();
|
||||
|
||||
// The change notes must be forcefully reloaded so that the SUBMIT
|
||||
// approval that we added earlier is visible
|
||||
commit.notes().reload();
|
||||
if (s == CommitMergeStatus.CLEAN_MERGE) {
|
||||
setMerged(c, message(c, txt + getByAccountName(commit)),
|
||||
mergeResultRev);
|
||||
} else if (s == CommitMergeStatus.CLEAN_REBASE
|
||||
|| s == CommitMergeStatus.CLEAN_PICK) {
|
||||
setMerged(c, message(c, txt + " as " + commit.name()
|
||||
+ getByAccountName(commit)), mergeResultRev);
|
||||
} else if (s == CommitMergeStatus.ALREADY_MERGED) {
|
||||
setMerged(c, null, mergeResultRev);
|
||||
} else {
|
||||
throw new IllegalStateException("unexpected status " + s +
|
||||
" for change " + c.getId() + "; expected to previously fail fast");
|
||||
}
|
||||
} catch (OrmException | IOException err) {
|
||||
logWarn("Error updating change status for " + id, err);
|
||||
problemChanges.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (problemChanges.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
StringBuilder msg = new StringBuilder("Error updating status of change");
|
||||
if (problemChanges.size() == 1) {
|
||||
msg.append(' ').append(problemChanges.iterator().next());
|
||||
} else {
|
||||
msg.append('s').append(Joiner.on(", ").join(problemChanges));
|
||||
}
|
||||
throw new ResourceConflictException(msg.toString());
|
||||
}
|
||||
|
||||
private void updateSubmoduleSubscriptions(OpenBranch ob, SubmoduleOp subOp) {
|
||||
CodeReviewCommit branchTip = ob.oldTip;
|
||||
MergeTip mergeTip = ob.mergeTip;
|
||||
@@ -1042,211 +860,6 @@ public class MergeOp implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private ChangeMessage message(Change c, String body) {
|
||||
String uuid;
|
||||
try {
|
||||
uuid = ChangeUtil.messageUUID(db);
|
||||
} catch (OrmException e) {
|
||||
return null;
|
||||
}
|
||||
ChangeMessage m = new ChangeMessage(new ChangeMessage.Key(c.getId(), uuid),
|
||||
null, TimeUtil.nowTs(), c.currentPatchSetId());
|
||||
m.setMessage(body);
|
||||
return m;
|
||||
}
|
||||
|
||||
private void setMerged(Change c, ChangeMessage msg, ObjectId mergeResultRev)
|
||||
throws OrmException, IOException {
|
||||
logDebug("Setting change {} merged", c.getId());
|
||||
ChangeUpdate update = null;
|
||||
final PatchSetApproval submitter;
|
||||
PatchSet merged;
|
||||
try {
|
||||
db.changes().beginTransaction(c.getId());
|
||||
|
||||
// We must pull the patchset out of commits, because the patchset ID is
|
||||
// modified when using the cherry-pick merge strategy.
|
||||
CodeReviewCommit commit = commits.get(c.getId());
|
||||
PatchSet.Id mergedId = commit.change().currentPatchSetId();
|
||||
// TODO(dborowitz): Use PatchSetUtil after BatchUpdate migration.
|
||||
merged = db.patchSets().get(mergedId);
|
||||
c = setMergedPatchSet(commit.notes(), mergedId);
|
||||
submitter = approvalsUtil.getSubmitter(db, commit.notes(), mergedId);
|
||||
ChangeControl control = commit.getControl();
|
||||
update = updateFactory.create(control, c.getLastUpdatedOn());
|
||||
|
||||
// TODO(yyonas): we need to be able to change the author of the message
|
||||
// is not the person for whom the change was made. addMergedMessage
|
||||
// did this in the past.
|
||||
if (msg != null) {
|
||||
cmUtil.addChangeMessage(db, update, msg);
|
||||
}
|
||||
db.commit();
|
||||
|
||||
} finally {
|
||||
db.rollback();
|
||||
}
|
||||
update.commit();
|
||||
indexer.index(db, c);
|
||||
|
||||
try {
|
||||
mergedSenderFactory.create(
|
||||
c.getId(),
|
||||
submitter != null ? submitter.getAccountId() : null).sendAsync();
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot email merged notification for " + c.getId(), e);
|
||||
}
|
||||
if (submitter != null && mergeResultRev != null) {
|
||||
try {
|
||||
hooks.doChangeMergedHook(c,
|
||||
accountCache.get(submitter.getAccountId()).getAccount(),
|
||||
merged, db, mergeResultRev.name());
|
||||
} catch (OrmException ex) {
|
||||
logError("Cannot run hook for submitted patch set " + c.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Change setMergedPatchSet(final ChangeNotes notes,
|
||||
final PatchSet.Id merged) throws OrmException {
|
||||
return db.changes().atomicUpdate(notes.getChangeId(),
|
||||
new AtomicUpdate<Change>() {
|
||||
@Override
|
||||
public Change update(Change c) {
|
||||
c.setStatus(Change.Status.MERGED);
|
||||
c.setSubmissionId(submissionId);
|
||||
if (!merged.equals(c.currentPatchSetId())) {
|
||||
// Uncool; the patch set changed after we merged it.
|
||||
// Go back to the patch set that was actually merged.
|
||||
//
|
||||
try {
|
||||
c.setCurrentPatchSet(
|
||||
patchSetInfoFactory.get(db, notes, merged));
|
||||
} catch (PatchSetInfoNotAvailableException e1) {
|
||||
logError("Cannot read merged patch set " + merged, e1);
|
||||
}
|
||||
}
|
||||
ChangeUtil.updated(c);
|
||||
return c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setApproval(ChangeData cd) throws OrmException, IOException {
|
||||
Timestamp timestamp = TimeUtil.nowTs();
|
||||
ChangeControl control = cd.changeControl();
|
||||
PatchSet.Id psId = cd.currentPatchSet().getId();
|
||||
PatchSet.Id psIdNewRev = commits.get(cd.change().getId())
|
||||
.change().currentPatchSetId();
|
||||
|
||||
logDebug("Add approval for " + cd);
|
||||
ChangeUpdate update = updateFactory.create(control, timestamp);
|
||||
update.setPatchSetId(psId);
|
||||
update.putReviewer(caller.getAccountId(), REVIEWER);
|
||||
Optional<SubmitRecord> okRecord = findOkRecord(cd.getSubmitRecords());
|
||||
if (okRecord.isPresent()) {
|
||||
update.merge(ImmutableList.of(okRecord.get()));
|
||||
}
|
||||
db.changes().beginTransaction(cd.change().getId());
|
||||
try {
|
||||
BatchMetaDataUpdate batch = update.openUpdate();
|
||||
LabelNormalizer.Result normalized =
|
||||
approve(control, psId, caller, update, timestamp);
|
||||
batch.write(update, new CommitBuilder());
|
||||
|
||||
// If the submit strategy created a new revision (rebase, cherry-pick)
|
||||
// approve that as well
|
||||
if (!psIdNewRev.equals(psId)) {
|
||||
update.commit();
|
||||
// Create a new ChangeUpdate instance because we need to store meta data
|
||||
// on another patch set (psIdNewRev).
|
||||
update = updateFactory.create(control, timestamp);
|
||||
update.setPatchSetId(psIdNewRev);
|
||||
saveApprovals(normalized, update);
|
||||
batch = update.openUpdate();
|
||||
batch.write(update, new CommitBuilder());
|
||||
}
|
||||
db.commit();
|
||||
} finally {
|
||||
db.rollback();
|
||||
}
|
||||
update.commit();
|
||||
indexer.index(db, cd.change());
|
||||
}
|
||||
|
||||
private LabelNormalizer.Result approve(ChangeControl control,
|
||||
PatchSet.Id psId, IdentifiedUser user, ChangeUpdate update,
|
||||
Timestamp timestamp) throws OrmException {
|
||||
Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
|
||||
for (PatchSetApproval psa :
|
||||
approvalsUtil.byPatchSet(db, control, psId)) {
|
||||
if (!byKey.containsKey(psa.getKey())) {
|
||||
byKey.put(psa.getKey(), psa);
|
||||
}
|
||||
}
|
||||
|
||||
PatchSetApproval submit = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
psId,
|
||||
user.getAccountId(),
|
||||
LabelId.SUBMIT),
|
||||
(short) 1, TimeUtil.nowTs());
|
||||
byKey.put(submit.getKey(), submit);
|
||||
submit.setValue((short) 1);
|
||||
submit.setGranted(timestamp);
|
||||
|
||||
// Flatten out existing approvals for this patch set based upon the current
|
||||
// permissions. Once the change is closed the approvals are not updated at
|
||||
// presentation view time, except for zero votes used to indicate a reviewer
|
||||
// was added. So we need to make sure votes are accurate now. This way if
|
||||
// permissions get modified in the future, historical records stay accurate.
|
||||
LabelNormalizer.Result normalized =
|
||||
labelNormalizer.normalize(control, byKey.values());
|
||||
update.putApproval(submit.getLabel(), submit.getValue());
|
||||
saveApprovals(normalized, update);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private void saveApprovals(LabelNormalizer.Result normalized,
|
||||
ChangeUpdate update) throws OrmException {
|
||||
PatchSet.Id psId = update.getPatchSetId();
|
||||
db.patchSetApprovals().upsert(
|
||||
convertPatchSet(normalized.getNormalized(), psId));
|
||||
db.patchSetApprovals().delete(convertPatchSet(normalized.deleted(), psId));
|
||||
for (PatchSetApproval psa : normalized.updated()) {
|
||||
update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
|
||||
}
|
||||
for (PatchSetApproval psa : normalized.deleted()) {
|
||||
update.removeApprovalFor(psa.getAccountId(), psa.getLabel());
|
||||
}
|
||||
|
||||
// TODO(dborowitz): Don't use a label in notedb; just check when status
|
||||
// change happened.
|
||||
for (PatchSetApproval psa : normalized.unchanged()) {
|
||||
if (psa.isSubmit()) {
|
||||
logDebug("Adding submit label " + psa);
|
||||
update.putApprovalFor(
|
||||
psa.getAccountId(), psa.getLabel(), psa.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Iterable<PatchSetApproval> convertPatchSet(
|
||||
Iterable<PatchSetApproval> approvals, final PatchSet.Id psId) {
|
||||
return Iterables.transform(approvals,
|
||||
new Function<PatchSetApproval, PatchSetApproval>() {
|
||||
@Override
|
||||
public PatchSetApproval apply(PatchSetApproval in) {
|
||||
if (in.getPatchSetId().equals(psId)) {
|
||||
return in;
|
||||
} else {
|
||||
return new PatchSetApproval(psId, in);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openRepos(Collection<Project.NameKey> projects)
|
||||
throws IntegrationException {
|
||||
for (Project.NameKey project : projects) {
|
||||
@@ -1279,7 +892,8 @@ public class MergeOp implements AutoCloseable {
|
||||
//TODO(dborowitz): support InternalUser in ChangeUpdate
|
||||
ChangeControl control = changeControlFactory.controlFor(change,
|
||||
identifiedUserFactory.create(change.getOwner()));
|
||||
ChangeUpdate update = updateFactory.create(control);
|
||||
// TODO(dborowitz): Convert to BatchUpdate.
|
||||
ChangeUpdate update = changeUpdateFactory.create(control);
|
||||
try {
|
||||
change = db.changes().atomicUpdate(
|
||||
change.getId(),
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||
import com.google.gerrit.server.git.strategy.CommitMergeStatus;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
@@ -674,19 +673,15 @@ public class MergeUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Change.Id> findUnmergedChanges(List<ChangeData> cds,
|
||||
public Set<Change.Id> findUnmergedChanges(Set<Change.Id> expected,
|
||||
CodeReviewRevWalk rw, RevFlag canMergeFlag, CodeReviewCommit oldTip,
|
||||
CodeReviewCommit mergeTip) throws IntegrationException {
|
||||
Set<Change.Id> expected = Sets.newHashSetWithExpectedSize(cds.size());
|
||||
for (ChangeData cd : cds) {
|
||||
expected.add(cd.getId());
|
||||
}
|
||||
if (mergeTip == null) {
|
||||
return expected;
|
||||
}
|
||||
|
||||
try {
|
||||
Set<Change.Id> found = Sets.newHashSetWithExpectedSize(cds.size());
|
||||
Set<Change.Id> found = Sets.newHashSetWithExpectedSize(expected.size());
|
||||
rw.resetRetain(canMergeFlag);
|
||||
rw.sort(RevSort.TOPO);
|
||||
rw.markStart(mergeTip);
|
||||
|
||||
@@ -117,7 +117,6 @@ import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||
import com.google.gerrit.server.notedb.NotesMigration;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
@@ -1810,8 +1809,9 @@ public class ReceiveCommits {
|
||||
changeId,
|
||||
new BatchUpdate.Op() {
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws Exception {
|
||||
public boolean updateChange(ChangeContext ctx) {
|
||||
ctx.getUpdate(psId).setTopic(magicBranch.topic);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1827,7 +1827,7 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
private void submit(ChangeControl changeCtl, PatchSet ps)
|
||||
throws OrmException, ResourceConflictException {
|
||||
throws OrmException, RestApiException {
|
||||
Submit submit = submitProvider.get();
|
||||
RevisionResource rsrc = new RevisionResource(changes.parse(changeCtl), ps);
|
||||
try (MergeOp op = mergeOpProvider.get()) {
|
||||
@@ -2158,7 +2158,7 @@ public class ReceiveCommits {
|
||||
requestScopePropagator.wrap(new Callable<PatchSet.Id>() {
|
||||
@Override
|
||||
public PatchSet.Id call() throws OrmException, IOException,
|
||||
NoSuchChangeException, ResourceConflictException {
|
||||
RestApiException {
|
||||
try {
|
||||
if (magicBranch != null && magicBranch.edit) {
|
||||
return upsertEdit();
|
||||
@@ -2232,7 +2232,7 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
PatchSet.Id insertPatchSet(ReviewDb db) throws OrmException, IOException,
|
||||
ResourceConflictException {
|
||||
RestApiException {
|
||||
final Account.Id me = user.getAccountId();
|
||||
final List<FooterLine> footerLines = newCommit.getFooterLines();
|
||||
final MailRecipients recipients = new MailRecipients();
|
||||
|
||||
@@ -14,23 +14,18 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeIdenticalTreeException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
@@ -39,6 +34,7 @@ import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -49,86 +45,65 @@ public class CherryPick extends SubmitStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeTip run(CodeReviewCommit branchTip,
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) {
|
||||
List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
boolean first = true;
|
||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
Change.Id cid = n.change().getId();
|
||||
if (first && branchTip == null) {
|
||||
u.addOp(cid, new CherryPickUnbornRootOp(mergeTip, n));
|
||||
} else if (n.getParentCount() == 0) {
|
||||
u.addOp(cid, new CherryPickRootOp(n));
|
||||
} else if (n.getParentCount() == 1) {
|
||||
u.addOp(cid, new CherryPickOneOp(mergeTip, n));
|
||||
} else {
|
||||
u.addOp(cid, new CherryPickMultipleParentsOp(mergeTip, n));
|
||||
}
|
||||
first = false;
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
if (first && args.mergeTip.getInitialTip() == null) {
|
||||
ops.add(new CherryPickUnbornRootOp(n));
|
||||
} else if (n.getParentCount() == 0) {
|
||||
ops.add(new CherryPickRootOp(n));
|
||||
} else if (n.getParentCount() == 1) {
|
||||
ops.add(new CherryPickOneOp(n));
|
||||
} else {
|
||||
ops.add(new CherryPickMultipleParentsOp(n));
|
||||
}
|
||||
u.execute();
|
||||
} catch (UpdateException | RestApiException e) {
|
||||
throw new IntegrationException(
|
||||
"Cannot cherry-pick onto " + args.destBranch, e);
|
||||
first = false;
|
||||
}
|
||||
// TODO(dborowitz): When BatchUpdate is hoisted out of CherryPick,
|
||||
// SubmitStrategy should probably no longer return MergeTip, instead just
|
||||
// mutating a single shared MergeTip passed in from the caller.
|
||||
return mergeTip;
|
||||
return ops;
|
||||
}
|
||||
|
||||
private static class CherryPickUnbornRootOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private CherryPickUnbornRootOp(MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
private class CherryPickUnbornRootOp extends SubmitStrategyOp {
|
||||
private CherryPickUnbornRootOp(CodeReviewCommit toMerge) {
|
||||
super(CherryPick.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) {
|
||||
protected void updateRepoImpl(RepoContext ctx) {
|
||||
// The branch is unborn. Take fast-forward resolution to create the
|
||||
// branch.
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
args.mergeTip.moveTipTo(toMerge, toMerge);
|
||||
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CherryPickRootOp extends BatchUpdate.Op {
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private class CherryPickRootOp extends SubmitStrategyOp {
|
||||
private CherryPickRootOp(CodeReviewCommit toMerge) {
|
||||
this.toMerge = toMerge;
|
||||
super(CherryPick.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) {
|
||||
public void updateRepoImpl(RepoContext ctx) {
|
||||
// Refuse to merge a root commit into an existing branch, we cannot obtain
|
||||
// a delta for the cherry-pick to apply.
|
||||
toMerge.setStatusCode(CommitMergeStatus.CANNOT_CHERRY_PICK_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
private class CherryPickOneOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private class CherryPickOneOp extends SubmitStrategyOp {
|
||||
private PatchSet.Id psId;
|
||||
private CodeReviewCommit newCommit;
|
||||
private PatchSetInfo patchSetInfo;
|
||||
|
||||
private CherryPickOneOp(MergeTip mergeTip, CodeReviewCommit n) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = n;
|
||||
private CherryPickOneOp(CodeReviewCommit toMerge) {
|
||||
super(CherryPick.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) throws IOException {
|
||||
protected void updateRepoImpl(RepoContext ctx) throws IOException {
|
||||
// If there is only one parent, a cherry-pick can be done by taking the
|
||||
// delta relative to that one parent and redoing that on the current merge
|
||||
// tip.
|
||||
@@ -142,64 +117,57 @@ public class CherryPick extends SubmitStrategy {
|
||||
ctx.getWhen(), args.serverIdent.getTimeZone());
|
||||
try {
|
||||
newCommit = args.mergeUtil.createCherryPickFromCommit(
|
||||
args.repo, args.inserter, mergeTip.getCurrentTip(), toMerge,
|
||||
args.repo, args.inserter, args.mergeTip.getCurrentTip(), toMerge,
|
||||
committer, cherryPickCmtMsg, args.rw);
|
||||
newCommit.copyFrom(toMerge);
|
||||
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
|
||||
mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(newCommit);
|
||||
ctx.addRefUpdate(
|
||||
new ReceiveCommand(ObjectId.zeroId(), newCommit, psId.toRefName()));
|
||||
patchSetInfo =
|
||||
args.patchSetInfoFactory.get(ctx.getRevWalk(), newCommit, psId);
|
||||
} catch (MergeConflictException mce) {
|
||||
// Keep going in the case of a single merge failure; the goal is to
|
||||
// cherry-pick as many commits as possible.
|
||||
toMerge.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
|
||||
return;
|
||||
} catch (MergeIdenticalTreeException mie) {
|
||||
toMerge.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
|
||||
return;
|
||||
}
|
||||
// Initial copy doesn't have new patch set ID since change hasn't been
|
||||
// updated yet.
|
||||
newCommit.copyFrom(toMerge);
|
||||
newCommit.setPatchsetId(psId);
|
||||
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
|
||||
args.mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(newCommit);
|
||||
|
||||
ctx.addRefUpdate(
|
||||
new ReceiveCommand(ObjectId.zeroId(), newCommit, psId.toRefName()));
|
||||
patchSetInfo =
|
||||
args.patchSetInfoFactory.get(ctx.getRevWalk(), newCommit, psId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||
public void updateChangeImpl(ChangeContext ctx) throws OrmException,
|
||||
NoSuchChangeException {
|
||||
if (newCommit == null) {
|
||||
// Merge conflict; don't update change.
|
||||
return;
|
||||
}
|
||||
checkState(newCommit != null,
|
||||
"no new commit produced by CherryPick of %s, expected to fail fast",
|
||||
toMerge.change().getId());
|
||||
PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
|
||||
|
||||
args.psUtil.insert(ctx.getDb(), ctx.getUpdate(psId), psId, newCommit,
|
||||
false, prevPs != null ? prevPs.getGroups() : null, null);
|
||||
toMerge.change().setCurrentPatchSet(patchSetInfo);
|
||||
ctx.getChange().setCurrentPatchSet(patchSetInfo);
|
||||
ctx.saveChange();
|
||||
|
||||
List<PatchSetApproval> approvals = Lists.newArrayList();
|
||||
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(
|
||||
args.db, toMerge.getControl(), toMerge.getPatchsetId())) {
|
||||
approvals.add(new PatchSetApproval(psId, a));
|
||||
ctx.getUpdate(psId).putApproval(a.getLabel(), a.getValue());
|
||||
}
|
||||
args.db.patchSetApprovals().insert(approvals);
|
||||
// Don't copy approvals, as this is already taken care of by
|
||||
// SubmitStrategyOp.
|
||||
|
||||
newCommit.setControl(
|
||||
args.changeControlFactory.controlFor(toMerge.change(), args.caller));
|
||||
newCommit.setControl(ctx.getControl());
|
||||
}
|
||||
}
|
||||
|
||||
private class CherryPickMultipleParentsOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private CherryPickMultipleParentsOp(MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
private class CherryPickMultipleParentsOp extends SubmitStrategyOp {
|
||||
private CherryPickMultipleParentsOp(CodeReviewCommit toMerge) {
|
||||
super(CherryPick.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx)
|
||||
public void updateRepoImpl(RepoContext ctx)
|
||||
throws IntegrationException, IOException {
|
||||
if (args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)) {
|
||||
// One or more dependencies were not met. The status was already marked
|
||||
@@ -211,6 +179,7 @@ public class CherryPick extends SubmitStrategy {
|
||||
// with that merge present and replaced by an equivalent merge with a
|
||||
// different first parent. So instead behave as though MERGE_IF_NECESSARY
|
||||
// was configured.
|
||||
MergeTip mergeTip = args.mergeTip;
|
||||
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
} else {
|
||||
|
||||
@@ -14,17 +14,11 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
|
||||
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,44 +28,29 @@ public class FastForwardOnly extends SubmitStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeTip run(CodeReviewCommit branchTip,
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted =
|
||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
|
||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||
CodeReviewCommit newTipCommit =
|
||||
args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
|
||||
if (!newTipCommit.equals(branchTip)) {
|
||||
u.addOp(newTipCommit.change().getId(),
|
||||
new FastForwardOp(mergeTip, newTipCommit));
|
||||
}
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
u.addOp(n.change().getId(), new NotFastForwardOp(n));
|
||||
}
|
||||
u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
|
||||
|
||||
u.execute();
|
||||
} catch (RestApiException | UpdateException e) {
|
||||
if (e.getCause() instanceof IntegrationException) {
|
||||
throw new IntegrationException(e.getCause().getMessage(), e);
|
||||
}
|
||||
throw new IntegrationException(
|
||||
"Cannot fast-forward into " + args.destBranch);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
CodeReviewCommit newTipCommit = args.mergeUtil.getFirstFastForward(
|
||||
args.mergeTip.getInitialTip(), args.rw, sorted);
|
||||
if (!newTipCommit.equals(args.mergeTip.getInitialTip())) {
|
||||
ops.add(new FastForwardOp(args, newTipCommit));
|
||||
}
|
||||
return mergeTip;
|
||||
while (!sorted.isEmpty()) {
|
||||
ops.add(new NotFastForwardOp(sorted.remove(0)));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
private static class NotFastForwardOp extends BatchUpdate.Op {
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private class NotFastForwardOp extends SubmitStrategyOp {
|
||||
private NotFastForwardOp(CodeReviewCommit toMerge) {
|
||||
this.toMerge = toMerge;
|
||||
super(FastForwardOnly.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) {
|
||||
public void updateRepoImpl(RepoContext ctx) {
|
||||
toMerge.setStatusCode(CommitMergeStatus.NOT_FAST_FORWARD);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,27 +14,20 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class FastForwardOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
FastForwardOp(MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
class FastForwardOp extends SubmitStrategyOp {
|
||||
FastForwardOp(SubmitStrategy.Arguments args, CodeReviewCommit toMerge) {
|
||||
super(args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx)
|
||||
public void updateRepoImpl(RepoContext ctx)
|
||||
throws IntegrationException, IOException {
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
args.mergeTip.moveTipTo(toMerge, toMerge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
|
||||
/**
|
||||
* Operation for a change that is implicitly integrated by integrating another
|
||||
* commit.
|
||||
* <p>
|
||||
* Updates the change status and message based on {@link
|
||||
* CodeReviewCommit#getStatusCode()}, but does not touch the repository.
|
||||
*/
|
||||
class ImplicitIntegrateOp extends SubmitStrategyOp {
|
||||
ImplicitIntegrateOp(SubmitStrategy.Arguments args, CodeReviewCommit toMerge) {
|
||||
super(args, toMerge);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.Context;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
|
||||
class MarkCleanMergesOp extends BatchUpdate.Op {
|
||||
static Change.Id anyChangeId(Iterable<CodeReviewCommit> commits) {
|
||||
for (CodeReviewCommit c : commits) {
|
||||
if (c.change() != null) {
|
||||
return c.change().getId();
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"no CodeReviewCommits have changes: " + commits);
|
||||
}
|
||||
private final SubmitStrategy.Arguments args;
|
||||
private final MergeTip mergeTip;
|
||||
|
||||
MarkCleanMergesOp(SubmitStrategy.Arguments args,
|
||||
MergeTip mergeTip) {
|
||||
this.args = args;
|
||||
this.mergeTip = mergeTip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(Context ctx) throws IntegrationException {
|
||||
// TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
|
||||
// When hoisting BatchUpdate into MergeOp, we will need to teach
|
||||
// BatchUpdate how to produce CodeReviewRevWalks.
|
||||
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
|
||||
mergeTip.getCurrentTip(), args.alreadyAccepted);
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,10 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
|
||||
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,33 +27,22 @@ public class MergeAlways extends SubmitStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeTip run(CodeReviewCommit branchTip,
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted =
|
||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
|
||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||
if (branchTip == null) {
|
||||
// The branch is unborn. Take a fast-forward resolution to
|
||||
// create the branch.
|
||||
CodeReviewCommit first = sorted.remove(0);
|
||||
u.addOp(first.change().getId(), new FastForwardOp(mergeTip, first));
|
||||
}
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
u.addOp(n.change().getId(), new MergeOneOp(args, mergeTip, n));
|
||||
}
|
||||
u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
|
||||
|
||||
u.execute();
|
||||
} catch (RestApiException | UpdateException e) {
|
||||
if (e.getCause() instanceof IntegrationException) {
|
||||
throw new IntegrationException(e.getCause().getMessage(), e);
|
||||
}
|
||||
throw new IntegrationException(
|
||||
"Cannot merge into " + args.destBranch);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
if (args.mergeTip.getInitialTip() == null) {
|
||||
// The branch is unborn. Take a fast-forward resolution to
|
||||
// create the branch.
|
||||
CodeReviewCommit first = sorted.remove(0);
|
||||
ops.add(new FastForwardOp(args, first));
|
||||
}
|
||||
return mergeTip;
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
ops.add(new MergeOneOp(args, n));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean dryRun(SubmitDryRun.Arguments args,
|
||||
|
||||
@@ -14,16 +14,10 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import static com.google.gerrit.server.git.strategy.MarkCleanMergesOp.anyChangeId;
|
||||
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,42 +27,28 @@ public class MergeIfNecessary extends SubmitStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeTip run(CodeReviewCommit branchTip,
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted =
|
||||
args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
|
||||
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
|
||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||
// Start with the first fast-forward. This may create the branch if it did
|
||||
// not exist.
|
||||
CodeReviewCommit firstFastForward;
|
||||
if (branchTip == null) {
|
||||
firstFastForward = sorted.remove(0);
|
||||
} else {
|
||||
firstFastForward =
|
||||
args.mergeUtil.getFirstFastForward(branchTip, args.rw, sorted);
|
||||
}
|
||||
if (!firstFastForward.equals(branchTip)) {
|
||||
u.addOp(firstFastForward.change().getId(),
|
||||
new FastForwardOp(mergeTip, firstFastForward));
|
||||
}
|
||||
|
||||
// For every other commit do a pair-wise merge.
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
u.addOp(n.change().getId(), new MergeOneOp(args, mergeTip, n));
|
||||
}
|
||||
u.addOp(anyChangeId(toMerge), new MarkCleanMergesOp(args, mergeTip));
|
||||
|
||||
u.execute();
|
||||
} catch (RestApiException | UpdateException e) {
|
||||
if (e.getCause() instanceof IntegrationException) {
|
||||
throw new IntegrationException(e.getCause().getMessage(), e);
|
||||
}
|
||||
throw new IntegrationException(
|
||||
"Cannot merge into " + args.destBranch);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
CodeReviewCommit firstFastForward;
|
||||
if (args.mergeTip.getInitialTip() == null) {
|
||||
firstFastForward = sorted.remove(0);
|
||||
} else {
|
||||
firstFastForward = args.mergeUtil.getFirstFastForward(
|
||||
args.mergeTip.getInitialTip(), args.rw, sorted);
|
||||
}
|
||||
return mergeTip;
|
||||
if (!firstFastForward.equals(args.mergeTip.getInitialTip())) {
|
||||
ops.add(new FastForwardOp(args, firstFastForward));
|
||||
}
|
||||
|
||||
// For every other commit do a pair-wise merge.
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
ops.add(new MergeOneOp(args, n));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static boolean dryRun(SubmitDryRun.Arguments args,
|
||||
|
||||
@@ -14,34 +14,25 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class MergeOneOp extends BatchUpdate.Op {
|
||||
private final SubmitStrategy.Arguments args;
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
MergeOneOp(SubmitStrategy.Arguments args, MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.args = args;
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
class MergeOneOp extends SubmitStrategyOp {
|
||||
MergeOneOp(SubmitStrategy.Arguments args, CodeReviewCommit toMerge) {
|
||||
super(args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx)
|
||||
public void updateRepoImpl(RepoContext ctx)
|
||||
throws IntegrationException, IOException {
|
||||
PersonIdent caller = ctx.getUser().asIdentifiedUser().newCommitterIdent(
|
||||
ctx.getWhen(), ctx.getTimeZone());
|
||||
if (mergeTip.getCurrentTip() == null) {
|
||||
if (args.mergeTip.getCurrentTip() == null) {
|
||||
throw new IllegalStateException("cannot merge commit " + toMerge.name()
|
||||
+ " onto a null tip; expected at least one fast-forward prior to"
|
||||
+ " this operation");
|
||||
@@ -52,7 +43,7 @@ class MergeOneOp extends BatchUpdate.Op {
|
||||
CodeReviewCommit merged =
|
||||
args.mergeUtil.mergeOneCommit(caller, args.serverIdent,
|
||||
ctx.getRepository(), args.rw, ctx.getInserter(), args.canMergeFlag,
|
||||
args.destBranch, mergeTip.getCurrentTip(), toMerge);
|
||||
mergeTip.moveTipTo(merged, toMerge);
|
||||
args.destBranch, args.mergeTip.getCurrentTip(), toMerge);
|
||||
args.mergeTip.moveTipTo(merged, toMerge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,11 @@
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||
import com.google.gerrit.server.git.BatchUpdate.Context;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
@@ -30,13 +27,13 @@ import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.RebaseSorter;
|
||||
import com.google.gerrit.server.git.UpdateException;
|
||||
import com.google.gerrit.server.git.validators.CommitValidators;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -48,98 +45,76 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MergeTip run(final CodeReviewCommit branchTip,
|
||||
final Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
MergeTip mergeTip = new MergeTip(branchTip, toMerge);
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted = sort(toMerge);
|
||||
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
boolean first = true;
|
||||
|
||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
Change.Id cid = n.change().getId();
|
||||
if (first && branchTip == null) {
|
||||
u.addOp(cid, new RebaseUnbornRootOp(mergeTip, n));
|
||||
} else if (n.getParentCount() == 0) {
|
||||
u.addOp(cid, new RebaseRootOp(n));
|
||||
} else if (n.getParentCount() == 1) {
|
||||
u.addOp(cid, new RebaseOneOp(mergeTip, n));
|
||||
} else {
|
||||
u.addOp(cid, new RebaseMultipleParentsOp(mergeTip, n));
|
||||
}
|
||||
first = false;
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
if (first && args.mergeTip.getInitialTip() == null) {
|
||||
ops.add(new RebaseUnbornRootOp(n));
|
||||
} else if (n.getParentCount() == 0) {
|
||||
ops.add(new RebaseRootOp(n));
|
||||
} else if (n.getParentCount() == 1) {
|
||||
ops.add(new RebaseOneOp(n));
|
||||
} else {
|
||||
ops.add(new RebaseMultipleParentsOp(n));
|
||||
}
|
||||
u.execute();
|
||||
} catch (UpdateException | RestApiException e) {
|
||||
if (e.getCause() instanceof IntegrationException) {
|
||||
throw new IntegrationException(e.getCause().getMessage(), e);
|
||||
}
|
||||
throw new IntegrationException(
|
||||
"Cannot rebase onto " + args.destBranch, e);
|
||||
first = false;
|
||||
}
|
||||
return mergeTip;
|
||||
return ops;
|
||||
}
|
||||
|
||||
private class RebaseUnbornRootOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private RebaseUnbornRootOp(MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
private class RebaseUnbornRootOp extends SubmitStrategyOp {
|
||||
private RebaseUnbornRootOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) {
|
||||
public void updateRepoImpl(RepoContext ctx) {
|
||||
// The branch is unborn. Take fast-forward resolution to create the
|
||||
// branch.
|
||||
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
acceptMergeTip(mergeTip);
|
||||
args.mergeTip.moveTipTo(toMerge, toMerge);
|
||||
acceptMergeTip(args.mergeTip);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RebaseRootOp extends BatchUpdate.Op {
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private class RebaseRootOp extends SubmitStrategyOp {
|
||||
private RebaseRootOp(CodeReviewCommit toMerge) {
|
||||
this.toMerge = toMerge;
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) {
|
||||
public void updateRepoImpl(RepoContext ctx) {
|
||||
// Refuse to merge a root commit into an existing branch, we cannot obtain
|
||||
// a delta for the cherry-pick to apply.
|
||||
toMerge.setStatusCode(CommitMergeStatus.CANNOT_REBASE_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
private class RebaseOneOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private class RebaseOneOp extends SubmitStrategyOp {
|
||||
private RebaseChangeOp rebaseOp;
|
||||
private CodeReviewCommit newCommit;
|
||||
|
||||
private RebaseOneOp(MergeTip mergeTip, CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
private RebaseOneOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx)
|
||||
public void updateRepoImpl(RepoContext ctx)
|
||||
throws IntegrationException, InvalidChangeOperationException,
|
||||
RestApiException, IOException, OrmException {
|
||||
// TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
|
||||
// When hoisting BatchUpdate into MergeOp, we will need to teach
|
||||
// BatchUpdate how to produce CodeReviewRevWalks.
|
||||
if (args.mergeUtil.canFastForward(args.mergeSorter,
|
||||
mergeTip.getCurrentTip(), args.rw, toMerge)) {
|
||||
args.mergeTip.getCurrentTip(), args.rw, toMerge)) {
|
||||
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
acceptMergeTip(mergeTip);
|
||||
args.mergeTip.moveTipTo(toMerge, toMerge);
|
||||
acceptMergeTip(args.mergeTip);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,7 +122,7 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
||||
PatchSet origPs = args.psUtil.get(
|
||||
ctx.getDb(), toMerge.getControl().getNotes(), toMerge.getPatchsetId());
|
||||
rebaseOp = args.rebaseFactory.create(
|
||||
toMerge.getControl(), origPs, mergeTip.getCurrentTip().name())
|
||||
toMerge.getControl(), origPs, args.mergeTip.getCurrentTip().name())
|
||||
.setRunHooks(false)
|
||||
// Bypass approval copier since we're going to copy all approvals
|
||||
// later anyway.
|
||||
@@ -164,14 +139,15 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
||||
newCommit.copyFrom(toMerge);
|
||||
newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
|
||||
newCommit.setPatchsetId(rebaseOp.getPatchSetId());
|
||||
mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(mergeTip.getCurrentTip());
|
||||
acceptMergeTip(mergeTip);
|
||||
args.mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(args.mergeTip.getCurrentTip());
|
||||
acceptMergeTip(args.mergeTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateChange(ChangeContext ctx) throws NoSuchChangeException,
|
||||
InvalidChangeOperationException, OrmException, IOException {
|
||||
public void updateChangeImpl(ChangeContext ctx)
|
||||
throws NoSuchChangeException, InvalidChangeOperationException,
|
||||
OrmException, IOException {
|
||||
if (rebaseOp == null) {
|
||||
// Took the fast-forward option, nothing to do.
|
||||
return;
|
||||
@@ -180,45 +156,41 @@ public class RebaseIfNecessary extends SubmitStrategy {
|
||||
rebaseOp.updateChange(ctx);
|
||||
PatchSet.Id newPatchSetId = rebaseOp.getPatchSetId();
|
||||
List<PatchSetApproval> approvals = Lists.newArrayList();
|
||||
// Copy approvals from original patch set.
|
||||
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(ctx.getDb(),
|
||||
toMerge.getControl(), toMerge.getPatchsetId())) {
|
||||
ctx.getControl(), toMerge.getPatchsetId())) {
|
||||
approvals.add(new PatchSetApproval(newPatchSetId, a));
|
||||
}
|
||||
args.db.patchSetApprovals().insert(approvals);
|
||||
|
||||
toMerge.change().setCurrentPatchSet(
|
||||
args.patchSetInfoFactory.get(args.rw, mergeTip.getCurrentTip(),
|
||||
newPatchSetId));
|
||||
newCommit.setControl(
|
||||
args.changeControlFactory.controlFor(toMerge.change(), args.caller));
|
||||
ctx.getChange().setCurrentPatchSet(
|
||||
args.patchSetInfoFactory.get(
|
||||
args.rw, newCommit, newPatchSetId));
|
||||
newCommit.setControl(ctx.getControl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(Context ctx) throws OrmException {
|
||||
public void postUpdateImpl(Context ctx) throws OrmException {
|
||||
if (rebaseOp != null) {
|
||||
rebaseOp.postUpdate(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RebaseMultipleParentsOp extends BatchUpdate.Op {
|
||||
private final MergeTip mergeTip;
|
||||
private final CodeReviewCommit toMerge;
|
||||
|
||||
private RebaseMultipleParentsOp(MergeTip mergeTip,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.mergeTip = mergeTip;
|
||||
this.toMerge = toMerge;
|
||||
private class RebaseMultipleParentsOp extends SubmitStrategyOp {
|
||||
private RebaseMultipleParentsOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx)
|
||||
public void updateRepoImpl(RepoContext ctx)
|
||||
throws IntegrationException, IOException {
|
||||
// There are multiple parents, so this is a merge commit. We don't want
|
||||
// to rebase the merge as clients can't easily rebase their history with
|
||||
// that merge present and replaced by an equivalent merge with a different
|
||||
// first parent. So instead behave as though MERGE_IF_NECESSARY was
|
||||
// configured.
|
||||
MergeTip mergeTip = args.mergeTip;
|
||||
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
acceptMergeTip(mergeTip);
|
||||
|
||||
@@ -16,23 +16,31 @@ package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.ChangeHooks;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.config.FactoryModule;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ApprovalsUtil;
|
||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.PatchSetUtil;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||
import com.google.gerrit.server.git.EmailMerge;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.LabelNormalizer;
|
||||
import com.google.gerrit.server.git.MergeOp.CommitStatus;
|
||||
import com.google.gerrit.server.git.MergeSorter;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.MergeUtil;
|
||||
import com.google.gerrit.server.git.TagCache;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
@@ -47,8 +55,8 @@ import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -67,118 +75,153 @@ public abstract class SubmitStrategy {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(dborowitz): make non-public when converting to BatchUpdate.
|
||||
public static class Arguments {
|
||||
static class Arguments {
|
||||
interface Factory {
|
||||
Arguments create(
|
||||
Branch.NameKey destBranch,
|
||||
CommitStatus commits,
|
||||
CodeReviewRevWalk rw,
|
||||
IdentifiedUser caller,
|
||||
MergeTip mergeTip,
|
||||
ObjectInserter inserter,
|
||||
Repository repo,
|
||||
RevFlag canMergeFlag,
|
||||
ReviewDb db,
|
||||
Set<RevCommit> alreadyAccepted);
|
||||
Set<RevCommit> alreadyAccepted,
|
||||
String submissionId);
|
||||
}
|
||||
|
||||
final AccountCache accountCache;
|
||||
final ApprovalsUtil approvalsUtil;
|
||||
final BatchUpdate.Factory batchUpdateFactory;
|
||||
final ChangeControl.GenericFactory changeControlFactory;
|
||||
final ChangeHooks hooks;
|
||||
final ChangeMessagesUtil cmUtil;
|
||||
final EmailMerge.Factory mergedSenderFactory;
|
||||
final GitRepositoryManager repoManager;
|
||||
final LabelNormalizer labelNormalizer;
|
||||
final PatchSetInfoFactory patchSetInfoFactory;
|
||||
final PatchSetUtil psUtil;
|
||||
final ProjectCache projectCache;
|
||||
final PersonIdent serverIdent;
|
||||
final RebaseChangeOp.Factory rebaseFactory;
|
||||
final TagCache tagCache;
|
||||
|
||||
final Branch.NameKey destBranch;
|
||||
// TODO(dborowitz): make non-public when converting to BatchUpdate.
|
||||
public final CodeReviewRevWalk rw;
|
||||
final CodeReviewRevWalk rw;
|
||||
final CommitStatus commits;
|
||||
final IdentifiedUser caller;
|
||||
final MergeTip mergeTip;
|
||||
final ObjectInserter inserter;
|
||||
final Repository repo;
|
||||
// TODO(dborowitz): make non-public when converting to BatchUpdate.
|
||||
public final RevFlag canMergeFlag;
|
||||
final RevFlag canMergeFlag;
|
||||
final ReviewDb db;
|
||||
final Set<RevCommit> alreadyAccepted;
|
||||
final String submissionId;
|
||||
|
||||
final ProjectState project;
|
||||
final MergeSorter mergeSorter;
|
||||
// TODO(dborowitz): make non-public when converting to BatchUpdate.
|
||||
public final MergeUtil mergeUtil;
|
||||
final MergeUtil mergeUtil;
|
||||
|
||||
@AssistedInject
|
||||
Arguments(
|
||||
AccountCache accountCache,
|
||||
ApprovalsUtil approvalsUtil,
|
||||
BatchUpdate.Factory batchUpdateFactory,
|
||||
ChangeControl.GenericFactory changeControlFactory,
|
||||
ChangeHooks hooks,
|
||||
ChangeMessagesUtil cmUtil,
|
||||
EmailMerge.Factory mergedSenderFactory,
|
||||
GitRepositoryManager repoManager,
|
||||
LabelNormalizer labelNormalizer,
|
||||
MergeUtil.Factory mergeUtilFactory,
|
||||
PatchSetInfoFactory patchSetInfoFactory,
|
||||
PatchSetUtil psUtil,
|
||||
@GerritPersonIdent PersonIdent serverIdent,
|
||||
ProjectCache projectCache,
|
||||
RebaseChangeOp.Factory rebaseFactory,
|
||||
TagCache tagCache,
|
||||
@Assisted Branch.NameKey destBranch,
|
||||
@Assisted CommitStatus commits,
|
||||
@Assisted CodeReviewRevWalk rw,
|
||||
@Assisted IdentifiedUser caller,
|
||||
@Assisted MergeTip mergeTip,
|
||||
@Assisted ObjectInserter inserter,
|
||||
@Assisted Repository repo,
|
||||
@Assisted RevFlag canMergeFlag,
|
||||
@Assisted ReviewDb db,
|
||||
@Assisted Set<RevCommit> alreadyAccepted) {
|
||||
@Assisted Set<RevCommit> alreadyAccepted,
|
||||
@Assisted String submissionId) {
|
||||
this.accountCache = accountCache;
|
||||
this.approvalsUtil = approvalsUtil;
|
||||
this.batchUpdateFactory = batchUpdateFactory;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.hooks = hooks;
|
||||
this.mergedSenderFactory = mergedSenderFactory;
|
||||
this.repoManager = repoManager;
|
||||
this.cmUtil = cmUtil;
|
||||
this.labelNormalizer = labelNormalizer;
|
||||
this.patchSetInfoFactory = patchSetInfoFactory;
|
||||
this.psUtil = psUtil;
|
||||
this.projectCache = projectCache;
|
||||
this.rebaseFactory = rebaseFactory;
|
||||
this.tagCache = tagCache;
|
||||
|
||||
this.serverIdent = serverIdent;
|
||||
this.destBranch = destBranch;
|
||||
this.commits = commits;
|
||||
this.rw = rw;
|
||||
this.caller = caller;
|
||||
this.mergeTip = mergeTip;
|
||||
this.inserter = inserter;
|
||||
this.repo = repo;
|
||||
this.canMergeFlag = canMergeFlag;
|
||||
this.db = db;
|
||||
this.alreadyAccepted = alreadyAccepted;
|
||||
this.submissionId = submissionId;
|
||||
|
||||
this.project = checkNotNull(projectCache.get(destBranch.getParentKey()),
|
||||
"project not found: %s", destBranch.getParentKey());
|
||||
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
|
||||
this.mergeUtil = mergeUtilFactory.create(project);
|
||||
}
|
||||
|
||||
BatchUpdate newBatchUpdate(Timestamp when) {
|
||||
return batchUpdateFactory
|
||||
.create(db, destBranch.getParentKey(), caller, when)
|
||||
.setRepository(repo, rw, inserter);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dborowitz): make non-public when converting to BatchUpdate.
|
||||
public final Arguments args;
|
||||
final Arguments args;
|
||||
|
||||
SubmitStrategy(Arguments args) {
|
||||
this.args = checkNotNull(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this submit strategy.
|
||||
* Add operations to a batch update that execute this submit strategy.
|
||||
* <p>
|
||||
* If possible, the provided commits will be merged with this submit strategy.
|
||||
* Guarantees exactly one op is added to the update for each change in the
|
||||
* input set.
|
||||
*
|
||||
* @param currentTip the mergeTip
|
||||
* @param toMerge the list of submitted commits that should be merged using
|
||||
* this submit strategy. Implementations are responsible for ordering
|
||||
* of commits, and should not modify the input in place.
|
||||
* @return the new merge tip.
|
||||
* @throws IntegrationException
|
||||
* @param bu batch update to add operations to.
|
||||
* @param toMerge the set of submitted commits that should be merged using
|
||||
* this submit strategy. Implementations are responsible for ordering of
|
||||
* commits, and will not modify the input in place.
|
||||
* @throws IntegrationException if an error occurred initializing the
|
||||
* operations (as opposed to an error during execution, which will be
|
||||
* reported only when the batch update executes the operations).
|
||||
*/
|
||||
public abstract MergeTip run(CodeReviewCommit currentTip,
|
||||
public final void addOps(BatchUpdate bu, Set<CodeReviewCommit> toMerge)
|
||||
throws IntegrationException {
|
||||
List<SubmitStrategyOp> ops = buildOps(toMerge);
|
||||
Set<CodeReviewCommit> added = Sets.newHashSetWithExpectedSize(ops.size());
|
||||
for (SubmitStrategyOp op : ops) {
|
||||
bu.addOp(op.getId(), op);
|
||||
added.add(op.getCommit());
|
||||
}
|
||||
|
||||
// Fill in ops for any implicitly merged changes.
|
||||
for (CodeReviewCommit c : Sets.difference(toMerge, added)) {
|
||||
bu.addOp(c.change().getId(), new ImplicitIntegrateOp(args, c));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeOp.CommitStatus;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
@@ -49,10 +50,12 @@ public class SubmitStrategyFactory {
|
||||
public SubmitStrategy create(SubmitType submitType, ReviewDb db,
|
||||
Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter,
|
||||
RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted,
|
||||
Branch.NameKey destBranch, IdentifiedUser caller, CommitStatus commits)
|
||||
Branch.NameKey destBranch, IdentifiedUser caller, MergeTip mergeTip,
|
||||
CommitStatus commits, String submissionId)
|
||||
throws IntegrationException {
|
||||
SubmitStrategy.Arguments args = argsFactory.create(destBranch, commits, rw,
|
||||
caller, inserter, repo, canMergeFlag, db, alreadyAccepted);
|
||||
caller, mergeTip, inserter, repo, canMergeFlag, db, alreadyAccepted,
|
||||
submissionId);
|
||||
switch (submitType) {
|
||||
case CHERRY_PICK:
|
||||
return new CherryPick(args);
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.MergeOp.CommitStatus;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public class SubmitStrategyListener extends BatchUpdate.Listener {
|
||||
private final Collection<SubmitStrategy> strategies;
|
||||
private final CommitStatus commits;
|
||||
|
||||
public SubmitStrategyListener(Collection<SubmitStrategy> strategies,
|
||||
CommitStatus commits) {
|
||||
this.strategies = strategies;
|
||||
this.commits = commits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdateRepos() throws ResourceConflictException {
|
||||
try {
|
||||
markCleanMerges();
|
||||
checkCommitStatus();
|
||||
findUnmergedChanges();
|
||||
} catch (IntegrationException e) {
|
||||
throw new ResourceConflictException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void findUnmergedChanges()
|
||||
throws ResourceConflictException, IntegrationException {
|
||||
for (SubmitStrategy strategy : strategies) {
|
||||
if (strategy instanceof CherryPick) {
|
||||
// Might have picked a subset of changes, can't do this sanity check.
|
||||
continue;
|
||||
}
|
||||
SubmitStrategy.Arguments args = strategy.args;
|
||||
Set<Change.Id> unmerged = args.mergeUtil.findUnmergedChanges(
|
||||
args.commits.getChangeIds(args.destBranch), args.rw,
|
||||
args.canMergeFlag, args.mergeTip.getInitialTip(),
|
||||
args.mergeTip.getCurrentTip());
|
||||
for (Change.Id id : unmerged) {
|
||||
commits.problem(id,
|
||||
"internal error: change not reachable from new branch tip");
|
||||
}
|
||||
}
|
||||
commits.maybeFailVerbose();
|
||||
}
|
||||
|
||||
private void markCleanMerges() throws IntegrationException {
|
||||
for (SubmitStrategy strategy : strategies) {
|
||||
SubmitStrategy.Arguments args = strategy.args;
|
||||
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
|
||||
args.mergeTip.getCurrentTip(), args.alreadyAccepted);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCommitStatus() throws ResourceConflictException {
|
||||
for (Change.Id id : commits.getChangeIds()) {
|
||||
CodeReviewCommit commit = commits.get(id);
|
||||
CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
|
||||
if (s == null) {
|
||||
commits.problem(id,
|
||||
"internal error: change not processed by merge strategy");
|
||||
return;
|
||||
}
|
||||
switch (s) {
|
||||
case CLEAN_MERGE:
|
||||
case CLEAN_REBASE:
|
||||
case CLEAN_PICK:
|
||||
case ALREADY_MERGED:
|
||||
break; // Merge strategy accepted this change.
|
||||
|
||||
case PATH_CONFLICT:
|
||||
case REBASE_MERGE_CONFLICT:
|
||||
case MANUAL_RECURSIVE_MERGE:
|
||||
case CANNOT_CHERRY_PICK_ROOT:
|
||||
case NOT_FAST_FORWARD:
|
||||
// TODO(dborowitz): Reformat these messages to be more appropriate for
|
||||
// short problem descriptions.
|
||||
commits.problem(id,
|
||||
CharMatcher.is('\n').collapseFrom(s.getMessage(), ' '));
|
||||
break;
|
||||
|
||||
case MISSING_DEPENDENCY:
|
||||
commits.problem(id, "depends on change that was not submitted");
|
||||
break;
|
||||
|
||||
default:
|
||||
commits.problem(id, "unspecified merge failure: " + s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
commits.maybeFailVerbose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpdateChanges() throws ResourceConflictException {
|
||||
commits.maybeFail("Error updating status");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.common.data.SubmitRecord;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||
import com.google.gerrit.reviewdb.client.LabelId;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.BatchUpdate;
|
||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||
import com.google.gerrit.server.git.BatchUpdate.Context;
|
||||
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.IntegrationException;
|
||||
import com.google.gerrit.server.git.LabelNormalizer;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
abstract class SubmitStrategyOp extends BatchUpdate.Op {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(SubmitStrategyOp.class);
|
||||
|
||||
protected final SubmitStrategy.Arguments args;
|
||||
protected final CodeReviewCommit toMerge;
|
||||
|
||||
private ReceiveCommand command;
|
||||
private PatchSetApproval submitter;
|
||||
private ObjectId mergeResultRev;
|
||||
private PatchSet mergedPatchSet;
|
||||
private Change updatedChange;
|
||||
|
||||
protected SubmitStrategyOp(SubmitStrategy.Arguments args,
|
||||
CodeReviewCommit toMerge) {
|
||||
this.args = args;
|
||||
this.toMerge = toMerge;
|
||||
}
|
||||
|
||||
final Change.Id getId() {
|
||||
return toMerge.change().getId();
|
||||
}
|
||||
|
||||
final CodeReviewCommit getCommit() {
|
||||
return toMerge;
|
||||
}
|
||||
|
||||
protected final Branch.NameKey getDest() {
|
||||
return toMerge.change().getDest();
|
||||
}
|
||||
|
||||
protected final Project.NameKey getProject() {
|
||||
return getDest().getParentKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void updateRepo(RepoContext ctx) throws Exception {
|
||||
// Run the submit strategy implementation and record the merge tip state so
|
||||
// we can create the ref update.
|
||||
CodeReviewCommit tipBefore = args.mergeTip.getCurrentTip();
|
||||
updateRepoImpl(ctx);
|
||||
CodeReviewCommit tipAfter = args.mergeTip.getCurrentTip();
|
||||
|
||||
if (Objects.equals(tipBefore, tipAfter)) {
|
||||
return;
|
||||
} else if (tipAfter == null) {
|
||||
logDebug("No merge tip, no update to perform");
|
||||
return;
|
||||
}
|
||||
|
||||
checkProjectConfig(ctx, tipAfter);
|
||||
|
||||
// Needed by postUpdate, at which point mergeTip will have advanced further,
|
||||
// so it's easier to just snapshot the command.
|
||||
command = new ReceiveCommand(
|
||||
firstNonNull(tipBefore, ObjectId.zeroId()),
|
||||
tipAfter,
|
||||
getDest().get());
|
||||
ctx.addRefUpdate(command);
|
||||
}
|
||||
|
||||
private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit)
|
||||
throws IntegrationException {
|
||||
String refName = getDest().get();
|
||||
if (RefNames.REFS_CONFIG.equals(refName)) {
|
||||
logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
|
||||
try {
|
||||
ProjectConfig cfg = new ProjectConfig(getProject());
|
||||
cfg.load(ctx.getRepository(), commit);
|
||||
} catch (Exception e) {
|
||||
throw new IntegrationException("Submit would store invalid"
|
||||
+ " project configuration " + commit.name() + " for "
|
||||
+ getProject(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean updateChange(ChangeContext ctx) throws Exception {
|
||||
toMerge.setControl(ctx.getControl()); // Update change and notes from ctx.
|
||||
updateChangeImpl(ctx);
|
||||
|
||||
Change c = ctx.getChange();
|
||||
Change.Id id = c.getId();
|
||||
try {
|
||||
CodeReviewCommit commit = args.commits.get(id);
|
||||
CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
|
||||
logDebug("Status of change {} ({}) on {}: {}", id, commit.name(),
|
||||
c.getDest(), s);
|
||||
checkState(s != null,
|
||||
"status not set for change %s; expected to previously fail fast",
|
||||
id);
|
||||
setApproval(ctx, args.caller);
|
||||
|
||||
mergeResultRev = args.mergeTip != null
|
||||
? args.mergeTip.getMergeResults().get(commit) : null;
|
||||
String txt = s.getMessage();
|
||||
|
||||
ChangeMessage msg;
|
||||
if (s == CommitMergeStatus.CLEAN_MERGE) {
|
||||
msg = message(ctx, txt + getByAccountName());
|
||||
} else if (s == CommitMergeStatus.CLEAN_REBASE
|
||||
|| s == CommitMergeStatus.CLEAN_PICK) {
|
||||
msg = message(ctx, txt + " as " + commit.name() + getByAccountName());
|
||||
} else if (s == CommitMergeStatus.ALREADY_MERGED) {
|
||||
msg = null;
|
||||
} else {
|
||||
throw new IllegalStateException("unexpected status " + s +
|
||||
" for change " + c.getId() + "; expected to previously fail fast");
|
||||
}
|
||||
setMerged(ctx, msg);
|
||||
} catch (OrmException err) {
|
||||
String msg = "Error updating change status for " + id;
|
||||
log.error(msg, err);
|
||||
args.commits.logProblem(id, msg);
|
||||
// It's possible this happened before updating anything in the db, but
|
||||
// it's hard to know for sure, so just return true below to be safe.
|
||||
}
|
||||
updatedChange = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setApproval(ChangeContext ctx, IdentifiedUser user)
|
||||
throws OrmException {
|
||||
Change.Id id = ctx.getChange().getId();
|
||||
List<SubmitRecord> records = args.commits.getSubmitRecords(id);
|
||||
PatchSet.Id oldPsId = toMerge.getPatchsetId();
|
||||
PatchSet.Id newPsId = ctx.getChange().currentPatchSetId();
|
||||
|
||||
logDebug("Add approval for " + id);
|
||||
ChangeUpdate origPsUpdate = ctx.getUpdate(oldPsId);
|
||||
origPsUpdate.putReviewer(user.getAccountId(), REVIEWER);
|
||||
LabelNormalizer.Result normalized = approve(ctx, origPsUpdate);
|
||||
|
||||
ChangeUpdate newPsUpdate = ctx.getUpdate(newPsId);
|
||||
newPsUpdate.merge(records);
|
||||
// If the submit strategy created a new revision (rebase, cherry-pick), copy
|
||||
// approvals as well.
|
||||
if (!newPsId.equals(oldPsId)) {
|
||||
saveApprovals(normalized, ctx, newPsUpdate, true);
|
||||
submitter = convertPatchSet(newPsId).apply(submitter);
|
||||
}
|
||||
}
|
||||
|
||||
private LabelNormalizer.Result approve(ChangeContext ctx, ChangeUpdate update)
|
||||
throws OrmException {
|
||||
PatchSet.Id psId = update.getPatchSetId();
|
||||
Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
|
||||
for (PatchSetApproval psa : args.approvalsUtil.byPatchSet(
|
||||
ctx.getDb(), ctx.getControl(), psId)) {
|
||||
byKey.put(psa.getKey(), psa);
|
||||
}
|
||||
|
||||
submitter = new PatchSetApproval(
|
||||
new PatchSetApproval.Key(
|
||||
psId,
|
||||
ctx.getUser().getAccountId(),
|
||||
LabelId.SUBMIT),
|
||||
(short) 1, ctx.getWhen());
|
||||
byKey.put(submitter.getKey(), submitter);
|
||||
submitter.setValue((short) 1);
|
||||
submitter.setGranted(ctx.getWhen());
|
||||
|
||||
// Flatten out existing approvals for this patch set based upon the current
|
||||
// permissions. Once the change is closed the approvals are not updated at
|
||||
// presentation view time, except for zero votes used to indicate a reviewer
|
||||
// was added. So we need to make sure votes are accurate now. This way if
|
||||
// permissions get modified in the future, historical records stay accurate.
|
||||
LabelNormalizer.Result normalized =
|
||||
args.labelNormalizer.normalize(ctx.getControl(), byKey.values());
|
||||
update.putApproval(submitter.getLabel(), submitter.getValue());
|
||||
saveApprovals(normalized, ctx, update, false);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private void saveApprovals(LabelNormalizer.Result normalized,
|
||||
ChangeContext ctx, ChangeUpdate update, boolean includeUnchanged)
|
||||
throws OrmException {
|
||||
PatchSet.Id psId = update.getPatchSetId();
|
||||
ctx.getDb().patchSetApprovals().upsert(
|
||||
convertPatchSet(normalized.getNormalized(), psId));
|
||||
ctx.getDb().patchSetApprovals().delete(
|
||||
convertPatchSet(normalized.deleted(), psId));
|
||||
for (PatchSetApproval psa : normalized.updated()) {
|
||||
update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
|
||||
}
|
||||
for (PatchSetApproval psa : normalized.deleted()) {
|
||||
update.removeApprovalFor(psa.getAccountId(), psa.getLabel());
|
||||
}
|
||||
|
||||
// TODO(dborowitz): Don't use a label in notedb; just check when status
|
||||
// change happened.
|
||||
for (PatchSetApproval psa : normalized.unchanged()) {
|
||||
if (includeUnchanged || psa.isSubmit()) {
|
||||
logDebug("Adding submit label " + psa);
|
||||
update.putApprovalFor(
|
||||
psa.getAccountId(), psa.getLabel(), psa.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Function<PatchSetApproval, PatchSetApproval>
|
||||
convertPatchSet(final PatchSet.Id psId) {
|
||||
return new Function<PatchSetApproval, PatchSetApproval>() {
|
||||
@Override
|
||||
public PatchSetApproval apply(PatchSetApproval in) {
|
||||
if (in.getPatchSetId().equals(psId)) {
|
||||
return in;
|
||||
} else {
|
||||
return new PatchSetApproval(psId, in);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Iterable<PatchSetApproval> convertPatchSet(
|
||||
Iterable<PatchSetApproval> approvals, PatchSet.Id psId) {
|
||||
return Iterables.transform(approvals, convertPatchSet(psId));
|
||||
}
|
||||
|
||||
private String getByAccountName() {
|
||||
checkNotNull(submitter,
|
||||
"getByAccountName called before submitter populated");
|
||||
Account account =
|
||||
args.accountCache.get(submitter.getAccountId()).getAccount();
|
||||
if (account != null && account.getFullName() != null) {
|
||||
return " by " + account.getFullName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private ChangeMessage message(ChangeContext ctx, String body) {
|
||||
String uuid;
|
||||
try {
|
||||
uuid = ChangeUtil.messageUUID(ctx.getDb());
|
||||
} catch (OrmException e) {
|
||||
return null;
|
||||
}
|
||||
ChangeMessage m = new ChangeMessage(
|
||||
new ChangeMessage.Key(ctx.getChange().getId(), uuid),
|
||||
// TODO(dborowitz): Pre-BatchUpdate behavior wrote the merged message on
|
||||
// the old patch set ID, so that's what we do here. I don't think this
|
||||
// was intentional, and it should be changed.
|
||||
null, ctx.getWhen(), toMerge.change().currentPatchSetId());
|
||||
m.setMessage(body);
|
||||
return m;
|
||||
}
|
||||
|
||||
private void setMerged(ChangeContext ctx, ChangeMessage msg)
|
||||
throws OrmException {
|
||||
Change c = ctx.getChange();
|
||||
ReviewDb db = ctx.getDb();
|
||||
logDebug("Setting change {} merged", c.getId());
|
||||
// TODO(dborowitz): Use PatchSetUtil? But we don't have a recent notes.
|
||||
mergedPatchSet = db.patchSets().get(c.currentPatchSetId());
|
||||
c.setStatus(Change.Status.MERGED);
|
||||
c.setSubmissionId(args.submissionId);
|
||||
ctx.saveChange();
|
||||
|
||||
// TODO(dborowitz): We need to be able to change the author of the message,
|
||||
// which is not the user from the update context. addMergedMessage was able
|
||||
// to do this in the past.
|
||||
if (msg != null) {
|
||||
args.cmUtil.addChangeMessage(db, ctx.getUpdate(msg.getPatchSetId()), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void postUpdate(Context ctx) throws Exception {
|
||||
postUpdateImpl(ctx);
|
||||
|
||||
if (command != null) {
|
||||
args.tagCache.updateFastForward(
|
||||
getProject(),
|
||||
command.getRefName(),
|
||||
command.getOldId(),
|
||||
command.getNewId());
|
||||
// TODO(dborowitz): Move to BatchUpdate? Would also allow us to run once
|
||||
// per project even if multiple changes to refs/meta/config are submitted.
|
||||
if (RefNames.REFS_CONFIG.equals(getDest().get())) {
|
||||
args.projectCache.evict(getProject());
|
||||
ProjectState p = args.projectCache.get(getProject());
|
||||
args.repoManager.setProjectDescription(
|
||||
p.getProject().getNameKey(), p.getProject().getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
// Assume the change must have been merged at this point, otherwise we would
|
||||
// have failed fast in one of the other steps.
|
||||
try {
|
||||
args.mergedSenderFactory.create(getId(), submitter.getAccountId())
|
||||
.sendAsync();
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot email merged notification for " + getId(), e);
|
||||
}
|
||||
if (mergeResultRev != null) {
|
||||
try {
|
||||
args.hooks.doChangeMergedHook(updatedChange,
|
||||
args.accountCache.get(submitter.getAccountId()).getAccount(),
|
||||
mergedPatchSet, ctx.getDb(), mergeResultRev.name());
|
||||
} catch (OrmException ex) {
|
||||
logError("Cannot run hook for submitted patch set " + getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #updateRepo(RepoContext)
|
||||
* @param ctx
|
||||
*/
|
||||
protected void updateRepoImpl(RepoContext ctx) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #updateChange(ChangeContext)
|
||||
* @param ctx
|
||||
*/
|
||||
protected void updateChangeImpl(ChangeContext ctx) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #postUpdate(Context)
|
||||
* @param ctx
|
||||
*/
|
||||
protected void postUpdateImpl(Context ctx) throws Exception {
|
||||
}
|
||||
|
||||
protected final void logDebug(String msg, Object... args) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[" + this.args.submissionId + "]" + msg, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void logWarn(String msg, Throwable t) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("[" + args.submissionId + "]" + msg, t);
|
||||
}
|
||||
}
|
||||
|
||||
protected void logError(String msg, Throwable t) {
|
||||
if (log.isErrorEnabled()) {
|
||||
if (t != null) {
|
||||
log.error("[" + args.submissionId + "]" + msg, t);
|
||||
} else {
|
||||
log.error("[" + args.submissionId + "]" + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void logError(String msg) {
|
||||
logError(msg, null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user