/**
   * 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;
          }
        });
  }