Index files affected by merge commits

This commit has the effect of changing ChangeData.currentFilePaths()
to return the diff of a merge commit to be between the merge commit's
new tree, and its 1st parent. That allows ChangeField.PATH to index
the files affected by a merge commit, which supports the conflicts
operator to identify open merges that may conflict with a change.

The only caller of PatchListCache.getDiffSummary() is
ChangeData.currentFilePaths().  The only caller of
ChangeData.currentFilePaths() is ChangeField.PATH (for indexing)
and query operators that test against a change.

A side-effect of this commit is the changed lines (added, deleted,
delta) for merge commits will now report the delta introduced by the
merge, rather than the delta from the automerge.

The conflicts operator now detects a few less cases of conflicts.  The
most obvious change is a merge uploaded to the wrong branch may not
show a conflict with other changes on its current branch when its 1st
parent (from the other branch) introduces a conflicting change.

The conflicts operator now assumes merges are made "correctly", such
that `git log --first-parent` will traverse the main-branch history,
and not the side-branch. This requires users to do something like:

  git checkout -b my-merge origin/master
  git merge side-branch
  git push origin my-merge:refs/for/master

When creating a merge commit to merge "side-branch" into "master".
Since this is a typical workflow with git, most merges should already
fit this expected pattern.

A new change index schema version 46 is required to ensure changes are
reindexed with this new computation for the PATH field.

Bug: issue 3052
Change-Id: Iaa854447ed9abb77b3f8fe6c818723e804e0a8d4
This commit is contained in:
Shawn Pearce
2017-08-02 08:40:51 -07:00
committed by Alice Kober-Sotzek
parent ab7b0a8f0c
commit 7ecae33e24
10 changed files with 118 additions and 167 deletions

View File

@@ -50,6 +50,7 @@ import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.api.changes.ReviewResult; import com.google.gerrit.extensions.api.changes.ReviewResult;
import com.google.gerrit.extensions.api.changes.ReviewerInfo; import com.google.gerrit.extensions.api.changes.ReviewerInfo;
import com.google.gerrit.extensions.client.Comment.Range; import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.Side; import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.AccountInfo;
@@ -92,7 +93,11 @@ import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.DiffSummary;
import com.google.gerrit.server.patch.DiffSummaryKey;
import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.permissions.ChangePermission; import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.LabelPermission; import com.google.gerrit.server.permissions.LabelPermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
@@ -129,6 +134,7 @@ import java.util.OptionalInt;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@Singleton @Singleton
public class PostReview public class PostReview
@@ -206,14 +212,14 @@ public class PostReview
protected Response<ReviewResult> applyImpl( protected Response<ReviewResult> applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource revision, ReviewInput input) BatchUpdate.Factory updateFactory, RevisionResource revision, ReviewInput input)
throws RestApiException, UpdateException, OrmException, IOException, throws RestApiException, UpdateException, OrmException, IOException,
PermissionBackendException, ConfigInvalidException { PermissionBackendException, ConfigInvalidException, PatchListNotAvailableException {
return apply(updateFactory, revision, input, TimeUtil.nowTs()); return apply(updateFactory, revision, input, TimeUtil.nowTs());
} }
public Response<ReviewResult> apply( public Response<ReviewResult> apply(
BatchUpdate.Factory updateFactory, RevisionResource revision, ReviewInput input, Timestamp ts) BatchUpdate.Factory updateFactory, RevisionResource revision, ReviewInput input, Timestamp ts)
throws RestApiException, UpdateException, OrmException, IOException, throws RestApiException, UpdateException, OrmException, IOException,
PermissionBackendException, ConfigInvalidException { PermissionBackendException, ConfigInvalidException, PatchListNotAvailableException {
// Respect timestamp, but truncate at change created-on time. // Respect timestamp, but truncate at change created-on time.
ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn()); ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn());
if (revision.getEdit().isPresent()) { if (revision.getEdit().isPresent()) {
@@ -553,7 +559,7 @@ public class PostReview
private <T extends CommentInput> void checkComments( private <T extends CommentInput> void checkComments(
RevisionResource revision, Map<String, List<T>> commentsPerPath) RevisionResource revision, Map<String, List<T>> commentsPerPath)
throws OrmException, BadRequestException { throws BadRequestException, PatchListNotAvailableException {
Set<String> revisionFilePaths = getAffectedFilePaths(revision); Set<String> revisionFilePaths = getAffectedFilePaths(revision);
for (Map.Entry<String, List<T>> entry : commentsPerPath.entrySet()) { for (Map.Entry<String, List<T>> entry : commentsPerPath.entrySet()) {
String path = entry.getKey(); String path = entry.getKey();
@@ -569,9 +575,14 @@ public class PostReview
} }
} }
private Set<String> getAffectedFilePaths(RevisionResource revision) throws OrmException { private Set<String> getAffectedFilePaths(RevisionResource revision)
ChangeData changeData = changeDataFactory.create(db.get(), revision.getNotes()); throws PatchListNotAvailableException {
return new HashSet<>(changeData.filePaths(revision.getPatchSet())); ObjectId newId = ObjectId.fromString(revision.getPatchSet().getRevision().get());
DiffSummaryKey key =
DiffSummaryKey.fromPatchListKey(
PatchListKey.againstDefaultBase(newId, Whitespace.IGNORE_NONE));
DiffSummary ds = patchListCache.getDiffSummary(key, revision.getProject());
return new HashSet<>(ds.getPaths());
} }
private static void ensurePathRefersToAvailableOrMagicFile( private static void ensurePathRefersToAvailableOrMagicFile(
@@ -600,7 +611,7 @@ public class PostReview
private void checkRobotComments( private void checkRobotComments(
RevisionResource revision, Map<String, List<RobotCommentInput>> in) RevisionResource revision, Map<String, List<RobotCommentInput>> in)
throws BadRequestException, OrmException { throws BadRequestException, PatchListNotAvailableException {
cleanUpComments(in); cleanUpComments(in);
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) { for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
String commentPath = e.getKey(); String commentPath = e.getKey();

View File

@@ -267,7 +267,7 @@ public class MergeValidators {
if (!cd.currentFilePaths().contains(AccountConfig.ACCOUNT_CONFIG)) { if (!cd.currentFilePaths().contains(AccountConfig.ACCOUNT_CONFIG)) {
return; return;
} }
} catch (OrmException e) { } catch (IOException | OrmException e) {
log.error("Cannot validate account update", e); log.error("Cannot validate account update", e);
throw new MergeValidationException("account validation unavailable"); throw new MergeValidationException("account validation unavailable");
} }

View File

@@ -145,10 +145,13 @@ public class ChangeField {
.buildRepeatable(cd -> firstNonNull(cd.currentFilePaths(), ImmutableList.of())); .buildRepeatable(cd -> firstNonNull(cd.currentFilePaths(), ImmutableList.of()));
public static Set<String> getFileParts(ChangeData cd) throws OrmException { public static Set<String> getFileParts(ChangeData cd) throws OrmException {
List<String> paths = cd.currentFilePaths(); List<String> paths;
if (paths == null) { try {
return ImmutableSet.of(); paths = cd.currentFilePaths();
} catch (IOException e) {
throw new OrmException(e);
} }
Splitter s = Splitter.on('/').omitEmptyStrings(); Splitter s = Splitter.on('/').omitEmptyStrings();
Set<String> r = new HashSet<>(); Set<String> r = new HashSet<>();
for (String path : paths) { for (String path : paths) {

View File

@@ -86,7 +86,9 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
ChangeField.PENDING_REVIEWER, ChangeField.PENDING_REVIEWER,
ChangeField.PENDING_REVIEWER_BY_EMAIL); ChangeField.PENDING_REVIEWER_BY_EMAIL);
static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF); @Deprecated static final Schema<ChangeData> V45 = schema(V44, ChangeField.REVERT_OF);
static final Schema<ChangeData> V46 = schema(V45);
public static final String NAME = "changes"; public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions(); public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();

View File

@@ -30,9 +30,6 @@ public interface PatchListCache {
IntraLineDiff getIntraLineDiff(IntraLineDiffKey key, IntraLineDiffArgs args); IntraLineDiff getIntraLineDiff(IntraLineDiffKey key, IntraLineDiffArgs args);
DiffSummary getDiffSummary(Change change, PatchSet patchSet)
throws PatchListNotAvailableException;
DiffSummary getDiffSummary(DiffSummaryKey key, Project.NameKey project) DiffSummary getDiffSummary(DiffSummaryKey key, Project.NameKey project)
throws PatchListNotAvailableException; throws PatchListNotAvailableException;
} }

View File

@@ -163,16 +163,6 @@ public class PatchListCacheImpl implements PatchListCache {
return new IntraLineDiff(IntraLineDiff.Status.DISABLED); return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
} }
@Override
public DiffSummary getDiffSummary(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
Project.NameKey project = change.getProject();
ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
return getDiffSummary(
DiffSummaryKey.fromPatchListKey(PatchListKey.againstDefaultBase(b, ws)), project);
}
@Override @Override
public DiffSummary getDiffSummary(DiffSummaryKey key, Project.NameKey project) public DiffSummary getDiffSummary(DiffSummaryKey key, Project.NameKey project)
throws PatchListNotAvailableException { throws PatchListNotAvailableException {

View File

@@ -35,6 +35,7 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelTypes; import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.SubmitRecord; import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitTypeRecord; import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException; import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
@@ -67,7 +68,9 @@ import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.DiffSummary; import com.google.gerrit.server.patch.DiffSummary;
import com.google.gerrit.server.patch.DiffSummaryKey;
import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException; import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
@@ -367,8 +370,8 @@ public class ChangeData {
private Collection<PatchSet> patchSets; private Collection<PatchSet> patchSets;
private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals; private ListMultimap<PatchSet.Id, PatchSetApproval> allApprovals;
private List<PatchSetApproval> currentApprovals; private List<PatchSetApproval> currentApprovals;
private Map<Integer, List<String>> files; private List<String> currentFiles;
private Map<Integer, Optional<DiffSummary>> diffSummaries; private Optional<DiffSummary> diffSummary;
private Collection<Comment> publishedComments; private Collection<Comment> publishedComments;
private Collection<RobotComment> robotComments; private Collection<RobotComment> robotComments;
private CurrentUser visibleTo; private CurrentUser visibleTo;
@@ -391,6 +394,7 @@ public class ChangeData {
private List<ReviewerStatusUpdate> reviewerUpdates; private List<ReviewerStatusUpdate> reviewerUpdates;
private PersonIdent author; private PersonIdent author;
private PersonIdent committer; private PersonIdent committer;
private int parentCount;
private Integer unresolvedCommentCount; private Integer unresolvedCommentCount;
private LabelTypes labelTypes; private LabelTypes labelTypes;
@@ -466,86 +470,61 @@ public class ChangeData {
return allUsersName; return allUsersName;
} }
private Map<Integer, List<String>> initFiles() {
if (files == null) {
files = new HashMap<>();
}
return files;
}
public void setCurrentFilePaths(List<String> filePaths) throws OrmException { public void setCurrentFilePaths(List<String> filePaths) throws OrmException {
PatchSet ps = currentPatchSet(); PatchSet ps = currentPatchSet();
if (ps != null) { if (ps != null) {
initFiles().put(ps.getPatchSetId(), ImmutableList.copyOf(filePaths)); currentFiles = ImmutableList.copyOf(filePaths);
} }
} }
public List<String> currentFilePaths() throws OrmException { public List<String> currentFilePaths() throws IOException, OrmException {
PatchSet ps = currentPatchSet(); if (currentFiles == null) {
return ps != null ? filePaths(ps) : null; if (!lazyLoad) {
return Collections.emptyList();
}
Optional<DiffSummary> p = getDiffSummary();
currentFiles = p.map(DiffSummary::getPaths).orElse(Collections.emptyList());
}
return currentFiles;
} }
public List<String> filePaths(PatchSet ps) throws OrmException { private Optional<DiffSummary> getDiffSummary() throws OrmException, IOException {
Integer psId = ps.getPatchSetId(); if (diffSummary == null) {
List<String> r = initFiles().get(psId);
if (r == null) {
Change c = change();
if (c == null) {
return null;
}
Optional<DiffSummary> p = getDiffSummary(c, ps);
if (!p.isPresent()) {
List<String> emptyFileList = Collections.emptyList();
if (lazyLoad) {
files.put(ps.getPatchSetId(), emptyFileList);
}
return emptyFileList;
}
r = p.get().getPaths();
files.put(psId, r);
}
return r;
}
private Optional<DiffSummary> getDiffSummary(Change c, PatchSet ps) {
Integer psId = ps.getId().get();
if (diffSummaries == null) {
diffSummaries = new HashMap<>();
}
Optional<DiffSummary> r = diffSummaries.get(psId);
if (r == null) {
if (!lazyLoad) { if (!lazyLoad) {
return Optional.empty(); return Optional.empty();
} }
try {
r = Optional.of(patchListCache.getDiffSummary(c, ps)); Change c = change();
} catch (PatchListNotAvailableException e) { PatchSet ps = currentPatchSet();
r = Optional.empty(); if (c == null || ps == null || !loadCommitData()) {
} return Optional.empty();
diffSummaries.put(psId, r);
}
return r;
} }
private Optional<ChangedLines> computeChangedLines() throws OrmException { ObjectId id = ObjectId.fromString(ps.getRevision().get());
Change c = change(); Whitespace ws = Whitespace.IGNORE_NONE;
if (c == null) { PatchListKey pk =
return Optional.empty(); parentCount > 1
? PatchListKey.againstParentNum(1, id, ws)
: PatchListKey.againstDefaultBase(id, ws);
DiffSummaryKey key = DiffSummaryKey.fromPatchListKey(pk);
try {
diffSummary = Optional.of(patchListCache.getDiffSummary(key, c.getProject()));
} catch (PatchListNotAvailableException e) {
diffSummary = Optional.empty();
} }
PatchSet ps = currentPatchSet();
if (ps == null) {
return Optional.empty();
} }
Optional<DiffSummary> ds = getDiffSummary(c, ps); return diffSummary;
}
private Optional<ChangedLines> computeChangedLines() throws OrmException, IOException {
Optional<DiffSummary> ds = getDiffSummary();
if (ds.isPresent()) { if (ds.isPresent()) {
return Optional.of(ds.get().getChangedLines()); return Optional.of(ds.get().getChangedLines());
} }
return Optional.empty(); return Optional.empty();
} }
public Optional<ChangedLines> changedLines() throws OrmException { public Optional<ChangedLines> changedLines() throws OrmException, IOException {
if (changedLines == null) { if (changedLines == null) {
if (!lazyLoad) { if (!lazyLoad) {
return Optional.empty(); return Optional.empty();
@@ -744,6 +723,7 @@ public class ChangeData {
commitFooters = c.getFooterLines(); commitFooters = c.getFooterLines();
author = c.getAuthorIdent(); author = c.getAuthorIdent();
committer = c.getCommitterIdent(); committer = c.getCommitterIdent();
parentCount = c.getParentCount();
} }
return true; return true;
} }

View File

@@ -17,8 +17,8 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.common.data.SubmitTypeRecord; import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.IntegrationException; import com.google.gerrit.server.git.IntegrationException;
@@ -28,20 +28,15 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments; import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
public class ConflictsPredicate { public class ConflictsPredicate {
// UI code may depend on this string, so use caution when changing. // UI code may depend on this string, so use caution when changing.
@@ -51,9 +46,15 @@ public class ConflictsPredicate {
public static Predicate<ChangeData> create(Arguments args, String value, Change c) public static Predicate<ChangeData> create(Arguments args, String value, Change c)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
ChangeDataCache changeDataCache = ChangeData cd;
new ChangeDataCache(c, args.db, args.changeDataFactory, args.projectCache); List<String> files;
List<String> files = listFiles(c, args, changeDataCache); try {
cd = args.changeDataFactory.create(args.db.get(), c);
files = cd.currentFilePaths();
} catch (IOException e) {
throw new OrmException(e);
}
if (3 + files.size() > args.indexConfig.maxTerms()) { if (3 + files.size() > args.indexConfig.maxTerms()) {
// Short-circuit with a nice error message if we exceed the index // Short-circuit with a nice error message if we exceed the index
// backend's term limit. This assumes that "conflicts:foo" is the entire // backend's term limit. This assumes that "conflicts:foo" is the entire
@@ -73,61 +74,29 @@ public class ConflictsPredicate {
and.add(new RefPredicate(c.getDest().get())); and.add(new RefPredicate(c.getDest().get()));
and.add(Predicate.not(new LegacyChangeIdPredicate(c.getId()))); and.add(Predicate.not(new LegacyChangeIdPredicate(c.getId())));
and.add(Predicate.or(filePredicates)); and.add(Predicate.or(filePredicates));
ChangeDataCache changeDataCache = new ChangeDataCache(cd, args.projectCache);
and.add(new CheckConflict(ChangeQueryBuilder.FIELD_CONFLICTS, value, args, c, changeDataCache)); and.add(new CheckConflict(ChangeQueryBuilder.FIELD_CONFLICTS, value, args, c, changeDataCache));
return Predicate.and(and); return Predicate.and(and);
} }
private static List<String> listFiles(Change c, Arguments args, ChangeDataCache changeDataCache)
throws OrmException {
try (Repository repo = args.repoManager.openRepository(c.getProject());
RevWalk rw = new RevWalk(repo)) {
RevCommit ps = rw.parseCommit(changeDataCache.getTestAgainst());
if (ps.getParentCount() > 1) {
String dest = c.getDest().get();
Ref destBranch = repo.getRefDatabase().exactRef(dest);
rw.setRevFilter(RevFilter.MERGE_BASE);
rw.markStart(rw.parseCommit(destBranch.getObjectId()));
rw.markStart(ps);
RevCommit base = rw.next();
// TODO(zivkov): handle the case with multiple merge bases
List<String> files = new ArrayList<>();
try (TreeWalk tw = new TreeWalk(repo)) {
if (base != null) {
tw.setFilter(TreeFilter.ANY_DIFF);
tw.addTree(base.getTree());
}
tw.addTree(ps.getTree());
tw.setRecursive(true);
while (tw.next()) {
files.add(tw.getPathString());
}
}
return files;
}
return args.changeDataFactory.create(args.db.get(), c).currentFilePaths();
} catch (IOException e) {
throw new OrmException(e);
}
}
private static final class CheckConflict extends ChangeOperatorPredicate { private static final class CheckConflict extends ChangeOperatorPredicate {
private final Arguments args; private final Arguments args;
private final Change c; private final Branch.NameKey dest;
private final ChangeDataCache changeDataCache; private final ChangeDataCache changeDataCache;
CheckConflict( CheckConflict(
String field, String value, Arguments args, Change c, ChangeDataCache changeDataCache) { String field, String value, Arguments args, Change c, ChangeDataCache changeDataCache) {
super(field, value); super(field, value);
this.args = args; this.args = args;
this.c = c; this.dest = c.getDest();
this.changeDataCache = changeDataCache; this.changeDataCache = changeDataCache;
} }
@Override @Override
public boolean match(ChangeData object) throws OrmException { public boolean match(ChangeData object) throws OrmException {
Change otherChange = object.change(); Change otherChange = object.change();
if (otherChange == null || !otherChange.getDest().equals(c.getDest())) { if (otherChange == null || !otherChange.getDest().equals(dest)) {
return false; return false;
} }
@@ -136,13 +105,17 @@ public class ConflictsPredicate {
return false; return false;
} }
ProjectState projectState;
try {
projectState = changeDataCache.getProjectState();
} catch (NoSuchProjectException | OrmException e) {
return false;
}
ObjectId other = ObjectId.fromString(object.currentPatchSet().getRevision().get()); ObjectId other = ObjectId.fromString(object.currentPatchSet().getRevision().get());
ConflictKey conflictsKey = ConflictKey conflictsKey =
new ConflictKey( new ConflictKey(
changeDataCache.getTestAgainst(), changeDataCache.getTestAgainst(), other, str.type, projectState.isUseContentMerge());
other,
str.type,
changeDataCache.getProjectState().isUseContentMerge());
Boolean conflicts = args.conflictsCache.getIfPresent(conflictsKey); Boolean conflicts = args.conflictsCache.getIfPresent(conflictsKey);
if (conflicts != null) { if (conflicts != null) {
return conflicts; return conflicts;
@@ -187,41 +160,31 @@ public class ConflictsPredicate {
} }
} }
public static class ChangeDataCache { private static class ChangeDataCache {
protected final Change change; private final ChangeData cd;
protected final Provider<ReviewDb> db; private final ProjectCache projectCache;
protected final ChangeData.Factory changeDataFactory;
protected final ProjectCache projectCache;
protected ObjectId testAgainst; private ObjectId testAgainst;
protected ProjectState projectState; private ProjectState projectState;
protected Set<ObjectId> alreadyAccepted; private Set<ObjectId> alreadyAccepted;
public ChangeDataCache( ChangeDataCache(ChangeData cd, ProjectCache projectCache) {
Change change, this.cd = cd;
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
ProjectCache projectCache) {
this.change = change;
this.db = db;
this.changeDataFactory = changeDataFactory;
this.projectCache = projectCache; this.projectCache = projectCache;
} }
protected ObjectId getTestAgainst() throws OrmException { ObjectId getTestAgainst() throws OrmException {
if (testAgainst == null) { if (testAgainst == null) {
testAgainst = testAgainst = ObjectId.fromString(cd.currentPatchSet().getRevision().get());
ObjectId.fromString(
changeDataFactory.create(db.get(), change).currentPatchSet().getRevision().get());
} }
return testAgainst; return testAgainst;
} }
protected ProjectState getProjectState() { ProjectState getProjectState() throws OrmException, NoSuchProjectException {
if (projectState == null) { if (projectState == null) {
projectState = projectCache.get(change.getProject()); projectState = projectCache.get(cd.project());
if (projectState == null) { if (projectState == null) {
throw new IllegalStateException(new NoSuchProjectException(change.getProject())); throw new NoSuchProjectException(cd.project());
} }
} }
return projectState; return projectState;

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.server.index.change.ChangeField; import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -26,8 +27,13 @@ public class EqualsPathPredicate extends ChangeIndexPredicate {
@Override @Override
public boolean match(ChangeData object) throws OrmException { public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths(); List<String> files;
return files != null && Collections.binarySearch(files, value) >= 0; try {
files = object.currentFilePaths();
} catch (IOException e) {
throw new OrmException(e);
}
return Collections.binarySearch(files, value) >= 0;
} }
@Override @Override

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.server.query.change;
import com.google.gerrit.server.index.change.ChangeField; import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.util.RegexListSearcher; import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import java.io.IOException;
import java.util.List; import java.util.List;
public class RegexPathPredicate extends ChangeRegexPredicate { public class RegexPathPredicate extends ChangeRegexPredicate {
@@ -26,15 +27,13 @@ public class RegexPathPredicate extends ChangeRegexPredicate {
@Override @Override
public boolean match(ChangeData object) throws OrmException { public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths(); List<String> files;
if (files != null) { try {
return RegexListSearcher.ofStrings(getValue()).hasMatch(files); files = object.currentFilePaths();
} catch (IOException e) {
throw new OrmException(e);
} }
// The ChangeData can't do expensive lookups right now. Bypass return RegexListSearcher.ofStrings(getValue()).hasMatch(files);
// them and include the result anyway. We might be able to do
// a narrow later on to a smaller set.
//
return true;
} }
@Override @Override