Merge "Add new submit strategy "Rebase Always"."
This commit is contained in:
@@ -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
|
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.
|
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]]
|
[[content_merge]]
|
||||||
If `Allow content merges` is enabled, Gerrit will try
|
If `Allow content merges` is enabled, Gerrit will try
|
||||||
to do a content merge when a path conflict occurs.
|
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_ALWAYS;
|
||||||
import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
|
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_IF_NECESSARY;
|
||||||
|
import static com.google.gerrit.extensions.client.SubmitType.REBASE_ALWAYS;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@@ -123,6 +124,10 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
|||||||
+ "gerrit:commit_message(M),"
|
+ "gerrit:commit_message(M),"
|
||||||
+ "regex_matches('.*REBASE_IF_NECESSARY.*', M),"
|
+ "regex_matches('.*REBASE_IF_NECESSARY.*', M),"
|
||||||
+ "!.\n"
|
+ "!.\n"
|
||||||
|
+ "submit_type(rebase_always) :-"
|
||||||
|
+ "gerrit:commit_message(M),"
|
||||||
|
+ "regex_matches('.*REBASE_ALWAYS.*', M),"
|
||||||
|
+ "!.\n"
|
||||||
+ "submit_type(merge_always) :-"
|
+ "submit_type(merge_always) :-"
|
||||||
+ "gerrit:commit_message(M),"
|
+ "gerrit:commit_message(M),"
|
||||||
+ "regex_matches('.*MERGE_ALWAYS.*', 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 r2 = createChange("master", "FAST_FORWARD_ONLY 2");
|
||||||
PushOneCommit.Result r3 = createChange("master", "MERGE_IF_NECESSARY 3");
|
PushOneCommit.Result r3 = createChange("master", "MERGE_IF_NECESSARY 3");
|
||||||
PushOneCommit.Result r4 = createChange("master", "REBASE_IF_NECESSARY 4");
|
PushOneCommit.Result r4 = createChange("master", "REBASE_IF_NECESSARY 4");
|
||||||
PushOneCommit.Result r5 = createChange("master", "MERGE_ALWAYS 5");
|
PushOneCommit.Result r5 = createChange("master", "REBASE_ALWAYS 5");
|
||||||
PushOneCommit.Result r6 = createChange("master", "CHERRY_PICK 6");
|
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, r1.getChangeId());
|
||||||
assertSubmitType(MERGE_IF_NECESSARY, r2.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, r4.getChangeId());
|
||||||
assertSubmitType(MERGE_IF_NECESSARY, r5.getChangeId());
|
assertSubmitType(MERGE_IF_NECESSARY, r5.getChangeId());
|
||||||
assertSubmitType(MERGE_IF_NECESSARY, r6.getChangeId());
|
assertSubmitType(MERGE_IF_NECESSARY, r6.getChangeId());
|
||||||
|
assertSubmitType(MERGE_IF_NECESSARY, r7.getChangeId());
|
||||||
|
|
||||||
setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
|
setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
|
||||||
|
|
||||||
@@ -173,8 +180,9 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
|
|||||||
assertSubmitType(FAST_FORWARD_ONLY, r2.getChangeId());
|
assertSubmitType(FAST_FORWARD_ONLY, r2.getChangeId());
|
||||||
assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
|
assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
|
||||||
assertSubmitType(REBASE_IF_NECESSARY, r4.getChangeId());
|
assertSubmitType(REBASE_IF_NECESSARY, r4.getChangeId());
|
||||||
assertSubmitType(MERGE_ALWAYS, r5.getChangeId());
|
assertSubmitType(REBASE_ALWAYS, r5.getChangeId());
|
||||||
assertSubmitType(CHERRY_PICK, r6.getChangeId());
|
assertSubmitType(MERGE_ALWAYS, r6.getChangeId());
|
||||||
|
assertSubmitType(CHERRY_PICK, r7.getChangeId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -61,14 +61,21 @@ public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
|
|||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Config submitByCherryPickConifg() {
|
protected static Config submitByCherryPickConfig() {
|
||||||
Config cfg = new Config();
|
Config cfg = new Config();
|
||||||
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
||||||
cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
|
cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
|
||||||
return cfg;
|
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();
|
Config cfg = new Config();
|
||||||
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
cfg.setBoolean("change", null, "submitWholeTopic", true);
|
||||||
cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
|
cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
|
||||||
|
@@ -53,12 +53,17 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
|
|||||||
|
|
||||||
@ConfigSuite.Config
|
@ConfigSuite.Config
|
||||||
public static Config cherryPick() {
|
public static Config cherryPick() {
|
||||||
return submitByCherryPickConifg();
|
return submitByCherryPickConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigSuite.Config
|
@ConfigSuite.Config
|
||||||
public static Config rebase() {
|
public static Config rebaseAlways() {
|
||||||
return submitByRebaseConifg();
|
return submitByRebaseAlwaysConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigSuite.Config
|
||||||
|
public static Config rebaseIfNecessary() {
|
||||||
|
return submitByRebaseIfNecessaryConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -129,10 +134,11 @@ public class SubmoduleSubscriptionsWholeTopicMergeIT
|
|||||||
assertThat(preview).containsKey(
|
assertThat(preview).containsKey(
|
||||||
new Branch.NameKey(p2, "refs/heads/master"));
|
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:
|
// each change is updated and the respective target branch is updated:
|
||||||
assertThat(preview).hasSize(5);
|
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
|
// 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.
|
// rebasing, or those two stay as is and the first is rebased.
|
||||||
// add in 2 master branches, expect 3 or 4:
|
// add in 2 master branches, expect 3 or 4:
|
||||||
|
@@ -146,7 +146,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
|||||||
Map<Branch.NameKey, RevTree> actual =
|
Map<Branch.NameKey, RevTree> actual =
|
||||||
fetchFromBundles(request);
|
fetchFromBundles(request);
|
||||||
|
|
||||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
if ((getSubmitType() == SubmitType.CHERRY_PICK)
|
||||||
|
|| (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
|
||||||
// The change is updated as well:
|
// The change is updated as well:
|
||||||
assertThat(actual).hasSize(2);
|
assertThat(actual).hasSize(2);
|
||||||
} else {
|
} else {
|
||||||
@@ -202,7 +203,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
|||||||
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
||||||
assertChangeMergedEvents(change.getChangeId(),
|
assertChangeMergedEvents(change.getChangeId(),
|
||||||
headAfterFirstSubmit.name());
|
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()
|
String change2hash = change2.getChange().currentPatchSet()
|
||||||
.getRevision().get();
|
.getRevision().get();
|
||||||
assertThat(msg).isEqualTo(
|
assertThat(msg).isEqualTo(
|
||||||
@@ -252,8 +254,14 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
|||||||
|
|
||||||
assertThat(actual).containsKey(
|
assertThat(actual).containsKey(
|
||||||
new Branch.NameKey(project, "refs/heads/master"));
|
new Branch.NameKey(project, "refs/heads/master"));
|
||||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
if (getSubmitType() == SubmitType.CHERRY_PICK){
|
||||||
|
// CherryPick ignores dependencies, thus only change and destination
|
||||||
|
// branch refs are modified.
|
||||||
assertThat(actual).hasSize(2);
|
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 {
|
} else {
|
||||||
assertThat(actual).hasSize(1);
|
assertThat(actual).hasSize(1);
|
||||||
}
|
}
|
||||||
@@ -409,6 +417,8 @@ public abstract class AbstractSubmit extends AbstractDaemonTest {
|
|||||||
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
if (getSubmitType() == SubmitType.CHERRY_PICK) {
|
||||||
assertThat(last).startsWith(
|
assertThat(last).startsWith(
|
||||||
"Change has been successfully cherry-picked as ");
|
"Change has been successfully cherry-picked as ");
|
||||||
|
} else if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
||||||
|
assertThat(last).startsWith("Change has been successfully rebased as");
|
||||||
} else {
|
} else {
|
||||||
assertThat(last).isEqualTo(
|
assertThat(last).isEqualTo(
|
||||||
"Change has been successfully merged by Administrator");
|
"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 = [
|
SUBMIT_UTIL_SRCS = [
|
||||||
'AbstractSubmit.java',
|
'AbstractSubmit.java',
|
||||||
'AbstractSubmitByMerge.java',
|
'AbstractSubmitByMerge.java',
|
||||||
|
'AbstractSubmitByRebase.java',
|
||||||
]
|
]
|
||||||
|
|
||||||
SUBMIT_TESTS = glob(['Submit*IT.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;
|
package com.google.gerrit.acceptance.rest.change;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.PushOneCommit;
|
||||||
import com.google.gerrit.acceptance.TestProjectInput;
|
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.InheritableBoolean;
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
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.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SubmitType getSubmitType() {
|
protected SubmitType getSubmitType() {
|
||||||
@@ -65,143 +49,6 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
|||||||
assertChangeMergedEvents(change.getChangeId(), head.name());
|
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
|
@Test
|
||||||
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
||||||
public void submitWithContentMerge() throws Exception {
|
public void submitWithContentMerge() throws Exception {
|
||||||
@@ -235,160 +82,4 @@ public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
|
|||||||
change2.getChangeId(), headAfterSecondSubmit.name(),
|
change2.getChangeId(), headAfterSecondSubmit.name(),
|
||||||
change3.getChangeId(), headAfterThirdSubmit.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,
|
FAST_FORWARD_ONLY,
|
||||||
MERGE_IF_NECESSARY,
|
MERGE_IF_NECESSARY,
|
||||||
REBASE_IF_NECESSARY,
|
REBASE_IF_NECESSARY,
|
||||||
|
REBASE_ALWAYS,
|
||||||
MERGE_ALWAYS,
|
MERGE_ALWAYS,
|
||||||
CHERRY_PICK
|
CHERRY_PICK
|
||||||
}
|
}
|
||||||
|
@@ -77,6 +77,7 @@ public interface AdminConstants extends Constants {
|
|||||||
String projectSubmitType_MERGE_ALWAYS();
|
String projectSubmitType_MERGE_ALWAYS();
|
||||||
String projectSubmitType_MERGE_IF_NECESSARY();
|
String projectSubmitType_MERGE_IF_NECESSARY();
|
||||||
String projectSubmitType_REBASE_IF_NECESSARY();
|
String projectSubmitType_REBASE_IF_NECESSARY();
|
||||||
|
String projectSubmitType_REBASE_ALWAYS();
|
||||||
String projectSubmitType_CHERRY_PICK();
|
String projectSubmitType_CHERRY_PICK();
|
||||||
|
|
||||||
String headingProjectState();
|
String headingProjectState();
|
||||||
|
@@ -54,6 +54,7 @@ headingAuditLog = Audit Log
|
|||||||
headingProjectSubmitType = Submit Type
|
headingProjectSubmitType = Submit Type
|
||||||
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
|
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
|
||||||
projectSubmitType_MERGE_IF_NECESSARY = Merge if Necessary
|
projectSubmitType_MERGE_IF_NECESSARY = Merge if Necessary
|
||||||
|
projectSubmitType_REBASE_ALWAYS = Rebase Always
|
||||||
projectSubmitType_REBASE_IF_NECESSARY = Rebase if Necessary
|
projectSubmitType_REBASE_IF_NECESSARY = Rebase if Necessary
|
||||||
projectSubmitType_MERGE_ALWAYS = Always Merge
|
projectSubmitType_MERGE_ALWAYS = Always Merge
|
||||||
projectSubmitType_CHERRY_PICK = Cherry Pick
|
projectSubmitType_CHERRY_PICK = Cherry Pick
|
||||||
|
@@ -43,6 +43,8 @@ public class Util {
|
|||||||
return C.projectSubmitType_MERGE_IF_NECESSARY();
|
return C.projectSubmitType_MERGE_IF_NECESSARY();
|
||||||
case REBASE_IF_NECESSARY:
|
case REBASE_IF_NECESSARY:
|
||||||
return C.projectSubmitType_REBASE_IF_NECESSARY();
|
return C.projectSubmitType_REBASE_IF_NECESSARY();
|
||||||
|
case REBASE_ALWAYS:
|
||||||
|
return C.projectSubmitType_REBASE_ALWAYS();
|
||||||
case MERGE_ALWAYS:
|
case MERGE_ALWAYS:
|
||||||
return C.projectSubmitType_MERGE_ALWAYS();
|
return C.projectSubmitType_MERGE_ALWAYS();
|
||||||
case CHERRY_PICK:
|
case CHERRY_PICK:
|
||||||
|
@@ -64,12 +64,15 @@ public class MergeabilityCacheImpl implements MergeabilityCache {
|
|||||||
|
|
||||||
private static final String CACHE_NAME = "mergeability";
|
private static final String CACHE_NAME = "mergeability";
|
||||||
|
|
||||||
public static final BiMap<SubmitType, Character> SUBMIT_TYPES = ImmutableBiMap.of(
|
public static final BiMap<SubmitType, Character> SUBMIT_TYPES =
|
||||||
SubmitType.FAST_FORWARD_ONLY, 'F',
|
new ImmutableBiMap.Builder<SubmitType, Character>()
|
||||||
SubmitType.MERGE_IF_NECESSARY, 'M',
|
.put(SubmitType.FAST_FORWARD_ONLY, 'F')
|
||||||
SubmitType.REBASE_IF_NECESSARY, 'R',
|
.put(SubmitType.MERGE_IF_NECESSARY, 'M')
|
||||||
SubmitType.MERGE_ALWAYS, 'A',
|
.put(SubmitType.REBASE_ALWAYS, 'P')
|
||||||
SubmitType.CHERRY_PICK, 'C');
|
.put(SubmitType.REBASE_IF_NECESSARY, 'R')
|
||||||
|
.put(SubmitType.MERGE_ALWAYS, 'A')
|
||||||
|
.put(SubmitType.CHERRY_PICK, 'C')
|
||||||
|
.build();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
|
checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
|
||||||
|
@@ -68,6 +68,7 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
|||||||
private CommitValidators.Policy validate;
|
private CommitValidators.Policy validate;
|
||||||
private boolean forceContentMerge;
|
private boolean forceContentMerge;
|
||||||
private boolean copyApprovals = true;
|
private boolean copyApprovals = true;
|
||||||
|
private boolean postMessage = true;
|
||||||
|
|
||||||
private RevCommit rebasedCommit;
|
private RevCommit rebasedCommit;
|
||||||
private PatchSet.Id rebasedPatchSetId;
|
private PatchSet.Id rebasedPatchSetId;
|
||||||
@@ -117,6 +118,11 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RebaseChangeOp setPostMessage(boolean postMessage) {
|
||||||
|
this.postMessage = postMessage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateRepo(RepoContext ctx) throws MergeConflictException,
|
public void updateRepo(RepoContext ctx) throws MergeConflictException,
|
||||||
InvalidChangeOperationException, RestApiException, IOException,
|
InvalidChangeOperationException, RestApiException, IOException,
|
||||||
@@ -153,10 +159,11 @@ public class RebaseChangeOp extends BatchUpdate.Op {
|
|||||||
.setDraft(originalPatchSet.isDraft())
|
.setDraft(originalPatchSet.isDraft())
|
||||||
.setNotify(NotifyHandling.NONE)
|
.setNotify(NotifyHandling.NONE)
|
||||||
.setFireRevisionCreated(fireRevisionCreated)
|
.setFireRevisionCreated(fireRevisionCreated)
|
||||||
.setCopyApprovals(copyApprovals)
|
.setCopyApprovals(copyApprovals);
|
||||||
.setMessage(
|
if (postMessage) {
|
||||||
"Patch Set " + rebasedPatchSetId.get()
|
patchSetInserter.setMessage("Patch Set " + rebasedPatchSetId.get()
|
||||||
+ ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
|
+ ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
|
||||||
|
}
|
||||||
|
|
||||||
if (base != null) {
|
if (base != null) {
|
||||||
patchSetInserter.setGroups(base.patchSet().getGroups());
|
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;
|
package com.google.gerrit.server.git.strategy;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.restapi.MergeConflictException;
|
public class RebaseIfNecessary extends RebaseSubmitStrategy {
|
||||||
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 {
|
|
||||||
|
|
||||||
RebaseIfNecessary(SubmitStrategy.Arguments args) {
|
RebaseIfNecessary(SubmitStrategy.Arguments args) {
|
||||||
super(args);
|
super(args, false);
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
return MergeIfNecessary.dryRun(args, tipCommit, toMergeCommit);
|
||||||
case REBASE_IF_NECESSARY:
|
case REBASE_IF_NECESSARY:
|
||||||
return RebaseIfNecessary.dryRun(args, tipCommit, toMergeCommit);
|
return RebaseIfNecessary.dryRun(args, tipCommit, toMergeCommit);
|
||||||
|
case REBASE_ALWAYS:
|
||||||
|
return RebaseAlways.dryRun(args, tipCommit, toMergeCommit);
|
||||||
default:
|
default:
|
||||||
String errorMsg = "No submit strategy for: " + submitType;
|
String errorMsg = "No submit strategy for: " + submitType;
|
||||||
log.error(errorMsg);
|
log.error(errorMsg);
|
||||||
|
@@ -71,6 +71,8 @@ public class SubmitStrategyFactory {
|
|||||||
return new MergeIfNecessary(args);
|
return new MergeIfNecessary(args);
|
||||||
case REBASE_IF_NECESSARY:
|
case REBASE_IF_NECESSARY:
|
||||||
return new RebaseIfNecessary(args);
|
return new RebaseIfNecessary(args);
|
||||||
|
case REBASE_ALWAYS:
|
||||||
|
return new RebaseAlways(args);
|
||||||
default:
|
default:
|
||||||
String errorMsg = "No submit strategy for: " + submitType;
|
String errorMsg = "No submit strategy for: " + submitType;
|
||||||
log.error(errorMsg);
|
log.error(errorMsg);
|
||||||
|
@@ -432,6 +432,7 @@ abstract class SubmitStrategyOp extends BatchUpdate.Op {
|
|||||||
case CHERRY_PICK:
|
case CHERRY_PICK:
|
||||||
return message(ctx, commit, CommitMergeStatus.CLEAN_PICK);
|
return message(ctx, commit, CommitMergeStatus.CLEAN_PICK);
|
||||||
case REBASE_IF_NECESSARY:
|
case REBASE_IF_NECESSARY:
|
||||||
|
case REBASE_ALWAYS:
|
||||||
return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
|
return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("unexpected submit type "
|
throw new IllegalStateException("unexpected submit type "
|
||||||
|
@@ -58,6 +58,10 @@ public class RepositoryConfigTest {
|
|||||||
configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY);
|
configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY);
|
||||||
assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
|
assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
|
||||||
.isEqualTo(SubmitType.REBASE_IF_NECESSARY);
|
.isEqualTo(SubmitType.REBASE_IF_NECESSARY);
|
||||||
|
|
||||||
|
configureDefaultSubmitType("*", SubmitType.REBASE_ALWAYS);
|
||||||
|
assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
|
||||||
|
.isEqualTo(SubmitType.REBASE_ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Reference in New Issue
Block a user