BatchUpdate: Create ChangeControl inside transaction
Previously, we were passing in ChangeControls to addOp, which contained their own defensive copy of the Change object. Callers already have that ChangeControl instance available, so they might be tempted to read/write the Change within it inside the body of a BatchUpdate.Op, which doesn't do what they expect. Instead, just pass in the Change.Id, and hoist the user setup into the BatchUpdate creation itself. (This has the side effect of preventing a single batch update from performing updates on behalf of multiple users, which is ok.) Push the ChangeControl creation into the BatchUpdate internals, and use the change that was read in the transaction rather than depending on a change previously read not in the transaction and passed in by the caller (via ChangeControl). This improves consistency in another important way: the Change instance accessed via ctx.getChangeUpdate().getChange() (aka ctx.getChangeUpdate().getChangeNotes().getChange()) was read in the same transaction as the change accessed via ctx.getChange(). (They are currently still two different instances due to the aforementioned defensive copy, but that is a quirk we will deal with as we continue working on notedb update semantics.) Change-Id: Ifab569aece17a4bcf18b180f2b90b2d5daa3c4ac
This commit is contained in:
@@ -93,9 +93,10 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
|
|||||||
final String msgTxt, final Account account)
|
final String msgTxt, final Account account)
|
||||||
throws RestApiException, UpdateException {
|
throws RestApiException, UpdateException {
|
||||||
Op op = new Op(msgTxt, account);
|
Op op = new Op(msgTxt, account);
|
||||||
|
Change c = control.getChange();
|
||||||
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
||||||
control.getChange().getProject(), TimeUtil.nowTs())) {
|
c.getProject(), control.getCurrentUser(), TimeUtil.nowTs())) {
|
||||||
u.addOp(control, op).execute();
|
u.addOp(c.getId(), op).execute();
|
||||||
}
|
}
|
||||||
return op.change;
|
return op.change;
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,7 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
|
|||||||
@Override
|
@Override
|
||||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||||
ResourceConflictException {
|
ResourceConflictException {
|
||||||
change = ctx.readChange();
|
change = ctx.getChange();
|
||||||
if (change == null || !change.getStatus().isOpen()) {
|
if (change == null || !change.getStatus().isOpen()) {
|
||||||
throw new ResourceConflictException("change is " + status(change));
|
throw new ResourceConflictException("change is " + status(change));
|
||||||
} else if (change.getStatus() == Change.Status.DRAFT) {
|
} else if (change.getStatus() == Change.Status.DRAFT) {
|
||||||
|
|||||||
@@ -215,8 +215,9 @@ public class CherryPickChange {
|
|||||||
PatchSet current = db.get().patchSets().get(change.currentPatchSetId());
|
PatchSet current = db.get().patchSets().get(change.currentPatchSetId());
|
||||||
|
|
||||||
try (BatchUpdate bu = batchUpdateFactory.create(
|
try (BatchUpdate bu = batchUpdateFactory.create(
|
||||||
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
|
db.get(), change.getDest().getParentKey(), identifiedUser,
|
||||||
bu.addOp(changeControl, inserter
|
TimeUtil.nowTs())) {
|
||||||
|
bu.addOp(change.getId(), inserter
|
||||||
.setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
|
.setMessage("Uploaded patch set " + newPatchSetId.get() + ".")
|
||||||
.setDraft(current.isDraft())
|
.setDraft(current.isDraft())
|
||||||
.setUploader(identifiedUser.getAccountId())
|
.setUploader(identifiedUser.getAccountId())
|
||||||
|
|||||||
@@ -466,8 +466,10 @@ public class ConsistencyChecker {
|
|||||||
PatchSetInserter inserter =
|
PatchSetInserter inserter =
|
||||||
patchSetInserterFactory.create(repo, rw, ctl, commit);
|
patchSetInserterFactory.create(repo, rw, ctl, commit);
|
||||||
try (BatchUpdate bu = updateFactory.create(
|
try (BatchUpdate bu = updateFactory.create(
|
||||||
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
|
db.get(), change.getDest().getParentKey(), user.get(),
|
||||||
bu.addOp(ctl, inserter.setValidatePolicy(CommitValidators.Policy.NONE)
|
TimeUtil.nowTs())) {
|
||||||
|
bu.addOp(change.getId(), inserter
|
||||||
|
.setValidatePolicy(CommitValidators.Policy.NONE)
|
||||||
.setRunHooks(false)
|
.setRunHooks(false)
|
||||||
.setSendMail(false)
|
.setSendMail(false)
|
||||||
.setAllowClosed(true)
|
.setAllowClosed(true)
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ public class PatchSetInserter extends BatchUpdate.Op {
|
|||||||
@Override
|
@Override
|
||||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||||
InvalidChangeOperationException {
|
InvalidChangeOperationException {
|
||||||
change = ctx.readChange();
|
change = ctx.getChange();
|
||||||
Change.Id id = change.getId();
|
Change.Id id = change.getId();
|
||||||
final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
|
final PatchSet.Id currentPatchSetId = change.currentPatchSetId();
|
||||||
if (!change.getStatus().isOpen() && !allowClosed) {
|
if (!change.getStatus().isOpen() && !allowClosed) {
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
|
|||||||
|
|
||||||
Op op = new Op(ctl, input != null ? input : new Input());
|
Op op = new Op(ctl, input != null ? input : new Input());
|
||||||
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
||||||
req.getChange().getProject(), TimeUtil.nowTs())) {
|
req.getChange().getProject(), ctl.getCurrentUser(), TimeUtil.nowTs())) {
|
||||||
u.addOp(ctl, op);
|
u.addOp(req.getChange().getId(), op);
|
||||||
u.execute();
|
u.execute();
|
||||||
}
|
}
|
||||||
return Strings.isNullOrEmpty(op.newTopicName)
|
return Strings.isNullOrEmpty(op.newTopicName)
|
||||||
@@ -102,7 +102,7 @@ public class PutTopic implements RestModifyView<ChangeResource, Input>,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateChange(ChangeContext ctx) throws OrmException {
|
public void updateChange(ChangeContext ctx) throws OrmException {
|
||||||
change = ctx.readChange();
|
change = ctx.getChange();
|
||||||
String newTopicName = Strings.nullToEmpty(input.topic);
|
String newTopicName = Strings.nullToEmpty(input.topic);
|
||||||
String oldTopicName = Strings.nullToEmpty(change.getTopic());
|
String oldTopicName = Strings.nullToEmpty(change.getTopic());
|
||||||
if (oldTopicName.equals(newTopicName)) {
|
if (oldTopicName.equals(newTopicName)) {
|
||||||
|
|||||||
@@ -288,8 +288,8 @@ public class RebaseChange {
|
|||||||
.setRunHooks(runHooks);
|
.setRunHooks(runHooks);
|
||||||
|
|
||||||
try (BatchUpdate bu = updateFactory.create(
|
try (BatchUpdate bu = updateFactory.create(
|
||||||
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
|
db.get(), change.getProject(), uploader, TimeUtil.nowTs())) {
|
||||||
bu.addOp(changeControl, patchSetInserter.setMessage(
|
bu.addOp(change.getId(), patchSetInserter.setMessage(
|
||||||
"Patch Set " + patchSetInserter.getPatchSetId().get()
|
"Patch Set " + patchSetInserter.getPatchSetId().get()
|
||||||
+ ": Patch Set " + patchSetId.get() + " was rebased"));
|
+ ": Patch Set " + patchSetId.get() + " was rebased"));
|
||||||
bu.execute();
|
bu.execute();
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
|
|||||||
|
|
||||||
Op op = new Op(input);
|
Op op = new Op(input);
|
||||||
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
||||||
req.getChange().getProject(), TimeUtil.nowTs())) {
|
req.getChange().getProject(), ctl.getCurrentUser(), TimeUtil.nowTs())) {
|
||||||
u.addOp(ctl, op).execute();
|
u.addOp(req.getChange().getId(), op).execute();
|
||||||
}
|
}
|
||||||
return json.create(ChangeJson.NO_OPTIONS).format(op.change);
|
return json.create(ChangeJson.NO_OPTIONS).format(op.change);
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
|
|||||||
public void updateChange(ChangeContext ctx) throws OrmException,
|
public void updateChange(ChangeContext ctx) throws OrmException,
|
||||||
ResourceConflictException {
|
ResourceConflictException {
|
||||||
caller = (IdentifiedUser) ctx.getUser();
|
caller = (IdentifiedUser) ctx.getUser();
|
||||||
change = ctx.readChange();
|
change = ctx.getChange();
|
||||||
if (change == null || change.getStatus() != Status.ABANDONED) {
|
if (change == null || change.getStatus() != Status.ABANDONED) {
|
||||||
throw new ResourceConflictException("change is " + status(change));
|
throw new ResourceConflictException("change is " + status(change));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,8 +239,9 @@ public class ChangeEditUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (BatchUpdate bu = updateFactory.create(
|
try (BatchUpdate bu = updateFactory.create(
|
||||||
db.get(), change.getDest().getParentKey(), TimeUtil.nowTs())) {
|
db.get(), change.getProject(), ctl.getCurrentUser(),
|
||||||
bu.addOp(ctl, inserter
|
TimeUtil.nowTs())) {
|
||||||
|
bu.addOp(change.getId(), inserter
|
||||||
.setDraft(change.getStatus() == Status.DRAFT ||
|
.setDraft(change.getStatus() == Status.DRAFT ||
|
||||||
basePatchSet.isDraft())
|
basePatchSet.isDraft())
|
||||||
.setMessage(message.toString()));
|
.setMessage(message.toString()));
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
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 com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
@@ -31,7 +30,6 @@ import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
|||||||
import com.google.gerrit.server.index.ChangeIndexer;
|
import com.google.gerrit.server.index.ChangeIndexer;
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gwtorm.server.OrmException;
|
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import com.google.inject.assistedinject.AssistedInject;
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
@@ -46,7 +44,6 @@ import java.io.IOException;
|
|||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -75,7 +72,7 @@ import java.util.Map;
|
|||||||
public class BatchUpdate implements AutoCloseable {
|
public class BatchUpdate implements AutoCloseable {
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
public BatchUpdate create(ReviewDb db, Project.NameKey project,
|
public BatchUpdate create(ReviewDb db, Project.NameKey project,
|
||||||
Timestamp when);
|
CurrentUser user, Timestamp when);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Context {
|
public class Context {
|
||||||
@@ -128,8 +125,8 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Change readChange() throws OrmException {
|
public Change getChange() {
|
||||||
return db.changes().get(update.getChange().getId());
|
return update.getChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentUser getUser() {
|
public CurrentUser getUser() {
|
||||||
@@ -155,14 +152,15 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
private final ReviewDb db;
|
private final ReviewDb db;
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final ChangeIndexer indexer;
|
private final ChangeIndexer indexer;
|
||||||
|
private final ChangeControl.GenericFactory changeControlFactory;
|
||||||
private final ChangeUpdate.Factory changeUpdateFactory;
|
private final ChangeUpdate.Factory changeUpdateFactory;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
|
|
||||||
private final Project.NameKey project;
|
private final Project.NameKey project;
|
||||||
|
private final CurrentUser user;
|
||||||
private final Timestamp when;
|
private final Timestamp when;
|
||||||
|
|
||||||
private final ListMultimap<Change.Id, Op> ops = ArrayListMultimap.create();
|
private final ListMultimap<Change.Id, Op> ops = ArrayListMultimap.create();
|
||||||
private final Map<Change.Id, ChangeControl> changeControls = new HashMap<>();
|
|
||||||
private final List<CheckedFuture<?, IOException>> indexFutures =
|
private final List<CheckedFuture<?, IOException>> indexFutures =
|
||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
|
|
||||||
@@ -175,17 +173,21 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
@AssistedInject
|
@AssistedInject
|
||||||
BatchUpdate(GitRepositoryManager repoManager,
|
BatchUpdate(GitRepositoryManager repoManager,
|
||||||
ChangeIndexer indexer,
|
ChangeIndexer indexer,
|
||||||
|
ChangeControl.GenericFactory changeControlFactory,
|
||||||
ChangeUpdate.Factory changeUpdateFactory,
|
ChangeUpdate.Factory changeUpdateFactory,
|
||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
@Assisted ReviewDb db,
|
@Assisted ReviewDb db,
|
||||||
@Assisted Project.NameKey project,
|
@Assisted Project.NameKey project,
|
||||||
|
@Assisted CurrentUser user,
|
||||||
@Assisted Timestamp when) {
|
@Assisted Timestamp when) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.indexer = indexer;
|
this.indexer = indexer;
|
||||||
|
this.changeControlFactory = changeControlFactory;
|
||||||
this.changeUpdateFactory = changeUpdateFactory;
|
this.changeUpdateFactory = changeUpdateFactory;
|
||||||
this.gitRefUpdated = gitRefUpdated;
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
this.project = project;
|
this.project = project;
|
||||||
|
this.user = user;
|
||||||
this.when = when;
|
this.when = when;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +234,8 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
return inserter;
|
return inserter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BatchUpdate addOp(ChangeControl ctl, Op op) {
|
public BatchUpdate addOp(Change.Id id, Op op) {
|
||||||
Change.Id id = ctl.getChange().getId();
|
|
||||||
ChangeControl old = changeControls.get(id);
|
|
||||||
// TODO(dborowitz): Not sure this is guaranteed in general.
|
|
||||||
checkArgument(old == null || old == ctl,
|
|
||||||
"mismatched ChangeControls for change %s", id);
|
|
||||||
ops.put(id, op);
|
ops.put(id, op);
|
||||||
changeControls.put(id, ctl);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +299,10 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
try {
|
try {
|
||||||
for (Map.Entry<Change.Id, Collection<Op>> e : ops.asMap().entrySet()) {
|
for (Map.Entry<Change.Id, Collection<Op>> e : ops.asMap().entrySet()) {
|
||||||
Change.Id id = e.getKey();
|
Change.Id id = e.getKey();
|
||||||
ChangeUpdate update =
|
|
||||||
changeUpdateFactory.create(changeControls.get(id), when);
|
|
||||||
db.changes().beginTransaction(id);
|
db.changes().beginTransaction(id);
|
||||||
|
Change change = db.changes().get(id);
|
||||||
|
ChangeControl ctl = changeControlFactory.controlFor(change, user);
|
||||||
|
ChangeUpdate update = changeUpdateFactory.create(ctl, when);
|
||||||
try {
|
try {
|
||||||
for (Op op : e.getValue()) {
|
for (Op op : e.getValue()) {
|
||||||
op.updateChange(new ChangeContext(update));
|
op.updateChange(new ChangeContext(update));
|
||||||
|
|||||||
@@ -72,14 +72,15 @@ public class CherryPick extends SubmitStrategy {
|
|||||||
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
try (BatchUpdate u = args.newBatchUpdate(TimeUtil.nowTs())) {
|
||||||
while (!sorted.isEmpty()) {
|
while (!sorted.isEmpty()) {
|
||||||
CodeReviewCommit n = sorted.remove(0);
|
CodeReviewCommit n = sorted.remove(0);
|
||||||
|
Change.Id cid = n.change().getId();
|
||||||
if (first && branchTip == null) {
|
if (first && branchTip == null) {
|
||||||
u.addOp(n.getControl(), new CherryPickUnbornRootOp(mergeTip, n));
|
u.addOp(cid, new CherryPickUnbornRootOp(mergeTip, n));
|
||||||
} else if (n.getParentCount() == 0) {
|
} else if (n.getParentCount() == 0) {
|
||||||
u.addOp(n.getControl(), new CherryPickRootOp(n));
|
u.addOp(cid, new CherryPickRootOp(n));
|
||||||
} else if (n.getParentCount() == 1) {
|
} else if (n.getParentCount() == 1) {
|
||||||
u.addOp(n.getControl(), new CherryPickOneOp(mergeTip, n));
|
u.addOp(cid, new CherryPickOneOp(mergeTip, n));
|
||||||
} else {
|
} else {
|
||||||
u.addOp(n.getControl(), new CherryPickMultipleParentsOp(mergeTip, n));
|
u.addOp(cid, new CherryPickMultipleParentsOp(mergeTip, n));
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ public abstract class SubmitStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BatchUpdate newBatchUpdate(Timestamp when) {
|
BatchUpdate newBatchUpdate(Timestamp when) {
|
||||||
return batchUpdateFactory.create(db, destBranch.getParentKey(), when)
|
return batchUpdateFactory
|
||||||
|
.create(db, destBranch.getParentKey(), caller, when)
|
||||||
.setRepository(repo, rw, inserter);
|
.setRepository(repo, rw, inserter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1336,8 +1336,8 @@ public abstract class AbstractQueryChangesTest {
|
|||||||
.setRunHooks(false)
|
.setRunHooks(false)
|
||||||
.setValidatePolicy(CommitValidators.Policy.NONE);
|
.setValidatePolicy(CommitValidators.Policy.NONE);
|
||||||
try (BatchUpdate bu = updateFactory.create(
|
try (BatchUpdate bu = updateFactory.create(
|
||||||
db, c.getDest().getParentKey(), TimeUtil.nowTs())) {
|
db, c.getDest().getParentKey(), user, TimeUtil.nowTs())) {
|
||||||
bu.addOp(ctl, inserter);
|
bu.addOp(c.getId(), inserter);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user