Merge changes from topic 'patch-set-groups-2'
* changes: Convert GetRelated to use patch set groups where present Add patch set group field to secondary index Add a "groups" field to PatchSet Add a helper for assigning commits to groups heuristically Add a helper to sort ChangeDatas in RevWalk order
This commit is contained in:
@@ -33,6 +33,7 @@ import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||
import com.google.gerrit.server.git.GroupCollector;
|
||||
import com.google.gerrit.server.git.WorkQueue;
|
||||
import com.google.gerrit.server.index.ChangeIndexer;
|
||||
import com.google.gerrit.server.mail.CreateChangeSender;
|
||||
@@ -163,6 +164,11 @@ public class ChangeInserter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChangeInserter setGroups(Iterable<String> groups) {
|
||||
patchSet.setGroups(groups);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChangeInserter setHashtags(Set<String> hashtags) {
|
||||
this.hashtags = hashtags;
|
||||
return this;
|
||||
@@ -205,6 +211,9 @@ public class ChangeInserter {
|
||||
db.changes().beginTransaction(change.getId());
|
||||
try {
|
||||
ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
|
||||
if (patchSet.getGroups() == null) {
|
||||
patchSet.setGroups(GroupCollector.getDefaultGroups(patchSet));
|
||||
}
|
||||
db.patchSets().insert(Collections.singleton(patchSet));
|
||||
db.changes().insert(Collections.singleton(change));
|
||||
LabelTypes labelTypes = projectControl.getLabelTypes();
|
||||
|
||||
@@ -157,6 +157,7 @@ public class CreateChange implements
|
||||
try (Repository git = gitManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(git)) {
|
||||
ObjectId parentCommit;
|
||||
List<String> groups;
|
||||
if (input.baseChange != null) {
|
||||
List<Change> changes = changeUtil.findChanges(input.baseChange);
|
||||
if (changes.size() != 1) {
|
||||
@@ -172,6 +173,7 @@ public class CreateChange implements
|
||||
new PatchSet.Id(change.getId(),
|
||||
change.currentPatchSetId().get()));
|
||||
parentCommit = ObjectId.fromString(ps.getRevision().get());
|
||||
groups = ps.getGroups();
|
||||
} else {
|
||||
Ref destRef = git.getRef(refName);
|
||||
if (destRef == null) {
|
||||
@@ -179,6 +181,7 @@ public class CreateChange implements
|
||||
"Branch %s does not exist.", refName));
|
||||
}
|
||||
parentCommit = destRef.getObjectId();
|
||||
groups = null;
|
||||
}
|
||||
RevCommit mergeTip = rw.parseCommit(parentCommit);
|
||||
|
||||
@@ -208,6 +211,7 @@ public class CreateChange implements
|
||||
|
||||
change.setTopic(input.topic);
|
||||
ins.setDraft(input.status != null && input.status == ChangeStatus.DRAFT);
|
||||
ins.setGroups(groups);
|
||||
ins.insert();
|
||||
|
||||
return Response.created(json.format(change.getId()));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.events.CommitReceivedEvent;
|
||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||
import com.google.gerrit.server.git.BanCommit;
|
||||
import com.google.gerrit.server.git.GroupCollector;
|
||||
import com.google.gerrit.server.git.validators.CommitValidationException;
|
||||
import com.google.gerrit.server.git.validators.CommitValidators;
|
||||
import com.google.gerrit.server.index.ChangeIndexer;
|
||||
@@ -108,6 +109,7 @@ public class PatchSetInserter {
|
||||
private SshInfo sshInfo;
|
||||
private ValidatePolicy validatePolicy = ValidatePolicy.GERRIT;
|
||||
private boolean draft;
|
||||
private Iterable<String> groups;
|
||||
private boolean runHooks;
|
||||
private boolean sendMail;
|
||||
private Account.Id uploader;
|
||||
@@ -200,6 +202,11 @@ public class PatchSetInserter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PatchSetInserter setGroups(Iterable<String> groups) {
|
||||
this.groups = groups;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PatchSetInserter setRunHooks(boolean runHooks) {
|
||||
this.runHooks = runHooks;
|
||||
return this;
|
||||
@@ -239,12 +246,18 @@ public class PatchSetInserter {
|
||||
|
||||
db.changes().beginTransaction(c.getId());
|
||||
try {
|
||||
if (!db.changes().get(c.getId()).getStatus().isOpen()) {
|
||||
updatedChange = db.changes().get(c.getId());
|
||||
if (!updatedChange.getStatus().isOpen()) {
|
||||
throw new InvalidChangeOperationException(String.format(
|
||||
"Change %s is closed", c.getId()));
|
||||
}
|
||||
|
||||
ChangeUtil.insertAncestors(db, patchSet.getId(), commit);
|
||||
if (groups != null) {
|
||||
patchSet.setGroups(groups);
|
||||
} else {
|
||||
patchSet.setGroups(GroupCollector.getCurrentGroups(db, c));
|
||||
}
|
||||
db.patchSets().insert(Collections.singleton(patchSet));
|
||||
|
||||
SetMultimap<ReviewerState, Account.Id> oldReviewers = sendMail
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
// 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.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper to sort {@link ChangeData}s based on {@link RevWalk} ordering.
|
||||
* <p>
|
||||
* Split changes by project, and map each change to a single commit based on the
|
||||
* latest patch set. The set of patch sets considered may be limited by calling
|
||||
* {@link #includePatchSets(Set)}. Perform a standard {@link RevWalk} on each
|
||||
* project repository, and record the order in which each change's commit is
|
||||
* seen.
|
||||
* <p>
|
||||
* Once an order within each project is determined, groups of changes are sorted
|
||||
* based on the project name. This is slightly more stable than sorting on
|
||||
* something like the commit or change timestamp, as it will not unexpectedly
|
||||
* reorder large groups of changes on subsequent calls if one of the changes was
|
||||
* updated.
|
||||
*/
|
||||
class WalkSorter {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(WalkSorter.class);
|
||||
|
||||
private static final Ordering<List<PatchSetData>> PROJECT_LIST_SORTER =
|
||||
Ordering.natural().nullsFirst()
|
||||
.onResultOf(
|
||||
new Function<List<PatchSetData>, Project.NameKey>() {
|
||||
@Override
|
||||
public Project.NameKey apply(List<PatchSetData> in) {
|
||||
if (in == null || in.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return in.get(0).data().change().getProject();
|
||||
} catch (OrmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final Set<PatchSet.Id> includePatchSets;
|
||||
private boolean retainBody;
|
||||
|
||||
@Inject
|
||||
WalkSorter(GitRepositoryManager repoManager) {
|
||||
this.repoManager = repoManager;
|
||||
includePatchSets = new HashSet<>();
|
||||
}
|
||||
|
||||
public WalkSorter includePatchSets(Iterable<PatchSet.Id> patchSets) {
|
||||
Iterables.addAll(includePatchSets, patchSets);
|
||||
return this;
|
||||
}
|
||||
|
||||
public WalkSorter setRetainBody(boolean retainBody) {
|
||||
this.retainBody = retainBody;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Iterable<PatchSetData> sort(Iterable<ChangeData> in)
|
||||
throws OrmException, IOException {
|
||||
Multimap<Project.NameKey, ChangeData> byProject =
|
||||
ArrayListMultimap.create();
|
||||
for (ChangeData cd : in) {
|
||||
byProject.put(cd.change().getProject(), cd);
|
||||
}
|
||||
|
||||
List<List<PatchSetData>> sortedByProject =
|
||||
new ArrayList<>(byProject.keySet().size());
|
||||
for (Map.Entry<Project.NameKey, Collection<ChangeData>> e
|
||||
: byProject.asMap().entrySet()) {
|
||||
sortedByProject.add(sortProject(e.getKey(), e.getValue()));
|
||||
}
|
||||
Collections.sort(sortedByProject, PROJECT_LIST_SORTER);
|
||||
return Iterables.concat(sortedByProject);
|
||||
}
|
||||
|
||||
private List<PatchSetData> sortProject(Project.NameKey project,
|
||||
Collection<ChangeData> in) throws OrmException, IOException {
|
||||
try (Repository repo = repoManager.openRepository(project);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
rw.setRetainBody(retainBody);
|
||||
rw.sort(RevSort.TOPO);
|
||||
Multimap<RevCommit, PatchSetData> byCommit = byCommit(rw, in);
|
||||
if (byCommit.isEmpty()) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
// Walk from all patch set SHA-1s, and terminate as soon as we've found
|
||||
// everything we're looking for. This is equivalent to just sorting the
|
||||
// list of commits by the RevWalk's configured order.
|
||||
for (RevCommit c : byCommit.keySet()) {
|
||||
rw.markStart(c);
|
||||
}
|
||||
|
||||
int expected = byCommit.keySet().size();
|
||||
int found = 0;
|
||||
RevCommit c;
|
||||
List<PatchSetData> result = new ArrayList<>(expected);
|
||||
while (found < expected && (c = rw.next()) != null) {
|
||||
Collection<PatchSetData> psds = byCommit.get(c);
|
||||
if (!psds.isEmpty()) {
|
||||
found++;
|
||||
for (PatchSetData psd : psds) {
|
||||
result.add(psd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Multimap<RevCommit, PatchSetData> byCommit(RevWalk rw,
|
||||
Collection<ChangeData> in) throws OrmException, IOException {
|
||||
Multimap<RevCommit, PatchSetData> byCommit =
|
||||
ArrayListMultimap.create(in.size(), 1);
|
||||
for (ChangeData cd : in) {
|
||||
PatchSet maxPs = null;
|
||||
for (PatchSet ps : cd.patchSets()) {
|
||||
if (shouldInclude(ps)
|
||||
&& (maxPs == null || ps.getId().get() > maxPs.getId().get())) {
|
||||
maxPs = ps;
|
||||
}
|
||||
}
|
||||
if (maxPs == null) {
|
||||
continue; // No patch sets matched.
|
||||
}
|
||||
ObjectId id = ObjectId.fromString(maxPs.getRevision().get());
|
||||
try {
|
||||
RevCommit c = rw.parseCommit(id);
|
||||
byCommit.put(c, PatchSetData.create(cd, maxPs, c));
|
||||
} catch (MissingObjectException | IncorrectObjectTypeException e) {
|
||||
log.warn(
|
||||
"missing commit " + id.name() + " for patch set " + maxPs.getId(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
return byCommit;
|
||||
}
|
||||
|
||||
private boolean shouldInclude(PatchSet ps) {
|
||||
return includePatchSets.isEmpty() || includePatchSets.contains(ps.getId());
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
static abstract class PatchSetData {
|
||||
@VisibleForTesting
|
||||
static PatchSetData create(ChangeData cd, PatchSet ps, RevCommit commit) {
|
||||
return new AutoValue_WalkSorter_PatchSetData(cd, ps, commit);
|
||||
}
|
||||
|
||||
abstract ChangeData data();
|
||||
abstract PatchSet patchSet();
|
||||
abstract RevCommit commit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// 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.git;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.eclipse.jgit.revwalk.RevFlag.UNINTERESTING;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
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.server.ReviewDb;
|
||||
import com.google.gerrit.server.change.RevisionResource;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Helper for assigning groups to commits during {@link ReceiveCommits}.
|
||||
* <p>
|
||||
* For each commit encountered along a walk between the branch tip and the tip
|
||||
* of the push, the group of a commit is defined as follows:
|
||||
* <ul>
|
||||
* <li>If the commit is an existing patch set of a change, the group is read
|
||||
* from the group field in the corresponding {@link PatchSet} record.</li>
|
||||
* <li>If all of a commit's parents are merged into the branch, then its group
|
||||
* is its own SHA-1.</li>
|
||||
* <li>If the commit has a single parent that is not yet merged into the
|
||||
* branch, then its group is the same as the parent's group.<li>
|
||||
* <li>For a merge commit, choose a parent and use that parent's group. If one
|
||||
* of the parents has a group from a patch set, use that group, otherwise, use
|
||||
* the group from the first parent. In addition to setting this merge commit's
|
||||
* group, use the chosen group for all commits that would otherwise use a
|
||||
* group from the parents that were not chosen.</li>
|
||||
* <li>If a merge commit has multiple parents whose group comes from separate
|
||||
* patch sets, concatenate the groups from those parents together. This
|
||||
* indicates two side branches were pushed separately, followed by the merge.
|
||||
* <li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Callers must call {@link #visit(RevCommit)} on all commits between the
|
||||
* current branch tip and the tip of a push, in reverse topo order (parents
|
||||
* before children). Once all commits have been visited, call {@link
|
||||
* #getGroups()} for the result.
|
||||
*/
|
||||
public class GroupCollector {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(GroupCollector.class);
|
||||
|
||||
public static List<String> getCurrentGroups(ReviewDb db, Change c)
|
||||
throws OrmException {
|
||||
PatchSet ps = db.patchSets().get(c.currentPatchSetId());
|
||||
return ps != null ? ps.getGroups() : null;
|
||||
}
|
||||
|
||||
public static List<String> getDefaultGroups(PatchSet ps) {
|
||||
return ImmutableList.of(ps.getRevision().get());
|
||||
}
|
||||
|
||||
public static List<String> getGroups(RevisionResource rsrc) {
|
||||
if (rsrc.getEdit().isPresent()) {
|
||||
// Groups for an edit are just the base revision's groups, since they have
|
||||
// the same parent.
|
||||
return rsrc.getEdit().get().getBasePatchSet().getGroups();
|
||||
}
|
||||
return rsrc.getPatchSet().getGroups();
|
||||
}
|
||||
|
||||
private static interface Lookup {
|
||||
List<String> lookup(PatchSet.Id psId) throws OrmException;
|
||||
}
|
||||
|
||||
private final Multimap<ObjectId, PatchSet.Id> patchSetsBySha;
|
||||
private final Multimap<ObjectId, String> groups;
|
||||
private final SetMultimap<String, String> groupAliases;
|
||||
private final Lookup groupLookup;
|
||||
|
||||
private boolean done;
|
||||
|
||||
private GroupCollector(
|
||||
Multimap<ObjectId, PatchSet.Id> patchSetsBySha,
|
||||
Lookup groupLookup) {
|
||||
this.patchSetsBySha = patchSetsBySha;
|
||||
this.groupLookup = groupLookup;
|
||||
groups = ArrayListMultimap.create();
|
||||
groupAliases = HashMultimap.create();
|
||||
}
|
||||
|
||||
public GroupCollector(
|
||||
Multimap<ObjectId, Ref> changeRefsById,
|
||||
final ReviewDb db) {
|
||||
this(
|
||||
Multimaps.transformValues(
|
||||
changeRefsById,
|
||||
new Function<Ref, PatchSet.Id>() {
|
||||
@Override
|
||||
public PatchSet.Id apply(Ref in) {
|
||||
return PatchSet.Id.fromRef(in.getName());
|
||||
}
|
||||
}),
|
||||
new Lookup() {
|
||||
@Override
|
||||
public List<String> lookup(PatchSet.Id psId) throws OrmException {
|
||||
PatchSet ps = db.patchSets().get(psId);
|
||||
return ps != null ? ps.getGroups() : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
GroupCollector(
|
||||
Multimap<ObjectId, PatchSet.Id> patchSetsBySha,
|
||||
final ListMultimap<PatchSet.Id, String> groupLookup) {
|
||||
this(
|
||||
patchSetsBySha,
|
||||
new Lookup() {
|
||||
@Override
|
||||
public List<String> lookup(PatchSet.Id psId) {
|
||||
List<String> groups = groupLookup.get(psId);
|
||||
return !groups.isEmpty() ? groups : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void visit(RevCommit c) {
|
||||
checkState(!done, "visit() called after getGroups()");
|
||||
Set<RevCommit> interestingParents = getInterestingParents(c);
|
||||
|
||||
if (interestingParents.size() == 0) {
|
||||
// All parents are uninteresting: treat this commit as the root of a new
|
||||
// group of related changes.
|
||||
groups.put(c, c.name());
|
||||
return;
|
||||
} else if (interestingParents.size() == 1) {
|
||||
// Only one parent is new in this push. If it is the only parent, just use
|
||||
// that parent's group. If there are multiple parents, perhaps this commit
|
||||
// is a merge of a side branch. This commit belongs in that parent's group
|
||||
// in that case.
|
||||
groups.putAll(c, groups.get(interestingParents.iterator().next()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple parents, merging at least two branches containing new commits in
|
||||
// this push.
|
||||
Set<String> thisCommitGroups = new TreeSet<>();
|
||||
Set<String> parentGroupsNewInThisPush =
|
||||
Sets.newLinkedHashSetWithExpectedSize(interestingParents.size());
|
||||
for (RevCommit p : interestingParents) {
|
||||
Collection<String> parentGroups = groups.get(p);
|
||||
if (parentGroups.isEmpty()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"no group assigned to parent %s of commit %s", p.name(), c.name()));
|
||||
}
|
||||
|
||||
for (String parentGroup : parentGroups) {
|
||||
if (isGroupFromExistingPatchSet(p, parentGroup)) {
|
||||
// This parent's group is from an existing patch set, i.e. the parent
|
||||
// not new in this push. Use this group for the commit.
|
||||
thisCommitGroups.add(parentGroup);
|
||||
} else {
|
||||
// This parent's group is new in this push.
|
||||
parentGroupsNewInThisPush.add(parentGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<String> toAlias;
|
||||
if (thisCommitGroups.isEmpty()) {
|
||||
// All parent groups were new in this push. Pick the first one and alias
|
||||
// other parents' groups to this first parent.
|
||||
String firstParentGroup = parentGroupsNewInThisPush.iterator().next();
|
||||
thisCommitGroups = ImmutableSet.of(firstParentGroup);
|
||||
toAlias = Iterables.skip(parentGroupsNewInThisPush, 1);
|
||||
} else {
|
||||
// For each parent group that was new in this push, alias it to the actual
|
||||
// computed group(s) for this commit.
|
||||
toAlias = parentGroupsNewInThisPush;
|
||||
}
|
||||
groups.putAll(c, thisCommitGroups);
|
||||
for (String pg : toAlias) {
|
||||
groupAliases.putAll(pg, thisCommitGroups);
|
||||
}
|
||||
}
|
||||
|
||||
public SetMultimap<ObjectId, String> getGroups() throws OrmException {
|
||||
done = true;
|
||||
SetMultimap<ObjectId, String> result = MultimapBuilder
|
||||
.hashKeys(groups.keySet().size())
|
||||
.treeSetValues()
|
||||
.build();
|
||||
for (Map.Entry<ObjectId, Collection<String>> e
|
||||
: groups.asMap().entrySet()) {
|
||||
ObjectId id = e.getKey();
|
||||
result.putAll(id.copy(), resolveGroups(id, e.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<RevCommit> getInterestingParents(RevCommit commit) {
|
||||
Set<RevCommit> result =
|
||||
Sets.newLinkedHashSetWithExpectedSize(commit.getParentCount());
|
||||
for (RevCommit p : commit.getParents()) {
|
||||
if (!p.has(UNINTERESTING)) {
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isGroupFromExistingPatchSet(RevCommit commit, String group) {
|
||||
ObjectId id = parseGroup(commit, group);
|
||||
return id != null && patchSetsBySha.containsKey(id);
|
||||
}
|
||||
|
||||
private Set<String> resolveGroups(ObjectId forCommit,
|
||||
Collection<String> candidates) throws OrmException {
|
||||
Set<String> actual = Sets.newTreeSet();
|
||||
Set<String> done = Sets.newHashSetWithExpectedSize(candidates.size());
|
||||
Set<String> seen = Sets.newHashSetWithExpectedSize(candidates.size());
|
||||
Deque<String> todo = new ArrayDeque<>(candidates);
|
||||
// BFS through all aliases to find groups that are not aliased to anything
|
||||
// else.
|
||||
while (!todo.isEmpty()) {
|
||||
String g = todo.removeFirst();
|
||||
if (!seen.add(g)) {
|
||||
continue;
|
||||
}
|
||||
Set<String> aliases = groupAliases.get(g);
|
||||
if (aliases.isEmpty()) {
|
||||
if (!done.contains(g)) {
|
||||
Iterables.addAll(actual, resolveGroup(forCommit, g));
|
||||
done.add(g);
|
||||
}
|
||||
} else {
|
||||
todo.addAll(aliases);
|
||||
}
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
private ObjectId parseGroup(ObjectId forCommit, String group) {
|
||||
try {
|
||||
return ObjectId.fromString(group);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Shouldn't happen; some sort of corruption or manual tinkering?
|
||||
log.warn("group for commit {} is not a SHA-1: {}",
|
||||
forCommit.name(), group);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<String> resolveGroup(ObjectId forCommit, String group)
|
||||
throws OrmException {
|
||||
ObjectId id = parseGroup(forCommit, group);
|
||||
if (id != null) {
|
||||
PatchSet.Id psId = Iterables.getFirst(patchSetsBySha.get(id), null);
|
||||
if (psId != null) {
|
||||
List<String> groups = groupLookup.lookup(psId);
|
||||
// Group for existing patch set may be missing, e.g. if group has not
|
||||
// been migrated yet.
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableList.of(group);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import com.google.common.collect.LinkedListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
@@ -1467,6 +1468,10 @@ public class ReceiveCommits {
|
||||
private void selectNewAndReplacedChangesFromMagicBranch() {
|
||||
newChanges = Lists.newArrayList();
|
||||
final RevWalk walk = rp.getRevWalk();
|
||||
|
||||
Set<ObjectId> existing = changeRefsById().keySet();
|
||||
GroupCollector groupCollector = new GroupCollector(refsById, db);
|
||||
|
||||
walk.reset();
|
||||
walk.sort(RevSort.TOPO);
|
||||
walk.sort(RevSort.REVERSE, true);
|
||||
@@ -1486,7 +1491,6 @@ public class ReceiveCommits {
|
||||
magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null);
|
||||
}
|
||||
|
||||
Set<ObjectId> existing = changeRefsById().keySet();
|
||||
List<ChangeLookup> pending = Lists.newArrayList();
|
||||
final Set<Change.Key> newChangeIds = new HashSet<>();
|
||||
final int maxBatchChanges =
|
||||
@@ -1496,7 +1500,17 @@ public class ReceiveCommits {
|
||||
if (c == null) {
|
||||
break;
|
||||
}
|
||||
groupCollector.visit(c);
|
||||
if (existing.contains(c)) { // Commit is already tracked.
|
||||
// TODO(dborowitz): Corner case where an existing commit might need a
|
||||
// new group:
|
||||
// Let A<-B<-C, then:
|
||||
// 1. Push A to refs/heads/master
|
||||
// 2. Push B to refs/for/master
|
||||
// 3. Force push A~ to refs/heads/master
|
||||
// 4. Push C to refs/for/master.
|
||||
// B will be in existing so we aren't replacing the patch set. It used
|
||||
// to have its own group, but now needs to to be changed to A's group.
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1605,8 +1619,20 @@ public class ReceiveCommits {
|
||||
reject(magicBranch.cmd, "edit is not supported for new changes");
|
||||
return;
|
||||
}
|
||||
for (CreateRequest create : newChanges) {
|
||||
batch.addCommand(create.cmd);
|
||||
|
||||
try {
|
||||
Multimap<ObjectId, String> groups = groupCollector.getGroups();
|
||||
for (CreateRequest create : newChanges) {
|
||||
batch.addCommand(create.cmd);
|
||||
create.groups = groups.get(create.commit);
|
||||
}
|
||||
for (ReplaceRequest replace : replaceByChange.values()) {
|
||||
replace.groups = groups.get(replace.newCommit);
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
log.error("Error collecting groups for changes", e);
|
||||
reject(magicBranch.cmd, "internal server error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1646,6 +1672,7 @@ public class ReceiveCommits {
|
||||
final ReceiveCommand cmd;
|
||||
final ChangeInserter ins;
|
||||
boolean created;
|
||||
Collection<String> groups;
|
||||
|
||||
CreateRequest(RefControl ctl, RevCommit c, Change.Key changeKey)
|
||||
throws OrmException {
|
||||
@@ -1690,7 +1717,7 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
private void insertChange(ReviewDb db) throws OrmException, IOException {
|
||||
final PatchSet ps = ins.getPatchSet();
|
||||
final PatchSet ps = ins.setGroups(groups).getPatchSet();
|
||||
final Account.Id me = currentUser.getAccountId();
|
||||
final List<FooterLine> footerLines = commit.getFooterLines();
|
||||
final MailRecipients recipients = new MailRecipients();
|
||||
@@ -1845,6 +1872,7 @@ public class ReceiveCommits {
|
||||
String mergedIntoRef;
|
||||
boolean skip;
|
||||
private PatchSet.Id priorPatchSet;
|
||||
Collection<String> groups;
|
||||
|
||||
ReplaceRequest(final Change.Id toChange, final RevCommit newCommit,
|
||||
final ReceiveCommand cmd, final boolean checkMergedInto) {
|
||||
@@ -2020,6 +2048,7 @@ public class ReceiveCommits {
|
||||
newPatchSet.setCreatedOn(TimeUtil.nowTs());
|
||||
newPatchSet.setUploader(currentUser.getAccountId());
|
||||
newPatchSet.setRevision(toRevId(newCommit));
|
||||
newPatchSet.setGroups(groups);
|
||||
if (magicBranch != null && magicBranch.draft) {
|
||||
newPatchSet.setDraft(true);
|
||||
}
|
||||
@@ -2124,6 +2153,9 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
ChangeUtil.insertAncestors(db, newPatchSet.getId(), newCommit);
|
||||
if (newPatchSet.getGroups() == null) {
|
||||
newPatchSet.setGroups(GroupCollector.getCurrentGroups(db, change));
|
||||
}
|
||||
db.patchSets().insert(Collections.singleton(newPatchSet));
|
||||
|
||||
if (checkMergedInto) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.CommitMergeStatus;
|
||||
import com.google.gerrit.server.git.GroupCollector;
|
||||
import com.google.gerrit.server.git.MergeConflictException;
|
||||
import com.google.gerrit.server.git.MergeException;
|
||||
import com.google.gerrit.server.git.MergeIdenticalTreeException;
|
||||
@@ -186,6 +187,7 @@ public class CherryPick extends SubmitStrategy {
|
||||
args.db.changes().beginTransaction(n.change().getId());
|
||||
try {
|
||||
insertAncestors(args.db, ps.getId(), newCommit);
|
||||
ps.setGroups(GroupCollector.getCurrentGroups(args.db, n.change()));
|
||||
args.db.patchSets().insert(Collections.singleton(ps));
|
||||
n.change()
|
||||
.setCurrentPatchSet(patchSetInfoFactory.get(newCommit, ps.getId()));
|
||||
|
||||
@@ -531,6 +531,24 @@ public class ChangeField {
|
||||
}
|
||||
};
|
||||
|
||||
/** Opaque group identifiers for this change's patch sets. */
|
||||
public static final FieldDef<ChangeData, Iterable<String>> GROUP =
|
||||
new FieldDef.Repeatable<ChangeData, String>(
|
||||
"group", FieldType.EXACT, false) {
|
||||
@Override
|
||||
public Iterable<String> get(ChangeData input, FillArgs args)
|
||||
throws OrmException {
|
||||
Set<String> r = Sets.newHashSetWithExpectedSize(1);
|
||||
for (PatchSet ps : input.patchSets()) {
|
||||
List<String> groups = ps.getGroups();
|
||||
if (groups != null) {
|
||||
r.addAll(groups);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
public static class PatchSetProtoField
|
||||
extends FieldDef.Repeatable<ChangeData, byte[]> {
|
||||
public static final ProtobufCodec<PatchSet> CODEC =
|
||||
|
||||
@@ -202,6 +202,36 @@ public class ChangeSchemas {
|
||||
ChangeField.COMMENTBY,
|
||||
ChangeField.PATCH_SET);
|
||||
|
||||
static final Schema<ChangeData> V18 = schema(
|
||||
ChangeField.LEGACY_ID,
|
||||
ChangeField.ID,
|
||||
ChangeField.STATUS,
|
||||
ChangeField.PROJECT,
|
||||
ChangeField.PROJECTS,
|
||||
ChangeField.REF,
|
||||
ChangeField.TOPIC,
|
||||
ChangeField.UPDATED,
|
||||
ChangeField.FILE_PART,
|
||||
ChangeField.PATH,
|
||||
ChangeField.OWNER,
|
||||
ChangeField.REVIEWER,
|
||||
ChangeField.COMMIT,
|
||||
ChangeField.TR,
|
||||
ChangeField.LABEL,
|
||||
ChangeField.REVIEWED,
|
||||
ChangeField.COMMIT_MESSAGE,
|
||||
ChangeField.COMMENT,
|
||||
ChangeField.CHANGE,
|
||||
ChangeField.APPROVAL,
|
||||
ChangeField.MERGEABLE,
|
||||
ChangeField.ADDED,
|
||||
ChangeField.DELETED,
|
||||
ChangeField.DELTA,
|
||||
ChangeField.HASHTAG,
|
||||
ChangeField.COMMENTBY,
|
||||
ChangeField.PATCH_SET,
|
||||
ChangeField.GROUP);
|
||||
|
||||
private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
|
||||
return new Schema<>(ImmutableList.copyOf(fields));
|
||||
}
|
||||
|
||||
@@ -172,12 +172,13 @@ public class ChangeData {
|
||||
/**
|
||||
* Create an instance for testing only.
|
||||
* <p>
|
||||
* Attempting to lazy load data will fail with NPEs.
|
||||
* Attempting to lazy load data will fail with NPEs. Callers may consider
|
||||
* manually setting fields that can be set.
|
||||
*
|
||||
* @param id change ID
|
||||
* @return instance for testing.
|
||||
*/
|
||||
static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
|
||||
public static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
|
||||
ChangeData cd = new ChangeData(null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, id);
|
||||
cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.index.ChangeField;
|
||||
import com.google.gerrit.server.index.IndexPredicate;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class GroupPredicate extends IndexPredicate<ChangeData> {
|
||||
GroupPredicate(String group) {
|
||||
super(ChangeField.GROUP, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData cd) throws OrmException {
|
||||
for (PatchSet ps : cd.patchSets()) {
|
||||
List<String> groups = ps.getGroups();
|
||||
if (groups != null && groups.contains(getValue())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import static com.google.gerrit.server.query.Predicate.and;
|
||||
import static com.google.gerrit.server.query.Predicate.or;
|
||||
import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
|
||||
|
||||
import com.google.gerrit.common.Nullable;
|
||||
@@ -32,6 +33,8 @@ import com.google.inject.Inject;
|
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -145,6 +148,15 @@ public class InternalChangeQuery {
|
||||
return query(commit(AbbreviatedObjectId.fromObjectId(id)));
|
||||
}
|
||||
|
||||
public List<ChangeData> byProjectGroups(Project.NameKey project,
|
||||
Collection<String> groups) throws OrmException {
|
||||
List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
|
||||
for (String g : groups) {
|
||||
groupPredicates.add(new GroupPredicate(g));
|
||||
}
|
||||
return query(and(project(project), or(groupPredicates)));
|
||||
}
|
||||
|
||||
private List<ChangeData> query(Predicate<ChangeData> p) throws OrmException {
|
||||
try {
|
||||
return qp.queryChanges(p).changes();
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
public static final Class<Schema_107> C = Schema_107.class;
|
||||
public static final Class<Schema_108> C = Schema_108.class;
|
||||
|
||||
public static int getBinaryVersion() {
|
||||
return guessVersion(C);
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// 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.schema;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.GroupCollector;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
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.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Schema_108 extends SchemaVersion {
|
||||
private final GitRepositoryManager repoManager;
|
||||
|
||||
@Inject
|
||||
Schema_108(Provider<Schema_107> prior,
|
||||
GitRepositoryManager repoManager) {
|
||||
super(prior);
|
||||
this.repoManager = repoManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
|
||||
ui.message("Listing all changes ...");
|
||||
SetMultimap<Project.NameKey, Change.Id> openByProject =
|
||||
getOpenChangesByProject(db);
|
||||
ui.message("done");
|
||||
|
||||
ui.message("Updating groups for open changes ...");
|
||||
int i = 0;
|
||||
for (Map.Entry<Project.NameKey, Collection<Change.Id>> e
|
||||
: openByProject.asMap().entrySet()) {
|
||||
try (Repository repo = repoManager.openRepository(e.getKey());
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
updateProjectGroups(db, repo, rw, (Set<Change.Id>) e.getValue());
|
||||
} catch (IOException err) {
|
||||
throw new OrmException(err);
|
||||
}
|
||||
if (++i % 100 == 0) {
|
||||
ui.message(" done " + i + " projects ...");
|
||||
}
|
||||
}
|
||||
ui.message("done");
|
||||
}
|
||||
|
||||
private static void updateProjectGroups(ReviewDb db, Repository repo,
|
||||
RevWalk rw, Set<Change.Id> changes) throws OrmException, IOException {
|
||||
// Match sorting in ReceiveCommits.
|
||||
rw.reset();
|
||||
rw.sort(RevSort.TOPO);
|
||||
rw.sort(RevSort.REVERSE, true);
|
||||
|
||||
RefDatabase refdb = repo.getRefDatabase();
|
||||
for (Ref ref : refdb.getRefs(Constants.R_HEADS).values()) {
|
||||
RevCommit c = maybeParseCommit(rw, ref.getObjectId());
|
||||
if (c != null) {
|
||||
rw.markUninteresting(c);
|
||||
}
|
||||
}
|
||||
|
||||
Multimap<ObjectId, Ref> changeRefsBySha = ArrayListMultimap.create();
|
||||
Multimap<ObjectId, PatchSet.Id> patchSetsBySha = ArrayListMultimap.create();
|
||||
for (Ref ref : refdb.getRefs(RefNames.REFS_CHANGES).values()) {
|
||||
ObjectId id = ref.getObjectId();
|
||||
if (ref.getObjectId() == null) {
|
||||
continue;
|
||||
}
|
||||
id = id.copy();
|
||||
changeRefsBySha.put(id, ref);
|
||||
PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
|
||||
if (psId != null && changes.contains(psId.getParentKey())) {
|
||||
patchSetsBySha.put(id, psId);
|
||||
RevCommit c = maybeParseCommit(rw, id);
|
||||
if (c != null) {
|
||||
rw.markStart(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupCollector collector = new GroupCollector(changeRefsBySha, db);
|
||||
RevCommit c;
|
||||
while ((c = rw.next()) != null) {
|
||||
collector.visit(c);
|
||||
}
|
||||
|
||||
updateGroups(db, collector, patchSetsBySha);
|
||||
}
|
||||
|
||||
private static void updateGroups(ReviewDb db, GroupCollector collector,
|
||||
Multimap<ObjectId, PatchSet.Id> patchSetsBySha) throws OrmException {
|
||||
Map<PatchSet.Id, PatchSet> patchSets =
|
||||
db.patchSets().toMap(db.patchSets().get(patchSetsBySha.values()));
|
||||
for (Map.Entry<ObjectId, Collection<String>> e
|
||||
: collector.getGroups().asMap().entrySet()) {
|
||||
for (PatchSet.Id psId : patchSetsBySha.get(e.getKey())) {
|
||||
PatchSet ps = patchSets.get(psId);
|
||||
if (ps != null) {
|
||||
ps.setGroups(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.patchSets().update(patchSets.values());
|
||||
}
|
||||
|
||||
private SetMultimap<Project.NameKey, Change.Id> getOpenChangesByProject(
|
||||
ReviewDb db) throws OrmException {
|
||||
SetMultimap<Project.NameKey, Change.Id> openByProject =
|
||||
HashMultimap.create();
|
||||
for (Change c : db.changes().all()) {
|
||||
if (c.getStatus().isOpen()) {
|
||||
openByProject.put(c.getProject(), c.getId());
|
||||
}
|
||||
}
|
||||
return openByProject;
|
||||
}
|
||||
|
||||
private static RevCommit maybeParseCommit(RevWalk rw, ObjectId id)
|
||||
throws IOException {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
RevObject obj = rw.parseAny(id);
|
||||
return (obj instanceof RevCommit) ? (RevCommit) obj : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user