Add acceptance tests to test submit with different submit types

Verify that submitting with different submit types works as expected
and that the repository is updated correctly.

Change-Id: I2b2c752ea12aeb91400fd2172704ca291b80aa97
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
index 5f8faff..c17598f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/GitUtil.java
@@ -25,6 +25,7 @@
 import com.jcraft.jsch.Session;
 
 import org.eclipse.jgit.api.AddCommand;
+import org.eclipse.jgit.api.CheckoutCommand;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.CommitCommand;
 import org.eclipse.jgit.api.Git;
@@ -33,6 +34,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.JschConfigSessionFactory;
@@ -130,47 +132,58 @@
     addCmd.call();
   }
 
-  public static String createCommit(Git git, PersonIdent i, String msg)
+  public static Commit createCommit(Git git, PersonIdent i, String msg)
       throws GitAPIException, IOException {
-    return createCommit(git, i, msg, true, false);
+    return createCommit(git, i, msg, null);
   }
 
-  public static void amendCommit(Git git, PersonIdent i, String msg, String changeId)
+  public static Commit amendCommit(Git git, PersonIdent i, String msg, String changeId)
       throws GitAPIException, IOException {
     msg = ChangeIdUtil.insertId(msg, ObjectId.fromString(changeId.substring(1)));
-    createCommit(git, i, msg, false, true);
+    return createCommit(git, i, msg, changeId);
   }
 
-  private static String createCommit(Git git, PersonIdent i, String msg,
-      boolean insertChangeId, boolean amend) throws GitAPIException, IOException {
-    ObjectId changeId = null;
-    if (insertChangeId) {
-      changeId = computeChangeId(git, i, msg);
-      msg = ChangeIdUtil.insertId(msg, changeId);
-    }
+  private static Commit createCommit(Git git, PersonIdent i, String msg,
+      String changeId) throws GitAPIException, IOException {
 
     final CommitCommand commitCmd = git.commit();
-    commitCmd.setAmend(amend);
+    commitCmd.setAmend(changeId != null);
     commitCmd.setAuthor(i);
     commitCmd.setCommitter(i);
-    commitCmd.setMessage(msg);
-    commitCmd.call();
 
-    return changeId != null ? "I" + changeId.getName() : null;
+    if (changeId == null) {
+      ObjectId id = computeChangeId(git, i, msg);
+      changeId = "I" + id.getName();
+    }
+    msg = ChangeIdUtil.insertId(msg, ObjectId.fromString(changeId.substring(1)));
+    commitCmd.setMessage(msg);
+
+    RevCommit c = commitCmd.call();
+    return new Commit(c, changeId);
   }
 
   private static ObjectId computeChangeId(Git git, PersonIdent i, String msg)
       throws IOException {
     RevWalk rw = new RevWalk(git.getRepository());
     try {
-      RevCommit parent =
-          rw.lookupCommit(git.getRepository().getRef(Constants.HEAD).getObjectId());
-      return ChangeIdUtil.computeChangeId(parent.getTree(), parent.getId(), i, i, msg);
+      Ref head = git.getRepository().getRef(Constants.HEAD);
+      if (head.getObjectId() != null) {
+        RevCommit parent = rw.lookupCommit(head.getObjectId());
+        return ChangeIdUtil.computeChangeId(parent.getTree(), parent.getId(), i, i, msg);
+      } else {
+        return ChangeIdUtil.computeChangeId(null, null, i, i, msg);
+      }
     } finally {
       rw.release();
     }
   }
 
+  public static void checkout(Git git, String name) throws GitAPIException {
+    CheckoutCommand checkout = git.checkout();
+    checkout.setName(name);
+    checkout.call();
+  }
+
   public static PushResult pushHead(Git git, String ref, boolean pushTags)
       throws GitAPIException {
     PushCommand pushCmd = git.push();
@@ -181,4 +194,22 @@
     Iterable<PushResult> r = pushCmd.call();
     return Iterables.getOnlyElement(r);
   }
+
+  public static class Commit {
+    private final RevCommit commit;
+    private final String changeId;
+
+    Commit(RevCommit commit, String changeId) {
+      this.commit = commit;
+      this.changeId = changeId;
+    }
+
+    public RevCommit getCommit() {
+      return commit;
+    }
+
+    public String getChangeId() {
+      return changeId;
+    }
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java
index 7450565..1c0fafe 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/PushOneCommit.java
@@ -27,6 +27,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.GitUtil.Commit;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -36,7 +37,9 @@
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
@@ -82,15 +85,17 @@
   public Result to(Git git, String ref)
       throws GitAPIException, IOException {
     add(git, fileName, content);
+    Commit c;
     if (changeId != null) {
-      amendCommit(git, i, subject, changeId);
+      c = amendCommit(git, i, subject, changeId);
     } else {
-      changeId = createCommit(git, i, subject);
+      c = createCommit(git, i, subject);
+      changeId = c.getChangeId();
     }
     if (tagName != null) {
       git.tag().setName(tagName).setAnnotated(false).call();
     }
-    return new Result(db, ref, pushHead(git, ref, tagName != null), changeId, subject);
+    return new Result(db, ref, pushHead(git, ref, tagName != null), c, subject);
   }
 
   public void setTag(final String tagName) {
@@ -101,32 +106,40 @@
     private final ReviewDb db;
     private final String ref;
     private final PushResult result;
-    private final String changeId;
+    private final Commit commit;
     private final String subject;
 
-    private Result(ReviewDb db, String ref, PushResult result, String changeId,
+    private Result(ReviewDb db, String ref, PushResult result, Commit commit,
         String subject) {
       this.db = db;
       this.ref = ref;
       this.result = result;
-      this.changeId = changeId;
+      this.commit = commit;
       this.subject = subject;
     }
 
     public PatchSet.Id getPatchSetId() throws OrmException {
       return Iterables.getOnlyElement(
-          db.changes().byKey(new Change.Key(changeId))).currentPatchSetId();
+          db.changes().byKey(new Change.Key(commit.getChangeId()))).currentPatchSetId();
     }
 
     public String getChangeId() {
-      return changeId;
+      return commit.getChangeId();
+    }
+
+    public ObjectId getCommitId() {
+      return commit.getCommit().getId();
+    }
+
+    public RevCommit getCommit() {
+      return commit.getCommit();
     }
 
     public void assertChange(Change.Status expectedStatus,
         String expectedTopic, TestAccount... expectedReviewers)
         throws OrmException {
       Change c =
-          Iterables.getOnlyElement(db.changes().byKey(new Change.Key(changeId)).toList());
+          Iterables.getOnlyElement(db.changes().byKey(new Change.Key(commit.getChangeId())).toList());
       assertEquals(subject, c.getSubject());
       assertEquals(expectedStatus, c.getStatus());
       assertEquals(expectedTopic, Strings.emptyToNull(c.getTopic()));
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
new file mode 100644
index 0000000..5209a0c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -0,0 +1,256 @@
+// 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.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.GitUtil;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.diff.DiffFormatter;
+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.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public abstract class AbstractSubmit extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  @Inject
+  private GitRepositoryManager repoManager;
+
+  protected RestSession session;
+
+  private TestAccount admin;
+  private Project.NameKey project;
+  private ReviewDb db;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.admin();
+    session = new RestSession(server, admin);
+    initSsh(admin);
+
+    project = new Project.NameKey("p");
+
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  protected abstract SubmitType getSubmitType();
+
+  @Test
+  public void submitToEmptyRepo() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject(false);
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    assertEquals(change.getCommitId(), getRemoteHead().getId());
+  }
+
+  protected Git createProject() throws JSchException, IOException,
+      GitAPIException {
+    return createProject(true);
+  }
+
+  private Git createProject(boolean emptyCommit)
+      throws JSchException, IOException, GitAPIException {
+    SshSession sshSession = new SshSession(server, admin);
+    try {
+      GitUtil.createProject(sshSession, project.get(), null, emptyCommit);
+      setSubmitType(getSubmitType());
+      return cloneProject(sshSession.getUrl() + "/" + project.get());
+    } finally {
+      sshSession.close();
+    }
+  }
+
+  private void setSubmitType(SubmitType submitType) throws IOException {
+    ProjectConfigInput in = new ProjectConfigInput();
+    in.submit_type = submitType;
+    in.use_content_merge = InheritableBoolean.FALSE;
+    RestResponse r = session.put("/projects/" + project.get() + "/config", in);
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    r.consume();
+  }
+
+  protected void setUseContentMerge() throws IOException {
+    ProjectConfigInput in = new ProjectConfigInput();
+    in.use_content_merge = InheritableBoolean.TRUE;
+    RestResponse r = session.put("/projects/" + project.get() + "/config", in);
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    r.consume();
+  }
+
+  protected PushOneCommit.Result createChange(Git git) throws GitAPIException,
+      IOException {
+    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    return push.to(git, "refs/for/master");
+  }
+
+  protected PushOneCommit.Result createChange(Git git, String subject,
+      String fileName, String content) throws GitAPIException, IOException {
+    PushOneCommit push =
+        new PushOneCommit(db, admin.getIdent(), subject, fileName, content);
+    return push.to(git, "refs/for/master");
+  }
+
+  protected void submit(String changeId) throws IOException {
+    submit(changeId, HttpStatus.SC_OK);
+  }
+
+  protected void submitWithConflict(String changeId) throws IOException {
+    submit(changeId, HttpStatus.SC_CONFLICT);
+  }
+
+  private void submit(String changeId, int expectedStatus) throws IOException {
+    approve(changeId);
+    RestResponse r =
+        session.post("/changes/" + changeId + "/submit",
+            SubmitInput.waitForMerge());
+    assertEquals(expectedStatus, r.getStatusCode());
+    if (expectedStatus == HttpStatus.SC_OK) {
+      ChangeInfo change =
+          (new Gson()).fromJson(r.getReader(),
+              new TypeToken<ChangeInfo>() {}.getType());
+      assertEquals("MERGED", change.status);
+    }
+    r.consume();
+  }
+
+  private void approve(String changeId) throws IOException {
+    RestResponse r =
+        session.post("/changes/" + changeId + "/revisions/current/review",
+            ReviewInput.approve());
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    r.consume();
+  }
+
+  protected void assertCherryPick(Git localGit, boolean contentMerge) throws IOException {
+    assertRebase(localGit, contentMerge);
+    RevCommit remoteHead = getRemoteHead();
+    assertFalse(remoteHead.getFooterLines("Reviewed-On").isEmpty());
+    assertFalse(remoteHead.getFooterLines("Reviewed-By").isEmpty());
+  }
+
+  protected void assertRebase(Git localGit, boolean contentMerge) throws IOException {
+    Repository repo = localGit.getRepository();
+    RevCommit localHead = getHead(repo);
+    RevCommit remoteHead = getRemoteHead();
+    assertNotEquals(localHead.getId(), remoteHead.getId());
+    assertEquals(1, remoteHead.getParentCount());
+    if (!contentMerge) {
+      assertEquals(getLatestDiff(repo), getLatestRemoteDiff());
+    }
+    assertEquals(localHead.getShortMessage(), remoteHead.getShortMessage());
+  }
+
+  private RevCommit getHead(Repository repo) throws IOException {
+    return getHead(repo, "HEAD");
+  }
+
+  protected RevCommit getRemoteHead() throws IOException {
+    Repository repo = repoManager.openRepository(project);
+    try {
+      return getHead(repo, "refs/heads/master");
+    } finally {
+      repo.close();
+    }
+  }
+
+  private RevCommit getHead(Repository repo, String name) throws IOException {
+    try {
+      RevWalk rw = new RevWalk(repo);
+      try {
+        return rw.parseCommit(repo.getRef(name).getObjectId());
+      } finally {
+        rw.release();
+      }
+    } finally {
+      repo.close();
+    }
+  }
+
+  private String getLatestDiff(Repository repo) throws IOException {
+    ObjectId oldTreeId = repo.resolve("HEAD~1^{tree}");
+    ObjectId newTreeId = repo.resolve("HEAD^{tree}");
+    return getLatestDiff(repo, oldTreeId, newTreeId);
+  }
+
+  private String getLatestRemoteDiff() throws IOException {
+    Repository repo = repoManager.openRepository(project);
+    try {
+      RevWalk rw = new RevWalk(repo);
+      try {
+        ObjectId oldTreeId = repo.resolve("refs/heads/master~1^{tree}");
+        ObjectId newTreeId = repo.resolve("refs/heads/master^{tree}");
+        return getLatestDiff(repo, oldTreeId, newTreeId);
+      } finally {
+        rw.release();
+      }
+    } finally {
+      repo.close();
+    }
+  }
+
+  private String getLatestDiff(Repository repo, ObjectId oldTreeId,
+      ObjectId newTreeId) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    DiffFormatter fmt = new DiffFormatter(out);
+    fmt.setRepository(repo);
+    fmt.format(oldTreeId, newTreeId);
+    fmt.flush();
+    return out.toString();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
new file mode 100644
index 0000000..770e554
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByMerge.java
@@ -0,0 +1,93 @@
+// 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.gerrit.acceptance.git.GitUtil.checkout;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public abstract class AbstractSubmitByMerge extends AbstractSubmit {
+
+  @Test
+  public void submitWithMerge() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "b.txt", "other content");
+    submit(change2.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(2, head.getParentCount());
+    assertEquals(oldHead, head.getParent(0));
+    assertEquals(change2.getCommitId(), head.getParent(1));
+  }
+
+  @Test
+  public void submitWithContentMerge() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "aaa\nbbb\nccc\n");
+    submit(change.getChangeId());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
+    submit(change2.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, change.getCommitId().getName());
+    PushOneCommit.Result change3 =
+        createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
+    submit(change3.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(2, head.getParentCount());
+    assertEquals(oldHead, head.getParent(0));
+    assertEquals(change3.getCommitId(), head.getParent(1));
+  }
+
+  @Test
+  public void submitWithContentMerge_Conflict() throws JSchException,
+      IOException, GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "other content");
+    submitWithConflict(change2.getChangeId());
+    assertEquals(oldHead, getRemoteHead());
+  }
+}
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 dff94ce..3ae3700 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
@@ -1,14 +1,39 @@
 include_defs('//gerrit-acceptance-tests/tests.defs')
 
 acceptance_tests(
-  srcs = glob(['*IT.java']),
+  srcs = ['ChangeMessagesIT.java'],
   deps = [
     ':util',
     '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
   ],
 )
 
+acceptance_tests(
+  srcs = ['SubmitByCherryPickIT.java', 'SubmitByFastForwardIT.java',
+          'SubmitByMergeAlwaysIT.java', 'SubmitByMergeIfNecessaryIT.java',
+          'SubmitByRebaseIfNecessaryIT.java'],
+  deps = [
+    ':submit',
+    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+  ],
+)
+
+java_library(
+  name = 'submit',
+  srcs = ['AbstractSubmit.java', 'AbstractSubmitByMerge.java'],
+  deps = [
+    ':util',
+    '//gerrit-acceptance-tests:lib',
+    '//gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git:util',
+  ],
+)
+
 java_library(
   name = 'util',
-  srcs = ['ChangeInfo.java', 'ChangeMessageInfo.java'],
+  srcs = ['ChangeInfo.java', 'ChangeMessageInfo.java', 'ProjectConfigInput.java',
+          'ReviewInput.java', 'SubmitInput.java'],
+  deps = [
+    '//lib:guava',
+    '//gerrit-reviewdb:server',
+  ],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
index 4c9325e..3921028 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
@@ -18,4 +18,5 @@
 
 public class ChangeInfo {
   List<ChangeMessageInfo> messages;
+  String status;
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java
new file mode 100644
index 0000000..4d2e4b6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ProjectConfigInput.java
@@ -0,0 +1,23 @@
+// 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 com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+public class ProjectConfigInput {
+  public SubmitType submit_type;
+  public InheritableBoolean use_content_merge;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java
new file mode 100644
index 0000000..a5371d2
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ReviewInput.java
@@ -0,0 +1,30 @@
+// 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 com.google.common.collect.Maps;
+
+import java.util.Map;
+
+public class ReviewInput {
+  Map<String, Integer> labels;
+
+  public static ReviewInput approve() {
+    ReviewInput in = new ReviewInput();
+    in.labels = Maps.newHashMap();
+    in.labels.put("Code-Review", 2);
+    return in;
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
new file mode 100644
index 0000000..ccbbfb6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -0,0 +1,143 @@
+// 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.gerrit.acceptance.git.GitUtil.checkout;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SubmitByCherryPickIT extends AbstractSubmit {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.CHERRY_PICK;
+  }
+
+  @Test
+  public void submitWithCherryPickIfFastForwardPossible() throws JSchException,
+      IOException, GitAPIException {
+    Git git = createProject();
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    assertCherryPick(git, false);
+    assertEquals(change.getCommit().getParent(0),
+        getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitWithCherryPick() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "b.txt", "other content");
+    submit(change2.getChangeId());
+    assertCherryPick(git, false);
+    assertEquals(oldHead, getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitWithContentMerge() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "aaa\nbbb\nccc\n");
+    submit(change.getChangeId());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
+    submit(change2.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, change.getCommitId().getName());
+    PushOneCommit.Result change3 =
+        createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
+    submit(change3.getChangeId());
+    assertCherryPick(git, true);
+    assertEquals(oldHead, getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitWithContentMerge_Conflict() throws JSchException,
+      IOException, GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "other content");
+    submitWithConflict(change2.getChangeId());
+    assertEquals(oldHead, getRemoteHead());
+  }
+
+  @Test
+  public void submitOutOfOrder() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    createChange(git, "Change 2", "b.txt", "other content");
+    PushOneCommit.Result change3 =
+        createChange(git, "Change 3", "c.txt", "different content");
+    submit(change3.getChangeId());
+    assertCherryPick(git, false);
+    assertEquals(oldHead, getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitOutOfOrder_Conflict() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    createChange(git, "Change 2", "b.txt", "other content");
+    PushOneCommit.Result change3 =
+        createChange(git, "Change 3", "b.txt", "different content");
+    submitWithConflict(change3.getChangeId());
+    assertEquals(oldHead, getRemoteHead());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
new file mode 100644
index 0000000..9d56e18
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -0,0 +1,67 @@
+// 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.gerrit.acceptance.git.GitUtil.checkout;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SubmitByFastForwardIT extends AbstractSubmit {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.FAST_FORWARD_ONLY;
+  }
+
+  @Test
+  public void submitWithFastForward() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit oldHead = getRemoteHead();
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(change.getCommitId(), head.getId());
+    assertEquals(oldHead, head.getParent(0));
+  }
+
+  @Test
+  public void submitFastForwardNotPossible_Conflict() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "b.txt", "other content");
+    submitWithConflict(change2.getChangeId());
+    assertEquals(oldHead, getRemoteHead());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
new file mode 100644
index 0000000..6c671eb
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeAlwaysIT.java
@@ -0,0 +1,50 @@
+// 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 org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SubmitByMergeAlwaysIT extends AbstractSubmitByMerge {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.MERGE_ALWAYS;
+  }
+
+  @Test
+  public void submitWithMergeIfFastForwardPossible() throws JSchException,
+      IOException, GitAPIException {
+    Git git = createProject();
+    RevCommit oldHead = getRemoteHead();
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(2, head.getParentCount());
+    assertEquals(oldHead, head.getParent(0));
+    assertEquals(change.getCommitId(), head.getParent(1));
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
new file mode 100644
index 0000000..a5737a7
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByMergeIfNecessaryIT.java
@@ -0,0 +1,35 @@
+package com.google.gerrit.acceptance.rest.change;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.MERGE_IF_NECESSARY;
+  }
+
+  @Test
+  public void submitWithFastForward() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit oldHead = getRemoteHead();
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(change.getCommitId(), head.getId());
+    assertEquals(oldHead, head.getParent(0));
+  }
+}
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
new file mode 100644
index 0000000..07594a6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -0,0 +1,108 @@
+// 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.gerrit.acceptance.git.GitUtil.checkout;
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+
+import com.jcraft.jsch.JSchException;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.REBASE_IF_NECESSARY;
+  }
+
+  @Test
+  public void submitWithFastForward() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit oldHead = getRemoteHead();
+    PushOneCommit.Result change = createChange(git);
+    submit(change.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(change.getCommitId(), head.getId());
+    assertEquals(oldHead, head.getParent(0));
+  }
+
+  @Test
+  public void submitWithRebase() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "b.txt", "other content");
+    submit(change2.getChangeId());
+    assertRebase(git, false);
+    assertEquals(oldHead, getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitWithContentMerge() throws JSchException, IOException,
+      GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "aaa\nbbb\nccc\n");
+    submit(change.getChangeId());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "aaa\nbbb\nccc\nddd\n");
+    submit(change2.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, change.getCommitId().getName());
+    PushOneCommit.Result change3 =
+        createChange(git, "Change 3", "a.txt", "bbb\nccc\n");
+    submit(change3.getChangeId());
+    assertRebase(git, true);
+    assertEquals(oldHead, getRemoteHead().getParent(0));
+  }
+
+  @Test
+  public void submitWithContentMerge_Conflict() throws JSchException,
+      IOException, GitAPIException {
+    Git git = createProject();
+    setUseContentMerge();
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange(git, "Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit oldHead = getRemoteHead();
+    checkout(git, initialHead.getId().getName());
+    PushOneCommit.Result change2 =
+        createChange(git, "Change 2", "a.txt", "other content");
+    submitWithConflict(change2.getChangeId());
+    RevCommit head = getRemoteHead();
+    assertEquals(oldHead, head);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
new file mode 100644
index 0000000..8e1b340
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitInput.java
@@ -0,0 +1,25 @@
+// 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;
+
+public class SubmitInput {
+  boolean wait_for_merge;
+
+  public static SubmitInput waitForMerge() {
+    SubmitInput in = new SubmitInput();
+    in.wait_for_merge = true;
+    return in;
+  }
+}