Don't expose RepoView#getRepository()
For the purposes of fusing BatchUpdateOp#updateChange and updateRepo into a single batch update, implementations of updateChange need to be able to observe the ref updates from updateRepo before they are executed. If we expose the repository directly to updateRepo, it's too easy for them use it in a wrong way that won't see the results of updateRepo. That is why we limited the operations that are available through RepoView. Finish this refactoring by not exposing the Repository to any op phases, including updateRepo. The repo still needs to be directly accessed by the BatchUpdate internals so it can actually do the writes, which is fine as it doesn't escape this package. Change-Id: I0959aa2d2bce346a9a6d493c23a38301b43d1e5d
This commit is contained in:
@@ -246,6 +246,7 @@ junit_tests(
|
||||
"//lib:guava",
|
||||
"//lib:guava-retrying",
|
||||
"//lib:protobuf",
|
||||
"//lib:truth-java8-extension",
|
||||
"//lib/bouncycastle:bcprov",
|
||||
"//lib/bouncycastle:bcpkix",
|
||||
"//lib/dropwizard:dropwizard-core",
|
||||
|
||||
@@ -529,7 +529,7 @@ public class ChangeInserter implements InsertChangeOp {
|
||||
commitId,
|
||||
ctx.getIdentifiedUser())) {
|
||||
commitValidatorsFactory
|
||||
.forGerritCommits(refControl, new NoSshInfo(), ctx.getRepository())
|
||||
.forGerritCommits(refControl, new NoSshInfo(), ctx.getRevWalk())
|
||||
.validate(event);
|
||||
}
|
||||
} catch (CommitValidationException e) {
|
||||
|
||||
@@ -38,10 +38,10 @@ import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
|
||||
@@ -137,16 +137,14 @@ class DeleteChangeOp implements BatchUpdateOp {
|
||||
}
|
||||
|
||||
private boolean isPatchSetMerged(ChangeContext ctx, PatchSet patchSet) throws IOException {
|
||||
Repository repository = ctx.getRepository();
|
||||
Ref destinationRef = repository.exactRef(ctx.getChange().getDest().get());
|
||||
if (destinationRef == null) {
|
||||
Optional<ObjectId> destId = ctx.getRepoView().getRef(ctx.getChange().getDest().get());
|
||||
if (!destId.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RevWalk revWalk = ctx.getRevWalk();
|
||||
ObjectId objectId = ObjectId.fromString(patchSet.getRevision().get());
|
||||
return revWalk.isMergedInto(
|
||||
revWalk.parseCommit(objectId), revWalk.parseCommit(destinationRef.getObjectId()));
|
||||
return revWalk.isMergedInto(revWalk.parseCommit(objectId), revWalk.parseCommit(destId.get()));
|
||||
}
|
||||
|
||||
private void deleteChangeElementsFromDb(ChangeContext ctx, Change.Id id) throws OrmException {
|
||||
@@ -174,8 +172,8 @@ class DeleteChangeOp implements BatchUpdateOp {
|
||||
public void updateRepo(RepoContext ctx) throws IOException {
|
||||
String prefix = new PatchSet.Id(id, 1).toRefName();
|
||||
prefix = prefix.substring(0, prefix.length() - 1);
|
||||
for (Ref ref : ctx.getRepository().getRefDatabase().getRefs(prefix).values()) {
|
||||
ctx.addRefUpdate(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName()));
|
||||
for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(prefix).entrySet()) {
|
||||
ctx.addRefUpdate(new ReceiveCommand(e.getValue(), ObjectId.zeroId(), prefix + e.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
||||
commitId,
|
||||
ctx.getIdentifiedUser())) {
|
||||
commitValidatorsFactory
|
||||
.forGerritCommits(origCtl.getRefControl(), new NoSshInfo(), ctx.getRepository())
|
||||
.forGerritCommits(origCtl.getRefControl(), new NoSshInfo(), ctx.getRevWalk())
|
||||
.validate(event);
|
||||
} catch (CommitValidationException e) {
|
||||
throw new ResourceConflictException(e.getFullMessage());
|
||||
|
||||
@@ -160,7 +160,9 @@ public class RebaseChangeOp implements BatchUpdateOp {
|
||||
baseCommitId.name());
|
||||
|
||||
rebasedPatchSetId =
|
||||
ChangeUtil.nextPatchSetId(ctx.getRepository(), ctl.getChange().currentPatchSetId());
|
||||
ChangeUtil.nextPatchSetIdFromChangeRefsMap(
|
||||
ctx.getRepoView().getRefs(originalPatchSet.getId().getParentKey().toRefPrefix()),
|
||||
ctl.getChange().currentPatchSetId());
|
||||
patchSetInserter =
|
||||
patchSetInserterFactory
|
||||
.create(ctl, rebasedPatchSetId, rebasedCommit)
|
||||
@@ -241,7 +243,7 @@ public class RebaseChangeOp implements BatchUpdateOp {
|
||||
}
|
||||
|
||||
ThreeWayMerger merger =
|
||||
newMergeUtil().newThreeWayMerger(ctx.getInserter(), ctx.getRepository().getConfig());
|
||||
newMergeUtil().newThreeWayMerger(ctx.getInserter(), ctx.getRepoView().getConfig());
|
||||
merger.setBase(parentCommit);
|
||||
merger.merge(original, base);
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
|
||||
/**
|
||||
* Utility functions to manipulate change edits.
|
||||
@@ -222,7 +223,9 @@ public class ChangeEditUtil {
|
||||
new BatchUpdateOp() {
|
||||
@Override
|
||||
public void updateRepo(RepoContext ctx) throws Exception {
|
||||
deleteRef(ctx.getRepository(), edit);
|
||||
ctx.addRefUpdate(
|
||||
new ReceiveCommand(
|
||||
edit.getEditCommit().copy(), ObjectId.zeroId(), edit.getRefName()));
|
||||
}
|
||||
});
|
||||
bu.execute();
|
||||
|
||||
@@ -2778,7 +2778,7 @@ public class ReceiveCommits {
|
||||
CommitValidators validators =
|
||||
isMerged
|
||||
? commitValidatorsFactory.forMergedCommits(ctl)
|
||||
: commitValidatorsFactory.forReceiveCommits(ctl, sshInfo, repo);
|
||||
: commitValidatorsFactory.forReceiveCommits(ctl, sshInfo, repo, rw);
|
||||
messages.addAll(validators.validate(receiveEvent));
|
||||
} catch (CommitValidationException e) {
|
||||
logDebug("Commit validation failed on {}", c.name());
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.gerrit.common.FooterConstants.CHANGE_ID;
|
||||
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
|
||||
import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromReviewers;
|
||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
|
||||
import static org.eclipse.jgit.lib.Constants.R_HEADS;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -62,13 +63,11 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefDatabase;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.PushCertificate;
|
||||
@@ -199,15 +198,15 @@ public class ReplaceOp implements BatchUpdateOp {
|
||||
changeKindCache.getChangeKind(
|
||||
projectControl.getProject().getNameKey(),
|
||||
ctx.getRevWalk(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
priorCommitId,
|
||||
commitId);
|
||||
|
||||
if (checkMergedInto) {
|
||||
Ref mergedInto = findMergedInto(ctx, dest.get(), commit);
|
||||
String mergedInto = findMergedInto(ctx, dest.get(), commit);
|
||||
if (mergedInto != null) {
|
||||
mergedByPushOp =
|
||||
mergedByPushOpFactory.create(requestScopePropagator, patchSetId, mergedInto.getName());
|
||||
mergedByPushOpFactory.create(requestScopePropagator, patchSetId, mergedInto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,18 +511,17 @@ public class ReplaceOp implements BatchUpdateOp {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static Ref findMergedInto(Context ctx, String first, RevCommit commit) {
|
||||
private static String findMergedInto(Context ctx, String first, RevCommit commit) {
|
||||
try {
|
||||
RefDatabase refDatabase = ctx.getRepository().getRefDatabase();
|
||||
|
||||
Ref firstRef = refDatabase.exactRef(first);
|
||||
if (firstRef != null && isMergedInto(ctx.getRevWalk(), commit, firstRef)) {
|
||||
return firstRef;
|
||||
RevWalk rw = ctx.getRevWalk();
|
||||
Optional<ObjectId> firstId = ctx.getRepoView().getRef(first);
|
||||
if (firstId.isPresent() && rw.isMergedInto(commit, rw.parseCommit(firstId.get()))) {
|
||||
return first;
|
||||
}
|
||||
|
||||
for (Ref ref : refDatabase.getRefs(Constants.R_HEADS).values()) {
|
||||
if (isMergedInto(ctx.getRevWalk(), commit, ref)) {
|
||||
return ref;
|
||||
for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(R_HEADS).entrySet()) {
|
||||
if (rw.isMergedInto(commit, rw.parseCommit(e.getValue()))) {
|
||||
return R_HEADS + e.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -532,8 +530,4 @@ public class ReplaceOp implements BatchUpdateOp {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMergedInto(RevWalk rw, RevCommit commit, Ref ref) throws IOException {
|
||||
return rw.isMergedInto(commit, rw.parseCommit(ref.getObjectId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server.git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -44,4 +45,9 @@ public class RepoRefCache implements RefCache {
|
||||
ids.put(refName, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @return an unmodifiable view of the refs that have been cached by this instance. */
|
||||
public Map<String, Optional<ObjectId>> getCachedRefs() {
|
||||
return Collections.unmodifiableMap(ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,10 @@ public class CherryPick extends SubmitStrategy {
|
||||
// delta relative to that one parent and redoing that on the current merge
|
||||
// tip.
|
||||
args.rw.parseBody(toMerge);
|
||||
psId = ChangeUtil.nextPatchSetId(ctx.getRepository(), toMerge.change().currentPatchSetId());
|
||||
psId =
|
||||
ChangeUtil.nextPatchSetIdFromChangeRefsMap(
|
||||
ctx.getRepoView().getRefs(getId().toRefPrefix()),
|
||||
toMerge.change().currentPatchSetId());
|
||||
RevCommit mergeTip = args.mergeTip.getCurrentTip();
|
||||
args.rw.parseBody(mergeTip);
|
||||
String cherryPickCmtMsg = args.mergeUtil.createCommitMessageOnSubmit(toMerge, mergeTip);
|
||||
@@ -106,7 +109,7 @@ public class CherryPick extends SubmitStrategy {
|
||||
newCommit =
|
||||
args.mergeUtil.createCherryPickFromCommit(
|
||||
ctx.getInserter(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
args.mergeTip.getCurrentTip(),
|
||||
toMerge,
|
||||
committer,
|
||||
@@ -197,7 +200,7 @@ public class CherryPick extends SubmitStrategy {
|
||||
myIdent,
|
||||
args.rw,
|
||||
ctx.getInserter(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
args.destBranch,
|
||||
mergeTip.getCurrentTip(),
|
||||
toMerge);
|
||||
|
||||
@@ -42,7 +42,7 @@ class MergeOneOp extends SubmitStrategyOp {
|
||||
args.serverIdent,
|
||||
args.rw,
|
||||
ctx.getInserter(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
args.destBranch,
|
||||
args.mergeTip.getCurrentTip(),
|
||||
toMerge);
|
||||
|
||||
@@ -127,7 +127,9 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
|
||||
// RebaseAlways means we modify commit message.
|
||||
args.rw.parseBody(toMerge);
|
||||
newPatchSetId =
|
||||
ChangeUtil.nextPatchSetId(ctx.getRepository(), toMerge.change().currentPatchSetId());
|
||||
ChangeUtil.nextPatchSetIdFromChangeRefsMap(
|
||||
ctx.getRepoView().getRefs(getId().toRefPrefix()),
|
||||
toMerge.change().currentPatchSetId());
|
||||
RevCommit mergeTip = args.mergeTip.getCurrentTip();
|
||||
args.rw.parseBody(mergeTip);
|
||||
String cherryPickCmtMsg = args.mergeUtil.createCommitMessageOnSubmit(toMerge, mergeTip);
|
||||
@@ -137,7 +139,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
|
||||
newCommit =
|
||||
args.mergeUtil.createCherryPickFromCommit(
|
||||
ctx.getInserter(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
args.mergeTip.getCurrentTip(),
|
||||
toMerge,
|
||||
committer,
|
||||
@@ -269,7 +271,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
|
||||
caller,
|
||||
args.rw,
|
||||
ctx.getInserter(),
|
||||
ctx.getRepository().getConfig(),
|
||||
ctx.getRepoView().getConfig(),
|
||||
args.destBranch,
|
||||
mergeTip.getCurrentTip(),
|
||||
toMerge);
|
||||
|
||||
@@ -53,7 +53,6 @@ import com.google.gerrit.server.update.RepoContext;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -62,7 +61,6 @@ import java.util.Objects;
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.slf4j.Logger;
|
||||
@@ -168,19 +166,20 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
|
||||
}
|
||||
CodeReviewRevWalk rw = (CodeReviewRevWalk) ctx.getRevWalk();
|
||||
Change.Id id = getId();
|
||||
String refPrefix = id.toRefPrefix();
|
||||
|
||||
Collection<Ref> refs = ctx.getRepository().getRefDatabase().getRefs(id.toRefPrefix()).values();
|
||||
Map<String, ObjectId> refs = ctx.getRepoView().getRefs(refPrefix);
|
||||
List<CodeReviewCommit> commits = new ArrayList<>(refs.size());
|
||||
for (Ref ref : refs) {
|
||||
PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
|
||||
for (Map.Entry<String, ObjectId> e : refs.entrySet()) {
|
||||
PatchSet.Id psId = PatchSet.Id.fromRef(refPrefix + e.getKey());
|
||||
if (psId == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
CodeReviewCommit c = rw.parseCommit(ref.getObjectId());
|
||||
CodeReviewCommit c = rw.parseCommit(e.getValue());
|
||||
c.setPatchsetId(psId);
|
||||
commits.add(c);
|
||||
} catch (MissingObjectException | IncorrectObjectTypeException e) {
|
||||
} catch (MissingObjectException | IncorrectObjectTypeException ex) {
|
||||
continue; // Bogus ref, can't be merged into tip so we don't care.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,7 @@ public class CommitValidators {
|
||||
}
|
||||
|
||||
public CommitValidators forReceiveCommits(
|
||||
RefControl refControl, SshInfo sshInfo, Repository repo) throws IOException {
|
||||
try (RevWalk rw = new RevWalk(repo)) {
|
||||
RefControl refControl, SshInfo sshInfo, Repository repo, RevWalk rw) throws IOException {
|
||||
NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw);
|
||||
return new CommitValidators(
|
||||
ImmutableList.of(
|
||||
@@ -102,15 +101,13 @@ public class CommitValidators {
|
||||
new SignedOffByValidator(refControl),
|
||||
new ChangeIdValidator(
|
||||
refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
|
||||
new ConfigValidator(refControl, repo, allUsers),
|
||||
new ConfigValidator(refControl, rw, allUsers),
|
||||
new BannedCommitsValidator(rejectCommits),
|
||||
new PluginCommitValidationListener(pluginValidators),
|
||||
new BlockExternalIdUpdateListener(allUsers)));
|
||||
}
|
||||
}
|
||||
|
||||
public CommitValidators forGerritCommits(
|
||||
RefControl refControl, SshInfo sshInfo, Repository repo) {
|
||||
public CommitValidators forGerritCommits(RefControl refControl, SshInfo sshInfo, RevWalk rw) {
|
||||
return new CommitValidators(
|
||||
ImmutableList.of(
|
||||
new UploadMergesPermissionValidator(refControl),
|
||||
@@ -119,7 +116,7 @@ public class CommitValidators {
|
||||
new SignedOffByValidator(refControl),
|
||||
new ChangeIdValidator(
|
||||
refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
|
||||
new ConfigValidator(refControl, repo, allUsers),
|
||||
new ConfigValidator(refControl, rw, allUsers),
|
||||
new PluginCommitValidationListener(pluginValidators),
|
||||
new BlockExternalIdUpdateListener(allUsers)));
|
||||
}
|
||||
@@ -320,12 +317,12 @@ public class CommitValidators {
|
||||
/** If this is the special project configuration branch, validate the config. */
|
||||
public static class ConfigValidator implements CommitValidationListener {
|
||||
private final RefControl refControl;
|
||||
private final Repository repo;
|
||||
private final RevWalk rw;
|
||||
private final AllUsersName allUsers;
|
||||
|
||||
public ConfigValidator(RefControl refControl, Repository repo, AllUsersName allUsers) {
|
||||
public ConfigValidator(RefControl refControl, RevWalk rw, AllUsersName allUsers) {
|
||||
this.refControl = refControl;
|
||||
this.repo = repo;
|
||||
this.rw = rw;
|
||||
this.allUsers = allUsers;
|
||||
}
|
||||
|
||||
@@ -339,7 +336,7 @@ public class CommitValidators {
|
||||
|
||||
try {
|
||||
ProjectConfig cfg = new ProjectConfig(receiveEvent.project.getNameKey());
|
||||
cfg.load(repo, receiveEvent.command.getNewId());
|
||||
cfg.load(rw, receiveEvent.command.getNewId());
|
||||
if (!cfg.getValidationErrors().isEmpty()) {
|
||||
addError("Invalid project configuration:", messages);
|
||||
for (ValidationError err : cfg.getValidationErrors()) {
|
||||
@@ -367,7 +364,7 @@ public class CommitValidators {
|
||||
if (accountId != null) {
|
||||
try {
|
||||
WatchConfig wc = new WatchConfig(accountId);
|
||||
wc.load(repo, receiveEvent.command.getNewId());
|
||||
wc.load(rw, receiveEvent.command.getNewId());
|
||||
if (!wc.getValidationErrors().isEmpty()) {
|
||||
addError("Invalid project configuration:", messages);
|
||||
for (ValidationError err : wc.getValidationErrors()) {
|
||||
|
||||
@@ -322,11 +322,6 @@ public abstract class BatchUpdate implements AutoCloseable {
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
protected Repository getRepository() throws IOException {
|
||||
initRepository();
|
||||
return repoView.getRepository();
|
||||
}
|
||||
|
||||
protected RevWalk getRevWalk() throws IOException {
|
||||
initRepository();
|
||||
return repoView.getRevWalk();
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.TimeZone;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
|
||||
/**
|
||||
@@ -41,15 +40,14 @@ public interface Context {
|
||||
Project.NameKey getProject();
|
||||
|
||||
/**
|
||||
* Get an open repository instance for this project.
|
||||
* Get a read-only view of the open repository for this project.
|
||||
*
|
||||
* <p>Will be opened lazily if necessary; callers should not close the repo. In some phases of the
|
||||
* update, the repository might be read-only; see {@link BatchUpdateOp} for details.
|
||||
* <p>Will be opened lazily if necessary.
|
||||
*
|
||||
* @return repository instance.
|
||||
* @throws IOException if an error occurred opening the repo.
|
||||
*/
|
||||
Repository getRepository() throws IOException;
|
||||
RepoView getRepoView() throws IOException;
|
||||
|
||||
/**
|
||||
* Get a walk for this project.
|
||||
|
||||
@@ -140,19 +140,15 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
||||
}
|
||||
|
||||
class ContextImpl implements Context {
|
||||
private Repository repoWrapper;
|
||||
|
||||
@Override
|
||||
public Repository getRepository() throws IOException {
|
||||
if (repoWrapper == null) {
|
||||
repoWrapper = new ReadOnlyRepository(NoteDbBatchUpdate.this.getRepository());
|
||||
}
|
||||
return repoWrapper;
|
||||
public RepoView getRepoView() throws IOException {
|
||||
initRepository();
|
||||
return NoteDbBatchUpdate.this.repoView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevWalk getRevWalk() throws IOException {
|
||||
return NoteDbBatchUpdate.this.getRevWalk();
|
||||
return getRepoView().getRevWalk();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,14 +183,9 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
||||
}
|
||||
|
||||
private class RepoContextImpl extends ContextImpl implements RepoContext {
|
||||
@Override
|
||||
public Repository getRepository() throws IOException {
|
||||
return NoteDbBatchUpdate.this.getRepository();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter getInserter() throws IOException {
|
||||
return NoteDbBatchUpdate.this.getObjectInserter();
|
||||
return getRepoView().getInserter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,7 +361,8 @@ class NoteDbBatchUpdate extends BatchUpdate {
|
||||
logDebug("Executing change ops");
|
||||
Map<Change.Id, ChangeResult> result =
|
||||
Maps.newLinkedHashMapWithExpectedSize(ops.keySet().size());
|
||||
Repository repo = getRepository();
|
||||
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();
|
||||
|
||||
@@ -31,8 +31,7 @@ public interface RepoContext extends Context {
|
||||
/**
|
||||
* Add a command to the pending list of commands.
|
||||
*
|
||||
* <p>Callers should use this method instead of writing directly to the repository returned by
|
||||
* {@link #getRepository()}.
|
||||
* <p>This method is the only way of updating refs in the repository from a {@link BatchUpdateOp}.
|
||||
*
|
||||
* @param cmd ref update command.
|
||||
* @throws IOException if an error occurred opening the repo.
|
||||
|
||||
@@ -17,15 +17,33 @@ package com.google.gerrit.server.update;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
|
||||
/**
|
||||
* Restricted view of a {@link Repository} for use by {@link BatchUpdateOp} implementations.
|
||||
*
|
||||
* <p>This class serves two purposes in the context of {@link BatchUpdate}. First, the subset of
|
||||
* normal Repository functionality is purely read-only, which prevents implementors from modifying
|
||||
* the repository outside of {@link BatchUpdateOp#updateRepo}. Write operations can only be
|
||||
* performed by calling methods on {@link RepoContext}.
|
||||
*
|
||||
* <p>Second, the read methods take into account any pending operations on the repository that
|
||||
* implementations have staged using the write methods on {@link RepoContext}. Callers do not have
|
||||
* to worry about whether operations have been performed yet, and the implementation details may
|
||||
* differ between ReviewDb and NoteDb, but callers just don't need to care.
|
||||
*/
|
||||
public class RepoView {
|
||||
private final Repository repo;
|
||||
private final RevWalk rw;
|
||||
@@ -54,25 +72,112 @@ public class RepoView {
|
||||
closeRepo = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this repo's configuration.
|
||||
*
|
||||
* <p>This is the storage-level config you would get with {@link Repository#getConfig()}, not, for
|
||||
* example, the Gerrit-level project config.
|
||||
*
|
||||
* @return a defensive copy of the config; modifications have no effect on the underlying config.
|
||||
*/
|
||||
public Config getConfig() {
|
||||
return new Config(repo.getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an open revwalk on the repo.
|
||||
*
|
||||
* <p>Guaranteed to be able to read back any objects inserted in the repository via {@link
|
||||
* RepoContext#getInserter()}, even if objects have not been flushed to the underlying repo. In
|
||||
* particular this includes any object returned by {@link #getRef(String)}, even taking into
|
||||
* account not-yet-executed commands.
|
||||
*
|
||||
* @return revwalk.
|
||||
*/
|
||||
public RevWalk getRevWalk() {
|
||||
return rw;
|
||||
}
|
||||
|
||||
public ObjectInserter getInserter() {
|
||||
return inserter;
|
||||
}
|
||||
|
||||
public ChainedReceiveCommands getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single ref from the repo.
|
||||
*
|
||||
* <p>Takes into account any ref update commands added during the course of the update using
|
||||
* {@link RepoContext#addRefUpdate}, even if they have not yet been executed on the underlying
|
||||
* repo.
|
||||
*
|
||||
* <p>The results of individual ref lookups are cached: calling this method multiple times with
|
||||
* the same ref name will return the same result (unless a command was added in the meantime). The
|
||||
* repo is not reread.
|
||||
*
|
||||
* @param name exact ref name.
|
||||
* @return the value of the ref, if present.
|
||||
* @throws IOException if an error occurred.
|
||||
*/
|
||||
public Optional<ObjectId> getRef(String name) throws IOException {
|
||||
return getCommands().get(name);
|
||||
}
|
||||
|
||||
// TODO(dborowitz): Remove this so callers can't do arbitrary stuff.
|
||||
Repository getRepository() {
|
||||
return repo;
|
||||
/**
|
||||
* Look up refs by prefix.
|
||||
*
|
||||
* <p>Takes into account any ref update commands added during the course of the update using
|
||||
* {@link RepoContext#addRefUpdate}, even if they have not yet been executed on the underlying
|
||||
* repo.
|
||||
*
|
||||
* <p>For any ref that has previously been accessed with {@link #getRef(String)}, the value in the
|
||||
* result map will be that same cached value. Any refs that have <em>not</em> been previously
|
||||
* accessed are re-scanned from the repo on each call.
|
||||
*
|
||||
* @param prefix ref prefix; must end in '/' or else be empty.
|
||||
* @return a map of ref suffixes to SHA-1s. The refs are all under {@code prefix} and have the
|
||||
* prefix stripped; this matches the behavior of {@link
|
||||
* org.eclipse.jgit.lib.RefDatabase#getRefs(String)}.
|
||||
* @throws IOException if an error occurred.
|
||||
*/
|
||||
public Map<String, ObjectId> getRefs(String prefix) throws IOException {
|
||||
Map<String, ObjectId> result =
|
||||
new HashMap<>(
|
||||
Maps.transformValues(repo.getRefDatabase().getRefs(prefix), Ref::getObjectId));
|
||||
|
||||
// First, overwrite any cached reads from the underlying RepoRefCache. If any of these differ,
|
||||
// it's because a ref was updated after the RepoRefCache read it. It feels a little odd to
|
||||
// prefer the *old* value in this case, but it would be weirder to be inconsistent with getRef.
|
||||
//
|
||||
// Mostly this doesn't matter. If the caller was intending to write to the ref, they lost a
|
||||
// race, and they will get a lock failure. If they just want to read, well, the JGit interface
|
||||
// doesn't currently guarantee that any snapshot of multiple refs is consistent, so they were
|
||||
// probably out of luck anyway.
|
||||
commands
|
||||
.getRepoRefCache()
|
||||
.getCachedRefs()
|
||||
.forEach((k, v) -> updateRefIfPrefixMatches(result, prefix, k, v));
|
||||
|
||||
// Second, overwrite with any pending commands.
|
||||
commands
|
||||
.getCommands()
|
||||
.values()
|
||||
.forEach(
|
||||
c ->
|
||||
updateRefIfPrefixMatches(result, prefix, c.getRefName(), toOptional(c.getNewId())));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Optional<ObjectId> toOptional(ObjectId id) {
|
||||
return id.equals(ObjectId.zeroId()) ? Optional.empty() : Optional.of(id);
|
||||
}
|
||||
|
||||
private static void updateRefIfPrefixMatches(
|
||||
Map<String, ObjectId> map, String prefix, String fullRefName, Optional<ObjectId> maybeId) {
|
||||
if (!fullRefName.startsWith(prefix)) {
|
||||
return;
|
||||
}
|
||||
String suffix = fullRefName.substring(prefix.length());
|
||||
if (maybeId.isPresent()) {
|
||||
map.put(suffix, maybeId.get());
|
||||
} else {
|
||||
map.remove(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Not AutoCloseable so callers can't improperly close it. Plus it's never managed with a try
|
||||
@@ -84,4 +189,16 @@ public class RepoView {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
|
||||
Repository getRepository() {
|
||||
return repo;
|
||||
}
|
||||
|
||||
ObjectInserter getInserter() {
|
||||
return inserter;
|
||||
}
|
||||
|
||||
ChainedReceiveCommands getCommands() {
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,19 +111,15 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
}
|
||||
|
||||
class ContextImpl implements Context {
|
||||
private Repository repoWrapper;
|
||||
|
||||
@Override
|
||||
public Repository getRepository() throws IOException {
|
||||
if (repoWrapper == null) {
|
||||
repoWrapper = new ReadOnlyRepository(ReviewDbBatchUpdate.this.getRepository());
|
||||
}
|
||||
return repoWrapper;
|
||||
public RepoView getRepoView() throws IOException {
|
||||
initRepository();
|
||||
return ReviewDbBatchUpdate.this.repoView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevWalk getRevWalk() throws IOException {
|
||||
return ReviewDbBatchUpdate.this.getRevWalk();
|
||||
return getRepoView().getRevWalk();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,14 +154,9 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
}
|
||||
|
||||
private class RepoContextImpl extends ContextImpl implements RepoContext {
|
||||
@Override
|
||||
public Repository getRepository() throws IOException {
|
||||
return ReviewDbBatchUpdate.this.getRepository();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter getInserter() throws IOException {
|
||||
return ReviewDbBatchUpdate.this.getObjectInserter();
|
||||
return getRepoView().getInserter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,11 +191,6 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
return dbWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository getRepository() {
|
||||
return threadLocalRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevWalk getRevWalk() {
|
||||
return threadLocalRevWalk;
|
||||
@@ -534,9 +520,10 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
// updates on the change repo first.
|
||||
logDebug("Executing NoteDb updates for {} changes", tasks.size());
|
||||
try {
|
||||
BatchRefUpdate changeRefUpdate = getRepository().getRefDatabase().newBatchUpdate();
|
||||
initRepository();
|
||||
BatchRefUpdate changeRefUpdate = repoView.getRepository().getRefDatabase().newBatchUpdate();
|
||||
boolean hasAllUsersCommands = false;
|
||||
try (ObjectInserter ins = getRepository().newObjectInserter()) {
|
||||
try (ObjectInserter ins = repoView.getRepository().newObjectInserter()) {
|
||||
int objs = 0;
|
||||
for (ChangeTask task : tasks) {
|
||||
if (task.noteDbResult == null) {
|
||||
@@ -643,7 +630,8 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
public Void call() throws Exception {
|
||||
taskId = id.toString() + "-" + Thread.currentThread().getId();
|
||||
if (Thread.currentThread() == mainThread) {
|
||||
Repository repo = getRepository();
|
||||
initRepository();
|
||||
Repository repo = repoView.getRepository();
|
||||
try (RevWalk rw = new RevWalk(repo)) {
|
||||
call(ReviewDbBatchUpdate.this.db, repo, rw);
|
||||
}
|
||||
@@ -802,10 +790,10 @@ class ReviewDbBatchUpdate extends BatchUpdate {
|
||||
updateManagerFactory
|
||||
.create(ctx.getProject())
|
||||
.setChangeRepo(
|
||||
ctx.getRepository(),
|
||||
ctx.getRevWalk(),
|
||||
ctx.threadLocalRepo,
|
||||
ctx.threadLocalRevWalk,
|
||||
null,
|
||||
new ChainedReceiveCommands(ctx.getRepository()));
|
||||
new ChainedReceiveCommands(ctx.threadLocalRepo));
|
||||
if (ctx.getUser().isIdentifiedUser()) {
|
||||
updateManager.setRefLogIdent(
|
||||
ctx.getUser().asIdentifiedUser().newRefLogIdent(ctx.getWhen(), tz));
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static org.eclipse.jgit.lib.Constants.R_HEADS;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.testutil.InMemoryRepositoryManager;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.StoredConfig;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RepoViewTest {
|
||||
private static final String MASTER = "refs/heads/master";
|
||||
private static final String BRANCH = "refs/heads/branch";
|
||||
|
||||
private Repository repo;
|
||||
private TestRepository<?> tr;
|
||||
private RepoView view;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
|
||||
Project.NameKey project = new Project.NameKey("project");
|
||||
repo = repoManager.createRepository(project);
|
||||
tr = new TestRepository<>(repo);
|
||||
tr.branch(MASTER).commit().create();
|
||||
view = new RepoView(repoManager, project);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
view.close();
|
||||
repo.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConfigIsDefensiveCopy() throws Exception {
|
||||
StoredConfig orig = repo.getConfig();
|
||||
orig.setString("a", "config", "option", "yes");
|
||||
orig.save();
|
||||
|
||||
Config copy = view.getConfig();
|
||||
copy.setString("a", "config", "option", "no");
|
||||
|
||||
assertThat(orig.getString("a", "config", "option")).isEqualTo("yes");
|
||||
assertThat(repo.getConfig().getString("a", "config", "option")).isEqualTo("yes");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRef() throws Exception {
|
||||
ObjectId oldMaster = repo.exactRef(MASTER).getObjectId();
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(oldMaster);
|
||||
assertThat(repo.exactRef(BRANCH)).isNull();
|
||||
assertThat(view.getRef(MASTER)).hasValue(oldMaster);
|
||||
assertThat(view.getRef(BRANCH)).isEmpty();
|
||||
|
||||
tr.branch(MASTER).commit().create();
|
||||
tr.branch(BRANCH).commit().create();
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isNotEqualTo(oldMaster);
|
||||
assertThat(repo.exactRef(BRANCH)).isNotNull();
|
||||
assertThat(view.getRef(MASTER)).hasValue(oldMaster);
|
||||
assertThat(view.getRef(BRANCH)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRefsRescansWhenNotCaching() throws Exception {
|
||||
ObjectId oldMaster = repo.exactRef(MASTER).getObjectId();
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", oldMaster);
|
||||
|
||||
ObjectId newBranch = tr.branch(BRANCH).commit().create();
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", oldMaster, "branch", newBranch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRefsUsesCachedValueMatchingGetRef() throws Exception {
|
||||
ObjectId master1 = repo.exactRef(MASTER).getObjectId();
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master1);
|
||||
assertThat(view.getRef(MASTER)).hasValue(master1);
|
||||
|
||||
// Doesn't reflect new value for master.
|
||||
ObjectId master2 = tr.branch(MASTER).commit().create();
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(master2);
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master1);
|
||||
|
||||
// Branch wasn't previously cached, so does reflect new value.
|
||||
ObjectId branch1 = tr.branch(BRANCH).commit().create();
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master1, "branch", branch1);
|
||||
|
||||
// Looking up branch causes it to be cached.
|
||||
assertThat(view.getRef(BRANCH)).hasValue(branch1);
|
||||
ObjectId branch2 = tr.branch(BRANCH).commit().create();
|
||||
assertThat(repo.exactRef(BRANCH).getObjectId()).isEqualTo(branch2);
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master1, "branch", branch1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRefsReflectsCommands() throws Exception {
|
||||
ObjectId master1 = repo.exactRef(MASTER).getObjectId();
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master1);
|
||||
|
||||
ObjectId master2 = tr.commit().create();
|
||||
view.getCommands().add(new ReceiveCommand(master1, master2, MASTER));
|
||||
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(master1);
|
||||
assertThat(view.getRef(MASTER)).hasValue(master2);
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master2);
|
||||
|
||||
view.getCommands().add(new ReceiveCommand(master2, ObjectId.zeroId(), MASTER));
|
||||
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(master1);
|
||||
assertThat(view.getRef(MASTER)).isEmpty();
|
||||
assertThat(view.getRefs(R_HEADS)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRefsOverwritesCachedValueWithCommand() throws Exception {
|
||||
ObjectId master1 = repo.exactRef(MASTER).getObjectId();
|
||||
assertThat(view.getRef(MASTER)).hasValue(master1);
|
||||
|
||||
ObjectId master2 = tr.commit().create();
|
||||
view.getCommands().add(new ReceiveCommand(master1, master2, MASTER));
|
||||
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(master1);
|
||||
assertThat(view.getRef(MASTER)).hasValue(master2);
|
||||
assertThat(view.getRefs(R_HEADS)).containsExactly("master", master2);
|
||||
|
||||
view.getCommands().add(new ReceiveCommand(master2, ObjectId.zeroId(), MASTER));
|
||||
|
||||
assertThat(repo.exactRef(MASTER).getObjectId()).isEqualTo(master1);
|
||||
assertThat(view.getRef(MASTER)).isEmpty();
|
||||
assertThat(view.getRefs(R_HEADS)).isEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user