blob: aeebc108d8c14347d102adbe9a4760b837af895f [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 static com.google.gerrit.acceptance.GitUtil.getChangeId;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.Test;
public abstract class AbstractSubmitByRebase extends AbstractSubmit {
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Override
protected abstract SubmitType getSubmitType();
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitWithRebase() throws Throwable {
submitWithRebase(admin);
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitWithRebaseWithoutAddPatchSetPermission() throws Throwable {
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.ADD_PATCH_SET).ref("refs/*").group(REGISTERED_USERS))
.add(allow(Permission.SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
.add(
allowLabel(TestLabels.codeReview().getName())
.ref("refs/heads/*")
.group(REGISTERED_USERS)
.range(-2, 2))
.update();
submitWithRebase(user);
}
protected ImmutableList<PushOneCommit.Result> submitWithRebase(TestAccount submitter)
throws Throwable {
requestScopeOperations.setApiUser(submitter.id());
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
submit(change2.getChangeId());
assertRebase(testRepo, false);
RevCommit headAfterSecondSubmit = projectOperations.project(project).getHead("master");
assertThat(headAfterSecondSubmit.getParent(0)).isEqualTo(headAfterFirstSubmit);
assertApproved(change2.getChangeId(), submitter);
assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
assertSubmitter(change2.getChangeId(), 1, submitter);
assertSubmitter(change2.getChangeId(), 2, submitter);
assertPersonEquals(admin.newIdent(), headAfterSecondSubmit.getAuthorIdent());
assertPersonEquals(submitter.newIdent(), headAfterSecondSubmit.getCommitterIdent());
assertRefUpdatedEvents(
initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit);
assertChangeMergedEvents(
change.getChangeId(),
headAfterFirstSubmit.name(),
change2.getChangeId(),
headAfterSecondSubmit.name());
return ImmutableList.of(change, change2);
}
@Test
public void submitWithRebaseMultipleChanges() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "content");
submit(change1.getChangeId());
RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
assertCurrentRevision(change1.getChangeId(), 2, headAfterFirstSubmit);
} else {
assertThat(headAfterFirstSubmit.name()).isEqualTo(change1.getCommit().name());
}
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
assertThat(change2.getCommit().getParent(0)).isNotEqualTo(change1.getCommit());
PushOneCommit.Result change3 = createChange("Change 3", "c.txt", "third content");
PushOneCommit.Result change4 = createChange("Change 4", "d.txt", "fourth content");
approve(change2.getChangeId());
approve(change3.getChangeId());
submit(change4.getChangeId());
assertRebase(testRepo, false);
assertApproved(change2.getChangeId());
assertApproved(change3.getChangeId());
assertApproved(change4.getChangeId());
RevCommit headAfterSecondSubmit = parse(projectOperations.project(project).getHead("master"));
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
assertThat(parent.getShortMessage()).isEqualTo("Change 3");
assertThat(parent).isNotEqualTo(change3.getCommit());
assertCurrentRevision(change3.getChangeId(), 2, parent);
RevCommit grandparent = parse(parent.getParent(0));
assertThat(grandparent).isNotEqualTo(change2.getCommit());
assertCurrentRevision(change2.getChangeId(), 2, grandparent);
RevCommit greatgrandparent = parse(grandparent.getParent(0));
assertThat(greatgrandparent).isEqualTo(headAfterFirstSubmit);
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
assertCurrentRevision(change1.getChangeId(), 2, greatgrandparent);
} else {
assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
}
assertRefUpdatedEvents(
initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit);
assertChangeMergedEvents(
change1.getChangeId(),
headAfterFirstSubmit.name(),
change2.getChangeId(),
headAfterSecondSubmit.name(),
change3.getChangeId(),
headAfterSecondSubmit.name(),
change4.getChangeId(),
headAfterSecondSubmit.name());
}
@Test
public void submitWithRebaseMergeCommit() throws Throwable {
/*
* (HEAD, origin/master, origin/HEAD) Merge changes X,Y
|\
| * Merge branch 'master' into origin/master
| |\
| | * SHA Added a
| |/
* | Before
|/
* Initial empty repository
*/
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
PushOneCommit change2Push =
pushFactory.create(admin.newIdent(), testRepo, "Merge to master", "m.txt", "");
change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
PushOneCommit.Result change2 = change2Push.to("refs/for/master");
testRepo.reset(initialHead);
PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
approve(change3.getChangeId());
submit(change3.getChangeId());
approve(change1.getChangeId());
approve(change2.getChangeId());
submit(change2.getChangeId());
RevCommit newHead = projectOperations.project(project).getHead("master");
assertThat(newHead.getParentCount()).isEqualTo(2);
RevCommit headParent1 = parse(newHead.getParent(0).getId());
RevCommit headParent2 = parse(newHead.getParent(1).getId());
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
assertCurrentRevision(change3.getChangeId(), 2, headParent1.getId());
} else {
assertThat(change3.getCommit().getId()).isEqualTo(headParent1.getId());
}
assertThat(headParent1.getParentCount()).isEqualTo(1);
assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
assertThat(headParent2.getParentCount()).isEqualTo(2);
RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitWithContentMerge_Conflict() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
submit(change.getChangeId());
RevCommit headAfterFirstSubmit = projectOperations.project(project).getHead("master");
testRepo.reset(initialHead);
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
submitWithConflict(
change2.getChangeId(),
String.format(
"Cannot rebase %s: Change %s could not be rebased due to a conflict during merge.\n\n"
+ "merge conflict(s):\n"
+ "a.txt",
change2.getCommit().name(), change2.getChange().getId()));
RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head).isEqualTo(headAfterFirstSubmit);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
assertNoSubmitter(change2.getChangeId(), 1);
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
}
protected RevCommit parse(ObjectId id) throws Throwable {
try (Repository repo = repoManager.openRepository(project);
RevWalk rw = new RevWalk(repo)) {
RevCommit c = rw.parseCommit(id);
rw.parseBody(c);
return c;
}
}
@Test
public void submitAfterReorderOfCommits() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
// Create two commits and push.
RevCommit c1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
RevCommit c2 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
pushHead(testRepo, "refs/for/master", false);
String id1 = getChangeId(testRepo, c1).get();
String id2 = getChangeId(testRepo, c2).get();
// Swap the order of commits and push again.
testRepo.reset("HEAD~2");
testRepo.cherryPick(c2);
testRepo.cherryPick(c1);
pushHead(testRepo, "refs/for/master", false);
approve(id1);
approve(id2);
submit(id1);
RevCommit headAfterSubmit = projectOperations.project(project).getHead("master");
assertRefUpdatedEvents(initialHead, headAfterSubmit);
assertChangeMergedEvents(id2, headAfterSubmit.name(), id1, headAfterSubmit.name());
}
@Test
public void submitChangesAfterBranchOnSecond() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange();
approve(change.getChangeId());
PushOneCommit.Result change2 = createChange();
approve(change2.getChangeId());
Project.NameKey project = change2.getChange().change().getProject();
BranchNameKey branch = BranchNameKey.create(project, "branch");
createBranchWithRevision(branch, change2.getCommit().getName());
gApi.changes().id(change2.getChangeId()).current().submit();
assertMerged(change2.getChangeId());
assertMerged(change.getChangeId());
RevCommit newHead = projectOperations.project(this.project).getHead("master");
assertRefUpdatedEvents(initialHead, newHead);
assertChangeMergedEvents(
change.getChangeId(), newHead.name(), change2.getChangeId(), newHead.name());
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitFastForwardIdenticalTree() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "a");
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "a");
assertThat(change1.getCommit().getTree()).isEqualTo(change2.getCommit().getTree());
// for rebase if necessary, otherwise, the manual rebase of change2 will
// fail since change1 would be merged as fast forward
testRepo.reset(initialHead);
PushOneCommit.Result change0 = createChange("Change 0", "b.txt", "b");
submit(change0.getChangeId());
RevCommit headAfterChange0 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange0.getShortMessage()).isEqualTo("Change 0");
submit(change1.getChangeId());
RevCommit headAfterChange1 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange1.getShortMessage()).isEqualTo("Change 1");
assertThat(headAfterChange0).isEqualTo(headAfterChange1.getParent(0));
// Do manual rebase first.
gApi.changes().id(change2.getChangeId()).current().rebase();
submit(change2.getChangeId());
RevCommit headAfterChange2 = projectOperations.project(project).getHead("master");
assertThat(headAfterChange2.getShortMessage()).isEqualTo("Change 2");
assertThat(headAfterChange1).isEqualTo(headAfterChange2.getParent(0));
ChangeInfo info2 = info(change2.getChangeId());
assertThat(info2.status).isEqualTo(ChangeStatus.MERGED);
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitChainOneByOne() throws Throwable {
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
submit(change1.getChangeId());
submit(change2.getChangeId());
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitChainFailsOnRework() throws Throwable {
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
RevCommit headAfterChange1 = change1.getCommit();
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
testRepo.reset(headAfterChange1);
change1 =
amendChange(change1.getChangeId(), "subject 1 amend", "fileName 2", "rework content 2");
submit(change1.getChangeId());
headAfterChange1 = projectOperations.project(project).getHead("master");
submitWithConflict(
change2.getChangeId(),
String.format(
"Cannot rebase %s: Change %s could not be rebased due to a conflict during merge.\n\n"
+ "merge conflict(s):\n"
+ "fileName 2",
change2.getCommit().name(), change2.getChange().getId()));
assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterChange1);
}
@Test
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
public void submitChainOneByOneManualRebase() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change1 = createChange("subject 1", "fileName 1", "content 1");
PushOneCommit.Result change2 = createChange("subject 2", "fileName 2", "content 2");
// for rebase if necessary, otherwise, the manual rebase of change2 will
// fail since change1 would be merged as fast forward
testRepo.reset(initialHead);
PushOneCommit.Result change = createChange();
submit(change.getChangeId());
submit(change1.getChangeId());
// Do manual rebase first.
gApi.changes().id(change2.getChangeId()).current().rebase();
submit(change2.getChangeId());
}
@Test
public void dependencyOnOutdatedPatchSetPreventsRebase() throws Throwable {
// Create a change
PushOneCommit change = pushFactory.create(user.newIdent(), testRepo, "fix", "a.txt", "foo");
PushOneCommit.Result changeResult = change.to("refs/for/master");
PatchSet.Id patchSetId = changeResult.getPatchSetId();
// Create a successor change.
PushOneCommit change2 =
pushFactory.create(user.newIdent(), testRepo, "feature", "b.txt", "bar");
PushOneCommit.Result change2Result = change2.to("refs/for/master");
// Create new patch set for first change.
testRepo.reset(changeResult.getCommit().name());
amendChange(changeResult.getChangeId());
// Approve both changes
approve(changeResult.getChangeId());
approve(change2Result.getChangeId());
// submit button is disabled.
assertSubmitDisabled(change2Result.getChangeId());
submitWithConflict(
change2Result.getChangeId(),
"Failed to submit 2 changes due to the following problems:\n"
+ "Change "
+ change2Result.getChange().getId()
+ ": Depends on change that was not submitted."
+ " Commit "
+ change2Result.getCommit().name()
+ " depends on commit "
+ changeResult.getCommit().name()
+ ", which is outdated patch set "
+ patchSetId.get()
+ " of change "
+ changeResult.getChange().getId()
+ ". The latest patch set is "
+ changeResult.getPatchSetId().get()
+ ".");
assertRefUpdatedEvents();
assertChangeMergedEvents();
}
}