Support setting current patch set in NoteDb

Until now, NoteDb was not explicitly recording the current patch set,
and instead just assuming that the highest-numbered non-deleted patch
set was current. This is usually fine, but there is at least one
corner case where it's not.

Specifically, when pushing an old patch set of a change directly to a
branch:

 * The change moves to state MERGED.
 * The currentPatchSetId field gets set to the corresponding patch
   set ID of the merged commit, even if this is less than the maximum
   patch set in the database.

Teach NoteDb to handle this case with a footer "Current: true"
indicating the patch set mentioned in the "Patch-set" footer is the
current patch set. Creating a new patch set also implicitly sets it to
current, matching existing data.

Various parts of the code in ChangeRebuilderImpl and ChangeBundle were
assuming that any entities referring to patch sets greater than the
current patch set were ignorable. Remove this assumption, and ensure
we're handling these entities properly.

Add a test for the above case.

Change-Id: I099fd703458ffd63a31009ceaaa1c1d2c59d4669
This commit is contained in:
Dave Borowitz
2016-12-12 12:58:32 -05:00
parent 81f334a77f
commit e5ee0d3865
13 changed files with 252 additions and 111 deletions

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.acceptance.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.assertPushOk;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -233,6 +234,34 @@ public class SubmitOnPushIT extends AbstractDaemonTest {
assertThat(cd.patchSet(psId2).getRevision().get()).isEqualTo(c2.name());
}
@Test
public void mergeOnPushToBranchWithOldPatchset() throws Exception {
grant(Permission.PUSH, project, "refs/heads/master");
PushOneCommit.Result r = pushTo("refs/for/master");
r.assertOkStatus();
RevCommit c1 = r.getCommit();
PatchSet.Id psId1 = r.getPatchSetId();
String changeId = r.getChangeId();
assertThat(psId1.get()).isEqualTo(1);
r = amendChange(changeId);
ChangeData cd = r.getChange();
PatchSet.Id psId2 = cd.change().currentPatchSetId();
assertThat(psId2.getParentKey()).isEqualTo(psId1.getParentKey());
assertThat(psId2.get()).isEqualTo(2);
testRepo.reset(c1);
assertPushOk(
pushHead(testRepo, "refs/heads/master", false), "refs/heads/master");
cd = changeDataFactory.create(db, project, psId1.getParentKey());
Change c = cd.change();
assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED);
assertThat(c.currentPatchSetId()).isEqualTo(psId1);
assertThat(cd.patchSets().stream().map(ps -> ps.getId()).collect(toList()))
.containsExactly(psId1, psId2);
}
@Test
public void mergeMultipleOnPushToBranchWithNewPatchset() throws Exception {
grant(Permission.PUSH, project, "refs/heads/master");

View File

@@ -49,7 +49,6 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.ChangeUtil;
@@ -73,6 +72,8 @@ import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.testutil.ConfigSuite;
@@ -150,6 +151,9 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
@Inject
private ChangeBundleReader bundleReader;
@Inject
private PatchSetInfoFactory patchSetInfoFactory;
@Before
public void setUp() throws Exception {
assume().that(NoteDbMode.readWrite()).isFalse();
@@ -849,28 +853,6 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
assertThat(notes.getComments()).isEmpty();
}
@Test
public void skipPatchSetsGreaterThanCurrentPatchSet() throws Exception {
PushOneCommit.Result r = createChange();
Change change = r.getChange().change();
Change.Id id = change.getId();
PatchSet badPs =
new PatchSet(new PatchSet.Id(id, change.currentPatchSetId().get() + 1));
badPs.setCreatedOn(TimeUtil.nowTs());
badPs.setUploader(new Account.Id(12345));
badPs.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
db.patchSets().insert(Collections.singleton(badPs));
indexer.index(db, change.getProject(), id);
checker.rebuildAndCheckChanges(id);
setNotesMigration(true, true);
ChangeNotes notes = notesFactory.create(db, project, id);
assertThat(notes.getPatchSets().keySet())
.containsExactly(change.currentPatchSetId());
}
@Test
public void leadingSpacesInSubject() throws Exception {
String subj = " " + PushOneCommit.SUBJECT;
@@ -1143,6 +1125,43 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
assertThat(ps.getId()).isEqualTo(psId1);
}
@Test
public void highestNumberedPatchSetIsNotCurrent() throws Exception {
PushOneCommit.Result r1 = createChange();
PatchSet.Id psId1 = r1.getPatchSetId();
Change.Id id = psId1.getParentKey();
PushOneCommit.Result r2 = amendChange(r1.getChangeId());
PatchSet.Id psId2 = r2.getPatchSetId();
try (BatchUpdate bu = batchUpdateFactory.create(db, project,
identifiedUserFactory.create(user.getId()), TimeUtil.nowTs())) {
bu.addOp(id, new BatchUpdate.Op() {
@Override
public boolean updateChange(ChangeContext ctx)
throws PatchSetInfoNotAvailableException {
ctx.getChange().setCurrentPatchSet(
patchSetInfoFactory.get(ctx.getDb(), ctx.getNotes(), psId1));
return true;
}
});
bu.execute();
}
ChangeNotes notes = notesFactory.create(db, project, id);
assertThat(psUtil.byChangeAsMap(db, notes).keySet())
.containsExactly(psId1, psId2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId1);
assertThat(db.changes().get(id).currentPatchSetId()).isEqualTo(psId1);
checker.rebuildAndCheckChanges(id);
setNotesMigration(true, true);
notes = notesFactory.create(db, project, id);
assertThat(psUtil.byChangeAsMap(db, notes).keySet())
.containsExactly(psId1, psId2);
assertThat(notes.getChange().currentPatchSetId()).isEqualTo(psId1);
}
private void assertChangesReadOnly(RestApiException e) throws Exception {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(UpdateException.class);

View File

@@ -135,6 +135,7 @@ public class MergedByPushOp extends BatchUpdate.Op {
// we cannot reconstruct the submit records for when this change was
// submitted, this is why we must fix the status
update.fixStatus(Change.Status.MERGED);
update.setCurrentPatchSet();
}
StringBuilder msgBuf = new StringBuilder();

View File

@@ -365,8 +365,7 @@ public class ChangeBundle {
}
private Predicate<PatchSet.Id> validPatchSetPredicate() {
Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate();
return p -> upToCurrent.apply(p) && patchSets.containsKey(p);
return patchSets::containsKey;
}
private Collection<ChangeMessage> filterChangeMessages() {
@@ -380,19 +379,6 @@ public class ChangeBundle {
});
}
private Predicate<PatchSet.Id> upToCurrentPredicate() {
PatchSet.Id current = change.currentPatchSetId();
if (current == null) {
return Predicates.alwaysFalse();
}
int max = current.get();
return p -> p.get() <= max;
}
private Map<PatchSet.Id, PatchSet> filterPatchSets() {
return Maps.filterKeys(patchSets, upToCurrentPredicate());
}
private static void diffChanges(List<String> diffs, ChangeBundle bundleA,
ChangeBundle bundleB) {
Change a = bundleA.change;
@@ -659,8 +645,8 @@ public class ChangeBundle {
private static void diffPatchSets(List<String> diffs, ChangeBundle bundleA,
ChangeBundle bundleB) {
Map<PatchSet.Id, PatchSet> as = bundleA.filterPatchSets();
Map<PatchSet.Id, PatchSet> bs = bundleB.filterPatchSets();
Map<PatchSet.Id, PatchSet> as = bundleA.patchSets;
Map<PatchSet.Id, PatchSet> bs = bundleB.patchSets;
for (PatchSet.Id id : diffKeySets(diffs, as, bs)) {
PatchSet a = as.get(id);
PatchSet b = bs.get(id);

View File

@@ -67,6 +67,7 @@ public class ChangeNoteUtil {
public static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
public static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id");
public static final FooterKey FOOTER_COMMIT = new FooterKey("Commit");
public static final FooterKey FOOTER_CURRENT = new FooterKey("Current");
public static final FooterKey FOOTER_GROUPS = new FooterKey("Groups");
public static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
public static final FooterKey FOOTER_LABEL = new FooterKey("Label");

View File

@@ -18,6 +18,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
@@ -90,7 +91,6 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -137,6 +137,7 @@ class ChangeNotesParser {
private final TreeMap<PatchSet.Id, PatchSet> patchSets;
private final Set<PatchSet.Id> deletedPatchSets;
private final Map<PatchSet.Id, PatchSetState> patchSetStates;
private final List<PatchSet.Id> currentPatchSets;
private final Map<ApprovalKey, PatchSetApproval> approvals;
private final List<PatchSetApproval> bufferedApprovals;
private final List<ChangeMessage> allChangeMessages;
@@ -157,7 +158,6 @@ class ChangeNotesParser {
private String originalSubject;
private String submissionId;
private String tag;
private PatchSet.Id currentPatchSetId;
private RevisionNoteMap<ChangeRevisionNote> revisionNoteMap;
ChangeNotesParser(Change.Id changeId, ObjectId tip, ChangeNotesRevWalk walk,
@@ -179,6 +179,7 @@ class ChangeNotesParser {
patchSets = Maps.newTreeMap(comparing(PatchSet.Id::get));
deletedPatchSets = new HashSet<>();
patchSetStates = new HashMap<>();
currentPatchSets = new ArrayList<>();
}
ChangeNotesState parseAll()
@@ -218,7 +219,7 @@ class ChangeNotesParser {
lastUpdatedOn,
ownerId,
branch,
currentPatchSetId,
buildCurrentPatchSetId(),
subject,
topic,
originalSubject,
@@ -239,6 +240,17 @@ class ChangeNotesParser {
comments);
}
private PatchSet.Id buildCurrentPatchSetId() {
// currentPatchSets are in parse order, i.e. newest first. Pick the first
// patch set that was marked as current, excluding deleted patch sets.
for (PatchSet.Id psId : currentPatchSets) {
if (patchSets.containsKey(psId)) {
return psId;
}
}
return null;
}
private Multimap<PatchSet.Id, PatchSetApproval> buildApprovals() {
Multimap<PatchSet.Id, PatchSetApproval> result =
MultimapBuilder.hashKeys().arrayListValues().build();
@@ -339,6 +351,7 @@ class ChangeNotesParser {
parsePatchSet(psId, currRev, accountId, ts);
}
parseGroups(psId, commit);
parseCurrentPatchSet(psId, commit);
if (submitRecords.isEmpty()) {
// Only parse the most recent set of submit records; any older ones are
@@ -485,6 +498,28 @@ class ChangeNotesParser {
ps.setGroups(PatchSet.splitGroups(groupsStr));
}
private void parseCurrentPatchSet(PatchSet.Id psId, ChangeNotesCommit commit)
throws ConfigInvalidException {
// This commit implies a new current patch set if either it creates a new
// patch set, or sets the current field explicitly.
boolean current = false;
if (parseOneFooter(commit, FOOTER_COMMIT) != null) {
current = true;
} else {
String currentStr = parseOneFooter(commit, FOOTER_CURRENT);
if (Boolean.TRUE.toString().equalsIgnoreCase(currentStr)) {
current = true;
} else if (currentStr != null) {
// Only "true" is allowed; unsetting the current patch set makes no
// sense.
throw invalidFooter(FOOTER_CURRENT, currentStr);
}
}
if (current) {
currentPatchSets.add(psId);
}
}
private void parseHashtags(ChangeNotesCommit commit)
throws ConfigInvalidException {
// Commits are parsed in reverse order and only the last set of hashtags
@@ -937,13 +972,7 @@ class ChangeNotesParser {
// (or otherwise missing) patch sets. This is safer than trying to prevent
// insertion, as it will also filter out items racily added after the patch
// set was deleted.
NavigableSet<PatchSet.Id> all = patchSets.navigableKeySet();
if (!all.isEmpty()) {
currentPatchSetId = all.last();
} else {
currentPatchSetId = null;
}
changeMessagesByPatchSet.keys().retainAll(all);
changeMessagesByPatchSet.keys().retainAll(patchSets.keySet());
int pruned = pruneEntitiesForMissingPatchSets(
allChangeMessages, ChangeMessage::getPatchSetId, missing);

View File

@@ -23,6 +23,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CURRENT;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_GROUPS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_LABEL;
@@ -144,6 +145,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private String pushCert;
private boolean isAllowWriteToNewtRef;
private String psDescription;
private boolean currentPatchSet;
private ChangeDraftUpdate draftUpdate;
private RobotCommentUpdate robotCommentUpdate;
@@ -440,6 +442,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
this.psState = psState;
}
public void setCurrentPatchSet() {
this.currentPatchSet = true;
}
public void setGroups(List<String> groups) {
checkNotNull(groups, "groups may not be null");
this.groups = groups;
@@ -567,6 +573,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
addPatchSetFooter(msg, ps);
if (currentPatchSet) {
addFooter(msg, FOOTER_CURRENT, Boolean.TRUE);
}
if (psDescription != null) {
addFooter(msg, FOOTER_PATCH_SET_DESCRIPTION, psDescription);
}
@@ -714,7 +724,8 @@ public class ChangeUpdate extends AbstractChangeUpdate {
&& psState == null
&& groups == null
&& tag == null
&& psDescription == null;
&& psDescription == null
&& !currentPatchSet;
}
ChangeDraftUpdate getDraftUpdate() {

View File

@@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -78,8 +79,6 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.sql.Timestamp;
@@ -93,9 +92,6 @@ import java.util.Optional;
import java.util.Set;
public class ChangeRebuilderImpl extends ChangeRebuilder {
private static final Logger log =
LoggerFactory.getLogger(ChangeRebuilderImpl.class);
/**
* The maximum amount of time between the ReviewDb timestamp of the first and
* last events batched together into a single NoteDb update.
@@ -275,7 +271,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
throw new NoPatchSetsException(change.getId());
}
PatchSet.Id currPsId = change.currentPatchSetId();
// We will rebuild all events, except for draft comments, in buckets based
// on author and timestamp.
List<Event> events = new ArrayList<>();
@@ -293,12 +288,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
Maps.newHashMapWithExpectedSize(bundle.getPatchSets().size());
for (PatchSet ps : bundle.getPatchSets()) {
if (ps.getId().get() > currPsId.get()) {
log.info(
"Skipping patch set {}, which is higher than current patch set {}",
ps.getId(), currPsId);
continue;
}
PatchSetEvent pse =
new PatchSetEvent(change, ps, manager.getChangeRepo().rw);
patchSetEvents.put(ps.getId(), pse);
@@ -342,7 +331,8 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
events.addAll(msgEvents);
}
sortAndFillEvents(change, noteDbChange, events, minPsNum);
sortAndFillEvents(
change, noteDbChange, bundle.getPatchSets(), events, minPsNum);
EventList<Event> el = new EventList<>();
for (Event e : events) {
@@ -401,8 +391,9 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
}
private void sortAndFillEvents(Change change, Change noteDbChange,
ImmutableCollection<PatchSet> patchSets,
List<Event> events, Integer minPsNum) {
Event finalUpdates = new FinalUpdatesEvent(change, noteDbChange);
Event finalUpdates = new FinalUpdatesEvent(change, noteDbChange, patchSets);
events.add(finalUpdates);
setPostSubmitDeps(events);
new EventSorter(events).sort();

View File

@@ -14,7 +14,11 @@
package com.google.gerrit.server.notedb.rebuild;
import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
import com.google.common.collect.ImmutableCollection;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gwtorm.server.OrmException;
@@ -23,12 +27,15 @@ import java.util.Objects;
class FinalUpdatesEvent extends Event {
private final Change change;
private final Change noteDbChange;
private final ImmutableCollection<PatchSet> patchSets;
FinalUpdatesEvent(Change change, Change noteDbChange) {
FinalUpdatesEvent(Change change, Change noteDbChange,
ImmutableCollection<PatchSet> patchSets) {
super(change.currentPatchSetId(), change.getOwner(), change.getOwner(),
change.getLastUpdatedOn(), change.getCreatedOn(), null);
this.change = change;
this.noteDbChange = noteDbChange;
this.patchSets = patchSets;
}
@Override
@@ -54,11 +61,20 @@ class FinalUpdatesEvent extends Event {
// TODO(dborowitz): Parse intermediate values out from messages.
update.setAssignee(change.getAssignee());
}
if (!patchSets.isEmpty() && !highestNumberedPatchSetIsCurrent()) {
update.setCurrentPatchSet();
}
if (!update.isEmpty()) {
update.setSubjectForCommit("Final NoteDb migration updates");
}
}
private boolean highestNumberedPatchSetIsCurrent() {
PatchSet.Id max =
patchSets.stream().map(PatchSet::getId).max(intKeyOrdering()).get();
return max.equals(change.currentPatchSetId());
}
@Override
protected boolean isSubmit() {
return change.getStatus() == Change.Status.MERGED;

View File

@@ -769,39 +769,6 @@ public class ChangeBundleTest extends GerritBaseTests {
+ "Only in B:\n " + cm1);
}
@Test
public void diffChangeMessagesIgnoresMessagesOnPatchSetGreaterThanCurrent()
throws Exception {
Change c = TestChanges.newChange(project, accountId);
PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
ps1.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
ps1.setUploader(accountId);
ps1.setCreatedOn(TimeUtil.nowTs());
PatchSet ps2 = new PatchSet(new PatchSet.Id(c.getId(), 2));
ps2.setRevision(new RevId("badc0feebadc0feebadc0feebadc0feebadc0fee"));
ps2.setUploader(accountId);
ps2.setCreatedOn(TimeUtil.nowTs());
assertThat(c.currentPatchSetId()).isEqualTo(ps1.getId());
ChangeMessage cm1 = new ChangeMessage(
new ChangeMessage.Key(c.getId(), "uuid1"),
accountId, TimeUtil.nowTs(), ps1.getId());
cm1.setMessage("a message");
ChangeMessage cm2 = new ChangeMessage(
new ChangeMessage.Key(c.getId(), "uuid2"),
accountId, TimeUtil.nowTs(), ps2.getId());
cm2.setMessage("other message");
ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm2),
patchSets(ps1, ps2), approvals(), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(cm1), patchSets(ps1),
approvals(), comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
}
@Test
public void diffPatchSetIdSets() throws Exception {
Change c = TestChanges.newChange(project, accountId);
@@ -919,7 +886,7 @@ public class ChangeBundleTest extends GerritBaseTests {
}
@Test
public void diffIgnoresPatchSetsGreaterThanCurrent() throws Exception {
public void diffPatchSetsGreaterThanCurrent() throws Exception {
Change c = TestChanges.newChange(project, accountId);
PatchSet ps1 = new PatchSet(new PatchSet.Id(c.getId(), 1));
@@ -932,6 +899,13 @@ public class ChangeBundleTest extends GerritBaseTests {
ps2.setCreatedOn(TimeUtil.nowTs());
assertThat(ps2.getId().get()).isGreaterThan(c.currentPatchSetId().get());
ChangeMessage cm1 = new ChangeMessage(
new ChangeMessage.Key(c.getId(), "uuid1"),
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
ChangeMessage cm2 = new ChangeMessage(
new ChangeMessage.Key(c.getId(), "uuid2"),
accountId, TimeUtil.nowTs(), c.currentPatchSetId());
PatchSetApproval a1 = new PatchSetApproval(
new PatchSetApproval.Key(
ps1.getId(), accountId, new LabelId("Code-Review")),
@@ -944,26 +918,44 @@ public class ChangeBundleTest extends GerritBaseTests {
TimeUtil.nowTs());
// Both ReviewDb.
ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(ps1),
ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(ps1),
approvals(a1), comments(), reviewers(), REVIEW_DB);
ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2),
approvals(a1, a2), comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
ChangeBundle b2 = new ChangeBundle(c, messages(cm1, cm2),
patchSets(ps1, ps2), approvals(a1, a2), comments(), reviewers(),
REVIEW_DB);
assertDiffs(b1, b2,
"ChangeMessage.Key sets differ: [] only in A; [" + cm2.getKey()
+ "] only in B",
"PatchSet.Id sets differ:"
+ " [] only in A; [" + ps2.getId() + "] only in B",
"PatchSetApproval.Key sets differ:"
+ " [] only in A; [" + a2.getKey() + "] only in B");
// One NoteDb.
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
b1 = new ChangeBundle(c, messages(cm1), patchSets(ps1), approvals(a1),
comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
b2 = new ChangeBundle(c, messages(cm1, cm2), patchSets(ps1, ps2), approvals(a1, a2),
comments(), reviewers(), REVIEW_DB);
assertNoDiffs(b1, b2);
assertNoDiffs(b2, b1);
assertDiffs(b1, b2,
"ChangeMessages differ for Change.Id " + c.getId() + "\n"
+ "Only in B:\n " + cm2,
"PatchSet.Id sets differ:"
+ " [] only in A; [" + ps2.getId() + "] only in B",
"PatchSetApproval.Key sets differ:"
+ " [] only in A; [" + a2.getKey() + "] only in B");
// Both NoteDb.
b1 = new ChangeBundle(c, messages(), patchSets(ps1), approvals(a1),
b1 = new ChangeBundle(c, messages(cm1), patchSets(ps1), approvals(a1),
comments(), reviewers(), NOTE_DB);
b2 = new ChangeBundle(c, messages(), patchSets(ps1, ps2), approvals(a1, a2),
b2 = new ChangeBundle(c, messages(cm1, cm2), patchSets(ps1, ps2), approvals(a1, a2),
comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
assertDiffs(b1, b2,
"ChangeMessages differ for Change.Id " + c.getId() + "\n"
+ "Only in B:\n " + cm2,
"PatchSet.Id sets differ:"
+ " [] only in A; [" + ps2.getId() + "] only in B",
"PatchSetApproval.Key sets differ:"
+ " [] only in A; [" + a2.getKey() + "] only in B");
}
@Test

View File

@@ -448,6 +448,26 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
+ "subject: This is a test change\n");
}
@Test
public void currentPatchSet() throws Exception {
assertParseSucceeds("Update change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Current: true");
assertParseSucceeds("Update change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Current: tRUe");
assertParseFails("Update change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Current: false");
assertParseFails("Update change\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Current: blah");
}
private RevCommit writeCommit(String body) throws Exception {
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
return writeCommit(body, noteUtil.newIdent(

View File

@@ -2631,6 +2631,38 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
assertThat(notes.getComments()).hasSize(numComments);
}
@Test
public void currentPatchSet() throws Exception {
Change c = newChange();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
incrementPatchSet(c);
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
ChangeUpdate update = newUpdate(c, changeOwner);
update.setPatchSetId(new PatchSet.Id(c.getId(), 1));
update.setCurrentPatchSet();
update.commit();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
incrementPatchSet(c);
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(3);
// Delete PS3, PS1 becomes current, as the most recent event explicitly set
// it to current.
update = newUpdate(c, changeOwner);
update.setPatchSetState(PatchSetState.DELETED);
update.commit();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
// Delete PS1, PS2 becomes current.
update = newUpdate(c, changeOwner);
update.setPatchSetId(new PatchSet.Id(c.getId(), 1));
update.setPatchSetState(PatchSetState.DELETED);
update.commit();
assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
}
private boolean testJson() {
return noteUtil.getWriteJson();
}

View File

@@ -356,6 +356,20 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
commit);
}
@Test
public void currentPatchSet() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setCurrentPatchSet();
update.commit();
assertBodyEquals("Update patch set 1\n"
+ "\n"
+ "Patch-set: 1\n"
+ "Current: true\n",
update.getResult());
}
private RevCommit parseCommit(ObjectId id) throws Exception {
if (id instanceof RevCommit) {
return (RevCommit) id;