blob: 29fda2d93a26b5b96cfdf6597b613443e849d74c [file] [log] [blame]
// 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 com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.Test;
import java.util.List;
public class SubmitByMergeIfNecessaryIT extends AbstractSubmitByMerge {
@Override
protected SubmitType getSubmitType() {
return SubmitType.MERGE_IF_NECESSARY;
}
@Test
public void submitWithFastForward() throws Exception {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
RevCommit updatedHead = getRemoteHead();
assertThat(updatedHead.getId()).isEqualTo(change.getCommit());
assertThat(updatedHead.getParent(0)).isEqualTo(initialHead);
assertSubmitter(change.getChangeId(), 1);
assertPersonEquals(admin.getIdent(), updatedHead.getAuthorIdent());
assertPersonEquals(admin.getIdent(), updatedHead.getCommitterIdent());
assertRefUpdatedEvents(initialHead, updatedHead);
assertChangeMergedEvents(change.getChangeId(), updatedHead.name());
}
@Test
public void submitMultipleChanges() throws Exception {
RevCommit initialHead = getRemoteHead();
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange("Change 1", "b", "b");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "c", "c");
testRepo.reset(initialHead);
PushOneCommit.Result change3 = createChange("Change 3", "d", "d");
PushOneCommit.Result change4 = createChange("Change 4", "e", "e");
PushOneCommit.Result change5 = createChange("Change 5", "f", "f");
// Change 2 is a fast-forward, no need to merge.
submit(change2.getChangeId());
RevCommit headAfterFirstSubmit = getRemoteLog().get(0);
assertThat(headAfterFirstSubmit.getShortMessage()).isEqualTo(
change2.getCommit().getShortMessage());
assertThat(headAfterFirstSubmit.getParent(0).getId()).isEqualTo(
initialHead.getId());
assertPersonEquals(admin.getIdent(), headAfterFirstSubmit.getAuthorIdent());
assertPersonEquals(admin.getIdent(), headAfterFirstSubmit.getCommitterIdent());
// We need to merge changes 3, 4 and 5.
approve(change3.getChangeId());
approve(change4.getChangeId());
submit(change5.getChangeId());
RevCommit headAfterSecondSubmit = getRemoteLog().get(0);
assertThat(headAfterSecondSubmit.getParent(1).getShortMessage()).isEqualTo(
change5.getCommit().getShortMessage());
assertThat(headAfterSecondSubmit.getParent(0).getShortMessage()).isEqualTo(
change2.getCommit().getShortMessage());
assertPersonEquals(admin.getIdent(), headAfterSecondSubmit.getAuthorIdent());
assertPersonEquals(serverIdent.get(), headAfterSecondSubmit.getCommitterIdent());
// First change stays untouched.
assertNew(change.getChangeId());
// The two submit operations should have resulted in two ref-update events
// and three change-merged events.
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
headAfterFirstSubmit, headAfterSecondSubmit);
assertChangeMergedEvents(change2.getChangeId(), headAfterFirstSubmit.name(),
change3.getChangeId(), headAfterSecondSubmit.name(),
change4.getChangeId(), headAfterSecondSubmit.name(),
change5.getChangeId(), headAfterSecondSubmit.name());
}
@Test
public void submitChangesAcrossRepos() throws Exception {
Project.NameKey p1 = createProject("project-where-we-submit");
Project.NameKey p2 = createProject("project-impacted-via-topic");
Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic");
RevCommit initialHead2 = getRemoteHead(p2, "master");
RevCommit initialHead3 = getRemoteHead(p3, "master");
TestRepository<?> repo1 = cloneProject(p1);
TestRepository<?> repo2 = cloneProject(p2);
TestRepository<?> repo3 = cloneProject(p3);
PushOneCommit.Result change1a = createChange(repo1, "master",
"An ancestor of the change we want to submit",
"a.txt", "1", "dependent-topic");
PushOneCommit.Result change1b = createChange(repo1, "master",
"We're interested in submitting this change",
"a.txt", "2", "topic-to-submit");
PushOneCommit.Result change2a = createChange(repo2, "master",
"indirection level 1",
"a.txt", "1", "topic-indirect");
PushOneCommit.Result change2b = createChange(repo2, "master",
"should go in with first change",
"a.txt", "2", "dependent-topic");
PushOneCommit.Result change3 = createChange(repo3, "master",
"indirection level 2",
"a.txt", "1", "topic-indirect");
approve(change1a.getChangeId());
approve(change2a.getChangeId());
approve(change2b.getChangeId());
approve(change3.getChangeId());
submit(change1b.getChangeId());
RevCommit tip1 = getRemoteLog(p1, "master").get(0);
RevCommit tip2 = getRemoteLog(p2, "master").get(0);
RevCommit tip3 = getRemoteLog(p3, "master").get(0);
assertThat(tip1.getShortMessage()).isEqualTo(
change1b.getCommit().getShortMessage());
if (isSubmitWholeTopicEnabled()) {
assertThat(tip2.getShortMessage()).isEqualTo(
change2b.getCommit().getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
change3.getCommit().getShortMessage());
} else {
assertThat(tip2.getShortMessage()).isEqualTo(
initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
initialHead3.getShortMessage());
}
}
@Test
public void submitChangesAcrossReposBlocked() throws Exception {
Project.NameKey p1 = createProject("project-where-we-submit");
Project.NameKey p2 = createProject("project-impacted-via-topic");
Project.NameKey p3 = createProject("project-impacted-indirectly-via-topic");
TestRepository<?> repo1 = cloneProject(p1);
TestRepository<?> repo2 = cloneProject(p2);
TestRepository<?> repo3 = cloneProject(p3);
RevCommit initialHead1 = getRemoteHead(p1, "master");
RevCommit initialHead2 = getRemoteHead(p2, "master");
RevCommit initialHead3 = getRemoteHead(p3, "master");
PushOneCommit.Result change1a = createChange(repo1, "master",
"An ancestor of the change we want to submit",
"a.txt", "1", "dependent-topic");
PushOneCommit.Result change1b = createChange(repo1, "master",
"we're interested to submit this change",
"a.txt", "2", "topic-to-submit");
PushOneCommit.Result change2a = createChange(repo2, "master",
"indirection level 2a",
"a.txt", "1", "topic-indirect");
PushOneCommit.Result change2b = createChange(repo2, "master",
"should go in with first change",
"a.txt", "2", "dependent-topic");
PushOneCommit.Result change3 = createChange(repo3, "master",
"indirection level 2b",
"a.txt", "1", "topic-indirect");
// Create a merge conflict for change3 which is only indirectly related
// via topics.
repo3.reset(initialHead3);
PushOneCommit.Result change3Conflict = createChange(repo3, "master",
"conflicting change",
"a.txt", "2\n2", "conflicting-topic");
submit(change3Conflict.getChangeId());
RevCommit tipConflict = getRemoteLog(p3, "master").get(0);
assertThat(tipConflict.getShortMessage()).isEqualTo(
change3Conflict.getCommit().getShortMessage());
approve(change1a.getChangeId());
approve(change2a.getChangeId());
approve(change2b.getChangeId());
approve(change3.getChangeId());
if (isSubmitWholeTopicEnabled()) {
submitWithConflict(change1b.getChangeId(),
"Failed to submit 5 changes due to the following problems:\n" +
"Change " + change3.getChange().getId() + ": Change could not be " +
"merged due to a path conflict. Please rebase the change locally " +
"and upload the rebased commit for review.");
} else {
submit(change1b.getChangeId());
}
RevCommit tip1 = getRemoteLog(p1, "master").get(0);
RevCommit tip2 = getRemoteLog(p2, "master").get(0);
RevCommit tip3 = getRemoteLog(p3, "master").get(0);
if (isSubmitWholeTopicEnabled()) {
assertThat(tip1.getShortMessage()).isEqualTo(
initialHead1.getShortMessage());
assertThat(tip2.getShortMessage()).isEqualTo(
initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
change3Conflict.getCommit().getShortMessage());
assertNoSubmitter(change1a.getChangeId(), 1);
assertNoSubmitter(change2a.getChangeId(), 1);
assertNoSubmitter(change2b.getChangeId(), 1);
assertNoSubmitter(change3.getChangeId(), 1);
} else {
assertThat(tip1.getShortMessage()).isEqualTo(
change1b.getCommit().getShortMessage());
assertThat(tip2.getShortMessage()).isEqualTo(
initialHead2.getShortMessage());
assertThat(tip3.getShortMessage()).isEqualTo(
change3Conflict.getCommit().getShortMessage());
assertNoSubmitter(change2a.getChangeId(), 1);
assertNoSubmitter(change2b.getChangeId(), 1);
assertNoSubmitter(change3.getChangeId(), 1);
}
}
@Test
public void submitWithMergedAncestorsOnOtherBranch() throws Exception {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change1 = createChange(testRepo, "master",
"base commit",
"a.txt", "1", "");
submit(change1.getChangeId());
RevCommit headAfterFirstSubmit = getRemoteHead();
gApi.projects()
.name(project.get())
.branch("branch")
.create(new BranchInput());
PushOneCommit.Result change2 = createChange(testRepo, "master",
"We want to commit this to master first",
"a.txt", "2", "");
submit(change2.getChangeId());
RevCommit headAfterSecondSubmit = getRemoteLog(project, "master").get(0);
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo(
change2.getCommit().getShortMessage());
RevCommit tip2 = getRemoteLog(project, "branch").get(0);
assertThat(tip2.getShortMessage()).isEqualTo(
change1.getCommit().getShortMessage());
PushOneCommit.Result change3 = createChange(testRepo, "branch",
"This commit is based on master, which includes change2, "
+ "but is targeted at branch, which doesn't include it.",
"a.txt", "3", "");
submit(change3.getChangeId());
List<RevCommit> log3 = getRemoteLog(project, "branch");
assertThat(log3.get(0).getShortMessage()).isEqualTo(
change3.getCommit().getShortMessage());
assertThat(log3.get(1).getShortMessage()).isEqualTo(
change2.getCommit().getShortMessage());
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
headAfterFirstSubmit, headAfterSecondSubmit);
assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name(),
change2.getChangeId(), headAfterSecondSubmit.name());
}
@Test
public void submitWithOpenAncestorsOnOtherBranch() throws Exception {
RevCommit initialHead = getRemoteHead();
PushOneCommit.Result change1 = createChange(testRepo, "master",
"base commit",
"a.txt", "1", "");
submit(change1.getChangeId());
RevCommit headAfterFirstSubmit = getRemoteHead();
gApi.projects()
.name(project.get())
.branch("branch")
.create(new BranchInput());
PushOneCommit.Result change2 = createChange(testRepo, "master",
"We want to commit this to master first",
"a.txt", "2", "");
approve(change2.getChangeId());
RevCommit tip1 = getRemoteLog(project, "master").get(0);
assertThat(tip1.getShortMessage()).isEqualTo(
change1.getCommit().getShortMessage());
RevCommit tip2 = getRemoteLog(project, "branch").get(0);
assertThat(tip2.getShortMessage()).isEqualTo(
change1.getCommit().getShortMessage());
PushOneCommit.Result change3a = createChange(testRepo, "branch",
"This commit is based on change2 pending for master, "
+ "but is targeted itself at branch, which doesn't include it.",
"a.txt", "3", "a-topic-here");
Project.NameKey p3 = createProject("project-related-to-change3");
TestRepository<?> repo3 = cloneProject(p3);
RevCommit repo3Head = getRemoteHead(p3, "master");
PushOneCommit.Result change3b = createChange(repo3, "master",
"some accompanying changes for change3a in another repo "
+ "tied together via topic",
"a.txt", "1", "a-topic-here");
approve(change3b.getChangeId());
String cnt = isSubmitWholeTopicEnabled() ? "2 changes" : "1 change";
submitWithConflict(change3a.getChangeId(),
"Failed to submit " + cnt + " due to the following problems:\n"
+ "Change " + change3a.getChange().getId() + ": depends on change that"
+ " was not submitted");
RevCommit tipbranch = getRemoteLog(project, "branch").get(0);
assertThat(tipbranch.getShortMessage()).isEqualTo(
change1.getCommit().getShortMessage());
RevCommit tipmaster = getRemoteLog(p3, "master").get(0);
assertThat(tipmaster.getShortMessage()).isEqualTo(
repo3Head.getShortMessage());
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name());
}
@Test
public void testGerritWorkflow() throws Exception {
RevCommit initialHead = getRemoteHead();
// We'll setup a master and a stable branch.
// Then we create a change to be applied to master, which is
// then cherry picked back to stable. The stable branch will
// be merged up into master again.
gApi.projects()
.name(project.get())
.branch("stable")
.create(new BranchInput());
// Push a change to master
PushOneCommit push =
pushFactory.create(db, user.getIdent(), testRepo,
"small fix", "a.txt", "2");
PushOneCommit.Result change = push.to("refs/for/master");
submit(change.getChangeId());
RevCommit headAfterFirstSubmit = getRemoteLog(project, "master").get(0);
assertThat(headAfterFirstSubmit.getShortMessage()).isEqualTo(
change.getCommit().getShortMessage());
// Now cherry pick to stable
CherryPickInput in = new CherryPickInput();
in.destination = "stable";
in.message = "This goes to stable as well\n"
+ headAfterFirstSubmit.getFullMessage();
ChangeApi orig = gApi.changes()
.id(change.getChangeId());
String cherryId = orig.current().cherryPick(in).id();
gApi.changes().id(cherryId).current().review(ReviewInput.approve());
gApi.changes().id(cherryId).current().submit();
// Create the merge locally
RevCommit stable = getRemoteHead(project, "stable");
RevCommit master = getRemoteHead(project, "master");
testRepo.git().fetch().call();
testRepo.git()
.branchCreate()
.setName("stable")
.setStartPoint(stable)
.call();
testRepo.git()
.branchCreate()
.setName("master")
.setStartPoint(master)
.call();
RevCommit merge = testRepo.commit()
.parent(master)
.parent(stable)
.message("Merge stable into master")
.insertChangeId()
.create();
testRepo.branch("refs/heads/master").update(merge);
testRepo.git().push()
.setRefSpecs(new RefSpec("refs/heads/master:refs/for/master"))
.call();
String changeId = GitUtil.getChangeId(testRepo, merge).get();
approve(changeId);
submit(changeId);
RevCommit headAfterSecondSubmit = getRemoteLog(project, "master").get(0);
assertThat(headAfterSecondSubmit.getShortMessage())
.isEqualTo(merge.getShortMessage());
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
headAfterFirstSubmit, headAfterSecondSubmit);
assertChangeMergedEvents(
change.getChangeId(), headAfterFirstSubmit.name(),
changeId, headAfterSecondSubmit.name());
}
@Test
public void openChangeForTargetBranchPreventsMerge() throws Exception {
gApi.projects()
.name(project.get())
.branch("stable")
.create(new BranchInput());
// Propose a change for master, but leave it open for master!
PushOneCommit change =
pushFactory.create(db, user.getIdent(), testRepo,
"small fix", "a.txt", "2");
PushOneCommit.Result change2result = change.to("refs/for/master");
// Now cherry pick to stable
CherryPickInput in = new CherryPickInput();
in.destination = "stable";
in.message = "it goes to stable branch";
ChangeApi orig = gApi.changes()
.id(change2result.getChangeId());
ChangeApi cherry = orig.current().cherryPick(in);
cherry.current().review(ReviewInput.approve());
cherry.current().submit();
// Create a commit locally
testRepo.git().fetch().setRefSpecs(new RefSpec("refs/heads/stable")).call();
PushOneCommit.Result change3 = createChange(testRepo, "stable",
"test","a.txt", "3", "");
submitWithConflict(change3.getChangeId(),
"Failed to submit 1 change due to the following problems:\n" +
"Change " + change3.getPatchSetId().getParentKey().get() +
": depends on change that was not submitted");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
@Test
@TestProjectInput(createEmptyCommit = false)
public void mergeWithMissingChange() throws Exception {
// create a draft change
PushOneCommit.Result draftResult = createDraftChange();
// create a new change based on the draft change
PushOneCommit.Result changeResult = createChange();
// delete the draft change
gApi.changes().id(draftResult.getChangeId()).delete();
// approve and submit the change
submitWithConflict(changeResult.getChangeId(),
"Failed to submit 1 change due to the following problems:\n"
+ "Change " + changeResult.getChange().getId()
+ ": depends on change that was not submitted");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
}