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 5f8faffdef..c17598f2cd 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.JSchException; 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.api.errors.GitAPIException; 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 @@ public class GitUtil { 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 @@ public class GitUtil { Iterable 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 74505659eb..1c0fafec3e 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.Iterables; 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 com.google.gwtorm.server.OrmException; 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 class PushOneCommit { 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 @@ public class PushOneCommit { 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 0000000000..5209a0c445 --- /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 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() {}.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 0000000000..770e55450e --- /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 dff94ce946..3ae37004f4 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 4c9325e46d..3921028b1c 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 @@ import java.util.List; public class ChangeInfo { List 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 0000000000..4d2e4b674f --- /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 0000000000..a5371d2912 --- /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 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 0000000000..ccbbfb664b --- /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 0000000000..9d56e18727 --- /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 0000000000..6c671eb4ba --- /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 0000000000..a5737a7444 --- /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 0000000000..07594a6c74 --- /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 0000000000..8e1b340e91 --- /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; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java index 493a40fcb2..2992bd996d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeAlways.java @@ -25,10 +25,15 @@ public class MergeAlways extends SubmitStrategy { } @Override - protected CodeReviewCommit _run(final CodeReviewCommit mergeTip, - final List toMerge) throws MergeException { + protected CodeReviewCommit _run(CodeReviewCommit mergeTip, + List toMerge) throws MergeException { args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); + if (mergeTip == null) { + // The branch is unborn. Take a fast-forward resolution to + // create the branch. + mergeTip = toMerge.remove(0); + } CodeReviewCommit newMergeTip = mergeTip; while (!toMerge.isEmpty()) { newMergeTip = diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java index c31edb2115..3a326fc7f6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeIfNecessary.java @@ -25,9 +25,16 @@ public class MergeIfNecessary extends SubmitStrategy { } @Override - protected CodeReviewCommit _run(final CodeReviewCommit mergeTip, - final List toMerge) throws MergeException { + protected CodeReviewCommit _run(CodeReviewCommit mergeTip, + List toMerge) throws MergeException { args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); + + if (mergeTip == null) { + // The branch is unborn. Take a fast-forward resolution to + // create the branch. + mergeTip = toMerge.remove(0); + } + CodeReviewCommit newMergeTip = args.mergeUtil.getFirstFastForward(mergeTip, args.rw, toMerge);