Exemplo n.º 1
0
  /**
   * Set up submodule URLs so that they correspond to the remote pertaining to the revision that has
   * been checked out.
   */
  public void setupSubmoduleUrls(Revision rev, TaskListener listener) throws GitException {
    String remote = null;

    // try to locate the remote repository from where this commit came from
    // (by using the heuristics that the branch name, if available, contains the remote name)
    // if we can figure out the remote, the other setupSubmoduleUrls method
    // look at its URL, and if it's a non-bare repository, we attempt to retrieve modules
    // from this checked out copy.
    //
    // the idea is that you have something like tree-structured repositories: at the root you have
    // corporate central repositories that you
    // ultimately push to, which all .gitmodules point to, then you have intermediate team local
    // repository,
    // which is assumed to be a non-bare repository (say, a checked out copy on a shared server
    // accessed via SSH)
    //
    // the abovementioned behaviour of the Git plugin makes it pick up submodules from this team
    // local repository,
    // not the corporate central.
    //
    // (Kohsuke: I have a bit of hesitation/doubt about such a behaviour change triggered by
    // seemingly indirect
    // evidence of whether the upstream is bare or not (not to mention the fact that you can't
    // reliably
    // figure out if the repository is bare or not just from the URL), but that's what apparently
    // has been implemented
    // and we care about the backward compatibility.)
    //
    // note that "figuring out which remote repository the commit came from" isn't a well-defined
    // question, and this is really a heuristics. The user might be telling us to build a specific
    // SHA1.
    // or maybe someone pushed directly to the workspace and so it may not correspond to any remote
    // branch.
    // so if we fail to figure this out, we back out and avoid being too clever. See JENKINS-10060
    // as an example
    // of where our trying to be too clever here is breaking stuff for people.
    for (Branch br : rev.getBranches()) {
      String b = br.getName();
      if (b != null) {
        int slash = b.indexOf('/');

        if (slash != -1) remote = getDefaultRemote(b.substring(0, slash));
      }

      if (remote != null) break;
    }

    if (remote == null) remote = getDefaultRemote();

    if (remote != null) setupSubmoduleUrls(remote, listener);
  }
  private Collection<Revision> getHeadRevision(
      boolean isPollCall, String singleBranch, GitClient git, TaskListener listener, BuildData data)
      throws InterruptedException {
    try {
      ObjectId sha1 = git.revParse(singleBranch);
      verbose(listener, "rev-parse {0} -> {1}", singleBranch, sha1);

      // if polling for changes don't select something that has
      // already been built as a build candidate
      if (isPollCall && data.hasBeenBuilt(sha1)) {
        verbose(listener, "{0} has already been built", sha1);
        return emptyList();
      }

      verbose(listener, "Found a new commit {0} to be built on {1}", sha1, singleBranch);
      Revision revision = new Revision(sha1);
      revision.getBranches().add(new Branch(singleBranch, sha1));
      return Collections.singletonList(revision);
      /*
      // calculate the revisions that are new compared to the last build
      List<Revision> candidateRevs = new ArrayList<Revision>();
      List<ObjectId> allRevs = git.revListAll(); // index 0 contains the newest revision
      if (data != null && allRevs != null) {
          Revision lastBuiltRev = data.getLastBuiltRevision();
          if (lastBuiltRev == null) {
              return Collections.singletonList(objectId2Revision(singleBranch, sha1));
          }
          int indexOfLastBuildRev = allRevs.indexOf(lastBuiltRev.getSha1());
          if (indexOfLastBuildRev == -1) {
              // mhmmm ... can happen when branches are switched.
              return Collections.singletonList(objectId2Revision(singleBranch, sha1));
          }
          List<ObjectId> newRevisionsSinceLastBuild = allRevs.subList(0, indexOfLastBuildRev);
          // translate list of ObjectIds into list of Revisions
          for (ObjectId objectId : newRevisionsSinceLastBuild) {
              candidateRevs.add(objectId2Revision(singleBranch, objectId));
          }
      }
      if (candidateRevs.isEmpty()) {
          return Collections.singletonList(objectId2Revision(singleBranch, sha1));
      }
      return candidateRevs;
      */
    } catch (GitException e) {
      // branch does not exist, there is nothing to build
      verbose(listener, "Failed to rev-parse: {0}", singleBranch);
      return emptyList();
    }
  }
  /**
   * 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;
          }
        });
  }
 private Revision objectId2Revision(String singleBranch, ObjectId sha1) {
   Revision revision = new Revision(sha1);
   revision.getBranches().add(new Branch(singleBranch, sha1));
   return revision;
 }