NoteDb: Avoid writing partial change graphs

If we naively turn on notedb.writeChanges without previously running
RebuildNoteDb to backfill existing changes, a single ChangeUpdate may
end up writing a partial note graph, for example containing a single
commit with only a "Topic" field and no other information. Such a
partial graph will almost certainly fail to parse; better to avoid
writing them out in the first place.

Teach NoteDbUpdateManager to skip writing updates unless the first
update in the list has a special allowWriteToNewRef. This means
writing a change will automatically "turn on" on a per-change basis as
soon as a backfill is completed, whether in a running server or in
between restarts.

There are three cases where we want to allow writing updates to new
refs:
 - Any ChangeDraftUpdate, since having a "partial" draft notes ref
   doesn't cause parsing to fail, and executing the same update
   multiple times is idempotent with respect to the note data.
 - A ChangeUpdate that creates a change for the first time, which is
   by definition not a partial graph. This is handled in BatchUpdate,
   which already knows which ChangeUpdates correspond to new changes.
 - In ChangeRebuilder, which again is guaranteed to write a complete
   graph.

Change-Id: Ic8c6c28e68aef22be7569d2b0cb409eae22e3034
This commit is contained in:
Dave Borowitz
2016-03-24 13:36:15 -04:00
parent f449ad4ce7
commit d2c9c2ed93
8 changed files with 100 additions and 6 deletions

View File

@@ -204,7 +204,9 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
protected ChangeUpdate newUpdate(Change c, CurrentUser user)
throws Exception {
return TestChanges.newUpdate(injector, c, user);
ChangeUpdate update = TestChanges.newUpdate(injector, c, user);
update.setAllowWriteToNewRef(true);
return update;
}
protected ChangeNotes newNotes(Change c) throws OrmException {

View File

@@ -14,13 +14,18 @@
package com.google.gerrit.testutil;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.PatchLineCommentsUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeBundle;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeRebuilder;
import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
@@ -29,6 +34,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,6 +47,7 @@ public class NoteDbChecker {
static final Logger log = LoggerFactory.getLogger(NoteDbChecker.class);
private final Provider<ReviewDb> dbProvider;
private final GitRepositoryManager repoManager;
private final TestNotesMigration notesMigration;
private final ChangeNotes.Factory notesFactory;
private final ChangeRebuilder changeRebuilder;
@@ -48,11 +55,13 @@ public class NoteDbChecker {
@Inject
NoteDbChecker(Provider<ReviewDb> dbProvider,
GitRepositoryManager repoManager,
TestNotesMigration notesMigration,
ChangeNotes.Factory notesFactory,
ChangeRebuilder changeRebuilder,
PatchLineCommentsUtil plcUtil) {
this.dbProvider = dbProvider;
this.repoManager = repoManager;
this.notesMigration = notesMigration;
this.notesFactory = notesFactory;
this.changeRebuilder = changeRebuilder;
@@ -106,6 +115,14 @@ public class NoteDbChecker {
checkActual(readExpected(changeIds), new ArrayList<String>());
}
public void assertNoChangeRef(Project.NameKey project, Change.Id changeId)
throws Exception {
try (Repository repo = repoManager.openMetadataRepository(project)) {
assertThat(repo.exactRef(ChangeNoteUtil.changeRefName(changeId)))
.isNull();
}
}
private List<ChangeBundle> readExpected(Iterable<Change.Id> changeIds)
throws Exception {
ReviewDb db = unwrapDb();