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