/** * Look back as far as needed to find a valid BuildData. BuildData may not be recorded if an * exception occurs in the plugin logic. * * @param build * @param clone * @return the last recorded build data */ public BuildData getBuildData(Run build, boolean clone) { BuildData buildData = null; while (build != null) { buildData = build.getAction(BuildData.class); if (buildData != null) break; build = build.getPreviousBuild(); } if (buildData == null) return null; if (clone) return buildData.clone(); else return buildData; }
private List<String> getFilteredTestCandidates(Integer maxAgeInDays, String ancestorCommitSha1) throws Exception { GitSCM gitSCM = new GitSCM("foo"); AncestryBuildChooser chooser = new AncestryBuildChooser(maxAgeInDays, ancestorCommitSha1); gitSCM.getExtensions().add(new BuildChooserSetting(chooser)); assertEquals(maxAgeInDays, chooser.getMaximumAgeInDays()); assertEquals(ancestorCommitSha1, chooser.getAncestorCommitSha1()); // mock necessary objects GitClient git = Mockito.spy(this.testGitClient); Mockito.when(git.getRemoteBranches()).thenReturn(this.testGitClient.getBranches()); BuildData buildData = Mockito.mock(BuildData.class); Mockito.when(buildData.hasBeenBuilt(git.revParse(rootCommit))).thenReturn(false); BuildChooserContext context = Mockito.mock(BuildChooserContext.class); Mockito.when(context.getEnvironment()).thenReturn(new EnvVars()); TaskListener listener = TaskListener.NULL; // get filtered candidates Collection<Revision> candidateRevisions = gitSCM .getBuildChooser() .getCandidateRevisions(true, "**-days-old-branch", git, listener, buildData, context); // transform revision candidates to sha1 strings List<String> candidateSha1s = Lists.newArrayList( Iterables.transform( candidateRevisions, new Function<Revision, String>() { public String apply(Revision rev) { return rev.getSha1String(); } })); return candidateSha1s; }
/** * @param monitor the progress monitor to use for reporting progress to the user. It is the * caller's responsibility to call done() on the given monitor. Accepts null, indicating that * no progress should be reported and that the operation cannot be cancelled. */ protected void doBuild(ToBeBuilt toBeBuilt, IProgressMonitor monitor, BuildType type) throws CoreException { buildLogger.log("Building " + getProject().getName()); // return early if there's nothing to do. // we reuse the isEmpty() impl from BuildData assuming that it doesnT access the ResourceSet // which is still null // and would be expensive to create. boolean indexingOnly = type == BuildType.RECOVERY; if (new BuildData(getProject().getName(), null, toBeBuilt, queuedBuildData, indexingOnly) .isEmpty()) return; SubMonitor progress = SubMonitor.convert(monitor, 2); ResourceSet resourceSet = getResourceSetProvider().get(getProject()); resourceSet .getLoadOptions() .put(ResourceDescriptionsProvider.NAMED_BUILDER_SCOPE, Boolean.TRUE); BuildData buildData = new BuildData( getProject().getName(), resourceSet, toBeBuilt, queuedBuildData, indexingOnly); ImmutableList<Delta> deltas = builderState.update(buildData, progress.newChild(1)); if (participant != null && !indexingOnly) { SourceLevelURICache sourceLevelURIs = buildData.getSourceLevelURICache(); Set<URI> sources = sourceLevelURIs.getSources(); participant.build( new BuildContext(this, resourceSet, deltas, sources, type), progress.newChild(1)); try { getProject().getWorkspace().checkpoint(false); } catch ( NoClassDefFoundError e) { // guard against broken Eclipse installations / bogus project configuration log.error(e.getMessage(), e); } } else { progress.worked(1); } resourceSet.eSetDeliver(false); resourceSet.getResources().clear(); resourceSet.eAdapters().clear(); }
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(); } }
/** * 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; } }); }
@Override public boolean checkout( final AbstractBuild build, Launcher launcher, final FilePath workspace, final BuildListener listener, File changelogFile) throws IOException, InterruptedException { listener .getLogger() .println( "Checkout:" + workspace.getName() + " / " + workspace.getRemote() + " - " + workspace.getChannel()); listener.getLogger().println("Using strategy: " + choosingStrategy); final String projectName = build.getProject().getName(); final int buildNumber = build.getNumber(); final String gitExe = getDescriptor().getGitExe(); final String buildnumber = "hudson-" + projectName + "-" + buildNumber; final BuildData buildData = getBuildData(build.getPreviousBuild(), true); if (buildData != null && buildData.lastBuild != null) { listener.getLogger().println("Last Built Revision: " + buildData.lastBuild.revision); } final EnvVars environment = build.getEnvironment(listener); final String singleBranch = getSingleBranch(build); Revision tempParentLastBuiltRev = null; if (build instanceof MatrixRun) { MatrixBuild parentBuild = ((MatrixRun) build).getParentBuild(); if (parentBuild != null) { BuildData parentBuildData = parentBuild.getAction(BuildData.class); if (parentBuildData != null) { tempParentLastBuiltRev = parentBuildData.getLastBuiltRevision(); } } } final Revision parentLastBuiltRev = tempParentLastBuiltRev; final Revision revToBuild = workspace.act( new FileCallable<Revision>() { private static final long serialVersionUID = 1L; public Revision invoke(File localWorkspace, VirtualChannel channel) throws IOException { FilePath ws = new FilePath(localWorkspace); listener .getLogger() .println( "Checkout:" + ws.getName() + " / " + ws.getRemote() + " - " + ws.getChannel()); IGitAPI git = new GitAPI(gitExe, ws, listener, environment); if (git.hasGitRepo()) { // It's an update listener.getLogger().println("Fetching changes from the remote Git repository"); for (RemoteConfig remoteRepository : getRepositories()) { fetchFrom(git, localWorkspace, listener, remoteRepository); } } else { listener.getLogger().println("Cloning the remote Git repository"); // Go through the repositories, trying to clone from one // boolean successfullyCloned = false; for (RemoteConfig rc : remoteRepositories) { try { git.clone(rc); successfullyCloned = true; break; } catch (GitException ex) { listener.error( "Error cloning remote repo '%s' : %s", rc.getName(), ex.getMessage()); if (ex.getCause() != null) { listener.error("Cause: %s", ex.getCause().getMessage()); } // Failed. Try the next one listener.getLogger().println("Trying next repository"); } } if (!successfullyCloned) { listener.error("Could not clone from a repository"); throw new GitException("Could not clone"); } // Also do a fetch for (RemoteConfig remoteRepository : getRepositories()) { fetchFrom(git, localWorkspace, listener, remoteRepository); } if (git.hasGitModules()) { git.submoduleInit(); git.submoduleUpdate(); } } if (parentLastBuiltRev != null) return parentLastBuiltRev; IBuildChooser buildChooser = createBuildChooser(git, listener, buildData); Collection<Revision> candidates = buildChooser.getCandidateRevisions(false, singleBranch); if (candidates.size() == 0) return null; return candidates.iterator().next(); } }); if (revToBuild == null) { // getBuildCandidates should make the last item the last build, so a re-build // will build the last built thing. listener.error("Nothing to do"); return false; } listener.getLogger().println("Commencing build of " + revToBuild); environment.put(GIT_COMMIT, revToBuild.getSha1String()); Object[] returnData; // Changelog, BuildData if (mergeOptions.doMerge()) { if (!revToBuild.containsBranchName(mergeOptions.getRemoteBranchName())) { returnData = workspace.act( new FileCallable<Object[]>() { private static final long serialVersionUID = 1L; public Object[] invoke(File localWorkspace, VirtualChannel channel) throws IOException { IGitAPI git = new GitAPI(gitExe, new FilePath(localWorkspace), listener, environment); IBuildChooser buildChooser = createBuildChooser(git, listener, buildData); // Do we need to merge this revision onto MergeTarget // Only merge if there's a branch to merge that isn't // us.. listener .getLogger() .println( "Merging " + revToBuild + " onto " + mergeOptions.getMergeTarget()); // checkout origin/blah ObjectId target = git.revParse(mergeOptions.getRemoteBranchName()); git.checkout(target.name()); try { git.merge(revToBuild.getSha1().name()); } catch (Exception ex) { listener .getLogger() .println( "Branch not suitable for integration as it does not merge cleanly"); // We still need to tag something to prevent // repetitive builds from happening - tag the // candidate // branch. git.checkout(revToBuild.getSha1().name()); git.tag(buildnumber, "Hudson Build #" + buildNumber); buildChooser.revisionBuilt(revToBuild, buildNumber, Result.FAILURE); return new Object[] {null, buildChooser.getData()}; } if (git.hasGitModules()) { git.submoduleUpdate(); } // Tag the successful merge git.tag(buildnumber, "Hudson Build #" + buildNumber); StringBuilder changeLog = new StringBuilder(); if (revToBuild.getBranches().size() > 0) listener .getLogger() .println("Warning : There are multiple branch changesets here"); try { for (Branch b : revToBuild.getBranches()) { Build lastRevWas = buildData == null ? null : buildData.getLastBuildOfBranch(b.getName()); if (lastRevWas != null) { changeLog.append( putChangelogDiffsIntoFile( git, b.name, lastRevWas.getSHA1().name(), revToBuild.getSha1().name())); } } } catch (GitException ge) { changeLog.append("Unable to retrieve changeset"); } Build buildData = buildChooser.revisionBuilt(revToBuild, buildNumber, null); GitUtils gu = new GitUtils(listener, git); buildData.mergeRevision = gu.getRevisionForSHA1(target); // Fetch the diffs into the changelog file return new Object[] {changeLog.toString(), buildChooser.getData()}; } }); BuildData returningBuildData = (BuildData) returnData[1]; build.addAction(returningBuildData); return changeLogResult((String) returnData[0], changelogFile); } } // No merge returnData = workspace.act( new FileCallable<Object[]>() { private static final long serialVersionUID = 1L; public Object[] invoke(File localWorkspace, VirtualChannel channel) throws IOException { IGitAPI git = new GitAPI(gitExe, new FilePath(localWorkspace), listener, environment); IBuildChooser buildChooser = createBuildChooser(git, listener, buildData); // Straight compile-the-branch listener.getLogger().println("Checking out " + revToBuild); git.checkout(revToBuild.getSha1().name()); // if( compileSubmoduleCompares ) if (doGenerateSubmoduleConfigurations) { SubmoduleCombinator combinator = new SubmoduleCombinator(git, listener, localWorkspace, submoduleCfg); combinator.createSubmoduleCombinations(); } if (git.hasGitModules()) { git.submoduleInit(); git.submoduleSync(); // Git submodule update will only 'fetch' from where it // regards as 'origin'. However, // it is possible that we are building from a // RemoteRepository with changes // that are not in 'origin' AND it may be a new module that // we've only just discovered. // So - try updating from all RRs, then use the submodule // Update to do the checkout for (RemoteConfig remoteRepository : getRepositories()) { fetchFrom(git, localWorkspace, listener, remoteRepository); } // Update to the correct checkout git.submoduleUpdate(); } // Tag the successful merge git.tag(buildnumber, "Hudson Build #" + buildNumber); StringBuilder changeLog = new StringBuilder(); int histories = 0; try { for (Branch b : revToBuild.getBranches()) { Build lastRevWas = buildData == null ? null : buildData.getLastBuildOfBranch(b.getName()); if (lastRevWas != null) { listener.getLogger().println("Recording changes in branch " + b.getName()); changeLog.append( putChangelogDiffsIntoFile( git, b.name, lastRevWas.getSHA1().name(), revToBuild.getSha1().name())); histories++; } else { listener.getLogger().println("No change to record in branch " + b.getName()); } } } catch (GitException ge) { changeLog.append("Unable to retrieve changeset"); } if (histories > 1) listener .getLogger() .println("Warning : There are multiple branch changesets here"); buildChooser.revisionBuilt(revToBuild, buildNumber, null); if (getClean()) { listener.getLogger().println("Cleaning workspace"); git.clean(); } // Fetch the diffs into the changelog file return new Object[] {changeLog.toString(), buildChooser.getData()}; } }); build.addAction((Action) returnData[1]); return changeLogResult((String) returnData[0], changelogFile); }