Merge "Include labels in change query results if submit record is not stored in index"
diff --git a/Documentation/project-configuration.txt b/Documentation/project-configuration.txt
index d71d19a..2703e4e 100644
--- a/Documentation/project-configuration.txt
+++ b/Documentation/project-configuration.txt
@@ -117,6 +117,13 @@
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.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
index 1033164..4ff2467 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
@@ -20,6 +20,7 @@
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 @@
+ "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 @@
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 @@
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 @@
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
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index dfeac35..cfa90e6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -61,14 +61,21 @@
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);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 43f19b9..44f3897 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -53,12 +53,17 @@
@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 @@
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:
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 3d1377e..9927c15 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -146,7 +146,8 @@
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 @@
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(
@@ -252,8 +254,14 @@
assertThat(actual).containsKey(
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);
+ } 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 @@
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");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
new file mode 100644
index 0000000..492fc05
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -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());
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index 04e71eb..365e3b6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -3,6 +3,7 @@
SUBMIT_UTIL_SRCS = [
'AbstractSubmit.java',
'AbstractSubmitByMerge.java',
+ 'AbstractSubmitByRebase.java',
]
SUBMIT_TESTS = glob(['Submit*IT.java'])
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
new file mode 100644
index 0000000..cf9651f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.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());
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 9b3fd15..431978d2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -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() {
@@ -67,143 +51,6 @@
@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 {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change =
@@ -235,160 +82,4 @@
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());
- }
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
index fcfeb01..b52e89a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
@@ -18,6 +18,7 @@
FAST_FORWARD_ONLY,
MERGE_IF_NECESSARY,
REBASE_IF_NECESSARY,
+ REBASE_ALWAYS,
MERGE_ALWAYS,
CHERRY_PICK
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 984c5a3..322af1b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -77,6 +77,7 @@
String projectSubmitType_MERGE_ALWAYS();
String projectSubmitType_MERGE_IF_NECESSARY();
String projectSubmitType_REBASE_IF_NECESSARY();
+ String projectSubmitType_REBASE_ALWAYS();
String projectSubmitType_CHERRY_PICK();
String headingProjectState();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 5e7b873..1ae3a16 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -54,6 +54,7 @@
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
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 81286ea..3a46203 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -43,6 +43,8 @@
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:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index 62d75aa..a6e0935 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -64,12 +64,15 @@
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,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index d3c300e..d39f4fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -68,6 +68,7 @@
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 @@
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 @@
.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());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java
new file mode 100644
index 0000000..26bb4c1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java
@@ -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);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 3270fc3..104074a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -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);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
new file mode 100644
index 0000000..64991096
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -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, 0);
+ } 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);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
index e60b947..a7dc367 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
@@ -122,6 +122,8 @@
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);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index 146eda1..f8ca32a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -71,6 +71,8 @@
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);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 7dacc6f..fdce19b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -432,6 +432,7 @@
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 "
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
index bf36738..cfa1f5e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
@@ -58,6 +58,10 @@
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