Convert GetRelated to use patch set groups where present
Query for any changes matching any of the groups of all patch sets of the change being viewed. Once we have the full list of candidate changes, determine which patch set we are interested in for the change, since we only display a single patch set of a change in the Related Changes tab. For each matching change, prefer the latest patch set that has at least one group in common with the patch set being viewed. But such a patch set may not be present, for example if a later change has not been rebased; in that case, just pick the latest patch set of the change. For a live migration, if there is a groups field on the patch set being viewed, assume that a new patch set of that change and any ancestor changes has been pushed since the groups code started running, and use the secondary index for the search. (Note that this assumption does not always hold for descendant changes, which might not get new patch sets at the same time. But it should usually hold.) If the field is null, default to the old PatchSetAncestors implementation; this covers all existing open changes in a live server without requiring a migration step immediately. We will still likely want to migrate existing groups of open changes when removing the PatchSetAncestors table, however. This feature can be turned off with an undocumented config option. This option is intended only to support testing and live migrations, and will be removed before the stable release containing this feature. Change-Id: I8d9030db018b64ea947976274115567a08def4b9
This commit is contained in:
@@ -14,258 +14,166 @@
|
||||
|
||||
package com.google.gerrit.server.change;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.common.CommitInfo;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.CommonConverters;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.change.WalkSorter.PatchSetData;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.git.GroupCollector;
|
||||
import com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IndexCollection;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class GetRelated implements RestReadView<RevisionResource> {
|
||||
private static final Logger log = LoggerFactory.getLogger(GetRelated.class);
|
||||
|
||||
private final GitRepositoryManager gitMgr;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final Provider<ReviewDb> db;
|
||||
private final GetRelatedByAncestors byAncestors;
|
||||
private final Provider<InternalChangeQuery> queryProvider;
|
||||
private final Provider<WalkSorter> sorter;
|
||||
private final IndexCollection indexes;
|
||||
private final boolean byAncestorsOnly;
|
||||
|
||||
@Inject
|
||||
GetRelated(GitRepositoryManager gitMgr,
|
||||
Provider<ReviewDb> db,
|
||||
Provider<InternalChangeQuery> queryProvider) {
|
||||
this.gitMgr = gitMgr;
|
||||
this.dbProvider = db;
|
||||
GetRelated(Provider<ReviewDb> db,
|
||||
@GerritServerConfig Config cfg,
|
||||
GetRelatedByAncestors byAncestors,
|
||||
Provider<InternalChangeQuery> queryProvider,
|
||||
Provider<WalkSorter> sorter,
|
||||
IndexCollection indexes) {
|
||||
this.db = db;
|
||||
this.byAncestors = byAncestors;
|
||||
this.queryProvider = queryProvider;
|
||||
this.sorter = sorter;
|
||||
this.indexes = indexes;
|
||||
byAncestorsOnly =
|
||||
cfg.getBoolean("change", null, "getRelatedByAncestors", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelatedInfo apply(RevisionResource rsrc)
|
||||
throws RepositoryNotFoundException, IOException, OrmException {
|
||||
try (Repository git = gitMgr.openRepository(rsrc.getChange().getProject());
|
||||
RevWalk rw = new RevWalk(git)) {
|
||||
Ref ref = git.getRef(rsrc.getChange().getDest().get());
|
||||
RelatedInfo info = new RelatedInfo();
|
||||
info.changes = walk(rsrc, rw, ref);
|
||||
return info;
|
||||
List<String> thisPatchSetGroups = GroupCollector.getGroups(rsrc);
|
||||
if (byAncestorsOnly
|
||||
|| thisPatchSetGroups == null
|
||||
|| !indexes.getSearchIndex().getSchema().hasField(ChangeField.GROUP)) {
|
||||
return byAncestors.getRelated(rsrc);
|
||||
}
|
||||
RelatedInfo relatedInfo = new RelatedInfo();
|
||||
relatedInfo.changes = getRelated(rsrc, thisPatchSetGroups);
|
||||
return relatedInfo;
|
||||
}
|
||||
|
||||
private List<ChangeAndCommit> walk(RevisionResource rsrc, RevWalk rw, Ref ref)
|
||||
throws OrmException, IOException {
|
||||
Map<Change.Id, ChangeData> changes = allOpenChanges(rsrc);
|
||||
Map<PatchSet.Id, PatchSet> patchSets = allPatchSets(rsrc, changes.values());
|
||||
|
||||
Map<String, PatchSet> commits = Maps.newHashMap();
|
||||
for (PatchSet p : patchSets.values()) {
|
||||
commits.put(p.getRevision().get(), p);
|
||||
private List<ChangeAndCommit> getRelated(RevisionResource rsrc,
|
||||
List<String> thisPatchSetGroups) throws OrmException, IOException {
|
||||
if (thisPatchSetGroups.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
RevCommit rev = rw.parseCommit(ObjectId.fromString(
|
||||
rsrc.getPatchSet().getRevision().get()));
|
||||
rw.sort(RevSort.TOPO);
|
||||
rw.markStart(rev);
|
||||
List<ChangeData> cds = queryProvider.get()
|
||||
.enforceVisibility(true)
|
||||
.byProjectGroups(
|
||||
rsrc.getChange().getProject(),
|
||||
getAllGroups(rsrc.getChange().getId()));
|
||||
List<ChangeAndCommit> result = new ArrayList<>(cds.size());
|
||||
|
||||
if (ref != null && ref.getObjectId() != null) {
|
||||
try {
|
||||
rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
|
||||
} catch (IncorrectObjectTypeException notCommit) {
|
||||
// Ignore and treat as new branch.
|
||||
PatchSet.Id editBaseId = rsrc.getEdit().isPresent()
|
||||
? rsrc.getEdit().get().getBasePatchSet().getId()
|
||||
: null;
|
||||
for (PatchSetData d : sorter.get()
|
||||
.includePatchSets(choosePatchSets(thisPatchSetGroups, cds))
|
||||
.setRetainBody(true)
|
||||
.sort(cds)) {
|
||||
PatchSet ps = d.patchSet();
|
||||
RevCommit commit;
|
||||
if (ps.getId().equals(editBaseId)) {
|
||||
// Replace base of an edit with the edit itself.
|
||||
ps = rsrc.getPatchSet();
|
||||
commit = rsrc.getEdit().get().getEditCommit();
|
||||
} else {
|
||||
commit = d.commit();
|
||||
}
|
||||
result.add(new ChangeAndCommit(d.data().change(), ps, commit));
|
||||
}
|
||||
|
||||
Set<Change.Id> added = Sets.newHashSet();
|
||||
List<ChangeAndCommit> parents = Lists.newArrayList();
|
||||
for (RevCommit c; (c = rw.next()) != null;) {
|
||||
PatchSet p = commits.get(c.name());
|
||||
Change g = null;
|
||||
if (p != null) {
|
||||
g = changes.get(p.getId().getParentKey()).change();
|
||||
added.add(p.getId().getParentKey());
|
||||
}
|
||||
parents.add(new ChangeAndCommit(g, p, c));
|
||||
}
|
||||
List<ChangeAndCommit> list = children(rsrc, rw, changes, patchSets, added);
|
||||
list.addAll(parents);
|
||||
|
||||
if (list.size() == 1) {
|
||||
ChangeAndCommit r = list.get(0);
|
||||
if (r.commit != null && r.commit.commit.equals(rsrc.getPatchSet().getRevision().get())) {
|
||||
if (result.size() == 1) {
|
||||
ChangeAndCommit r = result.get(0);
|
||||
if (r.commit != null
|
||||
&& r.commit.commit.equals(rsrc.getPatchSet().getRevision().get())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Change.Id, ChangeData> allOpenChanges(RevisionResource rsrc)
|
||||
throws OrmException {
|
||||
return ChangeData.asMap(
|
||||
queryProvider.get().byBranchOpen(rsrc.getChange().getDest()));
|
||||
private Set<String> getAllGroups(Change.Id changeId) throws OrmException {
|
||||
Set<String> result = new HashSet<>();
|
||||
for (PatchSet ps : db.get().patchSets().byChange(changeId)) {
|
||||
List<String> groups = ps.getGroups();
|
||||
if (groups != null) {
|
||||
result.addAll(groups);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<PatchSet.Id, PatchSet> allPatchSets(RevisionResource rsrc,
|
||||
Collection<ChangeData> cds) throws OrmException {
|
||||
Map<PatchSet.Id, PatchSet> r =
|
||||
Maps.newHashMapWithExpectedSize(cds.size() * 2);
|
||||
private static Set<PatchSet.Id> choosePatchSets(List<String> groups,
|
||||
List<ChangeData> cds) throws OrmException {
|
||||
// Prefer the latest patch set matching at least one group from this
|
||||
// revision; otherwise, just use the latest patch set overall.
|
||||
Set<PatchSet.Id> result = new HashSet<>();
|
||||
for (ChangeData cd : cds) {
|
||||
for (PatchSet p : cd.patchSets()) {
|
||||
r.put(p.getId(), p);
|
||||
Collection<PatchSet> patchSets = cd.patchSets();
|
||||
List<PatchSet> sameGroup = new ArrayList<>(patchSets.size());
|
||||
for (PatchSet ps : patchSets) {
|
||||
if (hasAnyGroup(ps, groups)) {
|
||||
sameGroup.add(ps);
|
||||
}
|
||||
}
|
||||
result.add(ChangeUtil.PS_ID_ORDER.max(
|
||||
!sameGroup.isEmpty() ? sameGroup : patchSets).getId());
|
||||
}
|
||||
|
||||
if (rsrc.getEdit().isPresent()) {
|
||||
r.put(rsrc.getPatchSet().getId(), rsrc.getPatchSet());
|
||||
}
|
||||
return r;
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ChangeAndCommit> children(RevisionResource rsrc, RevWalk rw,
|
||||
Map<Change.Id, ChangeData> changes, Map<PatchSet.Id, PatchSet> patchSets,
|
||||
Set<Change.Id> added)
|
||||
throws OrmException, IOException {
|
||||
// children is a map of parent commit name to PatchSet built on it.
|
||||
Multimap<String, PatchSet.Id> children = allChildren(changes.keySet());
|
||||
|
||||
RevFlag seenCommit = rw.newFlag("seenCommit");
|
||||
LinkedList<String> q = Lists.newLinkedList();
|
||||
seedQueue(rsrc, rw, seenCommit, patchSets, q);
|
||||
|
||||
ProjectControl projectCtl = rsrc.getControl().getProjectControl();
|
||||
Set<Change.Id> seenChange = Sets.newHashSet();
|
||||
List<ChangeAndCommit> graph = Lists.newArrayList();
|
||||
while (!q.isEmpty()) {
|
||||
String id = q.remove();
|
||||
|
||||
// For every matching change find the most recent patch set.
|
||||
Map<Change.Id, PatchSet.Id> matches = Maps.newHashMap();
|
||||
for (PatchSet.Id psId : children.get(id)) {
|
||||
PatchSet.Id e = matches.get(psId.getParentKey());
|
||||
if ((e == null || e.get() < psId.get())
|
||||
&& isVisible(projectCtl, changes, patchSets, psId)) {
|
||||
matches.put(psId.getParentKey(), psId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Change.Id, PatchSet.Id> e : matches.entrySet()) {
|
||||
ChangeData cd = changes.get(e.getKey());
|
||||
PatchSet ps = patchSets.get(e.getValue());
|
||||
if (cd == null || ps == null || !seenChange.add(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RevCommit c = rw.parseCommit(ObjectId.fromString(
|
||||
ps.getRevision().get()));
|
||||
if (!c.has(seenCommit)) {
|
||||
c.add(seenCommit);
|
||||
q.addFirst(ps.getRevision().get());
|
||||
if (added.add(ps.getId().getParentKey())) {
|
||||
rw.parseBody(c);
|
||||
graph.add(new ChangeAndCommit(cd.change(), ps, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
private static boolean hasAnyGroup(PatchSet ps, List<String> groups) {
|
||||
if (ps.getGroups() == null) {
|
||||
return false;
|
||||
}
|
||||
Collections.reverse(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
private boolean isVisible(ProjectControl projectCtl,
|
||||
Map<Change.Id, ChangeData> changes,
|
||||
Map<PatchSet.Id, PatchSet> patchSets,
|
||||
PatchSet.Id psId) throws OrmException {
|
||||
ChangeData cd = changes.get(psId.getParentKey());
|
||||
PatchSet ps = patchSets.get(psId);
|
||||
if (cd != null && ps != null) {
|
||||
// Related changes are in the same project, so reuse the existing
|
||||
// ProjectControl.
|
||||
ChangeControl ctl = projectCtl.controlFor(cd.change());
|
||||
return ctl.isVisible(dbProvider.get())
|
||||
&& ctl.isPatchVisible(ps, dbProvider.get());
|
||||
// Expected size of each list is 1, so nested linear search is fine.
|
||||
for (String g1 : ps.getGroups()) {
|
||||
for (String g2 : groups) {
|
||||
if (g1.equals(g2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void seedQueue(RevisionResource rsrc, RevWalk rw,
|
||||
RevFlag seenCommit, Map<PatchSet.Id, PatchSet> patchSets,
|
||||
LinkedList<String> q) throws IOException {
|
||||
RevCommit tip = rw.parseCommit(ObjectId.fromString(
|
||||
rsrc.getPatchSet().getRevision().get()));
|
||||
tip.add(seenCommit);
|
||||
q.add(tip.name());
|
||||
|
||||
Change.Id cId = rsrc.getChange().getId();
|
||||
for (PatchSet p : patchSets.values()) {
|
||||
if (cId.equals(p.getId().getParentKey())) {
|
||||
try {
|
||||
RevCommit c = rw.parseCommit(ObjectId.fromString(
|
||||
p.getRevision().get()));
|
||||
if (!c.has(seenCommit)) {
|
||||
c.add(seenCommit);
|
||||
q.add(c.name());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn(String.format(
|
||||
"Cannot read patch set %d of %d",
|
||||
p.getPatchSetId(), cId.get()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Multimap<String, PatchSet.Id> allChildren(Collection<Change.Id> ids)
|
||||
throws OrmException {
|
||||
ReviewDb db = dbProvider.get();
|
||||
List<ResultSet<PatchSetAncestor>> t =
|
||||
Lists.newArrayListWithCapacity(ids.size());
|
||||
for (Change.Id id : ids) {
|
||||
t.add(db.patchSetAncestors().byChange(id));
|
||||
}
|
||||
|
||||
Multimap<String, PatchSet.Id> r = ArrayListMultimap.create();
|
||||
for (ResultSet<PatchSetAncestor> rs : t) {
|
||||
for (PatchSetAncestor a : rs) {
|
||||
r.put(a.getAncestorRevision().get(), a.getPatchSet());
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public static class RelatedInfo {
|
||||
public List<ChangeAndCommit> changes;
|
||||
}
|
||||
@@ -300,5 +208,28 @@ public class GetRelated implements RestReadView<RevisionResource> {
|
||||
commit.author = CommonConverters.toGitPerson(c.getAuthorIdent());
|
||||
commit.subject = c.getShortMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("changeId", changeId)
|
||||
.add("commit", toString(commit))
|
||||
.add("_changeNumber", _changeNumber)
|
||||
.add("_revisionNumber", _revisionNumber)
|
||||
.add("_currentRevisionNumber", _currentRevisionNumber)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static String toString(CommitInfo commit) {
|
||||
return MoreObjects.toStringHelper(commit)
|
||||
.add("commit", commit.commit)
|
||||
.add("parent", commit.parents)
|
||||
.add("author", commit.author)
|
||||
.add("committer", commit.committer)
|
||||
.add("subject", commit.subject)
|
||||
.add("message", commit.message)
|
||||
.add("webLinks", commit.webLinks)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
// Copyright (C) 2015 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.change;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.change.GetRelated.ChangeAndCommit;
|
||||
import com.google.gerrit.server.change.GetRelated.RelatedInfo;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Implementation of {@link GetRelated} using {@link PatchSetAncestor}s. */
|
||||
class GetRelatedByAncestors {
|
||||
private static final Logger log = LoggerFactory.getLogger(GetRelated.class);
|
||||
|
||||
private final GitRepositoryManager gitMgr;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
private final Provider<InternalChangeQuery> queryProvider;
|
||||
|
||||
@Inject
|
||||
GetRelatedByAncestors(GitRepositoryManager gitMgr,
|
||||
Provider<ReviewDb> db,
|
||||
Provider<InternalChangeQuery> queryProvider) {
|
||||
this.gitMgr = gitMgr;
|
||||
this.dbProvider = db;
|
||||
this.queryProvider = queryProvider;
|
||||
}
|
||||
|
||||
public RelatedInfo getRelated(RevisionResource rsrc)
|
||||
throws RepositoryNotFoundException, IOException, OrmException {
|
||||
try (Repository git = gitMgr.openRepository(rsrc.getChange().getProject());
|
||||
RevWalk rw = new RevWalk(git)) {
|
||||
Ref ref = git.getRef(rsrc.getChange().getDest().get());
|
||||
RelatedInfo info = new RelatedInfo();
|
||||
info.changes = walk(rsrc, rw, ref);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ChangeAndCommit> walk(RevisionResource rsrc, RevWalk rw, Ref ref)
|
||||
throws OrmException, IOException {
|
||||
Map<Change.Id, ChangeData> changes = allOpenChanges(rsrc);
|
||||
Map<PatchSet.Id, PatchSet> patchSets = allPatchSets(rsrc, changes.values());
|
||||
|
||||
Map<String, PatchSet> commits = Maps.newHashMap();
|
||||
for (PatchSet p : patchSets.values()) {
|
||||
commits.put(p.getRevision().get(), p);
|
||||
}
|
||||
|
||||
RevCommit rev = rw.parseCommit(ObjectId.fromString(
|
||||
rsrc.getPatchSet().getRevision().get()));
|
||||
rw.sort(RevSort.TOPO);
|
||||
rw.markStart(rev);
|
||||
|
||||
if (ref != null && ref.getObjectId() != null) {
|
||||
try {
|
||||
rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
|
||||
} catch (IncorrectObjectTypeException notCommit) {
|
||||
// Ignore and treat as new branch.
|
||||
}
|
||||
}
|
||||
|
||||
Set<Change.Id> added = Sets.newHashSet();
|
||||
List<ChangeAndCommit> parents = Lists.newArrayList();
|
||||
for (RevCommit c; (c = rw.next()) != null;) {
|
||||
PatchSet p = commits.get(c.name());
|
||||
Change g = null;
|
||||
if (p != null) {
|
||||
g = changes.get(p.getId().getParentKey()).change();
|
||||
added.add(p.getId().getParentKey());
|
||||
}
|
||||
parents.add(new ChangeAndCommit(g, p, c));
|
||||
}
|
||||
List<ChangeAndCommit> list = children(rsrc, rw, changes, patchSets, added);
|
||||
list.addAll(parents);
|
||||
|
||||
if (list.size() == 1) {
|
||||
ChangeAndCommit r = list.get(0);
|
||||
if (r.commit != null && r.commit.commit.equals(rsrc.getPatchSet().getRevision().get())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private Map<Change.Id, ChangeData> allOpenChanges(RevisionResource rsrc)
|
||||
throws OrmException {
|
||||
return ChangeData.asMap(
|
||||
queryProvider.get().byBranchOpen(rsrc.getChange().getDest()));
|
||||
}
|
||||
|
||||
private Map<PatchSet.Id, PatchSet> allPatchSets(RevisionResource rsrc,
|
||||
Collection<ChangeData> cds) throws OrmException {
|
||||
Map<PatchSet.Id, PatchSet> r =
|
||||
Maps.newHashMapWithExpectedSize(cds.size() * 2);
|
||||
for (ChangeData cd : cds) {
|
||||
for (PatchSet p : cd.patchSets()) {
|
||||
r.put(p.getId(), p);
|
||||
}
|
||||
}
|
||||
|
||||
if (rsrc.getEdit().isPresent()) {
|
||||
r.put(rsrc.getPatchSet().getId(), rsrc.getPatchSet());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private List<ChangeAndCommit> children(RevisionResource rsrc, RevWalk rw,
|
||||
Map<Change.Id, ChangeData> changes, Map<PatchSet.Id, PatchSet> patchSets,
|
||||
Set<Change.Id> added)
|
||||
throws OrmException, IOException {
|
||||
// children is a map of parent commit name to PatchSet built on it.
|
||||
Multimap<String, PatchSet.Id> children = allChildren(changes.keySet());
|
||||
|
||||
RevFlag seenCommit = rw.newFlag("seenCommit");
|
||||
LinkedList<String> q = Lists.newLinkedList();
|
||||
seedQueue(rsrc, rw, seenCommit, patchSets, q);
|
||||
|
||||
ProjectControl projectCtl = rsrc.getControl().getProjectControl();
|
||||
Set<Change.Id> seenChange = Sets.newHashSet();
|
||||
List<ChangeAndCommit> graph = Lists.newArrayList();
|
||||
while (!q.isEmpty()) {
|
||||
String id = q.remove();
|
||||
|
||||
// For every matching change find the most recent patch set.
|
||||
Map<Change.Id, PatchSet.Id> matches = Maps.newHashMap();
|
||||
for (PatchSet.Id psId : children.get(id)) {
|
||||
PatchSet.Id e = matches.get(psId.getParentKey());
|
||||
if ((e == null || e.get() < psId.get())
|
||||
&& isVisible(projectCtl, changes, patchSets, psId)) {
|
||||
matches.put(psId.getParentKey(), psId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Change.Id, PatchSet.Id> e : matches.entrySet()) {
|
||||
ChangeData cd = changes.get(e.getKey());
|
||||
PatchSet ps = patchSets.get(e.getValue());
|
||||
if (cd == null || ps == null || !seenChange.add(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RevCommit c = rw.parseCommit(ObjectId.fromString(
|
||||
ps.getRevision().get()));
|
||||
if (!c.has(seenCommit)) {
|
||||
c.add(seenCommit);
|
||||
q.addFirst(ps.getRevision().get());
|
||||
if (added.add(ps.getId().getParentKey())) {
|
||||
rw.parseBody(c);
|
||||
graph.add(new ChangeAndCommit(cd.change(), ps, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.reverse(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
private boolean isVisible(ProjectControl projectCtl,
|
||||
Map<Change.Id, ChangeData> changes,
|
||||
Map<PatchSet.Id, PatchSet> patchSets,
|
||||
PatchSet.Id psId) throws OrmException {
|
||||
ChangeData cd = changes.get(psId.getParentKey());
|
||||
PatchSet ps = patchSets.get(psId);
|
||||
if (cd != null && ps != null) {
|
||||
// Related changes are in the same project, so reuse the existing
|
||||
// ProjectControl.
|
||||
ChangeControl ctl = projectCtl.controlFor(cd.change());
|
||||
return ctl.isVisible(dbProvider.get())
|
||||
&& ctl.isPatchVisible(ps, dbProvider.get());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void seedQueue(RevisionResource rsrc, RevWalk rw,
|
||||
RevFlag seenCommit, Map<PatchSet.Id, PatchSet> patchSets,
|
||||
LinkedList<String> q) throws IOException {
|
||||
RevCommit tip = rw.parseCommit(ObjectId.fromString(
|
||||
rsrc.getPatchSet().getRevision().get()));
|
||||
tip.add(seenCommit);
|
||||
q.add(tip.name());
|
||||
|
||||
Change.Id cId = rsrc.getChange().getId();
|
||||
for (PatchSet p : patchSets.values()) {
|
||||
if (cId.equals(p.getId().getParentKey())) {
|
||||
try {
|
||||
RevCommit c = rw.parseCommit(ObjectId.fromString(
|
||||
p.getRevision().get()));
|
||||
if (!c.has(seenCommit)) {
|
||||
c.add(seenCommit);
|
||||
q.add(c.name());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn(String.format(
|
||||
"Cannot read patch set %d of %d",
|
||||
p.getPatchSetId(), cId.get()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Multimap<String, PatchSet.Id> allChildren(Collection<Change.Id> ids)
|
||||
throws OrmException {
|
||||
ReviewDb db = dbProvider.get();
|
||||
List<ResultSet<PatchSetAncestor>> t =
|
||||
Lists.newArrayListWithCapacity(ids.size());
|
||||
for (Change.Id id : ids) {
|
||||
t.add(db.patchSetAncestors().byChange(id));
|
||||
}
|
||||
|
||||
Multimap<String, PatchSet.Id> r = ArrayListMultimap.create();
|
||||
for (ResultSet<PatchSetAncestor> rs : t) {
|
||||
for (PatchSetAncestor a : rs) {
|
||||
r.put(a.getAncestorRevision().get(), a.getPatchSet());
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user