Merge "Add new submit strategy "Rebase Always"."
This commit is contained in:
commit
42b7c5155b
@ -117,6 +117,13 @@ When Gerrit tries to do a merge, by default the merge will only
|
||||
succeed if there is no path conflict. A path conflict occurs when
|
||||
the same file has also been changed on the other side of the merge.
|
||||
|
||||
[[rebase_always]]
|
||||
* Rebase Always
|
||||
+
|
||||
Basically, the same as Rebase If Necesary, but it creates a new patchset even if
|
||||
fast forward is possible. In this regard, it's similar to Cherry Pick, but with
|
||||
the important distinction that Rebase Always does not ignore dependencies.
|
||||
|
||||
[[content_merge]]
|
||||
If `Allow content merges` is enabled, Gerrit will try
|
||||
to do a content merge when a path conflict occurs.
|
||||
|
@ -20,6 +20,7 @@ import static com.google.gerrit.extensions.client.SubmitType.FAST_FORWARD_ONLY;
|
||||
import static com.google.gerrit.extensions.client.SubmitType.MERGE_ALWAYS;
|
||||
import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
|
||||
import static com.google.gerrit.extensions.client.SubmitType.REBASE_IF_NECESSARY;
|
||||
import static com.google.gerrit.extensions.client.SubmitType.REBASE_ALWAYS;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -123,6 +124,10 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
||||
+ "gerrit:commit_message(M),"
|
||||
+ "regex_matches('.*REBASE_IF_NECESSARY.*', M),"
|
||||
+ "!.\n"
|
||||
+ "submit_type(rebase_always) :-"
|
||||
+ "gerrit:commit_message(M),"
|
||||
+ "regex_matches('.*REBASE_ALWAYS.*', M),"
|
||||
+ "!.\n"
|
||||
+ "submit_type(merge_always) :-"
|
||||
+ "gerrit:commit_message(M),"
|
||||
+ "regex_matches('.*MERGE_ALWAYS.*', M),"
|
||||
@ -157,8 +162,9 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
||||
PushOneCommit.Result r2 = createChange("master", "FAST_FORWARD_ONLY 2");
|
||||
PushOneCommit.Result r3 = createChange("master", "MERGE_IF_NECESSARY 3");
|
||||
PushOneCommit.Result r4 = createChange("master", "REBASE_IF_NECESSARY 4");
|
||||
PushOneCommit.Result r5 = createChange("master", "MERGE_ALWAYS 5");
|
||||
PushOneCommit.Result r6 = createChange("master", "CHERRY_PICK 6");
|
||||
PushOneCommit.Result r5 = createChange("master", "REBASE_ALWAYS 5");
|
||||
PushOneCommit.Result r6 = createChange("master", "MERGE_ALWAYS 6");
|
||||
PushOneCommit.Result r7 = createChange("master", "CHERRY_PICK 7");
|
||||
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r1.getChangeId());
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r2.getChangeId());
|
||||
@ -166,6 +172,7 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r4.getChangeId());
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r5.getChangeId());
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r6.getChangeId());
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r7.getChangeId());
|
||||
|
||||
setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
|
||||
|
||||
@ -173,8 +180,9 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
||||
assertSubmitType(FAST_FORWARD_ONLY, r2.getChangeId());
|
||||
assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
|
||||
assertSubmitType(REBASE_IF_NECESSARY, r4.getChangeId());
|
||||
assertSubmitType(MERGE_ALWAYS, r5.getChangeId());
|
||||
assertSubmitType(CHERRY_PICK, r6.getChangeId());
|
||||
assertSubmitType(REBASE_ALWAYS, r5.getChangeId());
|
||||
assertSubmitType(MERGE_ALWAYS, r6.getChangeId());
|
||||
assertSubmitType(CHERRY_PICK, r7.getChangeId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -61,14 +61,21 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected static Config submitByCherryPickConifg() {
|
||||
protected static Config submitByCherryPickConfig() {
|
||||
Config cfg = new Config();
|
||||
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
||||
cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected static Config submitByRebaseConifg() {
|
||||
protected static Config submitByRebaseAlwaysConfig() {
|
||||
Config cfg = new Config();
|
||||
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
||||
cfg.setEnum("project", null, "submitType", SubmitType.REBASE_ALWAYS);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected static Config submitByRebaseIfNecessaryConfig() {
|
||||
Config cfg = new Config();
|
||||
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
||||
cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
|
||||
|
@ -53,12 +53,17 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
|
||||
|
||||
@ConfigSuite.Config
|
||||
public static Config cherryPick() {
|
||||
return submitByCherryPickConifg();
|
||||
return submitByCherryPickConfig();
|
||||
}
|
||||
|
||||
@ConfigSuite.Config
|
||||
public static Config rebase() {
|
||||
return submitByRebaseConifg();
|
||||
public static Config rebaseAlways() {
|
||||
return submitByRebaseAlwaysConfig();
|
||||
}
|
||||
|
||||
@ConfigSuite.Config
|
||||
public static Config rebaseIfNecessary() {
|
||||
return submitByRebaseIfNecessaryConfig();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -129,10 +134,11 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
|
||||
assertThat(preview).containsKey(
|
||||
new Branch.NameKey(p2, "refs/heads/master"));
|
||||
|
||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
||||
if ((getSubmitType() == SubmitType.CHERRY_PICK)
|
||||
|| (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
|
||||
// each change is updated and the respective target branch is updated:
|
||||
assertThat(preview).hasSize(5);
|
||||
} else if (getSubmitType() == SubmitType.REBASE_IF_NECESSARY) {
|
||||
} else if ((getSubmitType() == SubmitType.REBASE_IF_NECESSARY)) {
|
||||
// Either the first is used first as is, then the second and third need
|
||||
// rebasing, or those two stay as is and the first is rebased.
|
||||
// add in 2 master branches, expect 3 or 4:
|
||||
|
@ -146,7 +146,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
||||
Map<Branch.NameKey, RevTree> actual =
|
||||
fetchFromBundles(request);
|
||||
|
||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
||||
if ((getSubmitType() == SubmitType.CHERRY_PICK)
|
||||
|| (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
|
||||
// The change is updated as well:
|
||||
assertThat(actual).hasSize(2);
|
||||
} else {
|
||||
@ -202,7 +203,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(),
|
||||
headAfterFirstSubmit.name());
|
||||
} else if(getSubmitType() == SubmitType.REBASE_IF_NECESSARY) {
|
||||
} else if ((getSubmitType() == SubmitType.REBASE_IF_NECESSARY)
|
||||
|| (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
|
||||
String change2hash = change2.getChange().currentPatchSet()
|
||||
.getRevision().get();
|
||||
assertThat(msg).isEqualTo(
|
||||
@ -253,7 +255,13 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
||||
assertThat(actual).containsKey(
|
||||
new Branch.NameKey(project, "refs/heads/master"));
|
||||
if (getSubmitType() == SubmitType.CHERRY_PICK){
|
||||
// CherryPick ignores dependencies, thus only change and destination
|
||||
// branch refs are modified.
|
||||
assertThat(actual).hasSize(2);
|
||||
} else if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
||||
// RebaseAlways takes care of dependencies, therefore Change{2,3,4} and
|
||||
// destination branch will be modified.
|
||||
assertThat(actual).hasSize(4);
|
||||
} else {
|
||||
assertThat(actual).hasSize(1);
|
||||
}
|
||||
@ -409,6 +417,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
||||
assertThat(last).startsWith(
|
||||
"Change has been successfully cherry-picked as ");
|
||||
} else if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
||||
assertThat(last).startsWith("Change has been successfully rebased as");
|
||||
} else {
|
||||
assertThat(last).isEqualTo(
|
||||
"Change has been successfully merged by Administrator");
|
||||
|
@ -0,0 +1,353 @@
|
||||
// Copyright (C) 2013 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.acceptance.rest.change;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
|
||||
import static com.google.gerrit.acceptance.GitUtil.pushHead;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.TestProjectInput;
|
||||
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||
import com.google.gerrit.extensions.client.InheritableBoolean;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.change.Submit.TestSubmitInput;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.junit.Test;
|
||||
|
||||
public abstract class AbstractSubmitByRebase extends AbstractSubmit {
|
||||
|
||||
@Override
|
||||
protected abstract SubmitType getSubmitType();
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithRebase() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
submit(change2.getChangeId());
|
||||
assertRebase(testRepo, false);
|
||||
RevCommit headAfterSecondSubmit = getRemoteHead();
|
||||
assertThat(headAfterSecondSubmit.getParent(0))
|
||||
.isEqualTo(headAfterFirstSubmit);
|
||||
assertApproved(change2.getChangeId());
|
||||
assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
|
||||
assertSubmitter(change2.getChangeId(), 1);
|
||||
assertSubmitter(change2.getChangeId(), 2);
|
||||
assertPersonEquals(admin.getIdent(),
|
||||
headAfterSecondSubmit.getAuthorIdent());
|
||||
assertPersonEquals(admin.getIdent(),
|
||||
headAfterSecondSubmit.getCommitterIdent());
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
|
||||
headAfterFirstSubmit, headAfterSecondSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitWithRebaseMultipleChanges() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change1 =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change1.getChangeId());
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
||||
assertCurrentRevision(change1.getChangeId(), 2, headAfterFirstSubmit);
|
||||
} else {
|
||||
assertThat(headAfterFirstSubmit.name())
|
||||
.isEqualTo(change1.getCommit().name());
|
||||
}
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
assertThat(change2.getCommit().getParent(0))
|
||||
.isNotEqualTo(change1.getCommit());
|
||||
PushOneCommit.Result change3 =
|
||||
createChange("Change 3", "c.txt", "third content");
|
||||
PushOneCommit.Result change4 =
|
||||
createChange("Change 4", "d.txt", "fourth content");
|
||||
approve(change2.getChangeId());
|
||||
approve(change3.getChangeId());
|
||||
submit(change4.getChangeId());
|
||||
|
||||
assertRebase(testRepo, false);
|
||||
assertApproved(change2.getChangeId());
|
||||
assertApproved(change3.getChangeId());
|
||||
assertApproved(change4.getChangeId());
|
||||
|
||||
RevCommit headAfterSecondSubmit = parse(getRemoteHead());
|
||||
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
|
||||
assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
|
||||
assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
|
||||
|
||||
RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
|
||||
assertThat(parent.getShortMessage()).isEqualTo("Change 3");
|
||||
assertThat(parent).isNotEqualTo(change3.getCommit());
|
||||
assertCurrentRevision(change3.getChangeId(), 2, parent);
|
||||
|
||||
RevCommit grandparent = parse(parent.getParent(0));
|
||||
assertThat(grandparent).isNotEqualTo(change2.getCommit());
|
||||
assertCurrentRevision(change2.getChangeId(), 2, grandparent);
|
||||
|
||||
RevCommit greatgrandparent = parse(grandparent.getParent(0));
|
||||
assertThat(greatgrandparent).isEqualTo(headAfterFirstSubmit);
|
||||
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
||||
assertCurrentRevision(change1.getChangeId(), 2, greatgrandparent);
|
||||
} else {
|
||||
assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
|
||||
}
|
||||
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
|
||||
headAfterFirstSubmit, headAfterSecondSubmit);
|
||||
assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name(),
|
||||
change3.getChangeId(), headAfterSecondSubmit.name(),
|
||||
change4.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitWithRebaseMergeCommit() throws Exception {
|
||||
/*
|
||||
* (HEAD, origin/master, origin/HEAD) Merge changes X,Y
|
||||
|\
|
||||
| * Merge branch 'master' into origin/master
|
||||
| |\
|
||||
| | * SHA Added a
|
||||
| |/
|
||||
* | Before
|
||||
|/
|
||||
* Initial empty repository
|
||||
*/
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
|
||||
|
||||
PushOneCommit change2Push = pushFactory.create(db, admin.getIdent(), testRepo,
|
||||
"Merge to master", "m.txt", "");
|
||||
change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
|
||||
PushOneCommit.Result change2 = change2Push.to("refs/for/master");
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
|
||||
|
||||
approve(change3.getChangeId());
|
||||
submit(change3.getChangeId());
|
||||
|
||||
approve(change1.getChangeId());
|
||||
approve(change2.getChangeId());
|
||||
submit(change2.getChangeId());
|
||||
|
||||
RevCommit newHead = getRemoteHead();
|
||||
assertThat(newHead.getParentCount()).isEqualTo(2);
|
||||
|
||||
RevCommit headParent1 = parse(newHead.getParent(0).getId());
|
||||
RevCommit headParent2 = parse(newHead.getParent(1).getId());
|
||||
|
||||
if (getSubmitType() == SubmitType.REBASE_ALWAYS){
|
||||
assertCurrentRevision(change3.getChangeId(), 2, headParent1.getId());
|
||||
} else {
|
||||
assertThat(change3.getCommit().getId()).isEqualTo(headParent1.getId());
|
||||
}
|
||||
assertThat(headParent1.getParentCount()).isEqualTo(1);
|
||||
assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
|
||||
|
||||
assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
|
||||
assertThat(headParent2.getParentCount()).isEqualTo(2);
|
||||
|
||||
RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
|
||||
RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
|
||||
|
||||
assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
|
||||
assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithContentMerge_Conflict() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "a.txt", "other content");
|
||||
submitWithConflict(change2.getChangeId(),
|
||||
"Cannot rebase " + change2.getCommit().name()
|
||||
+ ": The change could not be rebased due to a conflict during merge.");
|
||||
RevCommit head = getRemoteHead();
|
||||
assertThat(head).isEqualTo(headAfterFirstSubmit);
|
||||
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
|
||||
assertNoSubmitter(change2.getChangeId(), 1);
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repairChangeStateAfterFailure() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
Change.Id id2 = change2.getChange().getId();
|
||||
SubmitInput failAfterRefUpdates =
|
||||
new TestSubmitInput(new SubmitInput(), true);
|
||||
submit(change2.getChangeId(), failAfterRefUpdates,
|
||||
ResourceConflictException.class, "Failing after ref updates");
|
||||
RevCommit headAfterFailedSubmit = getRemoteHead();
|
||||
|
||||
// Bad: ref advanced but change wasn't updated.
|
||||
PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
|
||||
PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
|
||||
ChangeInfo info = gApi.changes().id(id2.get()).get();
|
||||
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
|
||||
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
|
||||
assertThat(getPatchSet(psId2)).isNull();
|
||||
|
||||
ObjectId rev2;
|
||||
try (Repository repo = repoManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
|
||||
assertThat(rev1).isNotNull();
|
||||
|
||||
rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
|
||||
assertThat(rev2).isNotNull();
|
||||
assertThat(rev2).isNotEqualTo(rev1);
|
||||
assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
|
||||
|
||||
assertThat(repo.exactRef("refs/heads/master").getObjectId())
|
||||
.isEqualTo(rev2);
|
||||
}
|
||||
|
||||
submit(change2.getChangeId());
|
||||
RevCommit headAfterSecondSubmit = getRemoteHead();
|
||||
assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
|
||||
|
||||
// Change status and patch set entities were updated, and branch tip stayed
|
||||
// the same.
|
||||
info = gApi.changes().id(id2.get()).get();
|
||||
assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
|
||||
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
|
||||
PatchSet ps2 = getPatchSet(psId2);
|
||||
assertThat(ps2).isNotNull();
|
||||
assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
|
||||
assertThat(Iterables.getLast(info.messages).message)
|
||||
.isEqualTo("Change has been successfully rebased as "
|
||||
+ rev2.name() + " by Administrator");
|
||||
|
||||
try (Repository repo = repoManager.openRepository(project)) {
|
||||
assertThat(repo.exactRef("refs/heads/master").getObjectId())
|
||||
.isEqualTo(rev2);
|
||||
}
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
protected RevCommit parse(ObjectId id) throws Exception {
|
||||
try (Repository repo = repoManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
RevCommit c = rw.parseCommit(id);
|
||||
rw.parseBody(c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitAfterReorderOfCommits() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
|
||||
// Create two commits and push.
|
||||
RevCommit c1 = commitBuilder()
|
||||
.add("a.txt", "1")
|
||||
.message("subject: 1")
|
||||
.create();
|
||||
RevCommit c2 = commitBuilder()
|
||||
.add("b.txt", "2")
|
||||
.message("subject: 2")
|
||||
.create();
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
|
||||
String id1 = getChangeId(testRepo, c1).get();
|
||||
String id2 = getChangeId(testRepo, c2).get();
|
||||
|
||||
// Swap the order of commits and push again.
|
||||
testRepo.reset("HEAD~2");
|
||||
testRepo.cherryPick(c2);
|
||||
testRepo.cherryPick(c1);
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
|
||||
approve(id1);
|
||||
approve(id2);
|
||||
submit(id1);
|
||||
RevCommit headAfterSubmit = getRemoteHead();
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterSubmit);
|
||||
assertChangeMergedEvents(id2, headAfterSubmit.name(),
|
||||
id1, headAfterSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitChangesAfterBranchOnSecond() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
|
||||
PushOneCommit.Result change = createChange();
|
||||
approve(change.getChangeId());
|
||||
|
||||
PushOneCommit.Result change2 = createChange();
|
||||
approve(change2.getChangeId());
|
||||
Project.NameKey project = change2.getChange().change().getProject();
|
||||
Branch.NameKey branch = new Branch.NameKey(project, "branch");
|
||||
createBranchWithRevision(branch, change2.getCommit().getName());
|
||||
gApi.changes().id(change2.getChangeId()).current().submit();
|
||||
assertMerged(change2.getChangeId());
|
||||
assertMerged(change.getChangeId());
|
||||
|
||||
RevCommit newHead = getRemoteHead();
|
||||
assertRefUpdatedEvents(initialHead, newHead);
|
||||
assertChangeMergedEvents(change.getChangeId(), newHead.name(),
|
||||
change2.getChangeId(), newHead.name());
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ include_defs('//gerrit-acceptance-tests/tests.defs')
|
||||
SUBMIT_UTIL_SRCS = [
|
||||
'AbstractSubmit.java',
|
||||
'AbstractSubmitByMerge.java',
|
||||
'AbstractSubmitByRebase.java',
|
||||
]
|
||||
|
||||
SUBMIT_TESTS = glob(['Submit*IT.java'])
|
||||
|
@ -0,0 +1,53 @@
|
||||
// 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.acceptance.rest.change;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.TestProjectInput;
|
||||
import com.google.gerrit.extensions.client.InheritableBoolean;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
|
||||
|
||||
@Override
|
||||
protected SubmitType getSubmitType() {
|
||||
return SubmitType.REBASE_ALWAYS;
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithPossibleFastForward() throws Exception {
|
||||
RevCommit oldHead = getRemoteHead();
|
||||
PushOneCommit.Result change = createChange();
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit head = getRemoteHead();
|
||||
assertThat(head.getId()).isNotEqualTo(change.getCommit());
|
||||
assertThat(head.getParent(0)).isEqualTo(oldHead);
|
||||
assertApproved(change.getChangeId());
|
||||
assertCurrentRevision(change.getChangeId(), 2, head);
|
||||
assertSubmitter(change.getChangeId(), 1);
|
||||
assertSubmitter(change.getChangeId(), 2);
|
||||
assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
|
||||
assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
|
||||
assertRefUpdatedEvents(oldHead, head);
|
||||
assertChangeMergedEvents(change.getChangeId(), head.name());
|
||||
}
|
||||
}
|
@ -15,32 +15,16 @@
|
||||
package com.google.gerrit.acceptance.rest.change;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.gerrit.acceptance.GitUtil.getChangeId;
|
||||
import static com.google.gerrit.acceptance.GitUtil.pushHead;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gerrit.acceptance.PushOneCommit;
|
||||
import com.google.gerrit.acceptance.TestProjectInput;
|
||||
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||
import com.google.gerrit.extensions.client.InheritableBoolean;
|
||||
import com.google.gerrit.extensions.client.SubmitType;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.change.Submit.TestSubmitInput;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
||||
public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
|
||||
|
||||
@Override
|
||||
protected SubmitType getSubmitType() {
|
||||
@ -65,143 +49,6 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
||||
assertChangeMergedEvents(change.getChangeId(), head.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithRebase() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
submit(change2.getChangeId());
|
||||
assertRebase(testRepo, false);
|
||||
RevCommit headAfterSecondSubmit = getRemoteHead();
|
||||
assertThat(headAfterSecondSubmit.getParent(0))
|
||||
.isEqualTo(headAfterFirstSubmit);
|
||||
assertApproved(change2.getChangeId());
|
||||
assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
|
||||
assertSubmitter(change2.getChangeId(), 1);
|
||||
assertSubmitter(change2.getChangeId(), 2);
|
||||
assertPersonEquals(admin.getIdent(),
|
||||
headAfterSecondSubmit.getAuthorIdent());
|
||||
assertPersonEquals(admin.getIdent(),
|
||||
headAfterSecondSubmit.getCommitterIdent());
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
|
||||
headAfterFirstSubmit, headAfterSecondSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitWithRebaseMultipleChanges() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change1 =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change1.getChangeId());
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
assertThat(headAfterFirstSubmit.name())
|
||||
.isEqualTo(change1.getCommit().name());
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
assertThat(change2.getCommit().getParent(0))
|
||||
.isNotEqualTo(change1.getCommit());
|
||||
PushOneCommit.Result change3 =
|
||||
createChange("Change 3", "c.txt", "third content");
|
||||
PushOneCommit.Result change4 =
|
||||
createChange("Change 4", "d.txt", "fourth content");
|
||||
approve(change2.getChangeId());
|
||||
approve(change3.getChangeId());
|
||||
submit(change4.getChangeId());
|
||||
|
||||
assertRebase(testRepo, false);
|
||||
assertApproved(change2.getChangeId());
|
||||
assertApproved(change3.getChangeId());
|
||||
assertApproved(change4.getChangeId());
|
||||
|
||||
RevCommit headAfterSecondSubmit = parse(getRemoteHead());
|
||||
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
|
||||
assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
|
||||
assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
|
||||
|
||||
RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
|
||||
assertThat(parent.getShortMessage()).isEqualTo("Change 3");
|
||||
assertThat(parent).isNotEqualTo(change3.getCommit());
|
||||
assertCurrentRevision(change3.getChangeId(), 2, parent);
|
||||
|
||||
RevCommit grandparent = parse(parent.getParent(0));
|
||||
assertThat(grandparent).isNotEqualTo(change2.getCommit());
|
||||
assertCurrentRevision(change2.getChangeId(), 2, grandparent);
|
||||
|
||||
RevCommit greatgrandparent = parse(grandparent.getParent(0));
|
||||
assertThat(greatgrandparent).isEqualTo(change1.getCommit());
|
||||
assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
|
||||
headAfterFirstSubmit, headAfterSecondSubmit);
|
||||
assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name(),
|
||||
change3.getChangeId(), headAfterSecondSubmit.name(),
|
||||
change4.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitWithRebaseMergeCommit() throws Exception {
|
||||
/*
|
||||
* (HEAD, origin/master, origin/HEAD) Merge changes X,Y
|
||||
|\
|
||||
| * Merge branch 'master' into origin/master
|
||||
| |\
|
||||
| | * SHA Added a
|
||||
| |/
|
||||
* | Before
|
||||
|/
|
||||
* Initial empty repository
|
||||
*/
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
|
||||
|
||||
PushOneCommit change2Push = pushFactory.create(db, admin.getIdent(), testRepo,
|
||||
"Merge to master", "m.txt", "");
|
||||
change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
|
||||
PushOneCommit.Result change2 = change2Push.to("refs/for/master");
|
||||
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
|
||||
|
||||
approve(change3.getChangeId());
|
||||
submit(change3.getChangeId());
|
||||
|
||||
approve(change1.getChangeId());
|
||||
approve(change2.getChangeId());
|
||||
submit(change2.getChangeId());
|
||||
|
||||
RevCommit newHead = getRemoteHead();
|
||||
assertThat(newHead.getParentCount()).isEqualTo(2);
|
||||
|
||||
RevCommit headParent1 = parse(newHead.getParent(0).getId());
|
||||
RevCommit headParent2 = parse(newHead.getParent(1).getId());
|
||||
|
||||
assertThat(headParent1.getId()).isEqualTo(change3.getCommit().getId());
|
||||
assertThat(headParent1.getParentCount()).isEqualTo(1);
|
||||
assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
|
||||
|
||||
assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
|
||||
assertThat(headParent2.getParentCount()).isEqualTo(2);
|
||||
|
||||
RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
|
||||
RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
|
||||
|
||||
assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
|
||||
assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithContentMerge() throws Exception {
|
||||
@ -235,160 +82,4 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
||||
change2.getChangeId(), headAfterSecondSubmit.name(),
|
||||
change3.getChangeId(), headAfterThirdSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||
public void submitWithContentMerge_Conflict() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "a.txt", "other content");
|
||||
submitWithConflict(change2.getChangeId(),
|
||||
"Cannot rebase " + change2.getCommit().name()
|
||||
+ ": The change could not be rebased due to a conflict during merge.");
|
||||
RevCommit head = getRemoteHead();
|
||||
assertThat(head).isEqualTo(headAfterFirstSubmit);
|
||||
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
|
||||
assertNoSubmitter(change2.getChangeId(), 1);
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repairChangeStateAfterFailure() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
PushOneCommit.Result change =
|
||||
createChange("Change 1", "a.txt", "content");
|
||||
submit(change.getChangeId());
|
||||
|
||||
RevCommit headAfterFirstSubmit = getRemoteHead();
|
||||
testRepo.reset(initialHead);
|
||||
PushOneCommit.Result change2 =
|
||||
createChange("Change 2", "b.txt", "other content");
|
||||
Change.Id id2 = change2.getChange().getId();
|
||||
SubmitInput failAfterRefUpdates =
|
||||
new TestSubmitInput(new SubmitInput(), true);
|
||||
submit(change2.getChangeId(), failAfterRefUpdates,
|
||||
ResourceConflictException.class, "Failing after ref updates");
|
||||
RevCommit headAfterFailedSubmit = getRemoteHead();
|
||||
|
||||
// Bad: ref advanced but change wasn't updated.
|
||||
PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
|
||||
PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
|
||||
ChangeInfo info = gApi.changes().id(id2.get()).get();
|
||||
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
|
||||
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
|
||||
assertThat(getPatchSet(psId2)).isNull();
|
||||
|
||||
ObjectId rev2;
|
||||
try (Repository repo = repoManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
|
||||
assertThat(rev1).isNotNull();
|
||||
|
||||
rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
|
||||
assertThat(rev2).isNotNull();
|
||||
assertThat(rev2).isNotEqualTo(rev1);
|
||||
assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
|
||||
|
||||
assertThat(repo.exactRef("refs/heads/master").getObjectId())
|
||||
.isEqualTo(rev2);
|
||||
}
|
||||
|
||||
submit(change2.getChangeId());
|
||||
RevCommit headAfterSecondSubmit = getRemoteHead();
|
||||
assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
|
||||
|
||||
// Change status and patch set entities were updated, and branch tip stayed
|
||||
// the same.
|
||||
info = gApi.changes().id(id2.get()).get();
|
||||
assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
|
||||
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
|
||||
PatchSet ps2 = getPatchSet(psId2);
|
||||
assertThat(ps2).isNotNull();
|
||||
assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
|
||||
assertThat(Iterables.getLast(info.messages).message)
|
||||
.isEqualTo("Change has been successfully rebased as "
|
||||
+ rev2.name() + " by Administrator");
|
||||
|
||||
try (Repository repo = repoManager.openRepository(project)) {
|
||||
assertThat(repo.exactRef("refs/heads/master").getObjectId())
|
||||
.isEqualTo(rev2);
|
||||
}
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
|
||||
change2.getChangeId(), headAfterSecondSubmit.name());
|
||||
}
|
||||
|
||||
private RevCommit parse(ObjectId id) throws Exception {
|
||||
try (Repository repo = repoManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
RevCommit c = rw.parseCommit(id);
|
||||
rw.parseBody(c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitAfterReorderOfCommits() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
|
||||
// Create two commits and push.
|
||||
RevCommit c1 = commitBuilder()
|
||||
.add("a.txt", "1")
|
||||
.message("subject: 1")
|
||||
.create();
|
||||
RevCommit c2 = commitBuilder()
|
||||
.add("b.txt", "2")
|
||||
.message("subject: 2")
|
||||
.create();
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
|
||||
String id1 = getChangeId(testRepo, c1).get();
|
||||
String id2 = getChangeId(testRepo, c2).get();
|
||||
|
||||
// Swap the order of commits and push again.
|
||||
testRepo.reset("HEAD~2");
|
||||
testRepo.cherryPick(c2);
|
||||
testRepo.cherryPick(c1);
|
||||
pushHead(testRepo, "refs/for/master", false);
|
||||
|
||||
approve(id1);
|
||||
approve(id2);
|
||||
submit(id1);
|
||||
RevCommit headAfterSubmit = getRemoteHead();
|
||||
|
||||
assertRefUpdatedEvents(initialHead, headAfterSubmit);
|
||||
assertChangeMergedEvents(id2, headAfterSubmit.name(),
|
||||
id1, headAfterSubmit.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submitChangesAfterBranchOnSecond() throws Exception {
|
||||
RevCommit initialHead = getRemoteHead();
|
||||
|
||||
PushOneCommit.Result change = createChange();
|
||||
approve(change.getChangeId());
|
||||
|
||||
PushOneCommit.Result change2 = createChange();
|
||||
approve(change2.getChangeId());
|
||||
Project.NameKey project = change2.getChange().change().getProject();
|
||||
Branch.NameKey branch = new Branch.NameKey(project, "branch");
|
||||
createBranchWithRevision(branch, change2.getCommit().getName());
|
||||
gApi.changes().id(change2.getChangeId()).current().submit();
|
||||
assertMerged(change2.getChangeId());
|
||||
assertMerged(change.getChangeId());
|
||||
|
||||
RevCommit newHead = getRemoteHead();
|
||||
assertRefUpdatedEvents(initialHead, newHead);
|
||||
assertChangeMergedEvents(change.getChangeId(), newHead.name(),
|
||||
change2.getChangeId(), newHead.name());
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ public enum SubmitType {
|
||||
FAST_FORWARD_ONLY,
|
||||
MERGE_IF_NECESSARY,
|
||||
REBASE_IF_NECESSARY,
|
||||
REBASE_ALWAYS,
|
||||
MERGE_ALWAYS,
|
||||
CHERRY_PICK
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public interface AdminConstants extends Constants {
|
||||
String projectSubmitType_MERGE_ALWAYS();
|
||||
String projectSubmitType_MERGE_IF_NECESSARY();
|
||||
String projectSubmitType_REBASE_IF_NECESSARY();
|
||||
String projectSubmitType_REBASE_ALWAYS();
|
||||
String projectSubmitType_CHERRY_PICK();
|
||||
|
||||
String headingProjectState();
|
||||
|
@ -54,6 +54,7 @@ headingAuditLog = Audit Log
|
||||
headingProjectSubmitType = Submit Type
|
||||
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
|
||||
projectSubmitType_MERGE_IF_NECESSARY = Merge if Necessary
|
||||
projectSubmitType_REBASE_ALWAYS = Rebase Always
|
||||
projectSubmitType_REBASE_IF_NECESSARY = Rebase if Necessary
|
||||
projectSubmitType_MERGE_ALWAYS = Always Merge
|
||||
projectSubmitType_CHERRY_PICK = Cherry Pick
|
||||
|
@ -43,6 +43,8 @@ public class Util {
|
||||
return C.projectSubmitType_MERGE_IF_NECESSARY();
|
||||
case REBASE_IF_NECESSARY:
|
||||
return C.projectSubmitType_REBASE_IF_NECESSARY();
|
||||
case REBASE_ALWAYS:
|
||||
return C.projectSubmitType_REBASE_ALWAYS();
|
||||
case MERGE_ALWAYS:
|
||||
return C.projectSubmitType_MERGE_ALWAYS();
|
||||
case CHERRY_PICK:
|
||||
|
@ -64,12 +64,15 @@ public class MergeabilityCacheImpl implements MergeabilityCache {
|
||||
|
||||
private static final String CACHE_NAME = "mergeability";
|
||||
|
||||
public static final BiMap<SubmitType, Character> SUBMIT_TYPES = ImmutableBiMap.of(
|
||||
SubmitType.FAST_FORWARD_ONLY, 'F',
|
||||
SubmitType.MERGE_IF_NECESSARY, 'M',
|
||||
SubmitType.REBASE_IF_NECESSARY, 'R',
|
||||
SubmitType.MERGE_ALWAYS, 'A',
|
||||
SubmitType.CHERRY_PICK, 'C');
|
||||
public static final BiMap<SubmitType, Character> SUBMIT_TYPES =
|
||||
new ImmutableBiMap.Builder<SubmitType, Character>()
|
||||
.put(SubmitType.FAST_FORWARD_ONLY, 'F')
|
||||
.put(SubmitType.MERGE_IF_NECESSARY, 'M')
|
||||
.put(SubmitType.REBASE_ALWAYS, 'P')
|
||||
.put(SubmitType.REBASE_IF_NECESSARY, 'R')
|
||||
.put(SubmitType.MERGE_ALWAYS, 'A')
|
||||
.put(SubmitType.CHERRY_PICK, 'C')
|
||||
.build();
|
||||
|
||||
static {
|
||||
checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
|
||||
|
@ -68,6 +68,7 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
||||
private CommitValidators.Policy validate;
|
||||
private boolean forceContentMerge;
|
||||
private boolean copyApprovals = true;
|
||||
private boolean postMessage = true;
|
||||
|
||||
private RevCommit rebasedCommit;
|
||||
private PatchSet.Id rebasedPatchSetId;
|
||||
@ -117,6 +118,11 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
||||
return this;
|
||||
}
|
||||
|
||||
public RebaseChangeOp setPostMessage(boolean postMessage) {
|
||||
this.postMessage = postMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) throws MergeConflictException,
|
||||
InvalidChangeOperationException, RestApiException, IOException,
|
||||
@ -153,10 +159,11 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
||||
.setDraft(originalPatchSet.isDraft())
|
||||
.setNotify(NotifyHandling.NONE)
|
||||
.setFireRevisionCreated(fireRevisionCreated)
|
||||
.setCopyApprovals(copyApprovals)
|
||||
.setMessage(
|
||||
"Patch Set " + rebasedPatchSetId.get()
|
||||
.setCopyApprovals(copyApprovals);
|
||||
if (postMessage) {
|
||||
patchSetInserter.setMessage("Patch Set " + rebasedPatchSetId.get()
|
||||
+ ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
|
||||
}
|
||||
|
||||
if (base != null) {
|
||||
patchSetInserter.setGroups(base.patchSet().getGroups());
|
||||
|
@ -0,0 +1,22 @@
|
||||
// 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;
|
||||
|
||||
public class RebaseAlways extends RebaseSubmitStrategy {
|
||||
|
||||
RebaseAlways(SubmitStrategy.Arguments args) {
|
||||
super(args, true);
|
||||
}
|
||||
}
|
@ -14,207 +14,9 @@
|
||||
|
||||
package com.google.gerrit.server.git.strategy;
|
||||
|
||||
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
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.MergeTip;
|
||||
import com.google.gerrit.server.git.RebaseSorter;
|
||||
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.List;
|
||||
|
||||
public class RebaseIfNecessary extends SubmitStrategy {
|
||||
public class RebaseIfNecessary extends RebaseSubmitStrategy {
|
||||
|
||||
RebaseIfNecessary(SubmitStrategy.Arguments args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted = sort(toMerge);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
boolean first = true;
|
||||
|
||||
for (CodeReviewCommit c : sorted) {
|
||||
if (c.getParentCount() > 1) {
|
||||
// Since there is a merge commit, sort and prune again using
|
||||
// MERGE_IF_NECESSARY semantics to avoid creating duplicate
|
||||
// commits.
|
||||
//
|
||||
sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
if (first && args.mergeTip.getInitialTip() == null) {
|
||||
ops.add(new FastForwardOp(args, 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));
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
private class RebaseRootOp extends SubmitStrategyOp {
|
||||
private RebaseRootOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 SubmitStrategyOp {
|
||||
private RebaseChangeOp rebaseOp;
|
||||
private CodeReviewCommit newCommit;
|
||||
|
||||
private RebaseOneOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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, args.mergeTip.getCurrentTip(),
|
||||
args.rw, toMerge)) {
|
||||
args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
|
||||
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
|
||||
acceptMergeTip(args.mergeTip);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stale read of patch set is ok; see comments in RebaseChangeOp.
|
||||
PatchSet origPs = args.psUtil.get(
|
||||
ctx.getDb(), toMerge.getControl().getNotes(), toMerge.getPatchsetId());
|
||||
rebaseOp = args.rebaseFactory.create(
|
||||
toMerge.getControl(), origPs, args.mergeTip.getCurrentTip().name())
|
||||
.setFireRevisionCreated(false)
|
||||
// Bypass approval copier since SubmitStrategyOp copy all approvals
|
||||
// later anyway.
|
||||
.setCopyApprovals(false)
|
||||
.setValidatePolicy(CommitValidators.Policy.NONE);
|
||||
try {
|
||||
rebaseOp.updateRepo(ctx);
|
||||
} catch (MergeConflictException | NoSuchChangeException e) {
|
||||
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
|
||||
throw new IntegrationException(
|
||||
"Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
|
||||
}
|
||||
newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
|
||||
newCommit = amendGitlink(newCommit);
|
||||
newCommit.copyFrom(toMerge);
|
||||
newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
|
||||
newCommit.setPatchsetId(rebaseOp.getPatchSetId());
|
||||
args.mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(args.mergeTip.getCurrentTip());
|
||||
acceptMergeTip(args.mergeTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchSet updateChangeImpl(ChangeContext ctx)
|
||||
throws NoSuchChangeException, ResourceConflictException,
|
||||
OrmException, IOException {
|
||||
if (rebaseOp == null) {
|
||||
// Took the fast-forward option, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
rebaseOp.updateChange(ctx);
|
||||
ctx.getChange().setCurrentPatchSet(
|
||||
args.patchSetInfoFactory.get(
|
||||
args.rw, newCommit, rebaseOp.getPatchSetId()));
|
||||
newCommit.setControl(ctx.getControl());
|
||||
return rebaseOp.getPatchSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdateImpl(Context ctx) throws OrmException {
|
||||
if (rebaseOp != null) {
|
||||
rebaseOp.postUpdate(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RebaseMultipleParentsOp extends SubmitStrategyOp {
|
||||
private RebaseMultipleParentsOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseIfNecessary.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) &&
|
||||
!args.submoduleOp.hasSubscription(args.destBranch)) {
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
} else {
|
||||
CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit(
|
||||
args.serverIdent, args.serverIdent, args.repo, args.rw,
|
||||
args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
|
||||
mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
|
||||
}
|
||||
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
|
||||
mergeTip.getCurrentTip(), args.alreadyAccepted);
|
||||
acceptMergeTip(mergeTip);
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptMergeTip(MergeTip mergeTip) {
|
||||
args.alreadyAccepted.add(mergeTip.getCurrentTip());
|
||||
}
|
||||
|
||||
private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
|
||||
throws IntegrationException {
|
||||
try {
|
||||
return new RebaseSorter(
|
||||
args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
|
||||
} catch (IOException e) {
|
||||
throw new IntegrationException("Commit sorting failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean dryRun(SubmitDryRun.Arguments args,
|
||||
CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
|
||||
throws IntegrationException {
|
||||
// Test for merge instead of cherry pick to avoid false negatives
|
||||
// on commit chains.
|
||||
return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
|
||||
&& args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip,
|
||||
toMerge);
|
||||
super(args, false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,289 @@
|
||||
// Copyright (C) 2012 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.Preconditions.checkState;
|
||||
import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
|
||||
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.change.RebaseChangeOp;
|
||||
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.MergeIdenticalTreeException;
|
||||
import com.google.gerrit.server.git.MergeTip;
|
||||
import com.google.gerrit.server.git.RebaseSorter;
|
||||
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 org.eclipse.jgit.lib.ObjectId;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This strategy covers RebaseAlways and RebaseIfNecessary ones.
|
||||
*/
|
||||
public class RebaseSubmitStrategy extends SubmitStrategy {
|
||||
private final boolean rebaseAlways;
|
||||
|
||||
RebaseSubmitStrategy(SubmitStrategy.Arguments args, boolean rebaseAlways) {
|
||||
super(args);
|
||||
this.rebaseAlways = rebaseAlways;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SubmitStrategyOp> buildOps(
|
||||
Collection<CodeReviewCommit> toMerge) throws IntegrationException {
|
||||
List<CodeReviewCommit> sorted = sort(toMerge);
|
||||
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
|
||||
boolean first = true;
|
||||
|
||||
for (CodeReviewCommit c : sorted) {
|
||||
if (c.getParentCount() > 1) {
|
||||
// Since there is a merge commit, sort and prune again using
|
||||
// MERGE_IF_NECESSARY semantics to avoid creating duplicate
|
||||
// commits.
|
||||
//
|
||||
sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!sorted.isEmpty()) {
|
||||
CodeReviewCommit n = sorted.remove(0);
|
||||
if (first && args.mergeTip.getInitialTip() == null) {
|
||||
// TODO(tandrii): Cherry-Pick strategy does this too, but it's wrong
|
||||
// and can be fixed.
|
||||
ops.add(new FastForwardOp(args, 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));
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
private class RebaseRootOp extends SubmitStrategyOp {
|
||||
private RebaseRootOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseSubmitStrategy.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 SubmitStrategyOp {
|
||||
private RebaseChangeOp rebaseOp;
|
||||
private CodeReviewCommit newCommit;
|
||||
private PatchSet.Id newPatchSetId;
|
||||
|
||||
private RebaseOneOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseSubmitStrategy.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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, args.mergeTip.getCurrentTip(),
|
||||
args.rw, toMerge)) {
|
||||
if (!rebaseAlways){
|
||||
args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
|
||||
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
|
||||
acceptMergeTip(args.mergeTip);
|
||||
return;
|
||||
}
|
||||
// RebaseAlways means we modify commit message.
|
||||
args.rw.parseBody(toMerge);
|
||||
newPatchSetId = ChangeUtil.nextPatchSetId(
|
||||
args.repo, toMerge.change().currentPatchSetId());
|
||||
// TODO(tandrii): add extension point to customize this commit message.
|
||||
String cherryPickCmtMsg =
|
||||
args.mergeUtil.createCherryPickCommitMessage(toMerge);
|
||||
|
||||
PersonIdent committer = args.caller.newCommitterIdent(ctx.getWhen(),
|
||||
args.serverIdent.getTimeZone());
|
||||
try {
|
||||
newCommit = args.mergeUtil.createCherryPickFromCommit(args.repo,
|
||||
args.inserter, args.mergeTip.getCurrentTip(), toMerge, committer,
|
||||
cherryPickCmtMsg, args.rw);
|
||||
} catch (MergeConflictException mce) {
|
||||
// Unlike in Cherry-pick case, this should never happen.
|
||||
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
|
||||
throw new IllegalStateException(
|
||||
"MergeConflictException on message edit must not happen");
|
||||
} catch (MergeIdenticalTreeException mie) {
|
||||
toMerge.setStatusCode(SKIPPED_IDENTICAL_TREE);
|
||||
return;
|
||||
}
|
||||
ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), newCommit,
|
||||
newPatchSetId.toRefName()));
|
||||
} else {
|
||||
// Stale read of patch set is ok; see comments in RebaseChangeOp.
|
||||
PatchSet origPs = args.psUtil.get(ctx.getDb(),
|
||||
toMerge.getControl().getNotes(), toMerge.getPatchsetId());
|
||||
// TODO(tandrii): add extension point to customize commit message while
|
||||
// rebasing.
|
||||
rebaseOp = args.rebaseFactory.create(
|
||||
toMerge.getControl(), origPs, args.mergeTip.getCurrentTip().name())
|
||||
.setFireRevisionCreated(false)
|
||||
// Bypass approval copier since SubmitStrategyOp copy all approvals
|
||||
// later anyway.
|
||||
.setCopyApprovals(false)
|
||||
.setValidatePolicy(CommitValidators.Policy.NONE)
|
||||
// Do not post message after inserting new patchset because there
|
||||
// will be one about change being merged already.
|
||||
.setPostMessage(false);
|
||||
try {
|
||||
rebaseOp.updateRepo(ctx);
|
||||
} catch (MergeConflictException | NoSuchChangeException e) {
|
||||
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
|
||||
throw new IntegrationException(
|
||||
"Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
|
||||
}
|
||||
newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
|
||||
newPatchSetId = rebaseOp.getPatchSetId();
|
||||
}
|
||||
newCommit = amendGitlink(newCommit);
|
||||
newCommit.copyFrom(toMerge);
|
||||
newCommit.setPatchsetId(newPatchSetId);
|
||||
newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
|
||||
args.mergeTip.moveTipTo(newCommit, newCommit);
|
||||
args.commits.put(args.mergeTip.getCurrentTip());
|
||||
acceptMergeTip(args.mergeTip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchSet updateChangeImpl(ChangeContext ctx)
|
||||
throws NoSuchChangeException, ResourceConflictException,
|
||||
OrmException, IOException {
|
||||
if (newCommit == null) {
|
||||
checkState(!rebaseAlways, "RebaseAlways must never fast forward");
|
||||
// Took the fast-forward option, nothing to do.
|
||||
return null;
|
||||
}
|
||||
PatchSet newPs;
|
||||
if (rebaseOp != null) {
|
||||
rebaseOp.updateChange(ctx);
|
||||
newPs = rebaseOp.getPatchSet();
|
||||
} else {
|
||||
// CherryPick
|
||||
PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
|
||||
newPs = args.psUtil.insert(ctx.getDb(), ctx.getRevWalk(),
|
||||
ctx.getUpdate(newPatchSetId), newPatchSetId, newCommit, false,
|
||||
prevPs != null ? prevPs.getGroups() : ImmutableList.<String> of(),
|
||||
null);
|
||||
}
|
||||
ctx.getChange().setCurrentPatchSet(args.patchSetInfoFactory
|
||||
.get(ctx.getRevWalk(), newCommit, newPatchSetId));
|
||||
newCommit.setControl(ctx.getControl());
|
||||
return newPs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdateImpl(Context ctx) throws OrmException {
|
||||
if (rebaseOp != null) {
|
||||
rebaseOp.postUpdate(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RebaseMultipleParentsOp extends SubmitStrategyOp {
|
||||
private RebaseMultipleParentsOp(CodeReviewCommit toMerge) {
|
||||
super(RebaseSubmitStrategy.this.args, toMerge);
|
||||
}
|
||||
|
||||
@Override
|
||||
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.
|
||||
// TODO(tandrii): this is not in spirit of RebaseAlways strategy because
|
||||
// the commit messages can not be modified in the process. It's also
|
||||
// possible to implement rebasing of merge commits. E.g., the Cherry Pick
|
||||
// REST endpoint already supports cherry-picking of merge commits.
|
||||
// For now, users of RebaseAlways strategy for whom changed commit footers
|
||||
// are important would be well advised to prohibit uploading patches with
|
||||
// merge commits.
|
||||
MergeTip mergeTip = args.mergeTip;
|
||||
if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) &&
|
||||
!args.submoduleOp.hasSubscription(args.destBranch)) {
|
||||
mergeTip.moveTipTo(toMerge, toMerge);
|
||||
} else {
|
||||
CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit(
|
||||
args.serverIdent, args.serverIdent, args.repo, args.rw,
|
||||
args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
|
||||
mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
|
||||
}
|
||||
args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
|
||||
mergeTip.getCurrentTip(), args.alreadyAccepted);
|
||||
acceptMergeTip(mergeTip);
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptMergeTip(MergeTip mergeTip) {
|
||||
args.alreadyAccepted.add(mergeTip.getCurrentTip());
|
||||
}
|
||||
|
||||
private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
|
||||
throws IntegrationException {
|
||||
try {
|
||||
return new RebaseSorter(
|
||||
args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
|
||||
} catch (IOException e) {
|
||||
throw new IntegrationException("Commit sorting failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean dryRun(SubmitDryRun.Arguments args,
|
||||
CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
|
||||
throws IntegrationException {
|
||||
// Test for merge instead of cherry pick to avoid false negatives
|
||||
// on commit chains.
|
||||
return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
|
||||
&& args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip,
|
||||
toMerge);
|
||||
}
|
||||
}
|
@ -122,6 +122,8 @@ public class SubmitDryRun {
|
||||
return MergeIfNecessary.dryRun(args, tipCommit, toMergeCommit);
|
||||
case REBASE_IF_NECESSARY:
|
||||
return RebaseIfNecessary.dryRun(args, tipCommit, toMergeCommit);
|
||||
case REBASE_ALWAYS:
|
||||
return RebaseAlways.dryRun(args, tipCommit, toMergeCommit);
|
||||
default:
|
||||
String errorMsg = "No submit strategy for: " + submitType;
|
||||
log.error(errorMsg);
|
||||
|
@ -71,6 +71,8 @@ public class SubmitStrategyFactory {
|
||||
return new MergeIfNecessary(args);
|
||||
case REBASE_IF_NECESSARY:
|
||||
return new RebaseIfNecessary(args);
|
||||
case REBASE_ALWAYS:
|
||||
return new RebaseAlways(args);
|
||||
default:
|
||||
String errorMsg = "No submit strategy for: " + submitType;
|
||||
log.error(errorMsg);
|
||||
|
@ -432,6 +432,7 @@ abstract class SubmitStrategyOp extends BatchUpdate.Op {
|
||||
case CHERRY_PICK:
|
||||
return message(ctx, commit, CommitMergeStatus.CLEAN_PICK);
|
||||
case REBASE_IF_NECESSARY:
|
||||
case REBASE_ALWAYS:
|
||||
return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
|
||||
default:
|
||||
throw new IllegalStateException("unexpected submit type "
|
||||
|
@ -58,6 +58,10 @@ public class RepositoryConfigTest {
|
||||
configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY);
|
||||
assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
|
||||
.isEqualTo(SubmitType.REBASE_IF_NECESSARY);
|
||||
|
||||
configureDefaultSubmitType("*", SubmitType.REBASE_ALWAYS);
|
||||
assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
|
||||
.isEqualTo(SubmitType.REBASE_ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user