blob: 07e4866bf4e08325aef98ab5b42e4f903e53d079 [file] [log] [blame]
// Copyright (C) 2014 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.server.change;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.extensions.common.ProblemInfo.Status.FIXED;
import static com.google.gerrit.extensions.common.ProblemInfo.Status.FIX_FAILED;
import static com.google.gerrit.testing.TestActionRefUpdateContext.openTestRefUpdateContext;
import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
import static com.google.gerrit.testing.TestChanges.newPatchSet;
import static java.util.Objects.requireNonNull;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.entities.SubmissionId;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ConsistencyChecker;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.RepoContext;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.TestChanges;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Inject private ChangeNotes.Factory changeNotesFactory;
@Inject private Provider<ConsistencyChecker> checkerProvider;
@Inject private IdentifiedUser.GenericFactory userFactory;
@Inject private ChangeInserter.Factory changeInserterFactory;
@Inject private PatchSetInserter.Factory patchSetInserterFactory;
@Inject private ChangeNoteUtil noteUtil;
@Inject private Sequences sequences;
private RevCommit tip;
private Account.Id adminId;
private ConsistencyChecker checker;
private TestRepository<InMemoryRepository> serverSideTestRepo;
@Before
public void setUp() throws Exception {
serverSideTestRepo =
new TestRepository<>((InMemoryRepository) repoManager.openRepository(project));
tip =
serverSideTestRepo
.getRevWalk()
.parseCommit(serverSideTestRepo.getRepository().exactRef("HEAD").getObjectId());
adminId = admin.id();
checker = checkerProvider.get();
}
@Test
public void validNewChange() throws Exception {
assertNoProblems(insertChange(), null);
}
@Test
public void validMergedChange() throws Exception {
ChangeNotes notes = mergeChange(incrementPatchSet(insertChange()));
assertNoProblems(notes, null);
}
@Test
public void missingOwner() throws Exception {
TestAccount owner = accountCreator.create("missing");
ChangeNotes notes = insertChange(owner);
deleteUserBranch(owner.id());
assertProblems(notes, null, problem("Missing change owner: " + owner.id()));
}
// No test for ref existing but object missing; InMemoryRepository won't let
// us do such a thing.
@Test
public void patchSetObjectAndRefMissing() throws Exception {
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
ChangeNotes notes = insertChange();
PatchSet ps = insertMissingPatchSet(notes, rev);
notes = reload(notes);
assertProblems(
notes,
null,
problem("Ref missing: " + ps.id().toRefName()),
problem("Object missing: patch set 2: deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
}
@Test
public void patchSetObjectAndRefMissingWithFix() throws Exception {
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
ChangeNotes notes = insertChange();
PatchSet ps = insertMissingPatchSet(notes, rev);
notes = reload(notes);
String refName = ps.id().toRefName();
assertProblems(
notes,
new FixInput(),
problem("Ref missing: " + refName),
problem("Object missing: patch set 2: " + rev));
}
@Test
public void patchSetRefMissing() throws Exception {
ChangeNotes notes = insertChange();
serverSideTestRepo.update("refs/other/foo", psUtil.current(notes).commitId());
String refName = notes.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
assertProblems(notes, null, problem("Ref missing: " + refName));
}
@Test
public void patchSetRefMissingWithFix() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo.update("refs/other/foo", commitId);
String refName = notes.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
assertProblems(
notes, new FixInput(), problem("Ref missing: " + refName, FIXED, "Repaired patch set ref"));
assertThat(serverSideTestRepo.getRepository().exactRef(refName).getObjectId())
.isEqualTo(commitId);
}
@Test
public void patchSetObjectAndRefMissingWithDeletingPatchSet() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps2 = insertMissingPatchSet(notes, rev2);
notes = reload(notes);
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
notes,
fix,
problem("Ref missing: " + ps2.id().toRefName()),
problem("Object missing: patch set 2: " + rev2, FIXED, "Deleted patch set"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(1);
assertThat(psUtil.get(notes, ps1.id())).isNotNull();
assertThat(psUtil.get(notes, ps2.id())).isNull();
}
@Test
public void patchSetMultipleObjectsMissingWithDeletingPatchSets() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps2 = insertMissingPatchSet(notes, rev2);
notes = incrementPatchSet(reload(notes));
PatchSet ps3 = psUtil.current(notes);
String rev4 = "c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee";
PatchSet ps4 = insertMissingPatchSet(notes, rev4);
notes = reload(notes);
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
notes,
fix,
problem("Ref missing: " + ps2.id().toRefName()),
problem("Object missing: patch set 2: " + rev2, FIXED, "Deleted patch set"),
problem("Ref missing: " + ps4.id().toRefName()),
problem("Object missing: patch set 4: " + rev4, FIXED, "Deleted patch set"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(3);
assertThat(psUtil.get(notes, ps1.id())).isNotNull();
assertThat(psUtil.get(notes, ps2.id())).isNull();
assertThat(psUtil.get(notes, ps3.id())).isNotNull();
assertThat(psUtil.get(notes, ps4.id())).isNull();
}
@Test
public void onlyPatchSetObjectMissingWithFix() throws Exception {
Change c = TestChanges.newChange(project, admin.id(), sequences.nextChangeId());
PatchSet.Id psId = c.currentPatchSetId();
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps = newPatchSet(psId, rev, adminId);
addNoteDbCommit(
c.getId(),
"Create change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Branch: "
+ c.getDest().branch()
+ "\n"
+ "Change-id: "
+ c.getKey().get()
+ "\n"
+ "Subject: Bogus subject\n"
+ "Commit: "
+ rev
+ "\n"
+ "Groups: "
+ rev
+ "\n");
ChangeNotes notes = changeNotesFactory.create(c.getProject(), c.getId());
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
notes,
fix,
problem("Ref missing: " + ps.id().toRefName()),
problem(
"Object missing: patch set 1: " + rev,
FIX_FAILED,
"Cannot delete patch set; no patch sets would remain"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId().get()).isEqualTo(1);
assertThat(psUtil.current(notes)).isNotNull();
}
@Test
public void duplicatePatchSetRevisions() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
notes = incrementPatchSet(notes, serverSideTestRepo.getRevWalk().parseCommit(ps1.commitId()));
assertProblems(
notes,
null,
problem("Multiple patch sets pointing to " + ps1.commitId().name() + ": [1, 2]"));
}
@Test
public void missingDestRef() throws Exception {
ChangeNotes notes = insertChange();
String ref = "refs/heads/master";
// Detach head so we're allowed to delete ref.
serverSideTestRepo.reset(serverSideTestRepo.getRepository().exactRef(ref).getObjectId());
RefUpdate ru = serverSideTestRepo.getRepository().updateRef(ref);
ru.setForceUpdate(true);
testRefAction(() -> assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED));
assertProblems(notes, null, problem("Destination ref not found (may be new branch): " + ref));
}
@Test
public void mergedChangeIsNotMerged() throws Exception {
ChangeNotes notes = insertChange();
try (RefUpdateContext ctx = openTestRefUpdateContext()) {
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(
notes.getChangeId(),
new BatchUpdateOp() {
@Override
public boolean updateChange(ChangeContext ctx) {
ctx.getChange().setStatus(Change.Status.MERGED);
ctx.getUpdate(ctx.getChange().currentPatchSetId())
.fixStatusToMerged(new SubmissionId(ctx.getChange()));
return true;
}
});
bu.execute();
}
}
notes = reload(notes);
ObjectId tip = getDestRef(notes);
assertProblems(
notes,
null,
problem(
"Patch set 1 ("
+ psUtil.current(notes).commitId().name()
+ ") is not merged into destination ref"
+ " refs/heads/master ("
+ tip.name()
+ "), but change status is MERGED"));
}
@Test
public void newChangeIsMerged() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
.branch(notes.getChange().getDest().branch())
.update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
assertProblems(
notes,
null,
problem(
"Patch set 1 ("
+ commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
+ commitId.name()
+ "), but change status is NEW"));
}
@Test
public void newChangeIsMergedWithFix() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
.branch(notes.getChange().getDest().branch())
.update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
assertProblems(
notes,
new FixInput(),
problem(
"Patch set 1 ("
+ commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
+ commitId.name()
+ "), but change status is NEW",
FIXED,
"Marked change as merged"));
notes = reload(notes);
assertThat(notes.getChange().isMerged()).isTrue();
assertNoProblems(notes, null);
}
@Test
public void extensionApiReturnsUpdatedValueAfterFix() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
.branch(notes.getChange().getDest().branch())
.update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
ChangeInfo info = gApi.changes().id(notes.getChangeId().get()).info();
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
info = gApi.changes().id(notes.getChangeId().get()).check(new FixInput());
assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
}
@Test
public void expectedMergedCommitIsLatestPatchSet() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId = psUtil.current(notes).commitId();
serverSideTestRepo
.branch(notes.getChange().getDest().branch())
.update(serverSideTestRepo.getRevWalk().parseCommit(commitId));
FixInput fix = new FixInput();
fix.expectMergedAs = commitId.name();
assertProblems(
notes,
fix,
problem(
"Patch set 1 ("
+ commitId.name()
+ ") is merged into destination ref"
+ " refs/heads/master ("
+ commitId.name()
+ "), but change status is NEW",
FIXED,
"Marked change as merged"));
notes = reload(notes);
assertThat(notes.getChange().isMerged()).isTrue();
assertNoProblems(notes, null);
}
@Test
public void expectedMergedCommitNotMergedIntoDestination() throws Exception {
ChangeNotes notes = insertChange();
RevCommit commit =
serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit);
FixInput fix = new FixInput();
RevCommit other = serverSideTestRepo.commit().message(commit.getFullMessage()).create();
fix.expectMergedAs = other.name();
assertProblems(
notes,
fix,
problem(
"Expected merged commit "
+ other.name()
+ " is not merged into destination ref refs/heads/master"
+ " ("
+ commit.name()
+ ")"));
}
@Test
public void createNewPatchSetForExpectedMergeCommitWithNoChangeId() throws Exception {
ChangeNotes notes = insertChange();
String dest = notes.getChange().getDest().branch();
RevCommit commit =
serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
RevCommit mergedAs =
serverSideTestRepo
.commit()
.parent(commit.getParent(0))
.message(commit.getShortMessage())
.create();
serverSideTestRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID)).isEmpty();
serverSideTestRepo.update(dest, mergedAs);
assertNoProblems(notes, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
notes,
fix,
problem(
"No patch set found for merged commit " + mergedAs.name(),
FIXED,
"Marked change as merged"),
problem(
"Expected merged commit " + mergedAs.name() + " has no associated patch set",
FIXED,
"Inserted as patch set 2"));
notes = reload(notes);
PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(mergedAs);
assertNoProblems(notes, null);
}
@Test
public void createNewPatchSetForExpectedMergeCommitWithChangeId() throws Exception {
ChangeNotes notes = insertChange();
String dest = notes.getChange().getDest().branch();
RevCommit commit =
serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
RevCommit mergedAs =
serverSideTestRepo
.commit()
.parent(commit.getParent(0))
.message(
commit.getShortMessage()
+ "\n"
+ "\n"
+ "Change-Id: "
+ notes.getChange().getKey().get()
+ "\n")
.create();
serverSideTestRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID))
.containsExactly(notes.getChange().getKey().get());
serverSideTestRepo.update(dest, mergedAs);
assertNoProblems(notes, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
notes,
fix,
problem(
"No patch set found for merged commit " + mergedAs.name(),
FIXED,
"Marked change as merged"),
problem(
"Expected merged commit " + mergedAs.name() + " has no associated patch set",
FIXED,
"Inserted as patch set 2"));
notes = reload(notes);
PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(mergedAs);
assertNoProblems(notes, null);
}
@Test
public void expectedMergedCommitIsOldPatchSetOfSameChange() throws Exception {
ChangeNotes notes = insertChange();
ObjectId commitId1 = psUtil.current(notes).commitId();
notes = incrementPatchSet(notes);
PatchSet ps2 = psUtil.current(notes);
serverSideTestRepo
.branch(notes.getChange().getDest().branch())
.update(serverSideTestRepo.getRevWalk().parseCommit(commitId1));
FixInput fix = new FixInput();
fix.expectMergedAs = commitId1.name();
assertProblems(
notes,
fix,
problem(
"No patch set found for merged commit " + commitId1.name(),
FIXED,
"Marked change as merged"),
problem(
"Expected merge commit "
+ commitId1.name()
+ " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED,
"Deleted patch set"),
problem(
"Expected merge commit "
+ commitId1.name()
+ " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED,
"Inserted as patch set 3"));
notes = reload(notes);
PatchSet.Id psId3 = PatchSet.id(notes.getChangeId(), 3);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId3);
assertThat(notes.getChange().isMerged()).isTrue();
assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps2.id(), psId3);
assertThat(psUtil.get(notes, psId3).commitId()).isEqualTo(commitId1);
}
@Test
public void expectedMergedCommitIsDanglingPatchSetOlderThanCurrent() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
// Create dangling ref so next ID in the database becomes 3.
PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
serverSideTestRepo.branch(psId2.toRefName()).update(commit2);
notes = incrementPatchSet(notes);
PatchSet ps3 = psUtil.current(notes);
assertThat(ps3.id().get()).isEqualTo(3);
serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit2);
FixInput fix = new FixInput();
fix.expectMergedAs = commit2.name();
assertProblems(
notes,
fix,
problem(
"No patch set found for merged commit " + commit2.name(),
FIXED,
"Marked change as merged"),
problem(
"Expected merge commit "
+ commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED,
"Deleted patch set"),
problem(
"Expected merge commit "
+ commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED,
"Inserted as patch set 4"));
notes = reload(notes);
PatchSet.Id psId4 = PatchSet.id(notes.getChangeId(), 4);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId4);
assertThat(notes.getChange().isMerged()).isTrue();
assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.id(), ps3.id(), psId4);
assertThat(psUtil.get(notes, psId4).commitId()).isEqualTo(commit2);
}
@Test
public void expectedMergedCommitIsDanglingPatchSetNewerThanCurrent() throws Exception {
ChangeNotes notes = insertChange();
PatchSet ps1 = psUtil.current(notes);
// Create dangling ref with no patch set.
PatchSet.Id psId2 = PatchSet.id(notes.getChangeId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
serverSideTestRepo.branch(psId2.toRefName()).update(commit2);
serverSideTestRepo.branch(notes.getChange().getDest().branch()).update(commit2);
FixInput fix = new FixInput();
fix.expectMergedAs = commit2.name();
assertProblems(
notes,
fix,
problem(
"No patch set found for merged commit " + commit2.name(),
FIXED,
"Marked change as merged"),
problem(
"Expected merge commit "
+ commit2.name()
+ " corresponds to patch set 2,"
+ " not the current patch set 1",
FIXED,
"Inserted as patch set 2"));
notes = reload(notes);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(notes.getChange().isMerged()).isTrue();
assertThat(psUtil.byChangeAsMap(notes).keySet()).containsExactly(ps1.id(), psId2);
assertThat(psUtil.get(notes, psId2).commitId()).isEqualTo(commit2);
}
@Test
public void expectedMergedCommitWithMismatchedChangeId() throws Exception {
ChangeNotes notes = insertChange();
String dest = notes.getChange().getDest().branch();
RevCommit parent = serverSideTestRepo.branch(dest).commit().message("parent").create();
RevCommit commit =
serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes).commitId());
serverSideTestRepo.branch(dest).update(commit);
String badId = "I0000000000000000000000000000000000000000";
RevCommit mergedAs =
serverSideTestRepo
.commit()
.parent(parent)
.message(commit.getShortMessage() + "\n\nChange-Id: " + badId + "\n")
.create();
serverSideTestRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID)).containsExactly(badId);
serverSideTestRepo.update(dest, mergedAs);
assertNoProblems(notes, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
notes,
fix,
problem(
"Expected merged commit "
+ mergedAs.name()
+ " has Change-Id: "
+ badId
+ ", but expected "
+ notes.getChange().getKey().get()));
}
@Test
public void expectedMergedCommitMatchesMultiplePatchSets() throws Exception {
ChangeNotes notes1 = insertChange();
PatchSet.Id psId1 = psUtil.current(notes1).id();
String dest = notes1.getChange().getDest().branch();
RevCommit commit =
serverSideTestRepo.getRevWalk().parseCommit(psUtil.current(notes1).commitId());
serverSideTestRepo.branch(dest).update(commit);
ChangeNotes notes2 = insertChange();
notes2 = incrementPatchSet(notes2, commit);
PatchSet.Id psId2 = psUtil.current(notes2).id();
ChangeNotes notes3 = insertChange();
notes3 = incrementPatchSet(notes3, commit);
PatchSet.Id psId3 = psUtil.current(notes3).id();
FixInput fix = new FixInput();
fix.expectMergedAs = commit.name();
assertProblems(
notes1,
fix,
problem(
"Multiple patch sets for expected merged commit "
+ commit.name()
+ ": ["
+ psId1
+ ", "
+ psId2
+ ", "
+ psId3
+ "]"));
}
private BatchUpdate newUpdate(Account.Id owner) {
return batchUpdateFactory.create(project, userFactory.create(owner), TimeUtil.now());
}
private ChangeNotes insertChange() throws Exception {
return insertChange(admin);
}
private ChangeNotes insertChange(TestAccount owner) throws Exception {
return insertChange(owner, "refs/heads/master");
}
private ChangeNotes insertChange(TestAccount owner, String dest) throws Exception {
Change.Id id = Change.id(sequences.nextChangeId());
return testRefAction(
() -> {
ChangeInserter ins;
try (BatchUpdate bu = newUpdate(owner.id())) {
RevCommit commit = patchSetCommit(PatchSet.id(id, 1));
bu.setNotify(NotifyResolver.Result.none());
ins =
changeInserterFactory
.create(id, commit, dest)
.setValidate(false)
.setFireRevisionCreated(false)
.setSendMail(false);
bu.insertChange(ins).execute();
}
return changeNotesFactory.create(project, ins.getChange().getId());
});
}
private PatchSet.Id nextPatchSetId(ChangeNotes notes) throws Exception {
return ChangeUtil.nextPatchSetId(
serverSideTestRepo.getRepository(), notes.getChange().currentPatchSetId());
}
private ChangeNotes incrementPatchSet(ChangeNotes notes) throws Exception {
return incrementPatchSet(notes, patchSetCommit(nextPatchSetId(notes)));
}
private ChangeNotes incrementPatchSet(ChangeNotes notes, RevCommit commit) throws Exception {
return testRefAction(
() -> {
PatchSetInserter ins;
try (BatchUpdate bu = newUpdate(notes.getChange().getOwner())) {
bu.setNotify(NotifyResolver.Result.none());
ins =
patchSetInserterFactory
.create(notes, nextPatchSetId(notes), commit)
.setValidate(false)
.setFireRevisionCreated(false);
bu.addOp(notes.getChangeId(), ins).execute();
}
return reload(notes);
});
}
private ChangeNotes reload(ChangeNotes notes) throws Exception {
return changeNotesFactory.create(notes.getChange().getProject(), notes.getChangeId());
}
private RevCommit patchSetCommit(PatchSet.Id psId) throws Exception {
RevCommit c = serverSideTestRepo.commit().parent(tip).message("Change " + psId).create();
return serverSideTestRepo.parseBody(c);
}
private PatchSet insertMissingPatchSet(ChangeNotes notes, String rev) throws Exception {
// Don't use BatchUpdate since we're manually updating the meta ref rather
// than using ChangeUpdate.
String subject = "Subject for missing commit";
Change c = new Change(notes.getChange());
PatchSet.Id psId = nextPatchSetId(notes);
c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
PatchSet ps = newPatchSet(psId, rev, adminId);
addNoteDbCommit(
c.getId(),
"Update patch set "
+ psId.get()
+ "\n"
+ "\n"
+ "Patch-set: "
+ psId.get()
+ "\n"
+ "Commit: "
+ rev
+ "\n"
+ "Subject: "
+ subject
+ "\n");
return ps;
}
private void deleteRef(String refName) throws Exception {
RefUpdate ru = serverSideTestRepo.getRepository().updateRef(refName, true);
ru.setForceUpdate(true);
testRefAction(() -> assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED));
}
private void addNoteDbCommit(Change.Id id, String commitMessage) throws Exception {
PersonIdent committer = serverIdent.get();
PersonIdent author =
noteUtil.newAccountIdIdent(
getAccount(admin.id()).id(), committer.getWhenAsInstant(), committer);
serverSideTestRepo
.branch(RefNames.changeMetaRef(id))
.commit()
.author(author)
.committer(committer)
.message(commitMessage)
.create();
}
private ObjectId getDestRef(ChangeNotes notes) throws Exception {
return serverSideTestRepo
.getRepository()
.exactRef(notes.getChange().getDest().branch())
.getObjectId();
}
private ChangeNotes mergeChange(ChangeNotes notes) throws Exception {
return testRefAction(
() -> {
ObjectId oldId = getDestRef(notes);
ObjectId newId = psUtil.current(notes).commitId();
String dest = notes.getChange().getDest().branch();
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(
notes.getChangeId(),
new BatchUpdateOp() {
@Override
public void updateRepo(RepoContext ctx) throws IOException {
ctx.addRefUpdate(oldId, newId, dest);
}
@Override
public boolean updateChange(ChangeContext ctx) {
ctx.getChange().setStatus(Change.Status.MERGED);
ctx.getUpdate(ctx.getChange().currentPatchSetId())
.fixStatusToMerged(new SubmissionId(ctx.getChange()));
return true;
}
});
bu.execute();
}
return reload(notes);
});
}
private static ProblemInfo problem(String message) {
ProblemInfo p = new ProblemInfo();
p.message = message;
return p;
}
private static ProblemInfo problem(String message, ProblemInfo.Status status, String outcome) {
ProblemInfo p = problem(message);
p.status = requireNonNull(status);
p.outcome = requireNonNull(outcome);
return p;
}
private void assertProblems(
ChangeNotes notes, @Nullable FixInput fix, ProblemInfo first, ProblemInfo... rest)
throws Exception {
List<ProblemInfo> expected = new ArrayList<>(1 + rest.length);
expected.add(first);
expected.addAll(Arrays.asList(rest));
assertThat(checker.check(notes, fix).problems()).containsExactlyElementsIn(expected).inOrder();
}
private void assertNoProblems(ChangeNotes notes, @Nullable FixInput fix) throws Exception {
assertThat(checker.check(notes, fix).problems()).isEmpty();
}
private void deleteUserBranch(Account.Id accountId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsers)) {
String refName = RefNames.refsUsers(accountId);
Ref ref = repo.exactRef(refName);
if (ref == null) {
return;
}
RefUpdate ru = repo.updateRef(refName);
ru.setExpectedOldObjectId(ref.getObjectId());
ru.setNewObjectId(ObjectId.zeroId());
ru.setForceUpdate(true);
Result result = testRefAction(() -> ru.delete());
if (result != Result.FORCED) {
throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
}
}
}
}