431 lines
17 KiB
Java
431 lines
17 KiB
Java
// 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.server.group.SystemGroupBackend.REGISTERED_USERS;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.gerrit.acceptance.PushOneCommit;
|
|
import com.google.gerrit.acceptance.TestAccount;
|
|
import com.google.gerrit.acceptance.TestProjectInput;
|
|
import com.google.gerrit.common.data.Permission;
|
|
import com.google.gerrit.extensions.api.changes.SubmitInput;
|
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
|
import com.google.gerrit.extensions.client.InheritableBoolean;
|
|
import com.google.gerrit.extensions.client.SubmitType;
|
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
|
import com.google.gerrit.reviewdb.client.Branch;
|
|
import com.google.gerrit.reviewdb.client.Change;
|
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
import com.google.gerrit.reviewdb.client.Project;
|
|
import com.google.gerrit.server.change.Submit.TestSubmitInput;
|
|
import com.google.gerrit.server.git.ProjectConfig;
|
|
import com.google.gerrit.server.project.Util;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
|
import org.eclipse.jgit.lib.Repository;
|
|
import org.eclipse.jgit.revwalk.RevCommit;
|
|
import org.eclipse.jgit.revwalk.RevWalk;
|
|
import org.junit.Test;
|
|
|
|
public abstract class AbstractSubmitByRebase extends AbstractSubmit {
|
|
|
|
@Override
|
|
protected abstract SubmitType getSubmitType();
|
|
|
|
@Test
|
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
|
public void submitWithRebase() throws Exception {
|
|
submitWithRebase(admin);
|
|
}
|
|
|
|
@Test
|
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
|
public void submitWithRebaseWithoutAddPatchSetPermission() throws Exception {
|
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
|
Util.block(cfg, Permission.ADD_PATCH_SET, REGISTERED_USERS, "refs/*");
|
|
Util.allow(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/heads/*");
|
|
Util.allow(
|
|
cfg,
|
|
Permission.forLabel(Util.codeReview().getName()),
|
|
-2,
|
|
2,
|
|
REGISTERED_USERS,
|
|
"refs/heads/*");
|
|
saveProjectConfig(project, cfg);
|
|
|
|
submitWithRebase(user);
|
|
}
|
|
|
|
private void submitWithRebase(TestAccount submitter) throws Exception {
|
|
setApiUser(submitter);
|
|
RevCommit initialHead = getRemoteHead();
|
|
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
|
|
submit(change.getChangeId());
|
|
|
|
RevCommit headAfterFirstSubmit = getRemoteHead();
|
|
testRepo.reset(initialHead);
|
|
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
|
|
submit(change2.getChangeId());
|
|
assertRebase(testRepo, false);
|
|
RevCommit headAfterSecondSubmit = getRemoteHead();
|
|
assertThat(headAfterSecondSubmit.getParent(0)).isEqualTo(headAfterFirstSubmit);
|
|
assertApproved(change2.getChangeId(), submitter);
|
|
assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
|
|
assertSubmitter(change2.getChangeId(), 1, submitter);
|
|
assertSubmitter(change2.getChangeId(), 2, submitter);
|
|
assertPersonEquals(admin.getIdent(), headAfterSecondSubmit.getAuthorIdent());
|
|
assertPersonEquals(submitter.getIdent(), headAfterSecondSubmit.getCommitterIdent());
|
|
|
|
assertRefUpdatedEvents(
|
|
initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit);
|
|
assertChangeMergedEvents(
|
|
change.getChangeId(),
|
|
headAfterFirstSubmit.name(),
|
|
change2.getChangeId(),
|
|
headAfterSecondSubmit.name());
|
|
}
|
|
|
|
@Test
|
|
public void submitWithRebaseMultipleChanges() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
PushOneCommit.Result change1 = createChange("Change 1", "a.txt", "content");
|
|
submit(change1.getChangeId());
|
|
RevCommit headAfterFirstSubmit = getRemoteHead();
|
|
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
|
assertCurrentRevision(change1.getChangeId(), 2, headAfterFirstSubmit);
|
|
} else {
|
|
assertThat(headAfterFirstSubmit.name()).isEqualTo(change1.getCommit().name());
|
|
}
|
|
|
|
testRepo.reset(initialHead);
|
|
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
|
|
assertThat(change2.getCommit().getParent(0)).isNotEqualTo(change1.getCommit());
|
|
PushOneCommit.Result change3 = createChange("Change 3", "c.txt", "third content");
|
|
PushOneCommit.Result change4 = createChange("Change 4", "d.txt", "fourth content");
|
|
approve(change2.getChangeId());
|
|
approve(change3.getChangeId());
|
|
submit(change4.getChangeId());
|
|
|
|
assertRebase(testRepo, false);
|
|
assertApproved(change2.getChangeId());
|
|
assertApproved(change3.getChangeId());
|
|
assertApproved(change4.getChangeId());
|
|
|
|
RevCommit headAfterSecondSubmit = parse(getRemoteHead());
|
|
assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
|
|
assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
|
|
assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
|
|
|
|
RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
|
|
assertThat(parent.getShortMessage()).isEqualTo("Change 3");
|
|
assertThat(parent).isNotEqualTo(change3.getCommit());
|
|
assertCurrentRevision(change3.getChangeId(), 2, parent);
|
|
|
|
RevCommit grandparent = parse(parent.getParent(0));
|
|
assertThat(grandparent).isNotEqualTo(change2.getCommit());
|
|
assertCurrentRevision(change2.getChangeId(), 2, grandparent);
|
|
|
|
RevCommit greatgrandparent = parse(grandparent.getParent(0));
|
|
assertThat(greatgrandparent).isEqualTo(headAfterFirstSubmit);
|
|
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
|
assertCurrentRevision(change1.getChangeId(), 2, greatgrandparent);
|
|
} else {
|
|
assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
|
|
}
|
|
|
|
assertRefUpdatedEvents(
|
|
initialHead, headAfterFirstSubmit, headAfterFirstSubmit, headAfterSecondSubmit);
|
|
assertChangeMergedEvents(
|
|
change1.getChangeId(),
|
|
headAfterFirstSubmit.name(),
|
|
change2.getChangeId(),
|
|
headAfterSecondSubmit.name(),
|
|
change3.getChangeId(),
|
|
headAfterSecondSubmit.name(),
|
|
change4.getChangeId(),
|
|
headAfterSecondSubmit.name());
|
|
}
|
|
|
|
@Test
|
|
public void submitWithRebaseMergeCommit() throws Exception {
|
|
/*
|
|
* (HEAD, origin/master, origin/HEAD) Merge changes X,Y
|
|
|\
|
|
| * Merge branch 'master' into origin/master
|
|
| |\
|
|
| | * SHA Added a
|
|
| |/
|
|
* | Before
|
|
|/
|
|
* Initial empty repository
|
|
*/
|
|
RevCommit initialHead = getRemoteHead();
|
|
PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
|
|
|
|
PushOneCommit change2Push =
|
|
pushFactory.create(db, admin.getIdent(), testRepo, "Merge to master", "m.txt", "");
|
|
change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
|
|
PushOneCommit.Result change2 = change2Push.to("refs/for/master");
|
|
|
|
testRepo.reset(initialHead);
|
|
PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
|
|
|
|
approve(change3.getChangeId());
|
|
submit(change3.getChangeId());
|
|
|
|
approve(change1.getChangeId());
|
|
approve(change2.getChangeId());
|
|
submit(change2.getChangeId());
|
|
|
|
RevCommit newHead = getRemoteHead();
|
|
assertThat(newHead.getParentCount()).isEqualTo(2);
|
|
|
|
RevCommit headParent1 = parse(newHead.getParent(0).getId());
|
|
RevCommit headParent2 = parse(newHead.getParent(1).getId());
|
|
|
|
if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
|
|
assertCurrentRevision(change3.getChangeId(), 2, headParent1.getId());
|
|
} else {
|
|
assertThat(change3.getCommit().getId()).isEqualTo(headParent1.getId());
|
|
}
|
|
assertThat(headParent1.getParentCount()).isEqualTo(1);
|
|
assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
|
|
|
|
assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
|
|
assertThat(headParent2.getParentCount()).isEqualTo(2);
|
|
|
|
RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
|
|
RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
|
|
|
|
assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
|
|
assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
|
|
}
|
|
|
|
@Test
|
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
|
public void submitWithContentMerge_Conflict() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
|
|
submit(change.getChangeId());
|
|
|
|
RevCommit headAfterFirstSubmit = getRemoteHead();
|
|
testRepo.reset(initialHead);
|
|
PushOneCommit.Result change2 = createChange("Change 2", "a.txt", "other content");
|
|
submitWithConflict(
|
|
change2.getChangeId(),
|
|
"Cannot rebase "
|
|
+ change2.getCommit().name()
|
|
+ ": The change could not be rebased due to a conflict during merge.");
|
|
RevCommit head = getRemoteHead();
|
|
assertThat(head).isEqualTo(headAfterFirstSubmit);
|
|
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
|
|
assertNoSubmitter(change2.getChangeId(), 1);
|
|
|
|
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
|
assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
|
|
}
|
|
|
|
@Test
|
|
public void repairChangeStateAfterFailure() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
|
|
submit(change.getChangeId());
|
|
|
|
RevCommit headAfterFirstSubmit = getRemoteHead();
|
|
testRepo.reset(initialHead);
|
|
PushOneCommit.Result change2 = createChange("Change 2", "b.txt", "other content");
|
|
Change.Id id2 = change2.getChange().getId();
|
|
SubmitInput failAfterRefUpdates = new TestSubmitInput(new SubmitInput(), true);
|
|
submit(
|
|
change2.getChangeId(),
|
|
failAfterRefUpdates,
|
|
ResourceConflictException.class,
|
|
"Failing after ref updates");
|
|
RevCommit headAfterFailedSubmit = getRemoteHead();
|
|
|
|
// Bad: ref advanced but change wasn't updated.
|
|
PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
|
|
PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
|
|
ChangeInfo info = gApi.changes().id(id2.get()).get();
|
|
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
|
|
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
|
|
assertThat(getPatchSet(psId2)).isNull();
|
|
|
|
ObjectId rev2;
|
|
try (Repository repo = repoManager.openRepository(project);
|
|
RevWalk rw = new RevWalk(repo)) {
|
|
ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
|
|
assertThat(rev1).isNotNull();
|
|
|
|
rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
|
|
assertThat(rev2).isNotNull();
|
|
assertThat(rev2).isNotEqualTo(rev1);
|
|
assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
|
|
|
|
assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
|
|
}
|
|
|
|
submit(change2.getChangeId());
|
|
RevCommit headAfterSecondSubmit = getRemoteHead();
|
|
assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
|
|
|
|
// Change status and patch set entities were updated, and branch tip stayed
|
|
// the same.
|
|
info = gApi.changes().id(id2.get()).get();
|
|
assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
|
|
assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
|
|
PatchSet ps2 = getPatchSet(psId2);
|
|
assertThat(ps2).isNotNull();
|
|
assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
|
|
assertThat(Iterables.getLast(info.messages).message)
|
|
.isEqualTo("Change has been successfully rebased as " + rev2.name() + " by Administrator");
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
assertThat(repo.exactRef("refs/heads/master").getObjectId()).isEqualTo(rev2);
|
|
}
|
|
|
|
assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
|
|
assertChangeMergedEvents(
|
|
change.getChangeId(),
|
|
headAfterFirstSubmit.name(),
|
|
change2.getChangeId(),
|
|
headAfterSecondSubmit.name());
|
|
}
|
|
|
|
protected RevCommit parse(ObjectId id) throws Exception {
|
|
try (Repository repo = repoManager.openRepository(project);
|
|
RevWalk rw = new RevWalk(repo)) {
|
|
RevCommit c = rw.parseCommit(id);
|
|
rw.parseBody(c);
|
|
return c;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void submitAfterReorderOfCommits() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
|
|
// Create two commits and push.
|
|
RevCommit c1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
|
|
RevCommit c2 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
|
|
pushHead(testRepo, "refs/for/master", false);
|
|
|
|
String id1 = getChangeId(testRepo, c1).get();
|
|
String id2 = getChangeId(testRepo, c2).get();
|
|
|
|
// Swap the order of commits and push again.
|
|
testRepo.reset("HEAD~2");
|
|
testRepo.cherryPick(c2);
|
|
testRepo.cherryPick(c1);
|
|
pushHead(testRepo, "refs/for/master", false);
|
|
|
|
approve(id1);
|
|
approve(id2);
|
|
submit(id1);
|
|
RevCommit headAfterSubmit = getRemoteHead();
|
|
|
|
assertRefUpdatedEvents(initialHead, headAfterSubmit);
|
|
assertChangeMergedEvents(id2, headAfterSubmit.name(), id1, headAfterSubmit.name());
|
|
}
|
|
|
|
@Test
|
|
public void submitChangesAfterBranchOnSecond() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
|
|
PushOneCommit.Result change = createChange();
|
|
approve(change.getChangeId());
|
|
|
|
PushOneCommit.Result change2 = createChange();
|
|
approve(change2.getChangeId());
|
|
Project.NameKey project = change2.getChange().change().getProject();
|
|
Branch.NameKey branch = new Branch.NameKey(project, "branch");
|
|
createBranchWithRevision(branch, change2.getCommit().getName());
|
|
gApi.changes().id(change2.getChangeId()).current().submit();
|
|
assertMerged(change2.getChangeId());
|
|
assertMerged(change.getChangeId());
|
|
|
|
RevCommit newHead = getRemoteHead();
|
|
assertRefUpdatedEvents(initialHead, newHead);
|
|
assertChangeMergedEvents(
|
|
change.getChangeId(), newHead.name(), change2.getChangeId(), newHead.name());
|
|
}
|
|
|
|
@Test
|
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
|
public void submitFastForwardIdenticalTree() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
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 = getRemoteHead();
|
|
assertThat(headAfterChange0.getShortMessage()).isEqualTo("Change 0");
|
|
|
|
submit(change1.getChangeId());
|
|
RevCommit headAfterChange1 = getRemoteHead();
|
|
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 = getRemoteHead();
|
|
assertThat(headAfterChange2.getShortMessage()).isEqualTo("Change 2");
|
|
assertThat(headAfterChange1).isEqualTo(headAfterChange2.getParent(0));
|
|
|
|
ChangeInfo info2 = get(change2.getChangeId());
|
|
assertThat(info2.status).isEqualTo(ChangeStatus.MERGED);
|
|
}
|
|
|
|
@Test
|
|
@TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
|
|
public void submitChainOneByOne() throws Exception {
|
|
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 submitChainOneByOneManualRebase() throws Exception {
|
|
RevCommit initialHead = getRemoteHead();
|
|
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());
|
|
}
|
|
}
|