ChangeRebuilder: Add dependencies on PatchSetEvent

This ensures events for patch sets don't get sorted before the
PatchSetEvent that created them. This can happen due to timestamp
skew, or the corner case described in If8ef0726.

Change-Id: I45b16437518fb396e1d42d4aedb21a42258364ac
This commit is contained in:
Dave Borowitz
2016-09-16 13:27:36 -04:00
parent c1e78eda69
commit 96586f59d8
5 changed files with 65 additions and 12 deletions

View File

@@ -715,6 +715,8 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
rin.message = "comment"; rin.message = "comment";
Timestamp ts = new Timestamp(c.getCreatedOn().getTime() + 2000); Timestamp ts = new Timestamp(c.getCreatedOn().getTime() + 2000);
assertThat(ts).isGreaterThan(c.getCreatedOn());
assertThat(ts).isLessThan(db.patchSets().get(psId).getCreatedOn());
RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId()); RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId());
postReview.get().apply(revRsrc, rin, ts); postReview.get().apply(revRsrc, rin, ts);

View File

@@ -624,17 +624,31 @@ public class ChangeBundle {
List<String> tempDiffs = new ArrayList<>(); List<String> tempDiffs = new ArrayList<>();
String temp = "temp"; String temp = "temp";
// ReviewDb allows timestamps before patch set was created, but NoteDb
// truncates this to the patch set creation timestamp.
Timestamp ta = a.getWrittenOn();
Timestamp tb = b.getWrittenOn();
PatchSet psa = bundleA.patchSets.get(a.getPatchSetId());
PatchSet psb = bundleB.patchSets.get(b.getPatchSetId());
boolean excludePatchSet = false; boolean excludePatchSet = false;
boolean excludeWrittenOn = false;
if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) { if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
excludePatchSet = a.getPatchSetId() == null; excludePatchSet = a.getPatchSetId() == null;
excludeWrittenOn = psa != null && psb != null
&& ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn());
} else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) { } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
excludePatchSet = b.getPatchSetId() == null; excludePatchSet = b.getPatchSetId() == null;
excludeWrittenOn = psa != null && psb != null
&& tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn());
} }
List<String> exclude = Lists.newArrayList("key"); List<String> exclude = Lists.newArrayList("key");
if (excludePatchSet) { if (excludePatchSet) {
exclude.add("patchset"); exclude.add("patchset");
} }
if (excludeWrittenOn) {
exclude.add("writtenOn");
}
diffColumnsExcluding( diffColumnsExcluding(
tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude); tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude);
@@ -673,7 +687,28 @@ public class ChangeBundle {
PatchSetApproval a = as.get(k); PatchSetApproval a = as.get(k);
PatchSetApproval b = bs.get(k); PatchSetApproval b = bs.get(k);
String desc = describe(k); String desc = describe(k);
diffColumns(diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b);
// ReviewDb allows timestamps before patch set was created, but NoteDb
// truncates this to the patch set creation timestamp.
Timestamp ta = a.getGranted();
Timestamp tb = b.getGranted();
PatchSet psa = checkNotNull(bundleA.patchSets.get(a.getPatchSetId()));
PatchSet psb = checkNotNull(bundleB.patchSets.get(b.getPatchSetId()));
boolean excludeGranted = false;
List<String> exclude = new ArrayList<>(1);
if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
excludeGranted =
ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn());
} else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
excludeGranted =
tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn());
}
if (excludeGranted) {
exclude.add("granted");
}
diffColumnsExcluding(
diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b, exclude);
} }
} }

View File

@@ -29,6 +29,7 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -92,6 +93,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -309,8 +311,8 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
deleteDraftRefs(change, manager.getAllUsersRepo()); deleteDraftRefs(change, manager.getAllUsersRepo());
Integer minPsNum = getMinPatchSetNum(bundle); Integer minPsNum = getMinPatchSetNum(bundle);
Set<PatchSet.Id> psIds = Map<PatchSet.Id, PatchSetEvent> patchSetEvents =
Sets.newHashSetWithExpectedSize(bundle.getPatchSets().size()); Maps.newHashMapWithExpectedSize(bundle.getPatchSets().size());
for (PatchSet ps : bundle.getPatchSets()) { for (PatchSet ps : bundle.getPatchSets()) {
if (ps.getId().get() > currPsId.get()) { if (ps.getId().get() > currPsId.get()) {
@@ -319,14 +321,15 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
ps.getId(), currPsId); ps.getId(), currPsId);
continue; continue;
} }
psIds.add(ps.getId()); PatchSetEvent pse =
events.add(new PatchSetEvent( new PatchSetEvent(change, ps, manager.getChangeRepo().rw);
change, ps, manager.getChangeRepo().rw)); patchSetEvents.put(ps.getId(), pse);
events.add(pse);
for (PatchLineComment c : getPatchLineComments(bundle, ps)) { for (PatchLineComment c : getPatchLineComments(bundle, ps)) {
PatchLineCommentEvent e = PatchLineCommentEvent e =
new PatchLineCommentEvent(c, change, ps, patchListCache); new PatchLineCommentEvent(c, change, ps, patchListCache);
if (c.getStatus() == Status.PUBLISHED) { if (c.getStatus() == Status.PUBLISHED) {
events.add(e); events.add(e.addDep(pse));
} else { } else {
draftCommentEvents.put(c.getAuthor(), e); draftCommentEvents.put(c.getAuthor(), e);
} }
@@ -334,8 +337,9 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
} }
for (PatchSetApproval psa : bundle.getPatchSetApprovals()) { for (PatchSetApproval psa : bundle.getPatchSetApprovals()) {
if (psIds.contains(psa.getPatchSetId())) { PatchSetEvent pse = patchSetEvents.get(psa.getPatchSetId());
events.add(new ApprovalEvent(psa, change.getCreatedOn())); if (pse != null) {
events.add(new ApprovalEvent(psa, change.getCreatedOn()).addDep(pse));
} }
} }
@@ -346,9 +350,18 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
Change noteDbChange = new Change(null, null, null, null, null); Change noteDbChange = new Change(null, null, null, null, null);
for (ChangeMessage msg : bundle.getChangeMessages()) { for (ChangeMessage msg : bundle.getChangeMessages()) {
if (msg.getPatchSetId() == null || psIds.contains(msg.getPatchSetId())) { if (msg.getPatchSetId() == null) {
// No dependency necessary; will get assigned to most recent patch set
// in sortAndFillEvents.
events.add( events.add(
new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn())); new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
continue;
}
PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
if (pse != null) {
events.add(
new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn())
.addDep(pse));
} }
} }

View File

@@ -66,8 +66,9 @@ abstract class Event implements Comparable<Event> {
who, update.getNullableAccountId()); who, update.getNullableAccountId());
} }
void addDep(Event e) { Event addDep(Event e) {
deps.add(e); deps.add(e);
return this;
} }
/** /**

View File

@@ -1260,7 +1260,9 @@ public class ChangeBundleTest {
} }
private static List<PatchSet> latest(Change c) { private static List<PatchSet> latest(Change c) {
return ImmutableList.of(new PatchSet(c.currentPatchSetId())); PatchSet ps = new PatchSet(c.currentPatchSetId());
ps.setCreatedOn(c.getLastUpdatedOn());
return ImmutableList.of(ps);
} }
private static List<PatchSetApproval> approvals(PatchSetApproval... ents) { private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {