/** * Determines which Revisions to build. * * <p>If only one branch is chosen and only one repository is listed, then just attempt to find * the latest revision number for the chosen branch. * * <p>If multiple branches are selected or the branches include wildcards, then use the advanced * usecase as defined in the getAdvancedCandidateRevisons method. * * @throws IOException * @throws GitException */ @Override public Collection<Revision> getCandidateRevisions( boolean isPollCall, String singleBranch, GitClient git, TaskListener listener, BuildData data, BuildChooserContext context) throws GitException, IOException, InterruptedException { verbose( listener, "getCandidateRevisions({0},{1},,,{2}) considering branches to build", isPollCall, singleBranch, data); // if the branch name contains more wildcards then the simple usecase // does not apply and we need to skip to the advanced usecase if (singleBranch == null || singleBranch.contains("*")) return getAdvancedCandidateRevisions( isPollCall, listener, new GitUtils(listener, git), data, context); // check if we're trying to build a specific commit // this only makes sense for a build, there is no // reason to poll for a commit if (!isPollCall && singleBranch.matches("[0-9a-f]{6,40}")) { try { ObjectId sha1 = git.revParse(singleBranch); Revision revision = new Revision(sha1); revision.getBranches().add(new Branch("detached", sha1)); verbose(listener, "Will build the detached SHA1 {0}", sha1); return Collections.singletonList(revision); } catch (GitException e) { // revision does not exist, may still be a branch // for example a branch called "badface" would show up here verbose(listener, "Not a valid SHA1 {0}", singleBranch); } } Collection<Revision> revisions = new ArrayList<Revision>(); // if it doesn't contain '/' then it could be either a tag or an unqualified branch if (!singleBranch.contains("/")) { // the 'branch' could actually be a tag: Set<String> tags = git.getTagNames(singleBranch); if (tags.size() != 0) { verbose(listener, "{0} is a tag"); return getHeadRevision(isPollCall, singleBranch, git, listener, data); } // <tt>BRANCH</tt> is recognized as a shorthand of <tt>*/BRANCH</tt> // so check all remotes to fully qualify this branch spec for (RemoteConfig config : gitSCM.getRepositories()) { String repository = config.getName(); String fqbn = repository + "/" + singleBranch; verbose( listener, "Qualifying {0} as a branch in repository {1} -> {2}", singleBranch, repository, fqbn); revisions.addAll(getHeadRevision(isPollCall, fqbn, git, listener, data)); } } else { // either the branch is qualified (first part should match a valid remote) // or it is still unqualified, but the branch name contains a '/' List<String> possibleQualifiedBranches = new ArrayList<String>(); boolean singleBranchIsQualified = false; for (RemoteConfig config : gitSCM.getRepositories()) { String repository = config.getName(); if (singleBranch.startsWith(repository + "/")) { singleBranchIsQualified = true; break; } String fqbn = repository + "/" + singleBranch; verbose( listener, "Qualifying {0} as a branch in repository {1} -> {2}", singleBranch, repository, fqbn); possibleQualifiedBranches.add(fqbn); } if (singleBranchIsQualified) { revisions.addAll(getHeadRevision(isPollCall, singleBranch, git, listener, data)); } else { for (String fqbn : possibleQualifiedBranches) { revisions.addAll(getHeadRevision(isPollCall, fqbn, git, listener, data)); } } } return revisions; }
/** * In order to determine which Revisions to build. * * <p>Does the following : 1. Find all the branch revisions 2. Filter out branches that we don't * care about from the revisions. Any Revisions with no interesting branches are dropped. 3. Get * rid of any revisions that are wholly subsumed by another revision we're considering. 4. Get rid * of any revisions that we've already built. 5. Sort revisions from old to new. * * <p>NB: Alternate BuildChooser implementations are possible - this may be beneficial if "only 1" * branch is to be built, as much of this work is irrelevant in that usecase. * * @throws IOException * @throws GitException */ private List<Revision> getAdvancedCandidateRevisions( boolean isPollCall, TaskListener listener, GitUtils utils, BuildData data, BuildChooserContext context) throws GitException, IOException, InterruptedException { EnvVars env = context.getBuild().getEnvironment(); // 1. Get all the (branch) revisions that exist List<Revision> revs = new ArrayList<Revision>(utils.getAllBranchRevisions()); verbose(listener, "Starting with all the branches: {0}", revs); // 2. Filter out any revisions that don't contain any branches that we // actually care about (spec) for (Iterator<Revision> i = revs.iterator(); i.hasNext(); ) { Revision r = i.next(); // filter out uninteresting branches for (Iterator<Branch> j = r.getBranches().iterator(); j.hasNext(); ) { Branch b = j.next(); boolean keep = false; for (BranchSpec bspec : gitSCM.getBranches()) { if (bspec.matches(b.getName(), env)) { keep = true; break; } } if (!keep) { verbose(listener, "Ignoring {0} because it doesn''t match branch specifier", b); j.remove(); } } // filter out HEAD ref if it's not the only ref if (r.getBranches().size() > 1) { for (Iterator<Branch> j = r.getBranches().iterator(); j.hasNext(); ) { Branch b = j.next(); if (HEAD.matches(b.getName(), env)) { verbose( listener, "Ignoring {0} because there''s named branch for this revision", b.getName()); j.remove(); } } } if (r.getBranches().size() == 0) { verbose( listener, "Ignoring {0} because we don''t care about any of the branches that point to it", r); i.remove(); } } verbose(listener, "After branch filtering: {0}", revs); // 3. We only want 'tip' revisions revs = utils.filterTipBranches(revs); verbose(listener, "After non-tip filtering: {0}", revs); // 4. Finally, remove any revisions that have already been built. verbose(listener, "Removing what''s already been built: {0}", data.getBuildsByBranchName()); Revision lastBuiltRevision = data.getLastBuiltRevision(); for (Iterator<Revision> i = revs.iterator(); i.hasNext(); ) { Revision r = i.next(); if (data.hasBeenBuilt(r.getSha1())) { i.remove(); // keep track of new branches pointing to the last built revision if (lastBuiltRevision != null && lastBuiltRevision.getSha1().equals(r.getSha1())) { lastBuiltRevision = r; } } } verbose(listener, "After filtering out what''s already been built: {0}", revs); // if we're trying to run a build (not an SCM poll) and nothing new // was found then just run the last build again but ensure that the branch list // is ordered according to the configuration. Sorting the branch list ensures // a deterministic value for GIT_BRANCH and allows a git-flow style workflow // with fast-forward merges between branches if (!isPollCall && revs.isEmpty() && lastBuiltRevision != null) { verbose( listener, "Nothing seems worth building, so falling back to the previously built revision: {0}", data.getLastBuiltRevision()); return Collections.singletonList( utils.sortBranchesForRevision(lastBuiltRevision, gitSCM.getBranches(), env)); } // 5. sort them by the date of commit, old to new // this ensures the fairness in scheduling. final List<Revision> in = revs; return utils.git.withRepository( new RepositoryCallback<List<Revision>>() { public List<Revision> invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException { Collections.sort(in, new CommitTimeComparator(repo)); return in; } }); }