public String prettyRevisionInfo(GitClient newgit, Revision r) { List<String> test3 = null; try { test3 = newgit.showRevision(r.getSha1()); } catch (GitException e1) { return ""; } catch (InterruptedException e1) { return ""; } String[] authorDate = test3.get(3).split(">"); String author = authorDate[0].replaceFirst("author ", "").replaceFirst("committer ", "") + ">"; String goodDate = null; try { String totmp = authorDate[1].trim().split("\\+")[0].trim(); long timestamp = Long.parseLong(totmp, 10) * 1000; Date date = new Date(); date.setTime(timestamp); goodDate = new SimpleDateFormat("yyyy:MM:dd HH:mm").format(date); } catch (Exception e) { e.toString(); } return r.getSha1String().substring(0, 8) + " " + goodDate + " " + author; }
public Revision getRevisionForSHA1(ObjectId sha1) throws GitException, IOException { for (Revision revision : getAllBranchRevisions()) { if (revision.getSha1().equals(sha1)) { return revision; } } return null; }
/** * Return the revision containing the branch name. * * @param branchName * @return * @throws IOException * @throws GitException */ public Revision getRevisionContainingBranch(String branchName) throws GitException, IOException { for (Revision revision : getAllBranchRevisions()) { for (Branch b : revision.getBranches()) { if (b.getName().equals(branchName)) { return revision; } } } return null; }
@Override public Revision decorateRevisionToBuild( GitSCM scm, AbstractBuild<?, ?> build, GitClient git, BuildListener listener, Revision rev) throws IOException, InterruptedException { String remoteBranchRef = GitSCM.getParameterString(options.getRef(), build.getEnvironment(listener)); // if the branch we are merging is already at the commit being built, the entire merge becomes // no-op // so there's nothing to do if (rev.containsBranchName(remoteBranchRef)) return rev; // Only merge if there's a branch to merge that isn't us.. listener .getLogger() .println( "Merging " + rev + " onto " + remoteBranchRef + " using " + scm.getUserMergeOptions().getMergeStrategy().toString() + " strategy"); // checkout origin/blah ObjectId target = git.revParse(remoteBranchRef); String paramLocalBranch = scm.getParamLocalBranch(build); git.checkoutBranch(paramLocalBranch, remoteBranchRef); try { MergeCommand cmd = git.merge().setRevisionToMerge(rev.getSha1()); for (GitSCMExtension ext : scm.getExtensions()) ext.decorateMergeCommand(scm, build, git, listener, cmd); cmd.execute(); } catch (GitException ex) { // merge conflict. First, avoid leaving any conflict markers in the working tree // by checking out some known clean state. We don't really mind what commit this is, // since the next build is going to pick its own commit to build, but 'rev' is as good any. git.checkoutBranch(paramLocalBranch, rev.getSha1String()); // record the fact that we've tried building 'rev' and it failed, or else // BuildChooser in future builds will pick up this same 'rev' again and we'll see the exact // same merge failure // all over again. scm.getBuildData(build).saveBuild(new Build(rev, build.getNumber(), FAILURE)); throw new AbortException("Branch not suitable for integration as it does not merge cleanly"); } build.addAction(new MergeRecord(remoteBranchRef, target.getName())); return new GitUtils(listener, git).getRevisionForSHA1(git.revParse(HEAD)); }
/** * Return a list of "Revisions" - where a revision knows about all the branch names that refer to * a SHA1. * * @return * @throws IOException * @throws GitException */ public Collection<Revision> getAllBranchRevisions() throws GitException, IOException { Map<ObjectId, Revision> revisions = new HashMap<ObjectId, Revision>(); List<Branch> branches = git.getRemoteBranches(); for (Branch b : branches) { Revision r = revisions.get(b.getSHA1()); if (r == null) { r = new Revision(b.getSHA1()); revisions.put(b.getSHA1(), r); } r.getBranches().add(b); } return revisions.values(); }
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(); } }
/** * Return a list of 'tip' branches (I.E. branches that aren't included entirely within another * branch). * * @param git * @return */ public Collection<Revision> filterTipBranches(Collection<Revision> revisions) { // If we have 3 branches that we might want to build // ----A--.---.--- B // \-----C // we only want (B) and (C), as (A) is an ancestor (old). List<Revision> l = new ArrayList<Revision>(revisions); OUTER: for (int i = 0; i < l.size(); i++) { for (int j = i + 1; j < l.size(); j++) { Revision ri = l.get(i); Revision rj = l.get(j); ObjectId commonAncestor = git.mergeBase(ri.getSha1(), rj.getSha1()); if (commonAncestor == null) { continue; } if (commonAncestor.equals(ri.getSha1())) { LOGGER.fine("filterTipBranches: " + rj + " subsumes " + ri); l.remove(i); i--; continue OUTER; } if (commonAncestor.equals(rj.getSha1())) { LOGGER.fine("filterTipBranches: " + ri + " subsumes " + rj); l.remove(j); j--; } } } return l; }
/** * 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; }
public Map<String, String> generateContents(AbstractProject<?, ?> project, GitSCM git) throws IOException, InterruptedException { Map<String, String> paramList = new LinkedHashMap<String, String>(); // for (AbstractProject<?,?> project : // Hudson.getInstance().getItems(AbstractProject.class)) { if (project.getSomeWorkspace() == null) { this.errorMessage = "noWorkspace"; } EnvVars environment = null; try { environment = project.getSomeBuildWithWorkspace().getEnvironment(TaskListener.NULL); } catch (Exception e) { } for (RemoteConfig repository : git.getRepositories()) { LOGGER.log( Level.INFO, "generateContents contenttype " + type + " RemoteConfig " + repository.getURIs()); for (URIish remoteURL : repository.getURIs()) { GitClient newgit = git.createClient( TaskListener.NULL, environment, new Run(project) {}, project.getSomeWorkspace()); FilePath wsDir = null; if (project.getSomeBuildWithWorkspace() != null) { wsDir = project.getSomeBuildWithWorkspace().getWorkspace(); if (wsDir == null || !wsDir.exists()) { LOGGER.log( Level.WARNING, "generateContents create wsDir " + wsDir + " for " + remoteURL); wsDir.mkdirs(); if (!wsDir.exists()) { LOGGER.log(Level.SEVERE, "generateContents wsDir.mkdirs() failed."); String errMsg = "!Failed To Create Workspace"; return Collections.singletonMap(errMsg, errMsg); } newgit.init(); newgit.clone(remoteURL.toASCIIString(), "origin", false, null); LOGGER.log(Level.INFO, "generateContents clone done"); } } else { // probably our first build. We cannot yet fill in any // values. LOGGER.log(Level.INFO, "getSomeBuildWithWorkspace is null"); String errMsg = "!No workspace. Please build the project at least once"; return Collections.singletonMap(errMsg, errMsg); } long time = -System.currentTimeMillis(); FetchCommand fetch = newgit.fetch_().from(remoteURL, repository.getFetchRefSpecs()); fetch.execute(); LOGGER.finest("Took " + (time + System.currentTimeMillis()) + "ms to fetch"); if (type.equalsIgnoreCase(PARAMETER_TYPE_REVISION)) { List<ObjectId> oid; if (this.branch != null && !this.branch.isEmpty()) { oid = newgit.revList(this.branch); } else { oid = newgit.revListAll(); } for (ObjectId noid : oid) { Revision r = new Revision(noid); paramList.put(r.getSha1String(), prettyRevisionInfo(newgit, r)); } } if (type.equalsIgnoreCase(PARAMETER_TYPE_TAG) || type.equalsIgnoreCase(PARAMETER_TYPE_TAG_BRANCH)) { Set<String> tagSet = newgit.getTagNames(tagFilter); ArrayList<String> orderedTagNames; if (this.getSortMode().getIsSorting()) { orderedTagNames = sortByName(tagSet); if (this.getSortMode().getIsDescending()) Collections.reverse(orderedTagNames); } else { orderedTagNames = new ArrayList<String>(tagSet); } for (String tagName : orderedTagNames) { paramList.put(tagName, tagName); } } if (type.equalsIgnoreCase(PARAMETER_TYPE_BRANCH) || type.equalsIgnoreCase(PARAMETER_TYPE_TAG_BRANCH)) { time = -System.currentTimeMillis(); Set<String> branchSet = new HashSet<String>(); final boolean wildcard = "*".equals(branchfilter); for (Branch branch : newgit.getRemoteBranches()) { // It'd be nice if we could filter on remote branches via the GitClient, // but that's not an option. final String branchName = branch.getName(); if (wildcard || branchName.matches(branchfilter)) { branchSet.add(branchName); } } LOGGER.finest("Took " + (time + System.currentTimeMillis()) + "ms to fetch branches"); time = -System.currentTimeMillis(); List<String> orderedBranchNames; if (this.getSortMode().getIsSorting()) { orderedBranchNames = sortByName(branchSet); if (this.getSortMode().getIsDescending()) Collections.reverse(orderedBranchNames); } else { orderedBranchNames = new ArrayList<String>(branchSet); } for (String branchName : orderedBranchNames) { paramList.put(branchName, branchName); } LOGGER.finest( "Took " + (time + System.currentTimeMillis()) + "ms to sort and add to param list."); } } break; } return paramList; }
@Override protected void before() throws Throwable { when(data.getLastBuiltRevision()).thenReturn(rev); data.lastBuild = new hudson.plugins.git.util.Build(rev, rev, 0, Result.SUCCESS); when(rev.getSha1()).thenReturn(ObjectId.fromString(SOME_SHA)); }