Skip to content

Commit

Permalink
filterTipBranches: Use O(n) instead of O(n^2) algorithm.
Browse files Browse the repository at this point in the history
Fixes JENKINS-5724.

Before, this made slaves run on 100% CPU for days when idle.

This makes it a few thousand times faster for a repo with 500 branches.
  • Loading branch information
nh2 committed Mar 31, 2014
1 parent b2a731e commit ba3271b
Showing 1 changed file with 35 additions and 36 deletions.
71 changes: 35 additions & 36 deletions src/main/java/hudson/plugins/git/util/GitUtils.java
Expand Up @@ -13,6 +13,7 @@
import hudson.slaves.NodeProperty; import hudson.slaves.NodeProperty;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.jenkinsci.plugins.gitclient.GitClient; import org.jenkinsci.plugins.gitclient.GitClient;
Expand Down Expand Up @@ -89,7 +90,7 @@ public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> bran
public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> branchOrder, EnvVars env) { public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> branchOrder, EnvVars env) {
ArrayList<Branch> orderedBranches = new ArrayList<Branch>(revision.getBranches().size()); ArrayList<Branch> orderedBranches = new ArrayList<Branch>(revision.getBranches().size());
ArrayList<Branch> revisionBranches = new ArrayList<Branch>(revision.getBranches()); ArrayList<Branch> revisionBranches = new ArrayList<Branch>(revision.getBranches());

for(BranchSpec branchSpec : branchOrder) { for(BranchSpec branchSpec : branchOrder) {
for (Iterator<Branch> i = revisionBranches.iterator(); i.hasNext();) { for (Iterator<Branch> i = revisionBranches.iterator(); i.hasNext();) {
Branch b = i.next(); Branch b = i.next();
Expand All @@ -111,75 +112,73 @@ public Revision sortBranchesForRevision(Revision revision, List<BranchSpec> bran
* @return filtered tip branches * @return filtered tip branches
*/ */
@WithBridgeMethods(Collection.class) @WithBridgeMethods(Collection.class)
public List<Revision> filterTipBranches(Collection<Revision> revisions) throws InterruptedException { public List<Revision> filterTipBranches(final Collection<Revision> revisions) throws InterruptedException {
// If we have 3 branches that we might want to build // If we have 3 branches that we might want to build
// ----A--.---.--- B // ----A--.---.--- B
// \-----C // \-----C


// we only want (B) and (C), as (A) is an ancestor (old). // we only want (B) and (C), as (A) is an ancestor (old).
final List<Revision> l = new ArrayList<Revision>(revisions); final List<Revision> l = new ArrayList<Revision>(revisions);

// Bypass any rev walks if only one branch or less // Bypass any rev walks if only one branch or less
if (l.size() <= 1) if (l.size() <= 1)
return l; return l;


try { try {
return git.withRepository(new RepositoryCallback<List<Revision>>() { return git.withRepository(new RepositoryCallback<List<Revision>>() {
public List<Revision> invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException { public List<Revision> invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
RevWalk walk = new RevWalk(repo);
// Commit nodes that we have already reached
Set<RevCommit> visited = new HashSet<RevCommit>();
// Commits nodes that are tips if we don't reach them walking back from
// another node
Map<RevCommit, Revision> tipCandidates = new HashMap<RevCommit, Revision>();

long calls = 0; long calls = 0;
final long start = System.currentTimeMillis();

RevWalk walk = new RevWalk(repo);


final boolean log = LOGGER.isLoggable(Level.FINE); final boolean log = LOGGER.isLoggable(Level.FINE);
final long start = System.currentTimeMillis();
if (log) if (log)
LOGGER.fine(MessageFormat.format( LOGGER.fine(MessageFormat.format(
"Computing merge base of {0} branches", l.size())); "Computing merge base of {0} branches", l.size()));


try { try {
walk.setRetainBody(false); walk.setRetainBody(false);
walk.setRevFilter(RevFilter.MERGE_BASE);
for (int i = 0; i < l.size(); i++) {
for (int j = i + 1; j < l.size(); j++) {
Revision revI = l.get(i);
Revision revJ = l.get(j);
ObjectId shaI = revI.getSha1();
ObjectId shaJ = revJ.getSha1();

walk.reset();
walk.markStart(walk.parseCommit(shaI));
walk.markStart(walk.parseCommit(shaJ));
ObjectId commonAncestor = walk.next();
calls++;


if (commonAncestor == null) // Each commit passed in starts as a potential tip.
continue; // We walk backwards in the commit's history, until we reach the
if (commonAncestor.equals(shaI)) { // beginning or a commit that we have already visited. In that case,
if (log) // we mark that one as not a potential tip.
LOGGER.fine("filterTipBranches: " + revJ for (Revision r : revisions) {
+ " subsumes " + revI); walk.reset();
l.remove(i); RevCommit head = walk.parseCommit(r.getSha1());
i--;
tipCandidates.put(head, r);

walk.markStart(head);
for (RevCommit commit : walk) {
calls++;
if (visited.contains(commit)) {
tipCandidates.remove(commit);
break; break;
} }
if (commonAncestor.equals(shaJ)) { visited.add(commit);
if (log)
LOGGER.fine("filterTipBranches: " + revI
+ " subsumes " + revJ);
l.remove(j);
j--;
}
} }
} }

} finally { } finally {
walk.release(); walk.release();
} }


if (log) if (log)
LOGGER.fine(MessageFormat.format( LOGGER.fine(MessageFormat.format(
"Computed {0} merge bases in {1} ms", calls, "Computed merge bases in {0} commit steps and {1} ms", calls,
(System.currentTimeMillis() - start))); (System.currentTimeMillis() - start)));


return l; return new ArrayList<Revision>(tipCandidates.values());
} }
}); });
} catch (IOException e) { } catch (IOException e) {
Expand Down Expand Up @@ -217,7 +216,7 @@ public static EnvVars getPollEnvironment(AbstractProject p, FilePath ws, Launche
} else { } else {
env = new EnvVars(System.getenv()); env = new EnvVars(System.getenv());
} }

p.getScm().buildEnvVars(b,env); p.getScm().buildEnvVars(b,env);


if (lastBuiltOn != null) { if (lastBuiltOn != null) {
Expand Down

0 comments on commit ba3271b

Please sign in to comment.