package hudson.plugins.git.util;
import hudson.model.Action;
import hudson.model.Result;
import hudson.plugins.git.Branch;
import hudson.plugins.git.BranchSpec;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.IGitAPI;
import hudson.plugins.git.Revision;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import org.spearce.jgit.lib.ObjectId;
public class BuildChooser implements IBuildChooser {
private final IGitAPI git;
private final GitUtils utils;
private final GitSCM gitSCM;
//-------- Data -----------
private final BuildData data;
public BuildChooser(GitSCM gitSCM, IGitAPI git, GitUtils utils, BuildData data)
{
this.gitSCM = gitSCM;
this.git = git;
this.utils = utils;
this.data = data == null ? new BuildData() : data;
}
/**
* Determines which Revisions to build.
*
* 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.
*
* 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
*/
public Collection<Revision> getCandidateRevisions(boolean isPollCall, String singleBranch)
throws GitException, IOException {
// 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);
// 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));
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
}
}
// 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) {
// its not a tag, so lets fully qualify the branch
String repository = gitSCM.getRepositories().get(0).getName();
singleBranch = repository + "/" + singleBranch;
}
}
try
{
ObjectId sha1 = git.revParse(singleBranch);
// if polling for changes don't select something that has
// already been built as a build candidate
if (isPollCall && data.hasBeenBuilt(sha1))
return Collections.<Revision>emptyList();
Revision revision = new Revision(sha1);
revision.getBranches().add(new Branch(singleBranch, sha1));
return Collections.singletonList(revision);
}
catch (GitException e)
{
// branch does not exist, there is nothing to build
return Collections.<Revision>emptyList();
}
}
/**
* In order to determine which Revisions to build.
*
* 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.
*
* NB: Alternate IBuildChooser 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 Collection<Revision> getAdvancedCandidateRevisions(boolean isPollCall) throws GitException, IOException
{
// 1. Get all the (branch) revisions that exist
Collection<Revision> revs = utils.getAllBranchRevisions();
// 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()))
{
keep = true;
break;
}
}
if (!keep) j.remove();
}
if (r.getBranches().size() == 0) i.remove();
}
// 3. We only want 'tip' revisions
revs = utils.filterTipBranches(revs);
// 4. Finally, remove any revisions that have already been built.
for (Iterator<Revision> i = revs.iterator(); i.hasNext();)
{
Revision r = i.next();
if (data.hasBeenBuilt(r.getSha1()))
{
i.remove();
}
}
// if we're trying to run a build (not an SCM poll) and nothing new
// was found then just run the last build again
if (!isPollCall && revs.size() == 0 && data.getLastBuiltRevision() != null)
{
return Collections.singletonList(data.getLastBuiltRevision());
}
return revs;
}
public Build revisionBuilt(Revision revision, int buildNumber, Result result )
{
Build build = new Build(revision, buildNumber, result);
data.saveBuild(build);
return build;
}
public Action getData()
{
return data;
}
}