diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java | 269 |
1 files changed, 0 insertions, 269 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java deleted file mode 100644 index 56d7ec0239..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/WalkSorter.java +++ /dev/null @@ -1,269 +0,0 @@ -// 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 static com.google.common.base.Preconditions.checkState; - -import com.google.auto.value.AutoValue; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.MultimapBuilder; -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 java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -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.RevFlag; -import org.eclipse.jgit.revwalk.RevWalk; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 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(Iterable)}. Perform a standard {@link RevWalk} on each project repository, do - * an approximate topo sort, 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( - (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 { - ListMultimap<Project.NameKey, ChangeData> byProject = - MultimapBuilder.hashKeys().arrayListValues().build(); - 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); - ListMultimap<RevCommit, PatchSetData> byCommit = byCommit(rw, in); - if (byCommit.isEmpty()) { - return ImmutableList.of(); - } else if (byCommit.size() == 1) { - return ImmutableList.of(byCommit.values().iterator().next()); - } - - // 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. - // - // Partially topo sort the list, ensuring no parent is emitted before a - // direct child that is also in the input set. This preserves the stable, - // expected sort in the case where many commits share the same timestamp, - // e.g. a quick rebase. It also avoids JGit's topo sort, which slurps all - // interesting commits at the beginning, which is a problem since we don't - // know which commits to mark as uninteresting. Finding a reasonable set - // of commits to mark uninteresting (the "rootmost" set) is at least as - // difficult as just implementing this partial topo sort ourselves. - // - // (This is slightly less efficient than JGit's topo sort, which uses a - // private in-degree field in RevCommit rather than multimaps. We assume - // the input size is small enough that this is not an issue.) - - Set<RevCommit> commits = byCommit.keySet(); - ListMultimap<RevCommit, RevCommit> children = collectChildren(commits); - ListMultimap<RevCommit, RevCommit> pending = - MultimapBuilder.hashKeys().arrayListValues().build(); - Deque<RevCommit> todo = new ArrayDeque<>(); - - RevFlag done = rw.newFlag("done"); - markStart(rw, commits); - int expected = commits.size(); - int found = 0; - RevCommit c; - List<PatchSetData> result = new ArrayList<>(expected); - while (found < expected && (c = rw.next()) != null) { - if (!commits.contains(c)) { - continue; - } - todo.clear(); - todo.add(c); - int i = 0; - while (!todo.isEmpty()) { - // Sanity check: we can't pop more than N pending commits, otherwise - // we have an infinite loop due to programmer error or something. - checkState(++i <= commits.size(), "Too many pending steps while sorting %s", commits); - RevCommit t = todo.removeFirst(); - if (t.has(done)) { - continue; - } - boolean ready = true; - for (RevCommit child : children.get(t)) { - if (!child.has(done)) { - pending.put(child, t); - ready = false; - } - } - if (ready) { - found += emit(t, byCommit, result, done); - todo.addAll(pending.get(t)); - } - } - } - return result; - } - } - - private static ListMultimap<RevCommit, RevCommit> collectChildren(Set<RevCommit> commits) { - ListMultimap<RevCommit, RevCommit> children = - MultimapBuilder.hashKeys().arrayListValues().build(); - for (RevCommit c : commits) { - for (RevCommit p : c.getParents()) { - if (commits.contains(p)) { - children.put(p, c); - } - } - } - return children; - } - - private static int emit( - RevCommit c, - ListMultimap<RevCommit, PatchSetData> byCommit, - List<PatchSetData> result, - RevFlag done) { - if (c.has(done)) { - return 0; - } - c.add(done); - Collection<PatchSetData> psds = byCommit.get(c); - if (!psds.isEmpty()) { - result.addAll(psds); - return 1; - } - return 0; - } - - private ListMultimap<RevCommit, PatchSetData> byCommit(RevWalk rw, Collection<ChangeData> in) - throws OrmException, IOException { - ListMultimap<RevCommit, PatchSetData> byCommit = - MultimapBuilder.hashKeys(in.size()).arrayListValues(1).build(); - 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()); - } - - private static void markStart(RevWalk rw, Iterable<RevCommit> commits) throws IOException { - for (RevCommit c : commits) { - rw.markStart(c); - } - } - - @AutoValue - abstract static 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(); - } -} |