blob: 37e551fd4137b8481ecbd1541d96651c3b289e81 [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.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
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.testutil.TestChanges.newPatchSet;
import static java.util.Collections.singleton;
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.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
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.reviewdb.client.Account;
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.reviewdb.client.RefNames;
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.PatchSetInserter;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.BatchUpdate.RepoContext;
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
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.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@NoHttpd
public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Inject
private ChangeControl.GenericFactory changeControlFactory;
@Inject
private Provider<ConsistencyChecker> checkerProvider;
@Inject
private IdentifiedUser.GenericFactory userFactory;
@Inject
private BatchUpdate.Factory updateFactory;
@Inject
private ChangeInserter.Factory changeInserterFactory;
@Inject
private PatchSetInserter.Factory patchSetInserterFactory;
@Inject
private ChangeNoteUtil noteUtil;
@Inject
@AnonymousCowardName
private String anonymousCowardName;
@Inject
private Sequences sequences;
private RevCommit tip;
private Account.Id adminId;
private ConsistencyChecker checker;
@Before
public void setUp() throws Exception {
// Ignore client clone of project; repurpose as server-side TestRepository.
testRepo = new TestRepository<>(
(InMemoryRepository) repoManager.openRepository(project));
tip = testRepo.getRevWalk().parseCommit(
testRepo.getRepository().exactRef("HEAD").getObjectId());
adminId = admin.getId();
checker = checkerProvider.get();
}
@Test
public void validNewChange() throws Exception {
assertNoProblems(insertChange(), null);
}
@Test
public void validMergedChange() throws Exception {
ChangeControl ctl = mergeChange(incrementPatchSet(insertChange()));
assertNoProblems(ctl, null);
}
@Test
public void missingOwner() throws Exception {
TestAccount owner = accounts.create("missing");
ChangeControl ctl = insertChange(owner);
db.accounts().deleteKeys(singleton(owner.getId()));
assertProblems(ctl, null,
problem("Missing change owner: " + owner.getId()));
}
@Test
public void missingRepo() throws Exception {
// NoteDb can't have a change without a repo.
assume().that(notesMigration.enabled()).isFalse();
ChangeControl ctl = insertChange();
Project.NameKey name = ctl.getProject().getNameKey();
((InMemoryRepositoryManager) repoManager).deleteRepository(name);
assertProblems(
ctl, null,
problem("Destination repository not found: " + name));
}
@Test
public void invalidRevision() throws Exception {
// NoteDb always parses the revision when inserting a patch set, so we can't
// create an invalid patch set.
assume().that(notesMigration.enabled()).isFalse();
ChangeControl ctl = insertChange();
PatchSet ps = newPatchSet(
ctl.getChange().currentPatchSetId(),
"fooooooooooooooooooooooooooooooooooooooo",
adminId);
db.patchSets().update(singleton(ps));
assertProblems(
ctl, null,
problem("Invalid revision on patch set 1:"
+ " fooooooooooooooooooooooooooooooooooooooo"));
}
// 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";
ChangeControl ctl = insertChange();
PatchSet ps = insertMissingPatchSet(ctl, rev);
ctl = reload(ctl);
assertProblems(
ctl, null,
problem("Ref missing: " + ps.getId().toRefName()),
problem(
"Object missing: patch set 2:"
+ " deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
}
@Test
public void patchSetObjectAndRefMissingWithFix() throws Exception {
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
ChangeControl ctl = insertChange();
PatchSet ps = insertMissingPatchSet(ctl, rev);
ctl = reload(ctl);
String refName = ps.getId().toRefName();
assertProblems(
ctl, new FixInput(),
problem("Ref missing: " + refName),
problem("Object missing: patch set 2: " + rev));
}
@Test
public void patchSetRefMissing() throws Exception {
ChangeControl ctl = insertChange();
testRepo.update(
"refs/other/foo",
ObjectId.fromString(
psUtil.current(db, ctl.getNotes()).getRevision().get()));
String refName = ctl.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
assertProblems(ctl, null, problem("Ref missing: " + refName));
}
@Test
public void patchSetRefMissingWithFix() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
testRepo.update("refs/other/foo", ObjectId.fromString(rev));
String refName = ctl.getChange().currentPatchSetId().toRefName();
deleteRef(refName);
assertProblems(
ctl, new FixInput(),
problem("Ref missing: " + refName, FIXED, "Repaired patch set ref"));
assertThat(testRepo.getRepository().exactRef(refName).getObjectId().name())
.isEqualTo(rev);
}
@Test
public void patchSetObjectAndRefMissingWithDeletingPatchSet()
throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps2 = insertMissingPatchSet(ctl, rev2);
ctl = reload(ctl);
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
ctl, fix,
problem("Ref missing: " + ps2.getId().toRefName()),
problem("Object missing: patch set 2: " + rev2,
FIXED, "Deleted patch set"));
ctl = reload(ctl);
assertThat(ctl.getChange().currentPatchSetId().get()).isEqualTo(1);
assertThat(psUtil.get(db, ctl.getNotes(), ps1.getId())).isNotNull();
assertThat(psUtil.get(db, ctl.getNotes(), ps2.getId())).isNull();
}
@Test
public void patchSetMultipleObjectsMissingWithDeletingPatchSets()
throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
String rev2 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps2 = insertMissingPatchSet(ctl, rev2);
ctl = incrementPatchSet(reload(ctl));
PatchSet ps3 = psUtil.current(db, ctl.getNotes());
String rev4 = "c0ffeeeec0ffeeeec0ffeeeec0ffeeeec0ffeeee";
PatchSet ps4 = insertMissingPatchSet(ctl, rev4);
ctl = reload(ctl);
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
ctl, fix,
problem("Ref missing: " + ps2.getId().toRefName()),
problem("Object missing: patch set 2: " + rev2,
FIXED, "Deleted patch set"),
problem("Ref missing: " + ps4.getId().toRefName()),
problem("Object missing: patch set 4: " + rev4,
FIXED, "Deleted patch set"));
ctl = reload(ctl);
assertThat(ctl.getChange().currentPatchSetId().get()).isEqualTo(3);
assertThat(psUtil.get(db, ctl.getNotes(), ps1.getId())).isNotNull();
assertThat(psUtil.get(db, ctl.getNotes(), ps2.getId())).isNull();
assertThat(psUtil.get(db, ctl.getNotes(), ps3.getId())).isNotNull();
assertThat(psUtil.get(db, ctl.getNotes(), ps4.getId())).isNull();
}
@Test
public void onlyPatchSetObjectMissingWithFix() throws Exception {
Change c = TestChanges.newChange(
project, admin.getId(), sequences.nextChangeId());
PatchSet.Id psId = c.currentPatchSetId();
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
PatchSet ps = newPatchSet(psId, rev, adminId);
db.changes().insert(singleton(c));
db.patchSets().insert(singleton(ps));
addNoteDbCommit(
c.getId(),
"Create change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Branch: " + c.getDest().get() + "\n"
+ "Change-id: " + c.getKey().get() + "\n"
+ "Subject: Bogus subject\n"
+ "Commit: " + rev + "\n"
+ "Groups: " + rev + "\n");
indexer.index(db, c.getProject(), c.getId());
IdentifiedUser user = userFactory.create(admin.getId());
ChangeControl ctl = changeControlFactory.controlFor(
db, c.getProject(), c.getId(), user);
FixInput fix = new FixInput();
fix.deletePatchSetIfCommitMissing = true;
assertProblems(
ctl, fix,
problem("Ref missing: " + ps.getId().toRefName()),
problem("Object missing: patch set 1: " + rev,
FIX_FAILED, "Cannot delete patch set; no patch sets would remain"));
ctl = reload(ctl);
assertThat(ctl.getChange().currentPatchSetId().get()).isEqualTo(1);
assertThat(psUtil.current(db, ctl.getNotes())).isNotNull();
}
@Test
public void currentPatchSetMissing() throws Exception {
// NoteDb can't create a change without a patch set.
assume().that(notesMigration.enabled()).isFalse();
ChangeControl ctl = insertChange();
db.patchSets().deleteKeys(singleton(ctl.getChange().currentPatchSetId()));
assertProblems(ctl, null, problem("Current patch set 1 not found"));
}
@Test
public void duplicatePatchSetRevisions() throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
String rev = ps1.getRevision().get();
ctl = incrementPatchSet(
ctl, testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
assertProblems(
ctl, null,
problem("Multiple patch sets pointing to " + rev + ": [1, 2]"));
}
@Test
public void missingDestRef() throws Exception {
ChangeControl ctl = insertChange();
String ref = "refs/heads/master";
// Detach head so we're allowed to delete ref.
testRepo.reset(testRepo.getRepository().exactRef(ref).getObjectId());
RefUpdate ru = testRepo.getRepository().updateRef(ref);
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
assertProblems(
ctl, null,
problem("Destination ref not found (may be new branch): " + ref));
}
@Test
public void mergedChangeIsNotMerged() throws Exception {
ChangeControl ctl = insertChange();
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(ctl.getId(), new BatchUpdate.Op() {
@Override
public boolean updateChange(ChangeContext ctx) throws OrmException {
ctx.getChange().setStatus(Change.Status.MERGED);
ctx.getUpdate(ctx.getChange().currentPatchSetId())
.fixStatus(Change.Status.MERGED);
return true;
}
});
bu.execute();
}
ctl = reload(ctl);
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
ObjectId tip = getDestRef(ctl);
assertProblems(
ctl, null,
problem(
"Patch set 1 (" + rev + ") is not merged into destination ref"
+ " refs/heads/master (" + tip.name()
+ "), but change status is MERGED"));
}
@Test
public void newChangeIsMerged() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
assertProblems(
ctl, null,
problem(
"Patch set 1 (" + rev + ") is merged into destination ref"
+ " refs/heads/master (" + rev
+ "), but change status is NEW"));
}
@Test
public void newChangeIsMergedWithFix() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
assertProblems(
ctl, new FixInput(),
problem(
"Patch set 1 (" + rev + ") is merged into destination ref"
+ " refs/heads/master (" + rev
+ "), but change status is NEW",
FIXED, "Marked change as merged"));
ctl = reload(ctl);
assertThat(ctl.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
assertNoProblems(ctl, null);
}
@Test
public void extensionApiReturnsUpdatedValueAfterFix() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
ChangeInfo info = gApi.changes()
.id(ctl.getId().get())
.info();
assertThat(info.status).isEqualTo(ChangeStatus.NEW);
info = gApi.changes()
.id(ctl.getId().get())
.check(new FixInput());
assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
}
@Test
public void expectedMergedCommitIsLatestPatchSet() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev)));
FixInput fix = new FixInput();
fix.expectMergedAs = rev;
assertProblems(
ctl, fix,
problem(
"Patch set 1 (" + rev + ") is merged into destination ref"
+ " refs/heads/master (" + rev + "), but change status is NEW",
FIXED, "Marked change as merged"));
ctl = reload(ctl);
assertThat(ctl.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
assertNoProblems(ctl, null);
}
@Test
public void expectedMergedCommitNotMergedIntoDestination() throws Exception {
ChangeControl ctl = insertChange();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
RevCommit commit =
testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
testRepo.branch(ctl.getChange().getDest().get()).update(commit);
FixInput fix = new FixInput();
RevCommit other =
testRepo.commit().message(commit.getFullMessage()).create();
fix.expectMergedAs = other.name();
assertProblems(
ctl, fix,
problem(
"Expected merged commit " + other.name()
+ " is not merged into destination ref refs/heads/master"
+ " (" + commit.name() + ")"));
}
@Test
public void createNewPatchSetForExpectedMergeCommitWithNoChangeId()
throws Exception {
ChangeControl ctl = insertChange();
String dest = ctl.getChange().getDest().get();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
RevCommit commit =
testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
RevCommit mergedAs = testRepo.commit().parent(commit.getParent(0))
.message(commit.getShortMessage()).create();
testRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID)).isEmpty();
testRepo.update(dest, mergedAs);
assertNoProblems(ctl, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
ctl, 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"));
ctl = reload(ctl);
PatchSet.Id psId2 = new PatchSet.Id(ctl.getId(), 2);
assertThat(ctl.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(psUtil.get(db, ctl.getNotes(), psId2).getRevision().get())
.isEqualTo(mergedAs.name());
assertNoProblems(ctl, null);
}
@Test
public void createNewPatchSetForExpectedMergeCommitWithChangeId()
throws Exception {
ChangeControl ctl = insertChange();
String dest = ctl.getChange().getDest().get();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
RevCommit commit =
testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
RevCommit mergedAs = testRepo.commit().parent(commit.getParent(0))
.message(commit.getShortMessage() + "\n"
+ "\n"
+ "Change-Id: " + ctl.getChange().getKey().get() + "\n").create();
testRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID))
.containsExactly(ctl.getChange().getKey().get());
testRepo.update(dest, mergedAs);
assertNoProblems(ctl, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
ctl, 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"));
ctl = reload(ctl);
PatchSet.Id psId2 = new PatchSet.Id(ctl.getId(), 2);
assertThat(ctl.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(psUtil.get(db, ctl.getNotes(), psId2).getRevision().get())
.isEqualTo(mergedAs.name());
assertNoProblems(ctl, null);
}
@Test
public void expectedMergedCommitIsOldPatchSetOfSameChange()
throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
String rev1 = ps1.getRevision().get();
ctl = incrementPatchSet(ctl);
PatchSet ps2 = psUtil.current(db, ctl.getNotes());
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev1)));
FixInput fix = new FixInput();
fix.expectMergedAs = rev1;
assertProblems(
ctl, fix,
problem(
"No patch set found for merged commit " + rev1,
FIXED, "Marked change as merged"),
problem(
"Expected merge commit " + rev1 + " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED, "Deleted patch set"),
problem(
"Expected merge commit " + rev1 + " corresponds to patch set 1,"
+ " not the current patch set 2",
FIXED, "Inserted as patch set 3"));
ctl = reload(ctl);
PatchSet.Id psId3 = new PatchSet.Id(ctl.getId(), 3);
assertThat(ctl.getChange().currentPatchSetId()).isEqualTo(psId3);
assertThat(ctl.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
assertThat(psUtil.byChangeAsMap(db, ctl.getNotes()).keySet())
.containsExactly(ps2.getId(), psId3);
assertThat(psUtil.get(db, ctl.getNotes(), psId3).getRevision().get())
.isEqualTo(rev1);
}
@Test
public void expectedMergedCommitIsDanglingPatchSetOlderThanCurrent()
throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
// Create dangling ref so next ID in the database becomes 3.
PatchSet.Id psId2 = new PatchSet.Id(ctl.getId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
String rev2 = commit2.name();
testRepo.branch(psId2.toRefName()).update(commit2);
ctl = incrementPatchSet(ctl);
PatchSet ps3 = psUtil.current(db, ctl.getNotes());
assertThat(ps3.getId().get()).isEqualTo(3);
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev2)));
FixInput fix = new FixInput();
fix.expectMergedAs = rev2;
assertProblems(
ctl, fix,
problem(
"No patch set found for merged commit " + rev2,
FIXED, "Marked change as merged"),
problem(
"Expected merge commit " + rev2 + " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED, "Deleted patch set"),
problem(
"Expected merge commit " + rev2 + " corresponds to patch set 2,"
+ " not the current patch set 3",
FIXED, "Inserted as patch set 4"));
ctl = reload(ctl);
PatchSet.Id psId4 = new PatchSet.Id(ctl.getId(), 4);
assertThat(ctl.getChange().currentPatchSetId()).isEqualTo(psId4);
assertThat(ctl.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
assertThat(psUtil.byChangeAsMap(db, ctl.getNotes()).keySet())
.containsExactly(ps1.getId(), ps3.getId(), psId4);
assertThat(psUtil.get(db, ctl.getNotes(), psId4).getRevision().get())
.isEqualTo(rev2);
}
@Test
public void expectedMergedCommitIsDanglingPatchSetNewerThanCurrent()
throws Exception {
ChangeControl ctl = insertChange();
PatchSet ps1 = psUtil.current(db, ctl.getNotes());
// Create dangling ref with no patch set.
PatchSet.Id psId2 = new PatchSet.Id(ctl.getId(), 2);
RevCommit commit2 = patchSetCommit(psId2);
String rev2 = commit2.name();
testRepo.branch(psId2.toRefName()).update(commit2);
testRepo.branch(ctl.getChange().getDest().get())
.update(testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev2)));
FixInput fix = new FixInput();
fix.expectMergedAs = rev2;
assertProblems(
ctl, fix,
problem(
"No patch set found for merged commit " + rev2,
FIXED, "Marked change as merged"),
problem(
"Expected merge commit " + rev2 + " corresponds to patch set 2,"
+ " not the current patch set 1",
FIXED, "Inserted as patch set 2"));
ctl = reload(ctl);
assertThat(ctl.getChange().currentPatchSetId()).isEqualTo(psId2);
assertThat(ctl.getChange().getStatus()).isEqualTo(Change.Status.MERGED);
assertThat(psUtil.byChangeAsMap(db, ctl.getNotes()).keySet())
.containsExactly(ps1.getId(), psId2);
assertThat(psUtil.get(db, ctl.getNotes(), psId2).getRevision().get())
.isEqualTo(rev2);
}
@Test
public void expectedMergedCommitWithMismatchedChangeId() throws Exception {
ChangeControl ctl = insertChange();
String dest = ctl.getChange().getDest().get();
RevCommit parent =
testRepo.branch(dest).commit().message("parent").create();
String rev = psUtil.current(db, ctl.getNotes()).getRevision().get();
RevCommit commit =
testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
testRepo.branch(dest).update(commit);
String badId = "I0000000000000000000000000000000000000000";
RevCommit mergedAs = testRepo.commit().parent(parent)
.message(commit.getShortMessage() + "\n"
+ "\n"
+ "Change-Id: " + badId + "\n")
.create();
testRepo.getRevWalk().parseBody(mergedAs);
assertThat(mergedAs.getFooterLines(FooterConstants.CHANGE_ID))
.containsExactly(badId);
testRepo.update(dest, mergedAs);
assertNoProblems(ctl, null);
FixInput fix = new FixInput();
fix.expectMergedAs = mergedAs.name();
assertProblems(
ctl, fix,
problem(
"Expected merged commit " + mergedAs.name() + " has Change-Id: "
+ badId + ", but expected " + ctl.getChange().getKey().get()));
}
@Test
public void expectedMergedCommitMatchesMultiplePatchSets()
throws Exception {
ChangeControl ctl1 = insertChange();
PatchSet.Id psId1 = psUtil.current(db, ctl1.getNotes()).getId();
String dest = ctl1.getChange().getDest().get();
String rev = psUtil.current(db, ctl1.getNotes()).getRevision().get();
RevCommit commit =
testRepo.getRevWalk().parseCommit(ObjectId.fromString(rev));
testRepo.branch(dest).update(commit);
ChangeControl ctl2 = insertChange();
ctl2 = incrementPatchSet(ctl2, commit);
PatchSet.Id psId2 = psUtil.current(db, ctl2.getNotes()).getId();
ChangeControl ctl3 = insertChange();
ctl3 = incrementPatchSet(ctl3, commit);
PatchSet.Id psId3 = psUtil.current(db, ctl3.getNotes()).getId();
FixInput fix = new FixInput();
fix.expectMergedAs = commit.name();
assertProblems(
ctl1, fix,
problem(
"Multiple patch sets for expected merged commit " + commit.name()
+ ": [" + psId1 + ", " + psId2 + ", " + psId3 + "]"));
}
private BatchUpdate newUpdate(Account.Id owner) {
return updateFactory.create(
db, project, userFactory.create(owner), TimeUtil.nowTs());
}
private ChangeControl insertChange() throws Exception {
return insertChange(admin);
}
private ChangeControl insertChange(TestAccount owner) throws Exception {
return insertChange(owner, "refs/heads/master");
}
private ChangeControl insertChange(TestAccount owner, String dest)
throws Exception {
Change.Id id = new Change.Id(sequences.nextChangeId());
ChangeInserter ins;
try (BatchUpdate bu = newUpdate(owner.getId())) {
RevCommit commit = patchSetCommit(new PatchSet.Id(id, 1));
ins = changeInserterFactory
.create(id, commit, dest)
.setValidatePolicy(CommitValidators.Policy.NONE)
.setNotify(NotifyHandling.NONE)
.setFireRevisionCreated(false)
.setSendMail(false);
bu.insertChange(ins).execute();
}
// Return control for admin regardless of owner.
return changeControlFactory.controlFor(
db, ins.getChange(), userFactory.create(adminId));
}
private PatchSet.Id nextPatchSetId(ChangeControl ctl) throws Exception {
return ChangeUtil.nextPatchSetId(
testRepo.getRepository(), ctl.getChange().currentPatchSetId());
}
private ChangeControl incrementPatchSet(ChangeControl ctl) throws Exception {
return incrementPatchSet(ctl, patchSetCommit(nextPatchSetId(ctl)));
}
private ChangeControl incrementPatchSet(ChangeControl ctl,
RevCommit commit) throws Exception {
PatchSetInserter ins;
try (BatchUpdate bu = newUpdate(ctl.getChange().getOwner())) {
ins = patchSetInserterFactory.create(ctl, nextPatchSetId(ctl), commit)
.setValidatePolicy(CommitValidators.Policy.NONE)
.setFireRevisionCreated(false)
.setSendMail(false);
bu.addOp(ctl.getId(), ins).execute();
}
return reload(ctl);
}
private ChangeControl reload(ChangeControl ctl) throws Exception {
return changeControlFactory.controlFor(
db, ctl.getChange().getProject(), ctl.getId(), ctl.getUser());
}
private RevCommit patchSetCommit(PatchSet.Id psId) throws Exception {
RevCommit c = testRepo
.commit()
.parent(tip)
.message("Change " + psId)
.create();
return testRepo.parseBody(c);
}
private PatchSet insertMissingPatchSet(ChangeControl ctl, 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(ctl.getChange());
PatchSet.Id psId = nextPatchSetId(ctl);
c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
PatchSet ps = newPatchSet(psId, rev, adminId);
db.patchSets().insert(singleton(ps));
db.changes().update(singleton(c));
addNoteDbCommit(
c.getId(),
"Update patch set " + psId.get() + "\n"
+ "\n"
+ "Patch-set: " + psId.get() + "\n"
+ "Commit: " + rev + "\n"
+ "Subject: " + subject + "\n");
indexer.index(db, c.getProject(), c.getId());
return ps;
}
private void deleteRef(String refName) throws Exception {
RefUpdate ru = testRepo.getRepository().updateRef(refName, true);
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
}
private void addNoteDbCommit(Change.Id id, String commitMessage)
throws Exception {
if (!notesMigration.commitChangeWrites()) {
return;
}
PersonIdent committer = serverIdent.get();
PersonIdent author = noteUtil.newIdent(
accountCache.get(admin.getId()).getAccount(),
committer.getWhen(),
committer,
anonymousCowardName);
testRepo.branch(RefNames.changeMetaRef(id))
.commit()
.author(author)
.committer(committer)
.message(commitMessage)
.create();
}
private ObjectId getDestRef(ChangeControl ctl) throws Exception {
return testRepo.getRepository()
.exactRef(ctl.getChange().getDest().get())
.getObjectId();
}
private ChangeControl mergeChange(ChangeControl ctl) throws Exception {
final ObjectId oldId = getDestRef(ctl);
final ObjectId newId = ObjectId.fromString(
psUtil.current(db, ctl.getNotes()).getRevision().get());
final String dest = ctl.getChange().getDest().get();
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(ctl.getId(), new BatchUpdate.Op() {
@Override
public void updateRepo(RepoContext ctx) throws IOException {
ctx.addRefUpdate(new ReceiveCommand(oldId, newId, dest));
}
@Override
public boolean updateChange(ChangeContext ctx) throws OrmException {
ctx.getChange().setStatus(Change.Status.MERGED);
ctx.getUpdate(ctx.getChange().currentPatchSetId())
.fixStatus(Change.Status.MERGED);
return true;
}
});
bu.execute();
}
return reload(ctl);
}
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 = checkNotNull(status);
p.outcome = checkNotNull(outcome);
return p;
}
private void assertProblems(ChangeControl ctl, @Nullable FixInput fix,
ProblemInfo first, ProblemInfo... rest) {
List<ProblemInfo> expected = new ArrayList<>(1 + rest.length);
expected.add(first);
expected.addAll(Arrays.asList(rest));
assertThat(checker.check(ctl, fix).problems())
.containsExactlyElementsIn(expected)
.inOrder();
}
private void assertNoProblems(ChangeControl ctl, @Nullable FixInput fix) {
assertThat(checker.check(ctl, fix).problems()).isEmpty();
}
}