Merge changes I8e67dc88,Ib7cdcd97
* changes: Don't fuse code and meta ref updates if it won't be atomic TestNotesMigration: Initialize from env during constructor
This commit is contained in:
@@ -32,8 +32,8 @@ same repository as code changes.
|
|||||||
- Storing the rest of account data is a work in progress.
|
- Storing the rest of account data is a work in progress.
|
||||||
- Storing group data is a work in progress.
|
- Storing group data is a work in progress.
|
||||||
|
|
||||||
To match the current configuration of `googlesource.com`, paste the following
|
To use a configuration similar to the current state of `googlesource.com`, paste
|
||||||
config snippet in your `gerrit.config`:
|
the following config snippet in your `gerrit.config`:
|
||||||
|
|
||||||
----
|
----
|
||||||
[noteDb "changes"]
|
[noteDb "changes"]
|
||||||
@@ -46,6 +46,9 @@ config snippet in your `gerrit.config`:
|
|||||||
This is the configuration that will be produced if you enable experimental
|
This is the configuration that will be produced if you enable experimental
|
||||||
NoteDb support on a new site with `init`.
|
NoteDb support on a new site with `init`.
|
||||||
|
|
||||||
|
`googlesource.com` additionally uses `fuseUpdates = true`, because its repo
|
||||||
|
backend supports atomic multi-ref transactions. Native JGit doesn't, so setting
|
||||||
|
this option on a dev server would fail.
|
||||||
|
|
||||||
For an example NoteDb change, poke around at this one:
|
For an example NoteDb change, poke around at this one:
|
||||||
----
|
----
|
||||||
@@ -99,6 +102,11 @@ previous options, unless otherwise noted.
|
|||||||
implementation of the `rebuild-note-db` program does not do this. +
|
implementation of the `rebuild-note-db` program does not do this. +
|
||||||
In this phase, it would be possible to delete the Changes tables out from
|
In this phase, it would be possible to delete the Changes tables out from
|
||||||
under a running server with no effect.
|
under a running server with no effect.
|
||||||
|
- `noteDb.changes.fuseUpdates=true`: Code and meta updates within a single
|
||||||
|
repository are fused into a single atomic `BatchRefUpdate`, so they either
|
||||||
|
all succeed or all fail. This mode is only possible on a backend that
|
||||||
|
supports atomic ref updates, which notably excludes the default file repository
|
||||||
|
backend.
|
||||||
|
|
||||||
[[migration]]
|
[[migration]]
|
||||||
== Migration
|
== Migration
|
||||||
|
@@ -348,7 +348,6 @@ public abstract class AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.getTestInjector().injectMembers(this);
|
server.getTestInjector().injectMembers(this);
|
||||||
notesMigration.setFromEnv();
|
|
||||||
Transport.register(inProcessProtocol);
|
Transport.register(inProcessProtocol);
|
||||||
toClose = Collections.synchronizedList(new ArrayList<Repository>());
|
toClose = Collections.synchronizedList(new ArrayList<Repository>());
|
||||||
admin = accounts.admin();
|
admin = accounts.admin();
|
||||||
@@ -528,6 +527,7 @@ public abstract class AbstractDaemonTest {
|
|||||||
server.stop();
|
server.stop();
|
||||||
server = null;
|
server = null;
|
||||||
}
|
}
|
||||||
|
notesMigration.resetFromEnv();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
|
protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
|
||||||
|
@@ -896,7 +896,14 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
|
|||||||
|
|
||||||
PushResult pr =
|
PushResult pr =
|
||||||
GitUtil.pushHead(testRepo, "refs/for/foo%base=" + rBase.getCommit().name(), false, false);
|
GitUtil.pushHead(testRepo, "refs/for/foo%base=" + rBase.getCommit().name(), false, false);
|
||||||
|
|
||||||
|
if (notesMigration.fuseUpdates()) {
|
||||||
|
// InMemoryRepository's atomic BatchRefUpdate implementation doesn't update the progress
|
||||||
|
// monitor. That's fine, we just care that there was at least one new change and no errors.
|
||||||
|
assertThat(pr.getMessages()).contains("changes: new: 1, done");
|
||||||
|
} else {
|
||||||
assertThat(pr.getMessages()).contains("changes: new: 1, refs: 1, done");
|
assertThat(pr.getMessages()).contains("changes: new: 1, refs: 1, done");
|
||||||
|
}
|
||||||
|
|
||||||
assertTwoChangesWithSameRevision(r);
|
assertTwoChangesWithSameRevision(r);
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,6 @@ import com.google.gerrit.server.update.BatchUpdate;
|
|||||||
import com.google.gerrit.server.update.BatchUpdateOp;
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
import com.google.gerrit.server.update.ChangeContext;
|
import com.google.gerrit.server.update.ChangeContext;
|
||||||
import com.google.gerrit.server.update.RepoContext;
|
import com.google.gerrit.server.update.RepoContext;
|
||||||
import com.google.gerrit.testutil.NoteDbMode;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
@@ -48,11 +47,12 @@ public class NoteDbOnlyIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.DISABLE_CHANGE_REVIEW_DB);
|
assume().that(notesMigration.disableChangeReviewDb()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateChangeFailureRollsBackRefUpdate() throws Exception {
|
public void updateChangeFailureRollsBackRefUpdate() throws Exception {
|
||||||
|
assume().that(notesMigration.fuseUpdates()).isTrue();
|
||||||
PushOneCommit.Result r = createChange();
|
PushOneCommit.Result r = createChange();
|
||||||
Change.Id id = r.getChange().getId();
|
Change.Id id = r.getChange().getId();
|
||||||
|
|
||||||
|
@@ -48,6 +48,7 @@ public class ConfigNotesMigration extends NotesMigration {
|
|||||||
|
|
||||||
// All of these names must be reflected in the allowed set in checkConfig.
|
// All of these names must be reflected in the allowed set in checkConfig.
|
||||||
private static final String DISABLE_REVIEW_DB = "disableReviewDb";
|
private static final String DISABLE_REVIEW_DB = "disableReviewDb";
|
||||||
|
private static final String FUSE_UPDATES = "fuseUpdates";
|
||||||
private static final String PRIMARY_STORAGE = "primaryStorage";
|
private static final String PRIMARY_STORAGE = "primaryStorage";
|
||||||
private static final String READ = "read";
|
private static final String READ = "read";
|
||||||
private static final String SEQUENCE = "sequence";
|
private static final String SEQUENCE = "sequence";
|
||||||
@@ -77,6 +78,8 @@ public class ConfigNotesMigration extends NotesMigration {
|
|||||||
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), SEQUENCE, true);
|
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), SEQUENCE, true);
|
||||||
cfg.setString(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.NOTE_DB.name());
|
cfg.setString(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.NOTE_DB.name());
|
||||||
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, true);
|
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, true);
|
||||||
|
// TODO(dborowitz): Set to true when FileRepository supports it.
|
||||||
|
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), FUSE_UPDATES, false);
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ public class ConfigNotesMigration extends NotesMigration {
|
|||||||
private final boolean readChangeSequence;
|
private final boolean readChangeSequence;
|
||||||
private final PrimaryStorage changePrimaryStorage;
|
private final PrimaryStorage changePrimaryStorage;
|
||||||
private final boolean disableChangeReviewDb;
|
private final boolean disableChangeReviewDb;
|
||||||
|
private final boolean fuseUpdates;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ConfigNotesMigration(@GerritServerConfig Config cfg) {
|
public ConfigNotesMigration(@GerritServerConfig Config cfg) {
|
||||||
@@ -103,6 +107,7 @@ public class ConfigNotesMigration extends NotesMigration {
|
|||||||
cfg.getEnum(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.REVIEW_DB);
|
cfg.getEnum(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.REVIEW_DB);
|
||||||
disableChangeReviewDb =
|
disableChangeReviewDb =
|
||||||
cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, false);
|
cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, false);
|
||||||
|
fuseUpdates = cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), FUSE_UPDATES, false);
|
||||||
|
|
||||||
checkArgument(
|
checkArgument(
|
||||||
!(disableChangeReviewDb && changePrimaryStorage != PrimaryStorage.NOTE_DB),
|
!(disableChangeReviewDb && changePrimaryStorage != PrimaryStorage.NOTE_DB),
|
||||||
@@ -133,4 +138,9 @@ public class ConfigNotesMigration extends NotesMigration {
|
|||||||
public boolean disableChangeReviewDb() {
|
public boolean disableChangeReviewDb() {
|
||||||
return disableChangeReviewDb;
|
return disableChangeReviewDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean fuseUpdates() {
|
||||||
|
return fuseUpdates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -82,6 +82,19 @@ public abstract class NotesMigration {
|
|||||||
*/
|
*/
|
||||||
public abstract boolean disableChangeReviewDb();
|
public abstract boolean disableChangeReviewDb();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fuse meta ref updates in the same batch as code updates.
|
||||||
|
*
|
||||||
|
* <p>When set, each {@link com.google.gerrit.server.update.BatchUpdate} results in a single
|
||||||
|
* {@link org.eclipse.jgit.lib.BatchRefUpdate} to update both code and meta refs atomically.
|
||||||
|
* Setting this option with a repository backend that does not support atomic multi-ref
|
||||||
|
* transactions ({@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}) is a
|
||||||
|
* configuration error, and all updates will fail at runtime.
|
||||||
|
*
|
||||||
|
* <p>Has no effect if {@link #disableChangeReviewDb()} is false.
|
||||||
|
*/
|
||||||
|
public abstract boolean fuseUpdates();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to fail when reading any data from NoteDb.
|
* Whether to fail when reading any data from NoteDb.
|
||||||
*
|
*
|
||||||
|
@@ -170,6 +170,7 @@ public class DeleteRef {
|
|||||||
private void deleteMultipleRefs(Repository r)
|
private void deleteMultipleRefs(Repository r)
|
||||||
throws OrmException, IOException, ResourceConflictException {
|
throws OrmException, IOException, ResourceConflictException {
|
||||||
BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
|
BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
|
||||||
|
batchUpdate.setAtomic(false);
|
||||||
List<String> refs =
|
List<String> refs =
|
||||||
prefix == null
|
prefix == null
|
||||||
? refsToDelete
|
? refsToDelete
|
||||||
|
@@ -94,7 +94,8 @@ public abstract class BatchUpdate implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public void configure() {
|
public void configure() {
|
||||||
factory(ReviewDbBatchUpdate.AssistedFactory.class);
|
factory(ReviewDbBatchUpdate.AssistedFactory.class);
|
||||||
factory(NoteDbBatchUpdate.AssistedFactory.class);
|
factory(FusedNoteDbBatchUpdate.AssistedFactory.class);
|
||||||
|
factory(UnfusedNoteDbBatchUpdate.AssistedFactory.class);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -103,22 +104,28 @@ public abstract class BatchUpdate implements AutoCloseable {
|
|||||||
public static class Factory {
|
public static class Factory {
|
||||||
private final NotesMigration migration;
|
private final NotesMigration migration;
|
||||||
private final ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory;
|
private final ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory;
|
||||||
private final NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory;
|
private final FusedNoteDbBatchUpdate.AssistedFactory fusedNoteDbBatchUpdateFactory;
|
||||||
|
private final UnfusedNoteDbBatchUpdate.AssistedFactory unfusedNoteDbBatchUpdateFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Factory(
|
Factory(
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
|
ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
|
||||||
NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory) {
|
FusedNoteDbBatchUpdate.AssistedFactory fusedNoteDbBatchUpdateFactory,
|
||||||
|
UnfusedNoteDbBatchUpdate.AssistedFactory unfusedNoteDbBatchUpdateFactory) {
|
||||||
this.migration = migration;
|
this.migration = migration;
|
||||||
this.reviewDbBatchUpdateFactory = reviewDbBatchUpdateFactory;
|
this.reviewDbBatchUpdateFactory = reviewDbBatchUpdateFactory;
|
||||||
this.noteDbBatchUpdateFactory = noteDbBatchUpdateFactory;
|
this.fusedNoteDbBatchUpdateFactory = fusedNoteDbBatchUpdateFactory;
|
||||||
|
this.unfusedNoteDbBatchUpdateFactory = unfusedNoteDbBatchUpdateFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BatchUpdate create(
|
public BatchUpdate create(
|
||||||
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when) {
|
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when) {
|
||||||
if (migration.disableChangeReviewDb()) {
|
if (migration.disableChangeReviewDb()) {
|
||||||
return noteDbBatchUpdateFactory.create(db, project, user, when);
|
if (migration.fuseUpdates()) {
|
||||||
|
return fusedNoteDbBatchUpdateFactory.create(db, project, user, when);
|
||||||
|
}
|
||||||
|
return unfusedNoteDbBatchUpdateFactory.create(db, project, user, when);
|
||||||
}
|
}
|
||||||
return reviewDbBatchUpdateFactory.create(db, project, user, when);
|
return reviewDbBatchUpdateFactory.create(db, project, user, when);
|
||||||
}
|
}
|
||||||
@@ -138,9 +145,15 @@ public abstract class BatchUpdate implements AutoCloseable {
|
|||||||
// copy them into an ImmutableList so there is no chance the callee can pollute the input
|
// copy them into an ImmutableList so there is no chance the callee can pollute the input
|
||||||
// collection.
|
// collection.
|
||||||
if (migration.disableChangeReviewDb()) {
|
if (migration.disableChangeReviewDb()) {
|
||||||
ImmutableList<NoteDbBatchUpdate> noteDbUpdates =
|
if (migration.fuseUpdates()) {
|
||||||
|
ImmutableList<FusedNoteDbBatchUpdate> noteDbUpdates =
|
||||||
(ImmutableList) ImmutableList.copyOf(updates);
|
(ImmutableList) ImmutableList.copyOf(updates);
|
||||||
NoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
|
FusedNoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
|
||||||
|
} else {
|
||||||
|
ImmutableList<UnfusedNoteDbBatchUpdate> noteDbUpdates =
|
||||||
|
(ImmutableList) ImmutableList.copyOf(updates);
|
||||||
|
UnfusedNoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ImmutableList<ReviewDbBatchUpdate> reviewDbUpdates =
|
ImmutableList<ReviewDbBatchUpdate> reviewDbUpdates =
|
||||||
(ImmutableList) ImmutableList.copyOf(updates);
|
(ImmutableList) ImmutableList.copyOf(updates);
|
||||||
|
@@ -16,6 +16,7 @@ package com.google.gerrit.server.update;
|
|||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
@@ -51,23 +52,25 @@ import java.util.TimeZone;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BatchUpdate} implementation that only supports NoteDb.
|
* {@link BatchUpdate} implementation using only NoteDb that updates code refs and meta refs in a
|
||||||
|
* single {@link org.eclipse.jgit.lib.BatchRefUpdate}.
|
||||||
*
|
*
|
||||||
* <p>Used when {@code noteDb.changes.disableReviewDb=true}, at which point ReviewDb is not
|
* <p>Used when {@code noteDb.changes.disableReviewDb=true}, at which point ReviewDb is not
|
||||||
* consulted during updates.
|
* consulted during updates.
|
||||||
*/
|
*/
|
||||||
class NoteDbBatchUpdate extends BatchUpdate {
|
class FusedNoteDbBatchUpdate extends BatchUpdate {
|
||||||
interface AssistedFactory {
|
interface AssistedFactory {
|
||||||
NoteDbBatchUpdate create(
|
FusedNoteDbBatchUpdate create(
|
||||||
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
|
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void execute(
|
static void execute(
|
||||||
ImmutableList<NoteDbBatchUpdate> updates,
|
ImmutableList<FusedNoteDbBatchUpdate> updates,
|
||||||
BatchUpdateListener listener,
|
BatchUpdateListener listener,
|
||||||
@Nullable RequestId requestId,
|
@Nullable RequestId requestId,
|
||||||
boolean dryrun)
|
boolean dryrun)
|
||||||
@@ -84,11 +87,11 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
try {
|
try {
|
||||||
switch (order) {
|
switch (order) {
|
||||||
case REPO_BEFORE_DB:
|
case REPO_BEFORE_DB:
|
||||||
for (NoteDbBatchUpdate u : updates) {
|
for (FusedNoteDbBatchUpdate u : updates) {
|
||||||
u.executeUpdateRepo();
|
u.executeUpdateRepo();
|
||||||
}
|
}
|
||||||
listener.afterUpdateRepos();
|
listener.afterUpdateRepos();
|
||||||
for (NoteDbBatchUpdate u : updates) {
|
for (FusedNoteDbBatchUpdate u : updates) {
|
||||||
handles.add(u.executeChangeOps(dryrun));
|
handles.add(u.executeChangeOps(dryrun));
|
||||||
}
|
}
|
||||||
for (ChangesHandle h : handles) {
|
for (ChangesHandle h : handles) {
|
||||||
@@ -109,10 +112,10 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
// TODO(dborowitz): This may still result in multiple updates to All-Users, but that's
|
// TODO(dborowitz): This may still result in multiple updates to All-Users, but that's
|
||||||
// currently not a big deal because multi-change batches generally aren't affecting
|
// currently not a big deal because multi-change batches generally aren't affecting
|
||||||
// drafts anyway.
|
// drafts anyway.
|
||||||
for (NoteDbBatchUpdate u : updates) {
|
for (FusedNoteDbBatchUpdate u : updates) {
|
||||||
handles.add(u.executeChangeOps(dryrun));
|
handles.add(u.executeChangeOps(dryrun));
|
||||||
}
|
}
|
||||||
for (NoteDbBatchUpdate u : updates) {
|
for (FusedNoteDbBatchUpdate u : updates) {
|
||||||
u.executeUpdateRepo();
|
u.executeUpdateRepo();
|
||||||
}
|
}
|
||||||
for (ChangesHandle h : handles) {
|
for (ChangesHandle h : handles) {
|
||||||
@@ -147,7 +150,7 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
|
u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
|
||||||
|
|
||||||
if (!dryrun) {
|
if (!dryrun) {
|
||||||
for (NoteDbBatchUpdate u : updates) {
|
for (FusedNoteDbBatchUpdate u : updates) {
|
||||||
u.executePostOps();
|
u.executePostOps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +162,7 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
class ContextImpl implements Context {
|
class ContextImpl implements Context {
|
||||||
@Override
|
@Override
|
||||||
public RepoView getRepoView() throws IOException {
|
public RepoView getRepoView() throws IOException {
|
||||||
return NoteDbBatchUpdate.this.getRepoView();
|
return FusedNoteDbBatchUpdate.this.getRepoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -268,7 +271,7 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
private final ReviewDb db;
|
private final ReviewDb db;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NoteDbBatchUpdate(
|
FusedNoteDbBatchUpdate(
|
||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
@GerritPersonIdent PersonIdent serverIdent,
|
@GerritPersonIdent PersonIdent serverIdent,
|
||||||
ChangeNotes.Factory changeNotesFactory,
|
ChangeNotes.Factory changeNotesFactory,
|
||||||
@@ -351,7 +354,7 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void execute() throws OrmException, IOException {
|
void execute() throws OrmException, IOException {
|
||||||
NoteDbBatchUpdate.this.batchRefUpdate = manager.execute(dryrun);
|
FusedNoteDbBatchUpdate.this.batchRefUpdate = manager.execute(dryrun);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CheckedFuture<?, IOException>> startIndexFutures() {
|
List<CheckedFuture<?, IOException>> startIndexFutures() {
|
||||||
@@ -382,16 +385,19 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
|||||||
private ChangesHandle executeChangeOps(boolean dryrun) throws Exception {
|
private ChangesHandle executeChangeOps(boolean dryrun) throws Exception {
|
||||||
logDebug("Executing change ops");
|
logDebug("Executing change ops");
|
||||||
initRepository();
|
initRepository();
|
||||||
|
Repository repo = repoView.getRepository();
|
||||||
|
checkState(
|
||||||
|
repo.getRefDatabase().performsAtomicTransactions(),
|
||||||
|
"cannot use noteDb.changes.fuseUpdates=true with a repository that does not support atomic"
|
||||||
|
+ " batch ref updates: %s",
|
||||||
|
repo);
|
||||||
|
|
||||||
ChangesHandle handle =
|
ChangesHandle handle =
|
||||||
new ChangesHandle(
|
new ChangesHandle(
|
||||||
updateManagerFactory
|
updateManagerFactory
|
||||||
.create(project)
|
.create(project)
|
||||||
.setChangeRepo(
|
.setChangeRepo(
|
||||||
repoView.getRepository(),
|
repo, repoView.getRevWalk(), repoView.getInserter(), repoView.getCommands()),
|
||||||
repoView.getRevWalk(),
|
|
||||||
repoView.getInserter(),
|
|
||||||
repoView.getCommands()),
|
|
||||||
dryrun);
|
dryrun);
|
||||||
if (user.isIdentifiedUser()) {
|
if (user.isIdentifiedUser()) {
|
||||||
handle.manager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
|
handle.manager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
|
@@ -0,0 +1,458 @@
|
|||||||
|
// Copyright (C) 2017 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.server.update;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static java.util.Comparator.comparing;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.util.concurrent.CheckedFuture;
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
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.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
|
import com.google.gerrit.server.index.change.ChangeIndexer;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
|
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
import com.google.gerrit.server.util.RequestId;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import org.eclipse.jgit.lib.NullProgressMonitor;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BatchUpdate} implementation that only supports NoteDb.
|
||||||
|
*
|
||||||
|
* <p>Used when {@code noteDb.changes.disableReviewDb=true}, at which point ReviewDb is not
|
||||||
|
* consulted during updates.
|
||||||
|
*/
|
||||||
|
class UnfusedNoteDbBatchUpdate extends BatchUpdate {
|
||||||
|
interface AssistedFactory {
|
||||||
|
UnfusedNoteDbBatchUpdate create(
|
||||||
|
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void execute(
|
||||||
|
ImmutableList<UnfusedNoteDbBatchUpdate> updates,
|
||||||
|
BatchUpdateListener listener,
|
||||||
|
@Nullable RequestId requestId,
|
||||||
|
boolean dryrun)
|
||||||
|
throws UpdateException, RestApiException {
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setRequestIds(updates, requestId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Order order = getOrder(updates, listener);
|
||||||
|
// TODO(dborowitz): Fuse implementations to use a single BatchRefUpdate between phases. Note
|
||||||
|
// that we may still need to respect the order, since op implementations may make assumptions
|
||||||
|
// about the order in which their methods are called.
|
||||||
|
switch (order) {
|
||||||
|
case REPO_BEFORE_DB:
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.executeUpdateRepo();
|
||||||
|
}
|
||||||
|
listener.afterUpdateRepos();
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.executeRefUpdates(dryrun);
|
||||||
|
}
|
||||||
|
listener.afterUpdateRefs();
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.reindexChanges(u.executeChangeOps(dryrun), dryrun);
|
||||||
|
}
|
||||||
|
listener.afterUpdateChanges();
|
||||||
|
break;
|
||||||
|
case DB_BEFORE_REPO:
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.reindexChanges(u.executeChangeOps(dryrun), dryrun);
|
||||||
|
}
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.executeUpdateRepo();
|
||||||
|
}
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.executeRefUpdates(dryrun);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("invalid execution order: " + order);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeIndexer.allAsList(
|
||||||
|
updates.stream().flatMap(u -> u.indexFutures.stream()).collect(toList()))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
// Fire ref update events only after all mutations are finished, since callers may assume a
|
||||||
|
// patch set ref being created means the change was created, or a branch advancing meaning
|
||||||
|
// some changes were closed.
|
||||||
|
updates
|
||||||
|
.stream()
|
||||||
|
.filter(u -> u.batchRefUpdate != null)
|
||||||
|
.forEach(
|
||||||
|
u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
|
||||||
|
|
||||||
|
if (!dryrun) {
|
||||||
|
for (UnfusedNoteDbBatchUpdate u : updates) {
|
||||||
|
u.executePostOps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
wrapAndThrowException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContextImpl implements Context {
|
||||||
|
@Override
|
||||||
|
public RepoView getRepoView() throws IOException {
|
||||||
|
return UnfusedNoteDbBatchUpdate.this.getRepoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RevWalk getRevWalk() throws IOException {
|
||||||
|
return getRepoView().getRevWalk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Project.NameKey getProject() {
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getWhen() {
|
||||||
|
return when;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeZone getTimeZone() {
|
||||||
|
return tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReviewDb getDb() {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CurrentUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Order getOrder() {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RepoContextImpl extends ContextImpl implements RepoContext {
|
||||||
|
@Override
|
||||||
|
public ObjectInserter getInserter() throws IOException {
|
||||||
|
return getRepoView().getInserterWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRefUpdate(ReceiveCommand cmd) throws IOException {
|
||||||
|
getRepoView().getCommands().add(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChangeContextImpl extends ContextImpl implements ChangeContext {
|
||||||
|
private final ChangeControl ctl;
|
||||||
|
private final Map<PatchSet.Id, ChangeUpdate> updates;
|
||||||
|
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
|
protected ChangeContextImpl(ChangeControl ctl) {
|
||||||
|
this.ctl = checkNotNull(ctl);
|
||||||
|
updates = new TreeMap<>(comparing(PatchSet.Id::get));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeUpdate getUpdate(PatchSet.Id psId) {
|
||||||
|
ChangeUpdate u = updates.get(psId);
|
||||||
|
if (u == null) {
|
||||||
|
u = changeUpdateFactory.create(ctl, when);
|
||||||
|
if (newChanges.containsKey(ctl.getId())) {
|
||||||
|
u.setAllowWriteToNewRef(true);
|
||||||
|
}
|
||||||
|
u.setPatchSetId(psId);
|
||||||
|
updates.put(psId, u);
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeControl getControl() {
|
||||||
|
return ctl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dontBumpLastUpdatedOn() {
|
||||||
|
// Do nothing; NoteDb effectively updates timestamp if and only if a commit was written to the
|
||||||
|
// change meta ref.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteChange() {
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Per-change result status from {@link #executeChangeOps}. */
|
||||||
|
private enum ChangeResult {
|
||||||
|
SKIPPED,
|
||||||
|
UPSERTED,
|
||||||
|
DELETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ChangeNotes.Factory changeNotesFactory;
|
||||||
|
private final ChangeControl.GenericFactory changeControlFactory;
|
||||||
|
private final ChangeUpdate.Factory changeUpdateFactory;
|
||||||
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
|
private final ChangeIndexer indexer;
|
||||||
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
|
private final ReviewDb db;
|
||||||
|
|
||||||
|
private List<CheckedFuture<?, IOException>> indexFutures;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UnfusedNoteDbBatchUpdate(
|
||||||
|
GitRepositoryManager repoManager,
|
||||||
|
@GerritPersonIdent PersonIdent serverIdent,
|
||||||
|
ChangeNotes.Factory changeNotesFactory,
|
||||||
|
ChangeControl.GenericFactory changeControlFactory,
|
||||||
|
ChangeUpdate.Factory changeUpdateFactory,
|
||||||
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
|
ChangeIndexer indexer,
|
||||||
|
GitReferenceUpdated gitRefUpdated,
|
||||||
|
@Assisted ReviewDb db,
|
||||||
|
@Assisted Project.NameKey project,
|
||||||
|
@Assisted CurrentUser user,
|
||||||
|
@Assisted Timestamp when) {
|
||||||
|
super(repoManager, serverIdent, project, user, when);
|
||||||
|
checkArgument(!db.changesTablesEnabled(), "expected Change tables to be disabled on %s", db);
|
||||||
|
this.changeNotesFactory = changeNotesFactory;
|
||||||
|
this.changeControlFactory = changeControlFactory;
|
||||||
|
this.changeUpdateFactory = changeUpdateFactory;
|
||||||
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
|
this.indexer = indexer;
|
||||||
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
|
this.db = db;
|
||||||
|
this.indexFutures = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(BatchUpdateListener listener) throws UpdateException, RestApiException {
|
||||||
|
execute(ImmutableList.of(this), listener, requestId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Context newContext() {
|
||||||
|
return new ContextImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeUpdateRepo() throws UpdateException, RestApiException {
|
||||||
|
try {
|
||||||
|
logDebug("Executing updateRepo on {} ops", ops.size());
|
||||||
|
RepoContextImpl ctx = new RepoContextImpl();
|
||||||
|
for (BatchUpdateOp op : ops.values()) {
|
||||||
|
op.updateRepo(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug("Executing updateRepo on {} RepoOnlyOps", repoOnlyOps.size());
|
||||||
|
for (RepoOnlyOp op : repoOnlyOps) {
|
||||||
|
op.updateRepo(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSubmitValidators != null && !getRefUpdates().isEmpty()) {
|
||||||
|
// Validation of refs has to take place here and not at the beginning of executeRefUpdates.
|
||||||
|
// Otherwise, failing validation in a second BatchUpdate object will happen *after* the
|
||||||
|
// first update's executeRefUpdates has finished, hence after first repo's refs have been
|
||||||
|
// updated, which is too late.
|
||||||
|
onSubmitValidators.validate(
|
||||||
|
project, ctx.getRevWalk().getObjectReader(), repoView.getCommands());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dborowitz): Don't flush when fusing phases.
|
||||||
|
if (repoView != null) {
|
||||||
|
logDebug("Flushing inserter");
|
||||||
|
repoView.getInserter().flush();
|
||||||
|
} else {
|
||||||
|
logDebug("No objects to flush");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Throwables.throwIfInstanceOf(e, RestApiException.class);
|
||||||
|
throw new UpdateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dborowitz): Don't execute non-change ref updates separately when fusing phases.
|
||||||
|
private void executeRefUpdates(boolean dryrun) throws IOException, RestApiException {
|
||||||
|
if (getRefUpdates().isEmpty()) {
|
||||||
|
logDebug("No ref updates to execute");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// May not be opened if the caller added ref updates but no new objects.
|
||||||
|
initRepository();
|
||||||
|
batchRefUpdate = repoView.getRepository().getRefDatabase().newBatchUpdate();
|
||||||
|
repoView.getCommands().addTo(batchRefUpdate);
|
||||||
|
logDebug("Executing batch of {} ref updates", batchRefUpdate.getCommands().size());
|
||||||
|
if (dryrun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force BatchRefUpdate to read newly referenced objects using a new RevWalk, rather than one
|
||||||
|
// that might have access to unflushed objects.
|
||||||
|
try (RevWalk updateRw = new RevWalk(repoView.getRepository())) {
|
||||||
|
batchRefUpdate.execute(updateRw, NullProgressMonitor.INSTANCE);
|
||||||
|
}
|
||||||
|
boolean ok = true;
|
||||||
|
for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
|
||||||
|
if (cmd.getResult() != ReceiveCommand.Result.OK) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
throw new RestApiException("BatchRefUpdate failed: " + batchRefUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Change.Id, ChangeResult> executeChangeOps(boolean dryrun) throws Exception {
|
||||||
|
logDebug("Executing change ops");
|
||||||
|
Map<Change.Id, ChangeResult> result =
|
||||||
|
Maps.newLinkedHashMapWithExpectedSize(ops.keySet().size());
|
||||||
|
initRepository();
|
||||||
|
Repository repo = repoView.getRepository();
|
||||||
|
// TODO(dborowitz): Teach NoteDbUpdateManager to allow reusing the same inserter and batch ref
|
||||||
|
// update as in executeUpdateRepo.
|
||||||
|
try (ObjectInserter ins = repo.newObjectInserter();
|
||||||
|
ObjectReader reader = ins.newReader();
|
||||||
|
RevWalk rw = new RevWalk(reader);
|
||||||
|
NoteDbUpdateManager updateManager =
|
||||||
|
updateManagerFactory
|
||||||
|
.create(project)
|
||||||
|
.setChangeRepo(repo, rw, ins, new ChainedReceiveCommands(repo))) {
|
||||||
|
if (user.isIdentifiedUser()) {
|
||||||
|
updateManager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
|
||||||
|
}
|
||||||
|
for (Map.Entry<Change.Id, Collection<BatchUpdateOp>> e : ops.asMap().entrySet()) {
|
||||||
|
Change.Id id = e.getKey();
|
||||||
|
ChangeContextImpl ctx = newChangeContext(id);
|
||||||
|
boolean dirty = false;
|
||||||
|
logDebug("Applying {} ops for change {}", e.getValue().size(), id);
|
||||||
|
for (BatchUpdateOp op : e.getValue()) {
|
||||||
|
dirty |= op.updateChange(ctx);
|
||||||
|
}
|
||||||
|
if (!dirty) {
|
||||||
|
logDebug("No ops reported dirty, short-circuiting");
|
||||||
|
result.put(id, ChangeResult.SKIPPED);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ChangeUpdate u : ctx.updates.values()) {
|
||||||
|
updateManager.add(u);
|
||||||
|
}
|
||||||
|
if (ctx.deleted) {
|
||||||
|
logDebug("Change {} was deleted", id);
|
||||||
|
updateManager.deleteChange(id);
|
||||||
|
result.put(id, ChangeResult.DELETED);
|
||||||
|
} else {
|
||||||
|
result.put(id, ChangeResult.UPSERTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dryrun) {
|
||||||
|
logDebug("Executing NoteDb updates");
|
||||||
|
updateManager.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeContextImpl newChangeContext(Change.Id id) throws OrmException {
|
||||||
|
logDebug("Opening change {} for update", id);
|
||||||
|
Change c = newChanges.get(id);
|
||||||
|
boolean isNew = c != null;
|
||||||
|
if (!isNew) {
|
||||||
|
// Pass a synthetic change into ChangeNotes.Factory, which will take care of checking for
|
||||||
|
// existence and populating columns from the parsed notes state.
|
||||||
|
// TODO(dborowitz): This dance made more sense when using Reviewdb; consider a nicer way.
|
||||||
|
c = ChangeNotes.Factory.newNoteDbOnlyChange(project, id);
|
||||||
|
} else {
|
||||||
|
logDebug("Change {} is new", id);
|
||||||
|
}
|
||||||
|
ChangeNotes notes = changeNotesFactory.createForBatchUpdate(c, !isNew);
|
||||||
|
ChangeControl ctl = changeControlFactory.controlFor(notes, user);
|
||||||
|
return new ChangeContextImpl(ctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reindexChanges(Map<Change.Id, ChangeResult> updateResults, boolean dryrun) {
|
||||||
|
if (dryrun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logDebug("Reindexing {} changes", updateResults.size());
|
||||||
|
for (Map.Entry<Change.Id, ChangeResult> e : updateResults.entrySet()) {
|
||||||
|
Change.Id id = e.getKey();
|
||||||
|
switch (e.getValue()) {
|
||||||
|
case UPSERTED:
|
||||||
|
indexFutures.add(indexer.indexAsync(project, id));
|
||||||
|
break;
|
||||||
|
case DELETED:
|
||||||
|
indexFutures.add(indexer.deleteAsync(id));
|
||||||
|
break;
|
||||||
|
case SKIPPED:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("unexpected result: " + e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executePostOps() throws Exception {
|
||||||
|
ContextImpl ctx = new ContextImpl();
|
||||||
|
for (BatchUpdateOp op : ops.values()) {
|
||||||
|
op.postUpdate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RepoOnlyOp op : repoOnlyOps) {
|
||||||
|
op.postUpdate(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -31,11 +31,13 @@ import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
|||||||
import com.google.gerrit.testutil.InMemoryDatabase;
|
import com.google.gerrit.testutil.InMemoryDatabase;
|
||||||
import com.google.gerrit.testutil.InMemoryModule;
|
import com.google.gerrit.testutil.InMemoryModule;
|
||||||
import com.google.gerrit.testutil.InMemoryRepositoryManager;
|
import com.google.gerrit.testutil.InMemoryRepositoryManager;
|
||||||
|
import com.google.gwtorm.server.SchemaFactory;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
@@ -45,19 +47,16 @@ import org.junit.Test;
|
|||||||
|
|
||||||
public class BatchUpdateTest {
|
public class BatchUpdateTest {
|
||||||
@Inject private AccountManager accountManager;
|
@Inject private AccountManager accountManager;
|
||||||
|
|
||||||
@Inject private IdentifiedUser.GenericFactory userFactory;
|
@Inject private IdentifiedUser.GenericFactory userFactory;
|
||||||
|
@Inject private SchemaFactory<ReviewDb> schemaFactory;
|
||||||
@Inject private InMemoryDatabase schemaFactory;
|
|
||||||
|
|
||||||
@Inject private InMemoryRepositoryManager repoManager;
|
@Inject private InMemoryRepositoryManager repoManager;
|
||||||
|
|
||||||
@Inject private SchemaCreator schemaCreator;
|
@Inject private SchemaCreator schemaCreator;
|
||||||
|
|
||||||
@Inject private ThreadLocalRequestContext requestContext;
|
@Inject private ThreadLocalRequestContext requestContext;
|
||||||
|
|
||||||
@Inject private BatchUpdate.Factory batchUpdateFactory;
|
@Inject private BatchUpdate.Factory batchUpdateFactory;
|
||||||
|
|
||||||
|
// Only for use in setting up/tearing down injector; other users should use schemaFactory.
|
||||||
|
@Inject private InMemoryDatabase inMemoryDatabase;
|
||||||
|
|
||||||
private LifecycleManager lifecycle;
|
private LifecycleManager lifecycle;
|
||||||
private ReviewDb db;
|
private ReviewDb db;
|
||||||
private TestRepository<InMemoryRepository> repo;
|
private TestRepository<InMemoryRepository> repo;
|
||||||
@@ -72,8 +71,10 @@ public class BatchUpdateTest {
|
|||||||
lifecycle.add(injector);
|
lifecycle.add(injector);
|
||||||
lifecycle.start();
|
lifecycle.start();
|
||||||
|
|
||||||
|
try (ReviewDb underlyingDb = inMemoryDatabase.getDatabase().open()) {
|
||||||
|
schemaCreator.create(underlyingDb);
|
||||||
|
}
|
||||||
db = schemaFactory.open();
|
db = schemaFactory.open();
|
||||||
schemaCreator.create(db);
|
|
||||||
Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
|
Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
|
||||||
user = userFactory.create(userId);
|
user = userFactory.create(userId);
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ public class BatchUpdateTest {
|
|||||||
if (db != null) {
|
if (db != null) {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
InMemoryDatabase.drop(schemaFactory);
|
InMemoryDatabase.drop(inMemoryDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -49,8 +49,10 @@ public class GerritServerTests extends GerritBaseTests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public void beforeTest() throws Exception {
|
public void beforeTest() throws Exception {
|
||||||
notesMigration = new TestNotesMigration().setFromEnv();
|
notesMigration = new TestNotesMigration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void afterTest() {}
|
public void afterTest() {
|
||||||
|
notesMigration.resetFromEnv();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,8 @@ import com.google.common.collect.Sets;
|
|||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
|
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
|
||||||
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
|
import com.google.inject.Inject;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
@@ -30,7 +32,7 @@ import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
|||||||
/** Repository manager that uses in-memory repositories. */
|
/** Repository manager that uses in-memory repositories. */
|
||||||
public class InMemoryRepositoryManager implements GitRepositoryManager {
|
public class InMemoryRepositoryManager implements GitRepositoryManager {
|
||||||
public static InMemoryRepository newRepository(Project.NameKey name) {
|
public static InMemoryRepository newRepository(Project.NameKey name) {
|
||||||
return new Repo(name);
|
return new Repo(new TestNotesMigration(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Description extends DfsRepositoryDescription {
|
public static class Description extends DfsRepositoryDescription {
|
||||||
@@ -49,11 +51,15 @@ public class InMemoryRepositoryManager implements GitRepositoryManager {
|
|||||||
public static class Repo extends InMemoryRepository {
|
public static class Repo extends InMemoryRepository {
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
private Repo(Project.NameKey name) {
|
private Repo(NotesMigration notesMigration, Project.NameKey name) {
|
||||||
super(new Description(name));
|
super(new Description(name));
|
||||||
// TODO(dborowitz): Allow atomic transactions when this is supported:
|
// Normally, mimic the behavior of JGit FileRepository, the standard Gerrit repository
|
||||||
// https://git.eclipse.org/r/#/c/61841/2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java@313
|
// backend, and don't support atomic ref updates. The exception is when we're testing with
|
||||||
setPerformsAtomicTransactions(false);
|
// fused ref updates, which requires atomic ref updates to function.
|
||||||
|
//
|
||||||
|
// TODO(dborowitz): Change to match the behavior of JGit FileRepository after fixing
|
||||||
|
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=515678
|
||||||
|
setPerformsAtomicTransactions(notesMigration.fuseUpdates());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,7 +78,18 @@ public class InMemoryRepositoryManager implements GitRepositoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Repo> repos = new HashMap<>();
|
private final NotesMigration notesMigration;
|
||||||
|
private final Map<String, Repo> repos;
|
||||||
|
|
||||||
|
public InMemoryRepositoryManager() {
|
||||||
|
this(new TestNotesMigration());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
InMemoryRepositoryManager(NotesMigration notesMigration) {
|
||||||
|
this.notesMigration = notesMigration;
|
||||||
|
this.repos = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized Repo openRepository(Project.NameKey name) throws RepositoryNotFoundException {
|
public synchronized Repo openRepository(Project.NameKey name) throws RepositoryNotFoundException {
|
||||||
@@ -89,7 +106,7 @@ public class InMemoryRepositoryManager implements GitRepositoryManager {
|
|||||||
throw new RepositoryCaseMismatchException(name);
|
throw new RepositoryCaseMismatchException(name);
|
||||||
}
|
}
|
||||||
} catch (RepositoryNotFoundException e) {
|
} catch (RepositoryNotFoundException e) {
|
||||||
repo = new Repo(name);
|
repo = new Repo(notesMigration, name);
|
||||||
repos.put(normalize(name), repo);
|
repos.put(normalize(name), repo);
|
||||||
}
|
}
|
||||||
return repo;
|
return repo;
|
||||||
|
@@ -35,6 +35,9 @@ public enum NoteDbMode {
|
|||||||
/** All change tables are entirely disabled. */
|
/** All change tables are entirely disabled. */
|
||||||
DISABLE_CHANGE_REVIEW_DB(true),
|
DISABLE_CHANGE_REVIEW_DB(true),
|
||||||
|
|
||||||
|
/** All change tables are entirely disabled, and code/meta ref updates are fused. */
|
||||||
|
FUSED(true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run tests with NoteDb disabled, then convert ReviewDb to NoteDb and check that the results
|
* Run tests with NoteDb disabled, then convert ReviewDb to NoteDb and check that the results
|
||||||
* match.
|
* match.
|
||||||
|
@@ -27,9 +27,12 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
private volatile boolean writeChanges;
|
private volatile boolean writeChanges;
|
||||||
private volatile PrimaryStorage changePrimaryStorage = PrimaryStorage.REVIEW_DB;
|
private volatile PrimaryStorage changePrimaryStorage = PrimaryStorage.REVIEW_DB;
|
||||||
private volatile boolean disableChangeReviewDb;
|
private volatile boolean disableChangeReviewDb;
|
||||||
|
private volatile boolean fuseUpdates;
|
||||||
private volatile boolean failOnLoad;
|
private volatile boolean failOnLoad;
|
||||||
|
|
||||||
public TestNotesMigration() {}
|
public TestNotesMigration() {
|
||||||
|
resetFromEnv();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readChanges() {
|
public boolean readChanges() {
|
||||||
@@ -53,6 +56,11 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
return disableChangeReviewDb;
|
return disableChangeReviewDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean fuseUpdates() {
|
||||||
|
return fuseUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
// Increase visbility from superclass, as tests may want to check whether
|
// Increase visbility from superclass, as tests may want to check whether
|
||||||
// NoteDb data is written in specific migration scenarios.
|
// NoteDb data is written in specific migration scenarios.
|
||||||
@Override
|
@Override
|
||||||
@@ -85,6 +93,11 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TestNotesMigration setFuseUpdates(boolean fuseUpdates) {
|
||||||
|
this.fuseUpdates = fuseUpdates;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public TestNotesMigration setFailOnLoad(boolean failOnLoad) {
|
public TestNotesMigration setFailOnLoad(boolean failOnLoad) {
|
||||||
this.failOnLoad = failOnLoad;
|
this.failOnLoad = failOnLoad;
|
||||||
return this;
|
return this;
|
||||||
@@ -94,31 +107,42 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
return setReadChanges(enabled).setWriteChanges(enabled);
|
return setReadChanges(enabled).setWriteChanges(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestNotesMigration setFromEnv() {
|
public TestNotesMigration resetFromEnv() {
|
||||||
switch (NoteDbMode.get()) {
|
switch (NoteDbMode.get()) {
|
||||||
case READ_WRITE:
|
case READ_WRITE:
|
||||||
setWriteChanges(true);
|
setWriteChanges(true);
|
||||||
setReadChanges(true);
|
setReadChanges(true);
|
||||||
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
||||||
setDisableChangeReviewDb(false);
|
setDisableChangeReviewDb(false);
|
||||||
|
setFuseUpdates(false);
|
||||||
break;
|
break;
|
||||||
case WRITE:
|
case WRITE:
|
||||||
setWriteChanges(true);
|
setWriteChanges(true);
|
||||||
setReadChanges(false);
|
setReadChanges(false);
|
||||||
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
||||||
setDisableChangeReviewDb(false);
|
setDisableChangeReviewDb(false);
|
||||||
|
setFuseUpdates(false);
|
||||||
break;
|
break;
|
||||||
case PRIMARY:
|
case PRIMARY:
|
||||||
setWriteChanges(true);
|
setWriteChanges(true);
|
||||||
setReadChanges(true);
|
setReadChanges(true);
|
||||||
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
|
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
|
||||||
setDisableChangeReviewDb(false);
|
setDisableChangeReviewDb(false);
|
||||||
|
setFuseUpdates(false);
|
||||||
break;
|
break;
|
||||||
case DISABLE_CHANGE_REVIEW_DB:
|
case DISABLE_CHANGE_REVIEW_DB:
|
||||||
setWriteChanges(true);
|
setWriteChanges(true);
|
||||||
setReadChanges(true);
|
setReadChanges(true);
|
||||||
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
|
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
|
||||||
setDisableChangeReviewDb(true);
|
setDisableChangeReviewDb(true);
|
||||||
|
setFuseUpdates(false);
|
||||||
|
break;
|
||||||
|
case FUSED:
|
||||||
|
setWriteChanges(true);
|
||||||
|
setReadChanges(true);
|
||||||
|
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
|
||||||
|
setDisableChangeReviewDb(true);
|
||||||
|
setFuseUpdates(true);
|
||||||
break;
|
break;
|
||||||
case CHECK:
|
case CHECK:
|
||||||
case OFF:
|
case OFF:
|
||||||
@@ -127,6 +151,7 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
setReadChanges(false);
|
setReadChanges(false);
|
||||||
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
|
||||||
setDisableChangeReviewDb(false);
|
setDisableChangeReviewDb(false);
|
||||||
|
setFuseUpdates(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@@ -137,6 +162,7 @@ public class TestNotesMigration extends NotesMigration {
|
|||||||
setReadChanges(other.readChanges());
|
setReadChanges(other.readChanges());
|
||||||
setChangePrimaryStorage(other.changePrimaryStorage());
|
setChangePrimaryStorage(other.changePrimaryStorage());
|
||||||
setDisableChangeReviewDb(other.disableChangeReviewDb());
|
setDisableChangeReviewDb(other.disableChangeReviewDb());
|
||||||
|
setFuseUpdates(other.fuseUpdates());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user