From bd844821413de7a0d72f45756b52ce45b78da16b Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Mon, 30 Jan 2017 16:47:58 -0800 Subject: [PATCH 01/18] [WIP JENKINS-24141] First work on abstracting out changelogs Need to add tests, but first need to determine if this actually makes sense as I've implemented it. --- .../main/java/hudson/model/AbstractBuild.java | 13 ++- .../java/hudson/model/AbstractProject.java | 60 ---------- core/src/main/java/hudson/model/Job.java | 83 +++++++++++++- core/src/main/java/hudson/model/View.java | 107 ++++++++++-------- .../src/main/java/jenkins/scm/RunWithSCM.java | 47 ++++++++ 5 files changed, 199 insertions(+), 111 deletions(-) create mode 100644 core/src/main/java/jenkins/scm/RunWithSCM.java diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index a096635dfcc7..6a2b75ae30aa 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -30,6 +30,7 @@ import hudson.FilePath; import hudson.Functions; import hudson.Launcher; +import jenkins.scm.RunWithSCM; import jenkins.util.SystemProperties; import hudson.console.ModelHyperlinkNote; import hudson.model.Fingerprint.BuildPtr; @@ -102,7 +103,7 @@ * @author Kohsuke Kawaguchi * @see AbstractProject */ -public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun { +public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCM { /** * Set if we want the blame information to flow from upstream to downstream build. @@ -331,8 +332,9 @@ public FilePath[] getModuleRoots() { * @return * can be empty but never null. */ + @Override @Exported - public Set getCulprits() { + @Nonnull public Set getCulprits() { if (culprits==null) { Set r = new HashSet(); R p = getPreviousCompletedBuild(); @@ -386,6 +388,7 @@ public int size() { * * @since 1.191 */ + @Override public boolean hasParticipant(User user) { for (ChangeLogSet.Entry e : getChangeSet()) try{ @@ -863,7 +866,7 @@ public Collection getBuildFingerprints() { * @return never null. */ @Exported - public ChangeLogSet getChangeSet() { + @Nonnull public ChangeLogSet getChangeSet() { synchronized (changeSetLock) { if (scm==null) { scm = NullChangeLogParser.INSTANCE; @@ -887,8 +890,8 @@ public ChangeLogSet getChangeSet() { return cs; } - @Restricted(DoNotUse.class) // for project-changes.jelly - public List> getChangeSets() { + @Override + @Nonnull public List> getChangeSets() { ChangeLogSet cs = getChangeSet(); return cs.isEmptySet() ? Collections.>emptyList() : Collections.>singletonList(cs); } diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index b3d9413635db..9291f9b0b7c7 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1971,66 +1971,6 @@ public HttpResponse doEnable() throws IOException, ServletException { } - /** - * RSS feed for changes in this project. - */ - public void doRssChangelog( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { - class FeedItem { - ChangeLogSet.Entry e; - int idx; - - public FeedItem(Entry e, int idx) { - this.e = e; - this.idx = idx; - } - - AbstractBuild getBuild() { - return e.getParent().build; - } - } - - List entries = new ArrayList(); - - for(R r=getLastBuild(); r!=null; r=r.getPreviousBuild()) { - int idx=0; - for( ChangeLogSet.Entry e : r.getChangeSet()) - entries.add(new FeedItem(e,idx++)); - } - - RSS.forwardToRss( - getDisplayName()+' '+getScm().getDescriptor().getDisplayName()+" changes", - getUrl()+"changes", - entries, new FeedAdapter() { - public String getEntryTitle(FeedItem item) { - return "#"+item.getBuild().number+' '+item.e.getMsg()+" ("+item.e.getAuthor()+")"; - } - - public String getEntryUrl(FeedItem item) { - return item.getBuild().getUrl()+"changes#detail"+item.idx; - } - - public String getEntryID(FeedItem item) { - return getEntryUrl(item); - } - - public String getEntryDescription(FeedItem item) { - StringBuilder buf = new StringBuilder(); - for(String path : item.e.getAffectedPaths()) - buf.append(path).append('\n'); - return buf.toString(); - } - - public Calendar getEntryTimestamp(FeedItem item) { - return item.getBuild().getTimestamp(); - } - - public String getEntryAuthor(FeedItem entry) { - return JenkinsLocationConfiguration.get().getAdminAddress(); - } - }, - req, rsp ); - } - /** * {@link AbstractProject} subtypes should implement this base class as a descriptor. * diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 88d39ac15fd0..d58ca23fb2e1 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -28,6 +28,7 @@ import hudson.EnvVars; import hudson.Extension; import hudson.ExtensionPoint; +import hudson.FeedAdapter; import hudson.PermalinkList; import hudson.Util; import hudson.cli.declarative.CLIResolver; @@ -36,6 +37,8 @@ import hudson.model.Fingerprint.RangeSet; import hudson.model.PermalinkProjectAction.Permalink; import hudson.model.listeners.ItemListener; +import hudson.scm.ChangeLogSet; +import hudson.scm.SCM; import hudson.search.QuickSilver; import hudson.search.SearchIndex; import hudson.search.SearchIndexBuilder; @@ -83,11 +86,14 @@ import jenkins.model.BuildDiscarderProperty; import jenkins.model.DirectlyModifiableTopLevelItemGroup; import jenkins.model.Jenkins; +import jenkins.model.JenkinsLocationConfiguration; import jenkins.model.ModelObjectWithChildren; import jenkins.model.ProjectNamingStrategy; import jenkins.model.RunIdMigrator; import jenkins.model.lazy.LazyBuildMixIn; +import jenkins.scm.RunWithSCM; import jenkins.security.HexStringConfidentialKey; +import jenkins.triggers.SCMTriggerItem; import jenkins.util.io.OnMaster; import net.sf.json.JSONException; import net.sf.json.JSONObject; @@ -1056,7 +1062,82 @@ public PermalinkList getPermalinks() { } return permalinks; } - + + /** + * RSS feed for changes in this project. + */ + public void doRssChangelog(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { + class FeedItem { + ChangeLogSet.Entry e; + int idx; + + public FeedItem(ChangeLogSet.Entry e, int idx) { + this.e = e; + this.idx = idx; + } + + Run getBuild() { + return e.getParent().build; + } + } + + List entries = new ArrayList(); + String scmDisplayName = ""; + if (this instanceof SCMTriggerItem) { + SCMTriggerItem scmItem = (SCMTriggerItem) this; + List scmNames = new ArrayList<>(); + for (SCM s : scmItem.getSCMs()) { + scmNames.add(s.getDescriptor().getDisplayName()); + } + scmDisplayName = " " + Util.join(scmNames, ", "); + + for (RunT r = getLastBuild(); r != null; r = r.getPreviousBuild()) { + int idx = 0; + if (r instanceof RunWithSCM) { + for (ChangeLogSet c : ((RunWithSCM) r).getChangeSets()) { + for (ChangeLogSet.Entry e : c) { + entries.add(new FeedItem(e, idx++)); + } + } + } + } + } + RSS.forwardToRss( + getDisplayName() + scmDisplayName + " changes", + getUrl() + "changes", + entries, new FeedAdapter() { + public String getEntryTitle(FeedItem item) { + return "#" + item.getBuild().number + ' ' + item.e.getMsg() + " (" + item.e.getAuthor() + ")"; + } + + public String getEntryUrl(FeedItem item) { + return item.getBuild().getUrl() + "changes#detail" + item.idx; + } + + public String getEntryID(FeedItem item) { + return getEntryUrl(item); + } + + public String getEntryDescription(FeedItem item) { + StringBuilder buf = new StringBuilder(); + for (String path : item.e.getAffectedPaths()) + buf.append(path).append('\n'); + return buf.toString(); + } + + public Calendar getEntryTimestamp(FeedItem item) { + return item.getBuild().getTimestamp(); + } + + public String getEntryAuthor(FeedItem entry) { + return JenkinsLocationConfiguration.get().getAdminAddress(); + } + }, + req, rsp); + } + + + @Override public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception { // not sure what would be really useful here. This needs more thoughts. // for the time being, I'm starting with permalinks diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index ea3d9117002c..68dae32760ac 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -62,6 +62,8 @@ import jenkins.model.item_category.Categories; import jenkins.model.item_category.Category; import jenkins.model.item_category.ItemCategory; +import jenkins.scm.RunWithSCM; +import jenkins.triggers.SCMTriggerItem; import jenkins.util.ProgressiveRendering; import jenkins.util.xml.XMLUtils; @@ -615,12 +617,12 @@ public static final class UserInfo implements Comparable { /** * Which project did this user commit? Can be null. */ - private AbstractProject project; + private Job project; /** @see UserAvatarResolver */ String avatar; - UserInfo(User user, AbstractProject p, Calendar lastChange) { + UserInfo(User user, Job p, Calendar lastChange) { this.user = user; this.project = p; this.lastChange = lastChange; @@ -637,7 +639,7 @@ public Calendar getLastChange() { } @Exported - public AbstractProject getProject() { + public Job getProject() { return project; } @@ -720,20 +722,25 @@ public People(View parent) { private Map getUserInfo(Collection items) { Map users = new HashMap(); for (Item item : items) { - for (Job job : item.getAllJobs()) { - if (job instanceof AbstractProject) { - AbstractProject p = (AbstractProject) job; - for (AbstractBuild build : p.getBuilds()) { - for (Entry entry : build.getChangeSet()) { - User user = entry.getAuthor(); - - UserInfo info = users.get(user); - if(info==null) - users.put(user,new UserInfo(user,p,build.getTimestamp())); - else - if(info.getLastChange().before(build.getTimestamp())) { - info.project = p; - info.lastChange = build.getTimestamp(); + for (Job job : item.getAllJobs()) { + if (job instanceof SCMTriggerItem) { + RunList> runs = job.getBuilds(); + for (Run r : runs) { + if (r instanceof RunWithSCM) { + RunWithSCM runWithSCM = (RunWithSCM) r; + + for (ChangeLogSet c: runWithSCM.getChangeSets()) { + for (Entry entry : c) { + User user = entry.getAuthor(); + + UserInfo info = users.get(user); + if (info == null) + users.put(user, new UserInfo(user, job, runWithSCM.getTimestamp())); + else if (info.getLastChange().before(runWithSCM.getTimestamp())) { + info.project = job; + info.lastChange = runWithSCM.getTimestamp(); + } + } } } } @@ -761,13 +768,19 @@ public Api getApi() { public static boolean isApplicable(Collection items) { for (Item item : items) { for (Job job : item.getAllJobs()) { - if (job instanceof AbstractProject) { - AbstractProject p = (AbstractProject) job; - for (AbstractBuild build : p.getBuilds()) { - for (Entry entry : build.getChangeSet()) { - User user = entry.getAuthor(); - if(user!=null) - return true; + if (job instanceof SCMTriggerItem) { + RunList> runs = job.getBuilds(); + + for (Run r : runs) { + if (r instanceof RunWithSCM) { + RunWithSCM runWithSCM = (RunWithSCM) r; + for (ChangeLogSet c : runWithSCM.getChangeSets()) { + for (Entry entry : c) { + User user = entry.getAuthor(); + if (user != null) + return true; + } + } } } } @@ -813,29 +826,33 @@ public AsynchPeople(View parent) { int itemCount = 0; for (Item item : items) { for (Job job : item.getAllJobs()) { - if (job instanceof AbstractProject) { - AbstractProject p = (AbstractProject) job; - RunList> builds = p.getBuilds(); + if (job instanceof SCMTriggerItem) { + RunList> runs = job.getBuilds(); int buildCount = 0; - for (AbstractBuild build : builds) { + for (Run r : runs) { if (canceled()) { return; } - for (ChangeLogSet.Entry entry : build.getChangeSet()) { - User user = entry.getAuthor(); - UserInfo info = users.get(user); - if (info == null) { - UserInfo userInfo = new UserInfo(user, p, build.getTimestamp()); - userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize); - synchronized (this) { - users.put(user, userInfo); - modified.add(user); - } - } else if (info.getLastChange().before(build.getTimestamp())) { - synchronized (this) { - info.project = p; - info.lastChange = build.getTimestamp(); - modified.add(user); + if (r instanceof RunWithSCM) { + RunWithSCM runWithSCM = (RunWithSCM) r; + for (ChangeLogSet c : runWithSCM.getChangeSets()) { + for (ChangeLogSet.Entry entry : c) { + User user = entry.getAuthor(); + UserInfo info = users.get(user); + if (info == null) { + UserInfo userInfo = new UserInfo(user, job, runWithSCM.getTimestamp()); + userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize); + synchronized (this) { + users.put(user, userInfo); + modified.add(user); + } + } else if (info.getLastChange().before(runWithSCM.getTimestamp())) { + synchronized (this) { + info.project = job; + info.lastChange = runWithSCM.getTimestamp(); + modified.add(user); + } + } } } } @@ -843,7 +860,7 @@ public AsynchPeople(View parent) { buildCount++; // TODO this defeats lazy-loading. Should rather do a breadth-first search, as in hudson.plugins.view.dashboard.builds.LatestBuilds // (though currently there is no quick implementation of RunMap.size() ~ idOnDisk.size(), which would be needed for proper progress) - progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1)); + progress((itemCount + 1.0 * buildCount / runs.size()) / (items.size() + 1)); } } } @@ -884,7 +901,7 @@ public AsynchPeople(View parent) { accumulate("avatar", i.avatar != null ? i.avatar : Stapler.getCurrentRequest().getContextPath() + Functions.getResourcePath() + "/images/" + iconSize + "/user.png"). accumulate("timeSortKey", i.getTimeSortKey()). accumulate("lastChangeTimeString", i.getLastChangeTimeString()); - AbstractProject p = i.getProject(); + Job p = i.getProject(); if (p != null) { entry.accumulate("projectUrl", p.getUrl()).accumulate("projectFullDisplayName", p.getFullDisplayName()); } diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java new file mode 100644 index 000000000000..2e76c7b8dec6 --- /dev/null +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * Copyright 2014 Jesse Glick. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package jenkins.scm; + +import hudson.model.User; +import hudson.scm.ChangeLogSet; + +import javax.annotation.Nonnull; +import java.util.Calendar; +import java.util.List; +import java.util.Set; + +/** + * @since 2.xxx + */ +public interface RunWithSCM { + + @Nonnull List> getChangeSets(); + + @Nonnull Set getCulprits(); + + @Nonnull Calendar getTimestamp(); + + boolean hasParticipant(User user); +} From 61acefc1dd0166403881487bc953e893b0a1dc19 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 31 Jan 2017 09:51:12 -0800 Subject: [PATCH 02/18] Switch to a mixin approach Also bumping to a distinct SNAPSHOT version for dependency builds. --- cli/pom.xml | 2 +- core/pom.xml | 2 +- .../main/java/hudson/model/AbstractBuild.java | 83 +++------ core/src/main/java/hudson/model/Job.java | 7 +- core/src/main/java/hudson/model/View.java | 84 +++++---- .../src/main/java/jenkins/scm/RunWithSCM.java | 47 ----- .../java/jenkins/scm/RunWithSCMMixIn.java | 161 ++++++++++++++++++ pom.xml | 2 +- test/pom.xml | 2 +- war/pom.xml | 2 +- 10 files changed, 242 insertions(+), 150 deletions(-) delete mode 100644 core/src/main/java/jenkins/scm/RunWithSCM.java create mode 100644 core/src/main/java/jenkins/scm/RunWithSCMMixIn.java diff --git a/cli/pom.xml b/cli/pom.xml index aa26f4a66fa9..16ecf94e1322 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.44-SNAPSHOT + 2.44-JENKINS-24141-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index d2577beff66e..b87b4c6dcffa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.44-SNAPSHOT + 2.44-JENKINS-24141-SNAPSHOT jenkins-core diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 6a2b75ae30aa..80e2bb3a9def 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -30,7 +30,8 @@ import hudson.FilePath; import hudson.Functions; import hudson.Launcher; -import jenkins.scm.RunWithSCM; +import jenkins.model.ParameterizedJobMixIn; +import jenkins.scm.RunWithSCMMixIn; import jenkins.util.SystemProperties; import hudson.console.ModelHyperlinkNote; import hudson.model.Fingerprint.BuildPtr; @@ -92,8 +93,6 @@ import jenkins.model.lazy.BuildReference; import jenkins.model.lazy.LazyBuildMixIn; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.DoNotUse; /** * Base implementation of {@link Run}s that build software. @@ -103,7 +102,7 @@ * @author Kohsuke Kawaguchi * @see AbstractProject */ -public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCM { +public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCMMixIn.RunWithSCM { /** * Set if we want the blame information to flow from upstream to downstream build. @@ -184,6 +183,23 @@ public final P getProject() { return runMixIn; } + @Override + public RunWithSCMMixIn getRunWithSCMMixIn() { + return new RunWithSCMMixIn() { + @SuppressWarnings("unchecked") // untypable + @Override protected R asRun() { + return (R) AbstractBuild.this; + } + + @Override + @Nonnull public List> getChangeSets() { + ChangeLogSet cs = getChangeSet(); + return cs.isEmptySet() ? Collections.>emptyList() : Collections.>singletonList(cs); + } + + }; + } + @Override protected final BuildReference createReference() { return getRunMixIn().createReference(); } @@ -335,52 +351,12 @@ public FilePath[] getModuleRoots() { @Override @Exported @Nonnull public Set getCulprits() { - if (culprits==null) { - Set r = new HashSet(); - R p = getPreviousCompletedBuild(); - if (p !=null && isBuilding()) { - Result pr = p.getResult(); - if (pr!=null && pr.isWorseThan(Result.SUCCESS)) { - // we are still building, so this is just the current latest information, - // but we seems to be failing so far, so inherit culprits from the previous build. - // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record - // this information - r.addAll(p.getCulprits()); - } - } - for (Entry e : getChangeSet()) - r.add(e.getAuthor()); - - if (upstreamCulprits) { - // If we have dependencies since the last successful build, add their authors to our list - if (getPreviousNotFailedBuild() != null) { - Map depmap = getDependencyChanges(getPreviousSuccessfulBuild()); - for (DependencyChange dep : depmap.values()) { - for (AbstractBuild b : dep.getBuilds()) { - for (Entry entry : b.getChangeSet()) { - r.add(entry.getAuthor()); - } - } - } - } - } - - return r; - } - - return new AbstractSet() { - public Iterator iterator() { - return new AdaptedIterator(culprits.iterator()) { - protected User adapt(String id) { - return User.get(id); - } - }; - } + return getRunWithSCMMixIn().getCulprits(); + } - public int size() { - return culprits.size(); - } - }; + @Override + @CheckForNull public Set getCulpritIds() { + return culprits; } /** @@ -390,14 +366,7 @@ public int size() { */ @Override public boolean hasParticipant(User user) { - for (ChangeLogSet.Entry e : getChangeSet()) - try{ - if (e.getAuthor()==user) - return true; - } catch (RuntimeException re) { - LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + getParent().getDisplayName() + ", " + getDisplayName(), re); - } - return false; + return getRunWithSCMMixIn().hasParticipant(user); } /** diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index d58ca23fb2e1..09b182b3bfde 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -91,10 +91,9 @@ import jenkins.model.ProjectNamingStrategy; import jenkins.model.RunIdMigrator; import jenkins.model.lazy.LazyBuildMixIn; -import jenkins.scm.RunWithSCM; +import jenkins.scm.RunWithSCMMixIn; import jenkins.security.HexStringConfidentialKey; import jenkins.triggers.SCMTriggerItem; -import jenkins.util.io.OnMaster; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; @@ -1093,8 +1092,8 @@ public FeedItem(ChangeLogSet.Entry e, int idx) { for (RunT r = getLastBuild(); r != null; r = r.getPreviousBuild()) { int idx = 0; - if (r instanceof RunWithSCM) { - for (ChangeLogSet c : ((RunWithSCM) r).getChangeSets()) { + if (r instanceof RunWithSCMMixIn.RunWithSCM) { + for (ChangeLogSet c : ((RunWithSCMMixIn.RunWithSCM) r).getChangeSets()) { for (ChangeLogSet.Entry e : c) { entries.add(new FeedItem(e, idx++)); } diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 68dae32760ac..49f0648e793f 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -59,10 +59,11 @@ import javax.annotation.Nonnull; import jenkins.model.Jenkins; import jenkins.model.ModelObjectWithChildren; +import jenkins.model.ModelObjectWithContextMenu; import jenkins.model.item_category.Categories; import jenkins.model.item_category.Category; import jenkins.model.item_category.ItemCategory; -import jenkins.scm.RunWithSCM; +import jenkins.scm.RunWithSCMMixIn; import jenkins.triggers.SCMTriggerItem; import jenkins.util.ProgressiveRendering; import jenkins.util.xml.XMLUtils; @@ -116,7 +117,9 @@ import java.util.logging.Logger; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static jenkins.model.Jenkins.*; +import static jenkins.model.Jenkins.checkGoodName; +import static jenkins.scm.RunWithSCMMixIn.*; + import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.QueryParameter; @@ -463,7 +466,7 @@ public List getComputers() { private boolean isRelevant(Collection

+ * This list at least always include people who made changes in this build, but + * if the previous build was a failure it also includes the culprit list from there. + * + * @return + * can be empty but never null. + */ + @Exported + @Nonnull public Set getCulprits() { + if (asRun().getCulpritIds() == null) { + Set r = new HashSet(); + RunT p = asRun().getPreviousCompletedBuild(); + if (p != null && asRun().isBuilding()) { + Result pr = p.getResult(); + if (pr != null && pr.isWorseThan(Result.SUCCESS)) { + // we are still building, so this is just the current latest information, + // but we seems to be failing so far, so inherit culprits from the previous build. + // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record + // this information + r.addAll(p.getCulprits()); + } + } + for (ChangeLogSet c : getChangeSets()) { + for (ChangeLogSet.Entry e : c) + r.add(e.getAuthor()); + } + + if (p instanceof AbstractBuild && upstreamCulprits) { + // If we have dependencies since the last successful build, add their authors to our list + if (p.getPreviousNotFailedBuild() != null) { + Map depmap = + ((AbstractBuild) p).getDependencyChanges((AbstractBuild)p.getPreviousSuccessfulBuild()); + for (AbstractBuild.DependencyChange dep : depmap.values()) { + for (AbstractBuild b : dep.getBuilds()) { + for (ChangeLogSet.Entry entry : b.getChangeSet()) { + r.add(entry.getAuthor()); + } + } + } + } + } + + return r; + } + + return new AbstractSet() { + public Iterator iterator() { + return new AdaptedIterator(asRun().getCulpritIds().iterator()) { + protected User adapt(String id) { + return User.get(id); + } + }; + } + + public int size() { + return asRun().getCulpritIds().size(); + } + }; + } + + /** + * Returns true if this user has made a commit to this build. + */ + public boolean hasParticipant(User user) { + for (ChangeLogSet c : getChangeSets()) { + for (ChangeLogSet.Entry e : c) + try { + if (e.getAuthor() == user) + return true; + } catch (RuntimeException re) { + LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + asRun().getParent().getDisplayName() + ", " + asRun().getDisplayName(), re); + } + } + return false; + } + + public interface RunWithSCM & Queue.Task, + RunT extends Run & RunWithSCM & Queue.Executable> { + @Nonnull + List> getChangeSets(); + + @Nonnull + Set getCulprits(); + + @CheckForNull + Set getCulpritIds(); + + RunWithSCMMixIn getRunWithSCMMixIn(); + + boolean hasParticipant(User user); + } + + private static final Logger LOGGER = Logger.getLogger(RunWithSCMMixIn.class.getName()); +} diff --git a/pom.xml b/pom.xml index b230d350e41d..5f2291e4ecbe 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.44-SNAPSHOT + 2.44-JENKINS-24141-SNAPSHOT pom Jenkins main module diff --git a/test/pom.xml b/test/pom.xml index 65f2cc6a7dcd..6087607a09f2 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.44-SNAPSHOT + 2.44-JENKINS-24141-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 282991fc1011..d4e169b7b1f8 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.44-SNAPSHOT + 2.44-JENKINS-24141-SNAPSHOT jenkins-war From d7d4a8244bde232473b79cf5a8ea453392bd3aa2 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 31 Jan 2017 10:01:34 -0800 Subject: [PATCH 03/18] Minor cleanup, fixing AbstractBuild.getChangeSets() --- core/src/main/java/hudson/model/AbstractBuild.java | 5 +---- core/src/main/java/hudson/model/AbstractProject.java | 5 ----- core/src/main/java/hudson/model/View.java | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 80e2bb3a9def..ce07ab0fdac1 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -30,7 +30,6 @@ import hudson.FilePath; import hudson.Functions; import hudson.Launcher; -import jenkins.model.ParameterizedJobMixIn; import jenkins.scm.RunWithSCMMixIn; import jenkins.util.SystemProperties; import hudson.console.ModelHyperlinkNote; @@ -72,7 +71,6 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.lang.ref.WeakReference; -import java.util.AbstractSet; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -861,8 +859,7 @@ public Collection getBuildFingerprints() { @Override @Nonnull public List> getChangeSets() { - ChangeLogSet cs = getChangeSet(); - return cs.isEmptySet() ? Collections.>emptyList() : Collections.>singletonList(cs); + return getRunWithSCMMixIn().getChangeSets(); } /** diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 9291f9b0b7c7..5a1dc16370ca 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -34,7 +34,6 @@ import hudson.EnvVars; import hudson.ExtensionList; import hudson.ExtensionPoint; -import hudson.FeedAdapter; import hudson.FilePath; import hudson.Functions; import hudson.Launcher; @@ -55,8 +54,6 @@ import hudson.model.queue.QueueTaskFuture; import hudson.model.queue.SubTask; import hudson.model.queue.SubTaskContributor; -import hudson.scm.ChangeLogSet; -import hudson.scm.ChangeLogSet.Entry; import hudson.scm.NullSCM; import hudson.scm.PollingResult; @@ -87,7 +84,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -107,7 +103,6 @@ import javax.servlet.ServletException; import jenkins.model.BlockedBecauseOfBuildInProgress; import jenkins.model.Jenkins; -import jenkins.model.JenkinsLocationConfiguration; import jenkins.model.ParameterizedJobMixIn; import jenkins.model.Uptime; import jenkins.model.lazy.LazyBuildMixIn; diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 49f0648e793f..f8fa9d91a39a 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -63,7 +63,6 @@ import jenkins.model.item_category.Categories; import jenkins.model.item_category.Category; import jenkins.model.item_category.ItemCategory; -import jenkins.scm.RunWithSCMMixIn; import jenkins.triggers.SCMTriggerItem; import jenkins.util.ProgressiveRendering; import jenkins.util.xml.XMLUtils; From 8ff1dd638b227dc27d8c7c1eb99298562b4c3721 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 25 Apr 2017 11:27:39 -0400 Subject: [PATCH 04/18] Add SCM.buildEnvVars(Run,Map) --- core/src/main/java/hudson/scm/SCM.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index c53f5f7449c2..9c9a85699bff 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -522,13 +522,22 @@ public void postCheckout(AbstractBuild build, Launcher launcher, FilePath w * (for example, SVN revision number.) * *

- * This method is invoked whenever someone does {@link AbstractBuild#getEnvironment(TaskListener)}, which + * This method is invoked whenever someone does {@link Run#getEnvironment(TaskListener)}, which * can be before/after your checkout method is invoked. So if you are going to provide information about * check out (like SVN revision number that was checked out), be prepared for the possibility that the * check out hasn't happened yet. */ - // TODO is an equivalent for Run needed? + public void buildEnvVars(@Nonnull Run build, @Nonnull Map env) { + if (build instanceof AbstractBuild) { + buildEnvVars((AbstractBuild)build, env); + } + } + + @Deprecated public void buildEnvVars(AbstractBuild build, Map env) { + if (Util.isOverridden(SCM.class, getClass(), "buildEnvVars", Run.class, Map.class)) { + buildEnvVars((Run) build, env); + } // default implementation is noop. } From 7fe3a4a3b957cf5517e634cdd25b1c5646334689 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Fri, 28 Apr 2017 16:30:09 -0400 Subject: [PATCH 05/18] Initial work on DependencyGraph --- .../main/java/hudson/DependencyRunner.java | 28 +- .../main/java/hudson/model/AbstractBuild.java | 251 +----------------- .../java/hudson/model/AbstractProject.java | 160 ++--------- .../java/hudson/model/DependencyGraph.java | 111 ++++---- core/src/main/java/hudson/model/Job.java | 146 +++++++++- core/src/main/java/hudson/model/Run.java | 251 ++++++++++++++++++ .../main/java/hudson/tasks/BuildTrigger.java | 69 ++--- .../main/java/hudson/tasks/Fingerprinter.java | 21 +- .../jenkins/model/DependencyDeclarer.java | 5 +- .../java/jenkins/scm/RunWithSCMMixIn.java | 17 +- .../jenkins/triggers/ReverseBuildTrigger.java | 6 +- .../hudson/model/DependencyGraphTest.java | 10 +- .../java/hudson/tasks/BuildTriggerTest.java | 12 +- .../java/hudson/tasks/FingerprinterTest.java | 28 +- 14 files changed, 581 insertions(+), 534 deletions(-) diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java index 03efea55e403..92a567866f89 100644 --- a/core/src/main/java/hudson/DependencyRunner.java +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -25,6 +25,7 @@ package hudson; import hudson.model.AbstractProject; +import hudson.model.Job; import jenkins.model.Jenkins; import hudson.security.ACL; @@ -76,20 +77,23 @@ public void run() { } } - private void populate(Collection projectList) { - for (AbstractProject p : projectList) { - if (polledProjects.contains(p)) { - // Project will be readded at the queue, so that we always use - // the longest path - LOGGER.fine("removing project " + p.getName() + " for re-add"); - polledProjects.remove(p); - } + private void populate(Collection projectList) { + for (Job j : projectList) { + if (j instanceof AbstractProject) { + AbstractProject p = (AbstractProject)j; + if (polledProjects.contains(p)) { + // Project will be readded at the queue, so that we always use + // the longest path + LOGGER.fine("removing project " + p.getName() + " for re-add"); + polledProjects.remove(p); + } - LOGGER.fine("adding project " + p.getName()); - polledProjects.add(p); + LOGGER.fine("adding project " + p.getName()); + polledProjects.add(p); - // Add all downstream dependencies - populate(p.getDownstreamProjects()); + // Add all downstream dependencies + populate(p.getDownstreamProjects()); + } } } diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 627a154368a4..ec50e1742662 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -102,11 +102,6 @@ */ public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCMMixIn.RunWithSCM { - /** - * Set if we want the blame information to flow from upstream to downstream build. - */ - private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); - /** * Name of the agent this project was built on. * Null or "" if built by the master. (null happens when we read old record that didn't have this information.) @@ -249,24 +244,6 @@ protected void setBuiltOnStr( String builtOn ) { this.builtOn = builtOn; } - /** - * Gets the nearest ancestor {@link AbstractBuild} that belongs to - * {@linkplain AbstractProject#getRootProject() the root project of getProject()} that - * dominates/governs/encompasses this build. - * - *

- * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs, - * and still in some of them, builds of child projects are related/tied to that of the parent project. - * In such a case, this method returns the governing build. - * - * @return never null. In the worst case the build dominates itself. - * @since 1.421 - * @see AbstractProject#getRootProject() - */ - public AbstractBuild getRootBuild() { - return this; - } - /** * Used to render the side panel "Back to project" link. * @@ -1061,10 +1038,10 @@ public String getWhyKeepLog() { // if any of the downstream project is configured with 'keep dependency component', // we need to keep this log OUTER: - for (AbstractProject p : getParent().getDownstreamProjects()) { + for (Job p : getParent().getDownstreamProjects()) { if (!p.isKeepDependencies()) continue; - AbstractBuild fb = p.getFirstBuild(); + Run fb = p.getFirstBuild(); if (fb==null) continue; // no active record // is there any active build that depends on us? @@ -1075,7 +1052,7 @@ public String getWhyKeepLog() { if (i b = p.getBuildByNumber(i); + Run b = p.getBuildByNumber(i); if (b!=null) return Messages.AbstractBuild_KeptBecause(b); } @@ -1085,225 +1062,11 @@ public String getWhyKeepLog() { } /** - * Gets the dependency relationship from this build (as the source) - * and that project (as the sink.) - * - * @return - * range of build numbers that represent which downstream builds are using this build. - * The range will be empty if no build of that project matches this (or there is no {@link FingerprintAction}), but it'll never be null. - */ - public RangeSet getDownstreamRelationship(AbstractProject that) { - RangeSet rs = new RangeSet(); - - FingerprintAction f = getAction(FingerprintAction.class); - if (f==null) return rs; - - // look for fingerprints that point to this build as the source, and merge them all - for (Fingerprint e : f.getFingerprints().values()) { - - if (upstreamCulprits) { - // With upstreamCulprits, we allow downstream relationships - // from intermediate jobs - rs.add(e.getRangeSet(that)); - } else { - BuildPtr o = e.getOriginal(); - if (o!=null && o.is(this)) - rs.add(e.getRangeSet(that)); - } - } - - return rs; - } - - /** - * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns - * the actual build objects, in ascending order. - * @since 1.150 - */ - public Iterable> getDownstreamBuilds(final AbstractProject that) { - final Iterable nums = getDownstreamRelationship(that).listNumbers(); - - return new Iterable>() { - public Iterator> iterator() { - return Iterators.removeNull( - new AdaptedIterator>(nums) { - protected AbstractBuild adapt(Integer item) { - return that.getBuildByNumber(item); - } - }); - } - }; - } - - /** - * Gets the dependency relationship from this build (as the sink) - * and that project (as the source.) - * - * @return - * Build number of the upstream build that feed into this build, - * or -1 if no record is available (for example if there is no {@link FingerprintAction}, even if there is an {@link Cause.UpstreamCause}). - */ - public int getUpstreamRelationship(AbstractProject that) { - FingerprintAction f = getAction(FingerprintAction.class); - if (f==null) return -1; - - int n = -1; - - // look for fingerprints that point to the given project as the source, and merge them all - for (Fingerprint e : f.getFingerprints().values()) { - if (upstreamCulprits) { - // With upstreamCulprits, we allow upstream relationships - // from intermediate jobs - Fingerprint.RangeSet rangeset = e.getRangeSet(that); - if (!rangeset.isEmpty()) { - n = Math.max(n, rangeset.listNumbersReverse().iterator().next()); - } - } else { - BuildPtr o = e.getOriginal(); - if (o!=null && o.belongsTo(that)) - n = Math.max(n,o.getNumber()); - } - } - - return n; - } - - /** - * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the - * actual build object. - * - * @return - * null if no such upstream build was found, or it was found but the - * build record is already lost. - */ - public AbstractBuild getUpstreamRelationshipBuild(AbstractProject that) { - int n = getUpstreamRelationship(that); - if (n==-1) return null; - return that.getBuildByNumber(n); - } - - /** - * Gets the downstream builds of this build, which are the builds of the - * downstream projects that use artifacts of this build. - * - * @return - * For each project with fingerprinting enabled, returns the range - * of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.) - */ - public Map getDownstreamBuilds() { - Map r = new HashMap(); - for (AbstractProject p : getParent().getDownstreamProjects()) { - if (p.isFingerprintConfigured()) - r.put(p,getDownstreamRelationship(p)); - } - return r; - } - - /** - * Gets the upstream builds of this build, which are the builds of the - * upstream projects whose artifacts feed into this build. - * @return empty if there is no {@link FingerprintAction} (even if there is an {@link Cause.UpstreamCause}) - * @see #getTransitiveUpstreamBuilds() - */ - public Map getUpstreamBuilds() { - return _getUpstreamBuilds(getParent().getUpstreamProjects()); - } - - /** - * Works like {@link #getUpstreamBuilds()} but also includes all the transitive - * dependencies as well. + * Retained for compatibility. */ - public Map getTransitiveUpstreamBuilds() { - return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects()); - } - - private Map _getUpstreamBuilds(Collection projects) { - Map r = new HashMap(); - for (AbstractProject p : projects) { - int n = getUpstreamRelationship(p); - if (n>=0) - r.put(p,n); - } - return r; - } - - /** - * Gets the changes in the dependency between the given build and this build. - * @return empty if there is no {@link FingerprintAction} - */ - public Map getDependencyChanges(AbstractBuild from) { - if (from==null) return Collections.emptyMap(); // make it easy to call this from views - FingerprintAction n = this.getAction(FingerprintAction.class); - FingerprintAction o = from.getAction(FingerprintAction.class); - if (n==null || o==null) return Collections.emptyMap(); - - Map ndep = n.getDependencies(true); - Map odep = o.getDependencies(true); - - Map r = new HashMap(); - - for (Map.Entry entry : odep.entrySet()) { - AbstractProject p = entry.getKey(); - Integer oldNumber = entry.getValue(); - Integer newNumber = ndep.get(p); - if (newNumber!=null && oldNumber.compareTo(newNumber)<0) { - r.put(p,new DependencyChange(p,oldNumber,newNumber)); - } - } - - return r; - } - - /** - * Represents a change in the dependency. - */ - public static final class DependencyChange { - /** - * The dependency project. - */ - public final AbstractProject project; - /** - * Version of the dependency project used in the previous build. - */ - public final int fromId; - /** - * {@link Build} object for {@link #fromId}. Can be null if the log is gone. - */ - public final AbstractBuild from; - /** - * Version of the dependency project used in this build. - */ - public final int toId; - - public final AbstractBuild to; - - public DependencyChange(AbstractProject project, int fromId, int toId) { - this.project = project; - this.fromId = fromId; - this.toId = toId; - this.from = project.getBuildByNumber(fromId); - this.to = project.getBuildByNumber(toId); - } - - /** - * Gets the {@link AbstractBuild} objects (fromId,toId]. - *

- * This method returns all such available builds in the ascending order - * of IDs, but due to log rotations, some builds may be already unavailable. - */ - public List getBuilds() { - List r = new ArrayList(); - - AbstractBuild b = project.getNearestBuild(fromId); - if (b!=null && b.getNumber()==fromId) - b = b.getNextBuild(); // fromId exclusive - - while (b!=null && b.getNumber()<=toId) { - r.add(b); - b = b.getNextBuild(); - } - - return r; + public static final class DependencyChange extends Run.DependencyChange { + public DependencyChange(Job project, int fromId, int toId) { + super(project, fromId, toId); } } diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index f6b3ed19690c..84f93ff40411 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -471,28 +471,6 @@ public String getBuildNowText() { return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, getParameterizedJobMixIn().getBuildNowText()); } - /** - * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}. - * - *

- * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs - * that acts as a single unit. This method can be used to find the top most dominating job that - * covers such a tree. - * - * @return never null. - * @see AbstractBuild#getRootBuild() - */ - public AbstractProject getRootProject() { - if (this instanceof TopLevelItem) { - return this; - } else { - ItemGroup p = this.getParent(); - if (p instanceof AbstractProject) - return ((AbstractProject) p).getRootProject(); - return this; - } - } - /** * Gets the directory where the module is checked out. * @@ -1111,9 +1089,9 @@ public BecauseOfBuildInProgress(@Nonnull AbstractBuild build) { * Because the downstream build is in progress, and we are configured to wait for that. */ public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage { - public final AbstractProject up; + public final Job up; - public BecauseOfDownstreamBuildInProgress(AbstractProject up) { + public BecauseOfDownstreamBuildInProgress(Job up) { this.up = up; } @@ -1127,9 +1105,9 @@ public String getShortDescription() { * Because the upstream build is in progress, and we are configured to wait for that. */ public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage { - public final AbstractProject up; + public final Job up; - public BecauseOfUpstreamBuildInProgress(AbstractProject up) { + public BecauseOfUpstreamBuildInProgress(Job up) { this.up = up; } @@ -1154,52 +1132,18 @@ public CauseOfBlockage getCauseOfBlockage() { } } if (blockBuildWhenDownstreamBuilding()) { - AbstractProject bup = getBuildingDownstream(); + Job bup = getBuildingDownstream(); if (bup!=null) return new BecauseOfDownstreamBuildInProgress(bup); } if (blockBuildWhenUpstreamBuilding()) { - AbstractProject bup = getBuildingUpstream(); + Job bup = getBuildingUpstream(); if (bup!=null) return new BecauseOfUpstreamBuildInProgress(bup); } return null; } - /** - * Returns the project if any of the downstream project is either - * building, waiting, pending or buildable. - *

- * This means eventually there will be an automatic triggering of - * the given project (provided that all builds went smoothly.) - */ - public AbstractProject getBuildingDownstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); - - for (AbstractProject tup : getTransitiveDownstreamProjects()) { - if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) - return tup; - } - return null; - } - - /** - * Returns the project if any of the upstream project is either - * building or is in the queue. - *

- * This means eventually there will be an automatic triggering of - * the given project (provided that all builds went smoothly.) - */ - public AbstractProject getBuildingUpstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); - - for (AbstractProject tup : getTransitiveUpstreamProjects()) { - if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) - return tup; - } - return null; - } - public List getSubTasks() { List r = new ArrayList(); r.add(this); @@ -1635,97 +1579,31 @@ public T getTrigger(Class clazz) { */ public abstract boolean isFingerprintConfigured(); - /** - * Gets the other {@link AbstractProject}s that should be built - * when a build of this project is completed. - */ - @Exported - public final List getDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getDownstream(this); - } - - @Exported - public final List getUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getUpstream(this); - } - /** * Returns only those upstream projects that defines {@link BuildTrigger} to this project. - * This is a subset of {@link #getUpstreamProjects()} + * This is a subset of {@link #getUpstreamProjects()}, only including {@link AbstractProject}s. *

No longer used in the UI. * @return A List of upstream projects that has a {@link BuildTrigger} to this project. */ public final List getBuildTriggerUpstreamProjects() { ArrayList result = new ArrayList(); - for (AbstractProject ap : getUpstreamProjects()) { - BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class); - if (buildTrigger != null) - if (buildTrigger.getChildProjects(ap).contains(this)) - result.add(ap); + for (Job j : getUpstreamProjects()) { + if (j instanceof AbstractProject) { + AbstractProject ap = (AbstractProject) j; + BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class); + if (buildTrigger != null) + if (buildTrigger.getChildProjects(ap).contains(this)) + result.add(ap); + } } return result; } - /** - * Gets all the upstream projects including transitive upstream projects. - * - * @since 1.138 - */ - public final Set getTransitiveUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this); - } - - /** - * Gets all the downstream projects including transitive downstream projects. - * - * @since 1.138 - */ - public final Set getTransitiveDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this); - } - - /** - * Gets the dependency relationship map between this project (as the source) - * and that project (as the sink.) - * - * @return - * can be empty but not null. build number of this project to the build - * numbers of that project. - */ - public SortedMap getRelationship(AbstractProject that) { - TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); - - checkAndRecord(that, r, this.getBuilds()); - // checkAndRecord(that, r, that.getBuilds()); - - return r; - } - - /** - * Helper method for getDownstreamRelationship. - * - * For each given build, find the build number range of the given project and put that into the map. - */ - private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) { - for (R build : builds) { - RangeSet rs = build.getDownstreamRelationship(that); - if(rs==null || rs.isEmpty()) - continue; - - int n = build.getNumber(); - - RangeSet value = r.get(n); - if(value==null) - r.put(n,rs); - else - value.add(rs); - } - } - /** * Builds the dependency graph. * Since 1.558, not abstract and by default includes dependencies contributed by {@link #triggers()}. */ + @Override protected void buildDependencyGraph(DependencyGraph graph) { triggers().buildDependencyGraph(this, graph); } @@ -2160,12 +2038,6 @@ List getSeeds() { return Items.findNearest(AbstractProject.class, name, context); } - private static final Comparator REVERSE_INTEGER_COMPARATOR = new Comparator() { - public int compare(Integer o1, Integer o2) { - return o2-o1; - } - }; - private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName()); /** diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index 237eba1b40f5..f52ec6459eee 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -68,17 +68,17 @@ * @see Jenkins#getDependencyGraph() * @author Kohsuke Kawaguchi */ -public class DependencyGraph implements Comparator { +public class DependencyGraph implements Comparator { - private Map> forward = new HashMap>(); - private Map> backward = new HashMap>(); + private Map> forward = new HashMap>(); + private Map> backward = new HashMap>(); private transient Map, Object> computationalData; private boolean built; - private Comparator> topologicalOrder; - private List> topologicallySorted; + private Comparator> topologicalOrder; + private List> topologicallySorted; /** * Builds the dependency graph. @@ -91,7 +91,7 @@ public void build() { SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); try { this.computationalData = new HashMap, Object>(); - for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) + for( Job p : Jenkins.getInstance().allItems(Job.class) ) p.buildDependencyGraph(this); forward = finalize(forward); @@ -110,36 +110,36 @@ public void build() { * See http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm */ private void topologicalDagSort() { - DirectedGraph g = new DirectedGraph() { + DirectedGraph g = new DirectedGraph() { @Override - protected Collection nodes() { - final Set nodes = new HashSet(); + protected Collection nodes() { + final Set nodes = new HashSet(); nodes.addAll(forward.keySet()); nodes.addAll(backward.keySet()); return nodes; } @Override - protected Collection forward(AbstractProject node) { + protected Collection forward(Job node) { return getDownstream(node); } }; - List> sccs = g.getStronglyConnectedComponents(); + List> sccs = g.getStronglyConnectedComponents(); - final Map topoOrder = new HashMap(); - topologicallySorted = new ArrayList>(); + final Map topoOrder = new HashMap(); + topologicallySorted = new ArrayList>(); int idx=0; - for (SCC scc : sccs) { - for (AbstractProject n : scc) { + for (SCC scc : sccs) { + for (Job n : scc) { topoOrder.put(n,idx++); topologicallySorted.add(n); } } - topologicalOrder = new Comparator>() { + topologicalOrder = new Comparator>() { @Override - public int compare(AbstractProject o1, AbstractProject o2) { + public int compare(Job o1, Job o2) { return topoOrder.get(o1)-topoOrder.get(o2); } }; @@ -179,7 +179,7 @@ public T getComputationalData(Class key) { * @return * can be empty but never null. */ - public List getDownstream(AbstractProject p) { + public List getDownstream(Job p) { return get(forward,p,false); } @@ -189,33 +189,34 @@ public List getDownstream(AbstractProject p) { * @return * can be empty but never null. */ - public List getUpstream(AbstractProject p) { + public List getUpstream(Job p) { return get(backward,p,true); } - private List get(Map> map, AbstractProject src, boolean up) { + private List get(Map> map, Job src, boolean up) { List v = map.get(src); if(v==null) return Collections.emptyList(); - List result = new ArrayList(v.size()); + List result = new ArrayList(v.size()); for (DependencyGroup d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject()); + return result; } /** * @since 1.341 */ - public List getDownstreamDependencies(AbstractProject p) { + public List getDownstreamDependencies(Job p) { return get(forward,p); } /** * @since 1.341 */ - public List getUpstreamDependencies(AbstractProject p) { + public List getUpstreamDependencies(Job p) { return get(backward,p); } - private List get(Map> map, AbstractProject src) { + private List get(Map> map, Job src) { List v = map.get(src); if(v==null) { return ImmutableList.of(); @@ -233,7 +234,7 @@ private List get(Map> map, Ab * @deprecated since 1.341; use {@link #addDependency(Dependency)} */ @Deprecated - public void addDependency(AbstractProject upstream, AbstractProject downstream) { + public void addDependency(Job upstream, Job downstream) { addDependency(new Dependency(upstream,downstream)); } @@ -251,8 +252,8 @@ public void addDependency(Dependency dep) { * @deprecated since 1.341 */ @Deprecated - public void addDependency(AbstractProject upstream, Collection downstream) { - for (AbstractProject p : downstream) + public void addDependency(Job upstream, Collection downstream) { + for (Job p : downstream) addDependency(upstream,p); } @@ -260,15 +261,15 @@ public void addDependency(AbstractProject upstream, Collection upstream, AbstractProject downstream) { - for (AbstractProject p : upstream) + public void addDependency(Collection upstream, Job downstream) { + for (Job p : upstream) addDependency(p,downstream); } /** * Lists up {@link DependencyDeclarer} from the collection and let them builds dependencies. */ - public void addDependencyDeclarers(AbstractProject upstream, Collection possibleDependecyDeclarers) { + public void addDependencyDeclarers(Job upstream, Collection possibleDependecyDeclarers) { for (Object o : possibleDependecyDeclarers) { if (o instanceof DependencyDeclarer) { DependencyDeclarer dd = (DependencyDeclarer) o; @@ -283,15 +284,15 @@ public void addDependencyDeclarers(AbstractProject upstream, Collection possi * A non-direct dependency is a path of dependency "edge"s from the source to the destination, * where the length is greater than 1. */ - public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) { - Set visited = new HashSet(); - Stack queue = new Stack(); + public boolean hasIndirectDependencies(Job src, Job dst) { + Set visited = new HashSet(); + Stack queue = new Stack(); queue.addAll(getDownstream(src)); queue.remove(dst); while(!queue.isEmpty()) { - AbstractProject p = queue.pop(); + Job p = queue.pop(); if(p==dst) return true; if(visited.add(p)) @@ -304,27 +305,27 @@ public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) /** * Gets all the direct and indirect upstream dependencies of the given project. */ - public Set getTransitiveUpstream(AbstractProject src) { + public Set getTransitiveUpstream(Job src) { return getTransitive(backward,src,true); } /** * Gets all the direct and indirect downstream dependencies of the given project. */ - public Set getTransitiveDownstream(AbstractProject src) { + public Set getTransitiveDownstream(Job src) { return getTransitive(forward,src,false); } - private Set getTransitive(Map> direction, AbstractProject src, boolean up) { - Set visited = new HashSet(); - Stack queue = new Stack(); + private Set getTransitive(Map> direction, Job src, boolean up) { + Set visited = new HashSet(); + Stack queue = new Stack(); queue.add(src); while(!queue.isEmpty()) { - AbstractProject p = queue.pop(); + Job p = queue.pop(); - for (AbstractProject child : get(direction,p,up)) { + for (Job child : get(direction,p,up)) { if(visited.add(child)) queue.add(child); } @@ -333,7 +334,7 @@ private Set getTransitive(Map> map, AbstractProject key, Dependency dep) { + private void add(Map> map, Job key, Dependency dep) { List set = map.get(key); if(set==null) { set = new ArrayList(); @@ -351,8 +352,8 @@ private void add(Map> map, AbstractProjec set.add(new DependencyGroup(dep)); } - private Map> finalize(Map> m) { - for (Entry> e : m.entrySet()) { + private Map> finalize(Map> m) { + for (Entry> e : m.entrySet()) { Collections.sort( e.getValue(), NAME_COMPARATOR ); e.setValue( Collections.unmodifiableList(e.getValue()) ); } @@ -371,7 +372,7 @@ public int compare(DependencyGroup lhs, DependencyGroup rhs) { /** * Compare two Projects based on the topological order defined by this Dependency Graph */ - public int compare(AbstractProject o1, AbstractProject o2) { + public int compare(Job o1, Job o2) { return topologicalOrder.compare(o1,o2); } @@ -383,7 +384,7 @@ public int compare(AbstractProject o1, AbstractProject o2) { * * @since 1.521 */ - public List> getTopologicallySorted() { + public List> getTopologicallySorted() { return topologicallySorted; } @@ -392,18 +393,18 @@ public List> getTopologicallySorted() { * @since 1.341 */ public static class Dependency { - private AbstractProject upstream, downstream; + private Job upstream, downstream; - public Dependency(AbstractProject upstream, AbstractProject downstream) { + public Dependency(Job upstream, Job downstream) { this.upstream = upstream; this.downstream = downstream; } - public AbstractProject getUpstreamProject() { + public Job getUpstreamProject() { return upstream; } - public AbstractProject getDownstreamProject() { + public Job getDownstreamProject() { return downstream; } @@ -420,7 +421,7 @@ public AbstractProject getDownstreamProject() { * @param actions Add Actions for the triggered build to this list; never null * @return True to trigger a build of the downstream project */ - public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, + public boolean shouldTriggerBuild(Run build, TaskListener listener, List actions) { return true; } @@ -462,7 +463,7 @@ private static class DependencyGroup { DependencyGroup(Dependency first) { this.upstream = first.getUpstreamProject(); - this.downstream= first.getDownstreamProject(); + this.downstream = first.getDownstreamProject(); group.add(first); } @@ -474,13 +475,13 @@ public Set getGroup() { return group; } - private AbstractProject upstream, downstream; + private Job upstream, downstream; - public AbstractProject getUpstreamProject() { + public Job getUpstreamProject() { return upstream; } - public AbstractProject getDownstreamProject() { + public Job getDownstreamProject() { return downstream; } } diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index a3ac3fdd9d2f..ed5c1ea75bc3 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -72,11 +72,14 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; @@ -179,6 +182,13 @@ public abstract class Job, RunT extends Run> properties = new CopyOnWriteList>(); + private static final Comparator REVERSE_INTEGER_COMPARATOR = new Comparator() { + public int compare(Integer o1, Integer o2) { + return o2-o1; + } + }; + + @Restricted(NoExternalUse.class) public transient RunIdMigrator runIdMigrator; @@ -332,6 +342,63 @@ public boolean isKeepDependencies() { return keepDependencies; } + + /** + * Returns the project if any of the downstream project is either + * building, waiting, pending or buildable. + *

+ * This means eventually there will be an automatic triggering of + * the given project (provided that all builds went smoothly.) + */ + public Job getBuildingDownstream() { + Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + + for (Job tup : getTransitiveDownstreamProjects()) { + if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) + return tup; + } + return null; + } + + /** + * Returns the project if any of the upstream project is either + * building or is in the queue. + *

+ * This means eventually there will be an automatic triggering of + * the given project (provided that all builds went smoothly.) + */ + public Job getBuildingUpstream() { + Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + + for (Job tup : getTransitiveUpstreamProjects()) { + if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) + return tup; + } + return null; + } + + /** + * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link Job}. + * + *

+ * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs + * that acts as a single unit. This method can be used to find the top most dominating job that + * covers such a tree. + * + * @return never null. + * @see Run#getRootBuild() + */ + public Job getRootProject() { + if (this instanceof TopLevelItem) { + return this; + } else { + ItemGroup p = this.getParent(); + if (p instanceof Job) + return ((Job) p).getRootProject(); + return this; + } + } + /** * Allocates a new buildCommand number. */ @@ -499,9 +566,86 @@ public Collection getAllJobs() { return Collections. singleton(this); } + /** + * Builds the dependency graph. No-op by default. + * @param graph + */ + protected void buildDependencyGraph(DependencyGraph graph) { + } + + /** + * Gets the other {@link Job}s that should be built + * when a build of this project is completed. + */ + @Exported + public final List getDownstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getDownstream(this); + } + + @Exported + public final List getUpstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getUpstream(this); + } + + /** + * Gets all the upstream projects including transitive upstream projects. + * + * @since 1.138 + */ + public final Set getTransitiveUpstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this); + } + + /** + * Gets all the downstream projects including transitive downstream projects. + * + * @since 1.138 + */ + public final Set getTransitiveDownstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this); + } + + /** + * Gets the dependency relationship map between this project (as the source) + * and that project (as the sink.) + * + * @return + * can be empty but not null. build number of this project to the build + * numbers of that project. + */ + public SortedMap getRelationship(AbstractProject that) { + TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); + + checkAndRecord(that, r, this.getBuilds()); + // checkAndRecord(that, r, that.getBuilds()); + + return r; + } + + /** + * Helper method for getDownstreamRelationship. + * + * For each given build, find the build number range of the given project and put that into the map. + */ + private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) { + for (RunT build : builds) { + RangeSet rs = build.getDownstreamRelationship(that); + if(rs==null || rs.isEmpty()) + continue; + + int n = build.getNumber(); + + RangeSet value = r.get(n); + if(value==null) + r.put(n,rs); + else + value.add(rs); + } + } + /** * Adds {@link JobProperty}. - * + * * @since 1.188 */ public void addProperty(JobProperty jobProp) throws IOException { diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 547901ce174a..1d3a1da2568b 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -44,6 +44,10 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.StandardOpenOption; + +import hudson.tasks.Fingerprinter; +import hudson.util.AdaptedIterator; +import hudson.util.Iterators; import jenkins.util.SystemProperties; import hudson.Util; import hudson.XmlFile; @@ -80,12 +84,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -143,6 +149,11 @@ public abstract class Run ,RunT extends Run> extends Actionable implements ExtensionPoint, Comparable, AccessControlled, PersistenceRoot, DescriptorByNameOwner, OnMaster { + /** + * Set if we want the blame information to flow from upstream to downstream build. + */ + private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); + /** * The original {@link Queue.Item#getId()} has not yet been mapped onto the {@link Run} instance. * @since 1.601 @@ -961,6 +972,246 @@ protected void dropLinks() { return nextBuild; } + /** + * Gets the dependency relationship from this build (as the source) + * and that project (as the sink.) + * + * @return + * range of build numbers that represent which downstream builds are using this build. + * The range will be empty if no build of that project matches this (or there is no {@link Fingerprinter.FingerprintAction}), but it'll never be null. + */ + public Fingerprint.RangeSet getDownstreamRelationship(Job that) { + Fingerprint.RangeSet rs = new Fingerprint.RangeSet(); + + Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class); + if (f==null) return rs; + + // look for fingerprints that point to this build as the source, and merge them all + for (Fingerprint e : f.getFingerprints().values()) { + + if (upstreamCulprits) { + // With upstreamCulprits, we allow downstream relationships + // from intermediate jobs + rs.add(e.getRangeSet(that)); + } else { + Fingerprint.BuildPtr o = e.getOriginal(); + if (o!=null && o.is(this)) + rs.add(e.getRangeSet(that)); + } + } + + return rs; + } + + /** + * Works like {@link #getDownstreamRelationship(Job)} but returns + * the actual build objects, in ascending order. + * @since 1.150 + */ + public Iterable> getDownstreamBuilds(final Job that) { + final Iterable nums = getDownstreamRelationship(that).listNumbers(); + + return new Iterable>() { + public Iterator> iterator() { + return Iterators.removeNull( + new AdaptedIterator>(nums) { + protected Run adapt(Integer item) { + return that.getBuildByNumber(item); + } + }); + } + }; + } + + /** + * Gets the dependency relationship from this build (as the sink) + * and that project (as the source.) + * + * @return + * Build number of the upstream build that feed into this build, + * or -1 if no record is available (for example if there is no {@link Fingerprinter.FingerprintAction}, even if there is an {@link Cause.UpstreamCause}). + */ + public int getUpstreamRelationship(Job that) { + Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class); + if (f==null) return -1; + + int n = -1; + + // look for fingerprints that point to the given project as the source, and merge them all + for (Fingerprint e : f.getFingerprints().values()) { + if (upstreamCulprits) { + // With upstreamCulprits, we allow upstream relationships + // from intermediate jobs + Fingerprint.RangeSet rangeset = e.getRangeSet(that); + if (!rangeset.isEmpty()) { + n = Math.max(n, rangeset.listNumbersReverse().iterator().next()); + } + } else { + Fingerprint.BuildPtr o = e.getOriginal(); + if (o!=null && o.belongsTo(that)) + n = Math.max(n,o.getNumber()); + } + } + + return n; + } + + /** + * Works like {@link #getUpstreamRelationship(Job)} but returns the + * actual build object. + * + * @return + * null if no such upstream build was found, or it was found but the + * build record is already lost. + */ + public Run getUpstreamRelationshipBuild(Job that) { + int n = getUpstreamRelationship(that); + if (n==-1) return null; + return that.getBuildByNumber(n); + } + + /** + * Gets the downstream builds of this build, which are the builds of the + * downstream projects that use artifacts of this build. + * + * @return + * For each project with fingerprinting enabled, returns the range + * of builds (which can be empty if no build uses the artifact from this build. + */ + public Map getDownstreamBuilds() { + Map r = new HashMap(); + for (Job p : getParent().getDownstreamProjects()) { + r.put(p,getDownstreamRelationship(p)); + } + return r; + } + + /** + * Gets the upstream builds of this build, which are the builds of the + * upstream projects whose artifacts feed into this build. + * @return empty if there is no {@link Fingerprinter.FingerprintAction} (even if there is an {@link Cause.UpstreamCause}) + * @see #getTransitiveUpstreamBuilds() + */ + public Map getUpstreamBuilds() { + return _getUpstreamBuilds(getParent().getUpstreamProjects()); + } + + /** + * Works like {@link #getUpstreamBuilds()} but also includes all the transitive + * dependencies as well. + */ + public Map getTransitiveUpstreamBuilds() { + return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects()); + } + + private Map _getUpstreamBuilds(Collection projects) { + Map r = new HashMap(); + for (Job p : projects) { + int n = getUpstreamRelationship(p); + if (n>=0) + r.put(p,n); + } + return r; + } + + /** + * Gets the changes in the dependency between the given build and this build. + * @return empty if there is no {@link Fingerprinter.FingerprintAction} + */ + public Map getDependencyChanges(Run from) { + if (from==null) return Collections.emptyMap(); // make it easy to call this from views + Fingerprinter.FingerprintAction n = this.getAction(Fingerprinter.FingerprintAction.class); + Fingerprinter.FingerprintAction o = from.getAction(Fingerprinter.FingerprintAction.class); + if (n==null || o==null) return Collections.emptyMap(); + + Map ndep = n.getDependencies(true); + Map odep = o.getDependencies(true); + + Map r = new HashMap(); + + for (Map.Entry entry : odep.entrySet()) { + Job p = entry.getKey(); + Integer oldNumber = entry.getValue(); + Integer newNumber = ndep.get(p); + if (newNumber!=null && oldNumber.compareTo(newNumber)<0) { + r.put(p,new Run.DependencyChange(p,oldNumber,newNumber)); + } + } + + return r; + } + + /** + * Gets the nearest ancestor {@link Run} that belongs to + * {@linkplain Job#getRootProject() the root project of getParent()} that + * dominates/governs/encompasses this build. + * + *

+ * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs, + * and still in some of them, builds of child projects are related/tied to that of the parent project. + * In such a case, this method returns the governing build. + * + * @return never null. In the worst case the build dominates itself. + * @since 1.421 + * @see Job#getRootProject() + */ + public Run getRootBuild() { + return this; + } + + /** + * Represents a change in the dependency. + */ + public static class DependencyChange { + /** + * The dependency project. + */ + public final Job project; + /** + * Version of the dependency project used in the previous build. + */ + public final int fromId; + /** + * {@link Run} object for {@link #fromId}. Can be null if the log is gone. + */ + public final Run from; + /** + * Version of the dependency project used in this build. + */ + public final int toId; + + public final Run to; + + public DependencyChange(Job project, int fromId, int toId) { + this.project = project; + this.fromId = fromId; + this.toId = toId; + this.from = project.getBuildByNumber(fromId); + this.to = project.getBuildByNumber(toId); + } + + /** + * Gets the {@link Run} objects (fromId,toId]. + *

+ * This method returns all such available builds in the ascending order + * of IDs, but due to log rotations, some builds may be already unavailable. + */ + public List getBuilds() { + List r = new ArrayList(); + + Run b = project.getNearestBuild(fromId); + if (b!=null && b.getNumber()==fromId) + b = b.getNextBuild(); // fromId exclusive + + while (b!=null && b.getNumber()<=toId) { + r.add(b); + b = b.getNextBuild(); + } + + return r; + } + } + /** * Returns the URL of this {@link Run}, relative to the context root of Hudson. * diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index d2b0a91f6b0d..b205e75352ab 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -245,21 +245,24 @@ public int compare(Dependency lhs, Dependency rhs) { SecurityContext orig = ACL.impersonate(auth); try { if (dep.shouldTriggerBuild(build, listener, buildActions)) { - AbstractProject p = dep.getDownstreamProject(); - // Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission: - if (p.isDisabled()) { - logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p))); - continue; - } - boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run)build), buildActions.toArray(new Action[buildActions.size()])); - if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) { - String name = ModelHyperlinkNote.encodeTo(p); - if (scheduled) { - logger.println(Messages.BuildTrigger_Triggering(name)); - } else { - logger.println(Messages.BuildTrigger_InQueue(name)); + Job j = dep.getDownstreamProject(); + if (j instanceof AbstractProject) { + AbstractProject p = (AbstractProject) j; + // Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission: + if (p.isDisabled()) { + logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p))); + continue; } - } // otherwise upstream users should not know that it happened + boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run) build), buildActions.toArray(new Action[buildActions.size()])); + if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) { + String name = ModelHyperlinkNote.encodeTo(p); + if (scheduled) { + logger.println(Messages.BuildTrigger_Triggering(name)); + } else { + logger.println(Messages.BuildTrigger_InQueue(name)); + } + } // otherwise upstream users should not know that it happened + } } } finally { SecurityContextHolder.setContext(orig); @@ -269,24 +272,28 @@ public int compare(Dependency lhs, Dependency rhs) { return true; } - public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { - for (AbstractProject p : getChildProjects(owner)) - graph.addDependency(new Dependency(owner, p) { - @Override - public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, - List actions) { - AbstractProject downstream = getDownstreamProject(); - if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders - LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()}); - return false; // do not even issue a warning to build log - } - if (!downstream.hasPermission(Item.BUILD)) { - listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream))); - return false; + public void buildDependencyGraph(Job j, DependencyGraph graph) { + if (j instanceof AbstractProject) { + AbstractProject owner = (AbstractProject) j; + + for (AbstractProject p : getChildProjects(owner)) + graph.addDependency(new Dependency(owner, p) { + @Override + public boolean shouldTriggerBuild(Run build, TaskListener listener, + List actions) { + Job downstream = getDownstreamProject(); + if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders + LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[]{Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()}); + return false; // do not even issue a warning to build log + } + if (!downstream.hasPermission(Item.BUILD)) { + listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream))); + return false; + } + return build.getResult().isBetterOrEqualTo(threshold); } - return build.getResult().isBetterOrEqualTo(threshold); - } - }); + }); + } } @Override diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java index fb45ef9cc2cb..94024d9a9b00 100644 --- a/core/src/main/java/hudson/tasks/Fingerprinter.java +++ b/core/src/main/java/hudson/tasks/Fingerprinter.java @@ -149,7 +149,7 @@ public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } - public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { + public void buildDependencyGraph(Job owner, DependencyGraph graph) { if (enableFingerprintsInDependencyGraph) { RunList builds = owner.getBuilds(); Set seenUpstreamProjects = new HashSet(); @@ -157,12 +157,12 @@ public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { for ( ListIterator iter = builds.listIterator(); iter.hasNext(); ) { Run build = (Run) iter.next(); for (FingerprintAction action : build.getActions(FingerprintAction.class)) { - for (AbstractProject key : action.getDependencies().keySet()) { + for (Job key : action.getDependencies().keySet()) { if (key == owner) { continue; // Avoid self references } - AbstractProject p = key; + Job p = key; // TODO is this harmful to call unconditionally, so it would apply also to MavenModule for example? if (key.getClass().getName().equals("hudson.matrix.MatrixConfiguration")) { p = key.getRootProject(); @@ -175,7 +175,7 @@ public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { seenUpstreamProjects.add(p.getName()); graph.addDependency(new Dependency(p, owner) { @Override - public boolean shouldTriggerBuild(AbstractBuild build, + public boolean shouldTriggerBuild(Run build, TaskListener listener, List actions) { // Fingerprints should not trigger builds. @@ -389,7 +389,7 @@ public synchronized Map getFingerprints() { /** * Gets the dependency to other existing builds in a map. */ - public Map getDependencies() { + public Map getDependencies() { return getDependencies(false); } @@ -400,8 +400,8 @@ public Map getDependencies() { * the result, even if it doesn't exist * @since 1.430 */ - public Map getDependencies(boolean includeMissing) { - Map r = new HashMap(); + public Map getDependencies(boolean includeMissing) { + Map r = new HashMap(); for (Fingerprint fp : getFingerprints().values()) { BuildPtr bp = fp.getOriginal(); @@ -411,11 +411,6 @@ public Map getDependencies(boolean includeMissing) { try { Job job = bp.getJob(); if (job==null) continue; // project no longer exists - if (!(job instanceof AbstractProject)) { - // Ignoring this for now. In the future we may want a dependency map function not limited to AbstractProject. - // (Could be used by getDependencyChanges if pulled up from AbstractBuild into Run, for example.) - continue; - } if (job.getParent()==build.getParent()) continue; // we are the parent of the build owner, that is almost like we are the owner if(!includeMissing && job.getBuildByNumber(bp.getNumber())==null) @@ -424,7 +419,7 @@ public Map getDependencies(boolean includeMissing) { Integer existing = r.get(job); if(existing!=null && existing>bp.getNumber()) continue; // the record in the map is already up to date - r.put((AbstractProject) job, bp.getNumber()); + r.put(job, bp.getNumber()); } catch (AccessDeniedException e) { // Need to log in to access this job, so ignore continue; diff --git a/core/src/main/java/jenkins/model/DependencyDeclarer.java b/core/src/main/java/jenkins/model/DependencyDeclarer.java index a6cb2c52c61e..deaee082aa7b 100644 --- a/core/src/main/java/jenkins/model/DependencyDeclarer.java +++ b/core/src/main/java/jenkins/model/DependencyDeclarer.java @@ -25,6 +25,7 @@ import hudson.model.AbstractProject; import hudson.model.DependencyGraph; +import hudson.model.Job; import hudson.tasks.BuildWrapper; import hudson.tasks.Builder; import hudson.tasks.Publisher; @@ -50,7 +51,7 @@ public interface DependencyDeclarer { // so that this concept can be extended elsewhere, like maven projects and so on. /** - * Invoked from {@link AbstractProject#buildDependencyGraph(DependencyGraph)}. + * Invoked from {@link Job#buildDependencyGraph(DependencyGraph)}. * * @param owner * The project that owns the publishers, builders, etc. @@ -61,5 +62,5 @@ public interface DependencyDeclarer { * @param graph * The dependency graph being built. Never null. */ - void buildDependencyGraph(AbstractProject owner, DependencyGraph graph); + void buildDependencyGraph(Job owner, DependencyGraph graph); } diff --git a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java b/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java index cc9b3986995c..0a18bdba6cc2 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java +++ b/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java @@ -95,12 +95,17 @@ public abstract class RunWithSCMMixIn & Queue.Task, if (p instanceof AbstractBuild && upstreamCulprits) { // If we have dependencies since the last successful build, add their authors to our list if (p.getPreviousNotFailedBuild() != null) { - Map depmap = - ((AbstractBuild) p).getDependencyChanges((AbstractBuild)p.getPreviousSuccessfulBuild()); - for (AbstractBuild.DependencyChange dep : depmap.values()) { - for (AbstractBuild b : dep.getBuilds()) { - for (ChangeLogSet.Entry entry : b.getChangeSet()) { - r.add(entry.getAuthor()); + Map depmap = + p.getDependencyChanges(p.getPreviousSuccessfulBuild()); + for (Run.DependencyChange dep : depmap.values()) { + for (Run rawRun : dep.getBuilds()) { + if (rawRun instanceof RunWithSCM) { + RunWithSCM b = (RunWithSCM) rawRun; + for (ChangeLogSet c : b.getChangeSets()) { + for (ChangeLogSet.Entry e : c) { + r.add(e.getAuthor()); + } + } } } } diff --git a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java index e9be86f3dd65..8ca337761a95 100644 --- a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java +++ b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java @@ -139,10 +139,10 @@ private boolean shouldTrigger(Run upstreamBuild, TaskListener listener) { return result != null && result.isBetterOrEqualTo(threshold); } - @Override public void buildDependencyGraph(final AbstractProject downstream, DependencyGraph graph) { - for (AbstractProject upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, AbstractProject.class)) { + @Override public void buildDependencyGraph(final Job downstream, DependencyGraph graph) { + for (Job upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, Job.class)) { graph.addDependency(new DependencyGraph.Dependency(upstream, downstream) { - @Override public boolean shouldTriggerBuild(AbstractBuild upstreamBuild, TaskListener listener, List actions) { + @Override public boolean shouldTriggerBuild(Run upstreamBuild, TaskListener listener, List actions) { return shouldTrigger(upstreamBuild, listener); } }); diff --git a/test/src/test/java/hudson/model/DependencyGraphTest.java b/test/src/test/java/hudson/model/DependencyGraphTest.java index 1bb7e134cf38..5a98c90fd962 100644 --- a/test/src/test/java/hudson/model/DependencyGraphTest.java +++ b/test/src/test/java/hudson/model/DependencyGraphTest.java @@ -93,10 +93,10 @@ private TestDeclarer(Result buildResult, AbstractProject down) { super(buildResult); this.down = down; } - public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { + public void buildDependencyGraph(Job owner, DependencyGraph graph) { graph.addDependency(new DependencyGraph.Dependency(owner, down) { @Override - public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, + public boolean shouldTriggerBuild(Run build, TaskListener listener, List actions) { // Trigger for ODD build number if (build.getNumber() % 2 == 1) { @@ -122,7 +122,7 @@ public void testItemReadPermission() throws Exception { // @LocalData for this test has jobs w/o anonymous Item.READ AbstractProject up = (AbstractProject) jenkins.getItem("hiddenUpstream"); assertNotNull("hiddenUpstream project not found", up); - List down = jenkins.getDependencyGraph().getDownstream(up); + List down = jenkins.getDependencyGraph().getDownstream(up); assertEquals("Should have one downstream project", 1, down.size()); } finally { SecurityContextHolder.clearContext(); @@ -150,9 +150,9 @@ public void testTopologicalSort() throws Exception { jenkins.rebuildDependencyGraph(); DependencyGraph g = jenkins.getDependencyGraph(); - List> sorted = g.getTopologicallySorted(); + List> sorted = g.getTopologicallySorted(); StringBuilder buf = new StringBuilder(); - for (AbstractProject p : sorted) { + for (Job p : sorted) { buf.append(p.getName()); } String r = buf.toString(); diff --git a/test/src/test/java/hudson/tasks/BuildTriggerTest.java b/test/src/test/java/hudson/tasks/BuildTriggerTest.java index fda4972badaa..e16b6d221c92 100644 --- a/test/src/test/java/hudson/tasks/BuildTriggerTest.java +++ b/test/src/test/java/hudson/tasks/BuildTriggerTest.java @@ -38,6 +38,7 @@ import hudson.model.DependencyGraph; import hudson.model.DependencyGraph.Dependency; import hudson.model.Item; +import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; @@ -367,7 +368,7 @@ private Dep(AbstractProject upstream, AbstractProject downstream) { } @Override - public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { + public boolean shouldTriggerBuild(Run build, TaskListener listener, List actions) { if (block) { try { Thread.sleep(5000); @@ -386,9 +387,12 @@ public SlowTrigger(String childProjects) { } @Override @SuppressWarnings("rawtypes") - public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { - for (AbstractProject ch: getChildProjects(owner)) { - graph.addDependency(new Dep(owner, ch)); + public void buildDependencyGraph(Job j, DependencyGraph graph) { + if (j instanceof AbstractProject) { + AbstractProject owner = (AbstractProject) j; + for (AbstractProject ch : getChildProjects(owner)) { + graph.addDependency(new Dep(owner, ch)); + } } } } diff --git a/test/src/test/java/hudson/tasks/FingerprinterTest.java b/test/src/test/java/hudson/tasks/FingerprinterTest.java index d26ce52d1ed1..680e0373d5d9 100644 --- a/test/src/test/java/hudson/tasks/FingerprinterTest.java +++ b/test/src/test/java/hudson/tasks/FingerprinterTest.java @@ -102,8 +102,8 @@ public static void setUp() throws Exception { j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); assertEquals(1, downstreamProjects.size()); assertEquals(1, upstreamProjects.size()); @@ -142,9 +142,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List downstreamProjects2 = upstream2.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List downstreamProjects2 = upstream2.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); assertEquals(1, downstreamProjects.size()); assertEquals(1, downstreamProjects2.size()); @@ -165,9 +165,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); - List upstreamProjects2 = downstream2.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); + List upstreamProjects2 = downstream2.getUpstreamProjects(); assertEquals(2, downstreamProjects.size()); assertEquals(1, upstreamProjects.size()); @@ -189,8 +189,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen Jenkins.getInstance().rebuildDependencyGraph(); - List upstreamProjects = downstream.getUpstreamProjects(); - List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); assertEquals(0, upstreamProjects.size()); assertEquals(0, downstreamProjects.size()); @@ -204,8 +204,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen Jenkins.getInstance().rebuildDependencyGraph(); - List upstreamProjects = p.getUpstreamProjects(); - List downstreamProjects = p.getDownstreamProjects(); + List upstreamProjects = p.getUpstreamProjects(); + List downstreamProjects = p.getDownstreamProjects(); assertEquals(0, upstreamProjects.size()); assertEquals(0, downstreamProjects.size()); @@ -229,9 +229,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen assertEquals("There should only be one FreestyleBuild", 1, builds.size()); FreeStyleBuild build = builds.iterator().next(); assertEquals(Result.SUCCESS, build.getResult()); - List downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject); + List downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject); assertTrue(downstream.contains(freestyleProject)); - List upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject); + List upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject); assertTrue(upstream.contains(matrixProject)); } From 4f3172c515d45632e903141392da516eadd872c9 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Mon, 1 May 2017 11:45:11 -0400 Subject: [PATCH 06/18] Normalize versioning because that was unneeded --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 2 +- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 4bcbb04a983e..1e5096e457ed 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.58-JENKINS-24141-SNAPSHOT + 2.58-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 06ae2351ffac..13677a1838aa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.58-JENKINS-24141-SNAPSHOT + 2.58-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 40fd6257bbc9..81f1893b314b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.58-JENKINS-24141-SNAPSHOT + 2.58-SNAPSHOT pom Jenkins main module diff --git a/test/pom.xml b/test/pom.xml index d2656af6ab2e..8b131c2c1dbb 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.58-JENKINS-24141-SNAPSHOT + 2.58-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 8f94559e3917..53d748c1d71a 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.58-JENKINS-24141-SNAPSHOT + 2.58-SNAPSHOT jenkins-war From e4b84a2f824e00ad7ff5f19783840da0bb4a26a4 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Mon, 1 May 2017 13:16:49 -0400 Subject: [PATCH 07/18] Revert "Initial work on DependencyGraph" This reverts commit 7fe3a4a3b957cf5517e634cdd25b1c5646334689. --- .../main/java/hudson/DependencyRunner.java | 28 +- .../main/java/hudson/model/AbstractBuild.java | 251 +++++++++++++++++- .../java/hudson/model/AbstractProject.java | 160 +++++++++-- .../java/hudson/model/DependencyGraph.java | 111 ++++---- core/src/main/java/hudson/model/Job.java | 146 +--------- core/src/main/java/hudson/model/Run.java | 251 ------------------ .../main/java/hudson/tasks/BuildTrigger.java | 69 +++-- .../main/java/hudson/tasks/Fingerprinter.java | 21 +- .../jenkins/model/DependencyDeclarer.java | 5 +- .../java/jenkins/scm/RunWithSCMMixIn.java | 17 +- .../jenkins/triggers/ReverseBuildTrigger.java | 6 +- .../hudson/model/DependencyGraphTest.java | 10 +- .../java/hudson/tasks/BuildTriggerTest.java | 12 +- .../java/hudson/tasks/FingerprinterTest.java | 28 +- 14 files changed, 534 insertions(+), 581 deletions(-) diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java index 92a567866f89..03efea55e403 100644 --- a/core/src/main/java/hudson/DependencyRunner.java +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -25,7 +25,6 @@ package hudson; import hudson.model.AbstractProject; -import hudson.model.Job; import jenkins.model.Jenkins; import hudson.security.ACL; @@ -77,23 +76,20 @@ public void run() { } } - private void populate(Collection projectList) { - for (Job j : projectList) { - if (j instanceof AbstractProject) { - AbstractProject p = (AbstractProject)j; - if (polledProjects.contains(p)) { - // Project will be readded at the queue, so that we always use - // the longest path - LOGGER.fine("removing project " + p.getName() + " for re-add"); - polledProjects.remove(p); - } + private void populate(Collection projectList) { + for (AbstractProject p : projectList) { + if (polledProjects.contains(p)) { + // Project will be readded at the queue, so that we always use + // the longest path + LOGGER.fine("removing project " + p.getName() + " for re-add"); + polledProjects.remove(p); + } - LOGGER.fine("adding project " + p.getName()); - polledProjects.add(p); + LOGGER.fine("adding project " + p.getName()); + polledProjects.add(p); - // Add all downstream dependencies - populate(p.getDownstreamProjects()); - } + // Add all downstream dependencies + populate(p.getDownstreamProjects()); } } diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index de21e154f66c..22286913354e 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -102,6 +102,11 @@ */ public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCMMixIn.RunWithSCM { + /** + * Set if we want the blame information to flow from upstream to downstream build. + */ + private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); + /** * Name of the agent this project was built on. * Null or "" if built by the master. (null happens when we read old record that didn't have this information.) @@ -244,6 +249,24 @@ protected void setBuiltOnStr( String builtOn ) { this.builtOn = builtOn; } + /** + * Gets the nearest ancestor {@link AbstractBuild} that belongs to + * {@linkplain AbstractProject#getRootProject() the root project of getProject()} that + * dominates/governs/encompasses this build. + * + *

+ * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs, + * and still in some of them, builds of child projects are related/tied to that of the parent project. + * In such a case, this method returns the governing build. + * + * @return never null. In the worst case the build dominates itself. + * @since 1.421 + * @see AbstractProject#getRootProject() + */ + public AbstractBuild getRootBuild() { + return this; + } + /** * Used to render the side panel "Back to project" link. * @@ -1038,10 +1061,10 @@ public String getWhyKeepLog() { // if any of the downstream project is configured with 'keep dependency component', // we need to keep this log OUTER: - for (Job p : getParent().getDownstreamProjects()) { + for (AbstractProject p : getParent().getDownstreamProjects()) { if (!p.isKeepDependencies()) continue; - Run fb = p.getFirstBuild(); + AbstractBuild fb = p.getFirstBuild(); if (fb==null) continue; // no active record // is there any active build that depends on us? @@ -1052,7 +1075,7 @@ public String getWhyKeepLog() { if (i b = p.getBuildByNumber(i); + AbstractBuild b = p.getBuildByNumber(i); if (b!=null) return Messages.AbstractBuild_KeptBecause(b); } @@ -1062,11 +1085,225 @@ public String getWhyKeepLog() { } /** - * Retained for compatibility. + * Gets the dependency relationship from this build (as the source) + * and that project (as the sink.) + * + * @return + * range of build numbers that represent which downstream builds are using this build. + * The range will be empty if no build of that project matches this (or there is no {@link FingerprintAction}), but it'll never be null. + */ + public RangeSet getDownstreamRelationship(AbstractProject that) { + RangeSet rs = new RangeSet(); + + FingerprintAction f = getAction(FingerprintAction.class); + if (f==null) return rs; + + // look for fingerprints that point to this build as the source, and merge them all + for (Fingerprint e : f.getFingerprints().values()) { + + if (upstreamCulprits) { + // With upstreamCulprits, we allow downstream relationships + // from intermediate jobs + rs.add(e.getRangeSet(that)); + } else { + BuildPtr o = e.getOriginal(); + if (o!=null && o.is(this)) + rs.add(e.getRangeSet(that)); + } + } + + return rs; + } + + /** + * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns + * the actual build objects, in ascending order. + * @since 1.150 + */ + public Iterable> getDownstreamBuilds(final AbstractProject that) { + final Iterable nums = getDownstreamRelationship(that).listNumbers(); + + return new Iterable>() { + public Iterator> iterator() { + return Iterators.removeNull( + new AdaptedIterator>(nums) { + protected AbstractBuild adapt(Integer item) { + return that.getBuildByNumber(item); + } + }); + } + }; + } + + /** + * Gets the dependency relationship from this build (as the sink) + * and that project (as the source.) + * + * @return + * Build number of the upstream build that feed into this build, + * or -1 if no record is available (for example if there is no {@link FingerprintAction}, even if there is an {@link Cause.UpstreamCause}). + */ + public int getUpstreamRelationship(AbstractProject that) { + FingerprintAction f = getAction(FingerprintAction.class); + if (f==null) return -1; + + int n = -1; + + // look for fingerprints that point to the given project as the source, and merge them all + for (Fingerprint e : f.getFingerprints().values()) { + if (upstreamCulprits) { + // With upstreamCulprits, we allow upstream relationships + // from intermediate jobs + Fingerprint.RangeSet rangeset = e.getRangeSet(that); + if (!rangeset.isEmpty()) { + n = Math.max(n, rangeset.listNumbersReverse().iterator().next()); + } + } else { + BuildPtr o = e.getOriginal(); + if (o!=null && o.belongsTo(that)) + n = Math.max(n,o.getNumber()); + } + } + + return n; + } + + /** + * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the + * actual build object. + * + * @return + * null if no such upstream build was found, or it was found but the + * build record is already lost. + */ + public AbstractBuild getUpstreamRelationshipBuild(AbstractProject that) { + int n = getUpstreamRelationship(that); + if (n==-1) return null; + return that.getBuildByNumber(n); + } + + /** + * Gets the downstream builds of this build, which are the builds of the + * downstream projects that use artifacts of this build. + * + * @return + * For each project with fingerprinting enabled, returns the range + * of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.) + */ + public Map getDownstreamBuilds() { + Map r = new HashMap(); + for (AbstractProject p : getParent().getDownstreamProjects()) { + if (p.isFingerprintConfigured()) + r.put(p,getDownstreamRelationship(p)); + } + return r; + } + + /** + * Gets the upstream builds of this build, which are the builds of the + * upstream projects whose artifacts feed into this build. + * @return empty if there is no {@link FingerprintAction} (even if there is an {@link Cause.UpstreamCause}) + * @see #getTransitiveUpstreamBuilds() + */ + public Map getUpstreamBuilds() { + return _getUpstreamBuilds(getParent().getUpstreamProjects()); + } + + /** + * Works like {@link #getUpstreamBuilds()} but also includes all the transitive + * dependencies as well. */ - public static final class DependencyChange extends Run.DependencyChange { - public DependencyChange(Job project, int fromId, int toId) { - super(project, fromId, toId); + public Map getTransitiveUpstreamBuilds() { + return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects()); + } + + private Map _getUpstreamBuilds(Collection projects) { + Map r = new HashMap(); + for (AbstractProject p : projects) { + int n = getUpstreamRelationship(p); + if (n>=0) + r.put(p,n); + } + return r; + } + + /** + * Gets the changes in the dependency between the given build and this build. + * @return empty if there is no {@link FingerprintAction} + */ + public Map getDependencyChanges(AbstractBuild from) { + if (from==null) return Collections.emptyMap(); // make it easy to call this from views + FingerprintAction n = this.getAction(FingerprintAction.class); + FingerprintAction o = from.getAction(FingerprintAction.class); + if (n==null || o==null) return Collections.emptyMap(); + + Map ndep = n.getDependencies(true); + Map odep = o.getDependencies(true); + + Map r = new HashMap(); + + for (Map.Entry entry : odep.entrySet()) { + AbstractProject p = entry.getKey(); + Integer oldNumber = entry.getValue(); + Integer newNumber = ndep.get(p); + if (newNumber!=null && oldNumber.compareTo(newNumber)<0) { + r.put(p,new DependencyChange(p,oldNumber,newNumber)); + } + } + + return r; + } + + /** + * Represents a change in the dependency. + */ + public static final class DependencyChange { + /** + * The dependency project. + */ + public final AbstractProject project; + /** + * Version of the dependency project used in the previous build. + */ + public final int fromId; + /** + * {@link Build} object for {@link #fromId}. Can be null if the log is gone. + */ + public final AbstractBuild from; + /** + * Version of the dependency project used in this build. + */ + public final int toId; + + public final AbstractBuild to; + + public DependencyChange(AbstractProject project, int fromId, int toId) { + this.project = project; + this.fromId = fromId; + this.toId = toId; + this.from = project.getBuildByNumber(fromId); + this.to = project.getBuildByNumber(toId); + } + + /** + * Gets the {@link AbstractBuild} objects (fromId,toId]. + *

+ * This method returns all such available builds in the ascending order + * of IDs, but due to log rotations, some builds may be already unavailable. + */ + public List getBuilds() { + List r = new ArrayList(); + + AbstractBuild b = project.getNearestBuild(fromId); + if (b!=null && b.getNumber()==fromId) + b = b.getNextBuild(); // fromId exclusive + + while (b!=null && b.getNumber()<=toId) { + r.add(b); + b = b.getNextBuild(); + } + + return r; } } diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 48754a35333b..7e003bfca930 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -471,6 +471,28 @@ public String getBuildNowText() { return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, getParameterizedJobMixIn().getBuildNowText()); } + /** + * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}. + * + *

+ * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs + * that acts as a single unit. This method can be used to find the top most dominating job that + * covers such a tree. + * + * @return never null. + * @see AbstractBuild#getRootBuild() + */ + public AbstractProject getRootProject() { + if (this instanceof TopLevelItem) { + return this; + } else { + ItemGroup p = this.getParent(); + if (p instanceof AbstractProject) + return ((AbstractProject) p).getRootProject(); + return this; + } + } + /** * Gets the directory where the module is checked out. * @@ -1090,9 +1112,9 @@ public BecauseOfBuildInProgress(@Nonnull AbstractBuild build) { * Because the downstream build is in progress, and we are configured to wait for that. */ public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage { - public final Job up; + public final AbstractProject up; - public BecauseOfDownstreamBuildInProgress(Job up) { + public BecauseOfDownstreamBuildInProgress(AbstractProject up) { this.up = up; } @@ -1106,9 +1128,9 @@ public String getShortDescription() { * Because the upstream build is in progress, and we are configured to wait for that. */ public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage { - public final Job up; + public final AbstractProject up; - public BecauseOfUpstreamBuildInProgress(Job up) { + public BecauseOfUpstreamBuildInProgress(AbstractProject up) { this.up = up; } @@ -1133,18 +1155,52 @@ public CauseOfBlockage getCauseOfBlockage() { } } if (blockBuildWhenDownstreamBuilding()) { - Job bup = getBuildingDownstream(); + AbstractProject bup = getBuildingDownstream(); if (bup!=null) return new BecauseOfDownstreamBuildInProgress(bup); } if (blockBuildWhenUpstreamBuilding()) { - Job bup = getBuildingUpstream(); + AbstractProject bup = getBuildingUpstream(); if (bup!=null) return new BecauseOfUpstreamBuildInProgress(bup); } return null; } + /** + * Returns the project if any of the downstream project is either + * building, waiting, pending or buildable. + *

+ * This means eventually there will be an automatic triggering of + * the given project (provided that all builds went smoothly.) + */ + public AbstractProject getBuildingDownstream() { + Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + + for (AbstractProject tup : getTransitiveDownstreamProjects()) { + if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) + return tup; + } + return null; + } + + /** + * Returns the project if any of the upstream project is either + * building or is in the queue. + *

+ * This means eventually there will be an automatic triggering of + * the given project (provided that all builds went smoothly.) + */ + public AbstractProject getBuildingUpstream() { + Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); + + for (AbstractProject tup : getTransitiveUpstreamProjects()) { + if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) + return tup; + } + return null; + } + public List getSubTasks() { List r = new ArrayList(); r.add(this); @@ -1580,31 +1636,97 @@ public T getTrigger(Class clazz) { */ public abstract boolean isFingerprintConfigured(); + /** + * Gets the other {@link AbstractProject}s that should be built + * when a build of this project is completed. + */ + @Exported + public final List getDownstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getDownstream(this); + } + + @Exported + public final List getUpstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getUpstream(this); + } + /** * Returns only those upstream projects that defines {@link BuildTrigger} to this project. - * This is a subset of {@link #getUpstreamProjects()}, only including {@link AbstractProject}s. + * This is a subset of {@link #getUpstreamProjects()} *

No longer used in the UI. * @return A List of upstream projects that has a {@link BuildTrigger} to this project. */ public final List getBuildTriggerUpstreamProjects() { ArrayList result = new ArrayList(); - for (Job j : getUpstreamProjects()) { - if (j instanceof AbstractProject) { - AbstractProject ap = (AbstractProject) j; - BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class); - if (buildTrigger != null) - if (buildTrigger.getChildProjects(ap).contains(this)) - result.add(ap); - } + for (AbstractProject ap : getUpstreamProjects()) { + BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class); + if (buildTrigger != null) + if (buildTrigger.getChildProjects(ap).contains(this)) + result.add(ap); } return result; } + /** + * Gets all the upstream projects including transitive upstream projects. + * + * @since 1.138 + */ + public final Set getTransitiveUpstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this); + } + + /** + * Gets all the downstream projects including transitive downstream projects. + * + * @since 1.138 + */ + public final Set getTransitiveDownstreamProjects() { + return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this); + } + + /** + * Gets the dependency relationship map between this project (as the source) + * and that project (as the sink.) + * + * @return + * can be empty but not null. build number of this project to the build + * numbers of that project. + */ + public SortedMap getRelationship(AbstractProject that) { + TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); + + checkAndRecord(that, r, this.getBuilds()); + // checkAndRecord(that, r, that.getBuilds()); + + return r; + } + + /** + * Helper method for getDownstreamRelationship. + * + * For each given build, find the build number range of the given project and put that into the map. + */ + private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) { + for (R build : builds) { + RangeSet rs = build.getDownstreamRelationship(that); + if(rs==null || rs.isEmpty()) + continue; + + int n = build.getNumber(); + + RangeSet value = r.get(n); + if(value==null) + r.put(n,rs); + else + value.add(rs); + } + } + /** * Builds the dependency graph. * Since 1.558, not abstract and by default includes dependencies contributed by {@link #triggers()}. */ - @Override protected void buildDependencyGraph(DependencyGraph graph) { triggers().buildDependencyGraph(this, graph); } @@ -2039,6 +2161,12 @@ List getSeeds() { return Items.findNearest(AbstractProject.class, name, context); } + private static final Comparator REVERSE_INTEGER_COMPARATOR = new Comparator() { + public int compare(Integer o1, Integer o2) { + return o2-o1; + } + }; + private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName()); /** diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index f52ec6459eee..237eba1b40f5 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -68,17 +68,17 @@ * @see Jenkins#getDependencyGraph() * @author Kohsuke Kawaguchi */ -public class DependencyGraph implements Comparator { +public class DependencyGraph implements Comparator { - private Map> forward = new HashMap>(); - private Map> backward = new HashMap>(); + private Map> forward = new HashMap>(); + private Map> backward = new HashMap>(); private transient Map, Object> computationalData; private boolean built; - private Comparator> topologicalOrder; - private List> topologicallySorted; + private Comparator> topologicalOrder; + private List> topologicallySorted; /** * Builds the dependency graph. @@ -91,7 +91,7 @@ public void build() { SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); try { this.computationalData = new HashMap, Object>(); - for( Job p : Jenkins.getInstance().allItems(Job.class) ) + for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) p.buildDependencyGraph(this); forward = finalize(forward); @@ -110,36 +110,36 @@ public void build() { * See http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm */ private void topologicalDagSort() { - DirectedGraph g = new DirectedGraph() { + DirectedGraph g = new DirectedGraph() { @Override - protected Collection nodes() { - final Set nodes = new HashSet(); + protected Collection nodes() { + final Set nodes = new HashSet(); nodes.addAll(forward.keySet()); nodes.addAll(backward.keySet()); return nodes; } @Override - protected Collection forward(Job node) { + protected Collection forward(AbstractProject node) { return getDownstream(node); } }; - List> sccs = g.getStronglyConnectedComponents(); + List> sccs = g.getStronglyConnectedComponents(); - final Map topoOrder = new HashMap(); - topologicallySorted = new ArrayList>(); + final Map topoOrder = new HashMap(); + topologicallySorted = new ArrayList>(); int idx=0; - for (SCC scc : sccs) { - for (Job n : scc) { + for (SCC scc : sccs) { + for (AbstractProject n : scc) { topoOrder.put(n,idx++); topologicallySorted.add(n); } } - topologicalOrder = new Comparator>() { + topologicalOrder = new Comparator>() { @Override - public int compare(Job o1, Job o2) { + public int compare(AbstractProject o1, AbstractProject o2) { return topoOrder.get(o1)-topoOrder.get(o2); } }; @@ -179,7 +179,7 @@ public T getComputationalData(Class key) { * @return * can be empty but never null. */ - public List getDownstream(Job p) { + public List getDownstream(AbstractProject p) { return get(forward,p,false); } @@ -189,34 +189,33 @@ public List getDownstream(Job p) { * @return * can be empty but never null. */ - public List getUpstream(Job p) { + public List getUpstream(AbstractProject p) { return get(backward,p,true); } - private List get(Map> map, Job src, boolean up) { + private List get(Map> map, AbstractProject src, boolean up) { List v = map.get(src); if(v==null) return Collections.emptyList(); - List result = new ArrayList(v.size()); + List result = new ArrayList(v.size()); for (DependencyGroup d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject()); - return result; } /** * @since 1.341 */ - public List getDownstreamDependencies(Job p) { + public List getDownstreamDependencies(AbstractProject p) { return get(forward,p); } /** * @since 1.341 */ - public List getUpstreamDependencies(Job p) { + public List getUpstreamDependencies(AbstractProject p) { return get(backward,p); } - private List get(Map> map, Job src) { + private List get(Map> map, AbstractProject src) { List v = map.get(src); if(v==null) { return ImmutableList.of(); @@ -234,7 +233,7 @@ private List get(Map> map, Job src) { * @deprecated since 1.341; use {@link #addDependency(Dependency)} */ @Deprecated - public void addDependency(Job upstream, Job downstream) { + public void addDependency(AbstractProject upstream, AbstractProject downstream) { addDependency(new Dependency(upstream,downstream)); } @@ -252,8 +251,8 @@ public void addDependency(Dependency dep) { * @deprecated since 1.341 */ @Deprecated - public void addDependency(Job upstream, Collection downstream) { - for (Job p : downstream) + public void addDependency(AbstractProject upstream, Collection downstream) { + for (AbstractProject p : downstream) addDependency(upstream,p); } @@ -261,15 +260,15 @@ public void addDependency(Job upstream, Collection downstream) { * @deprecated since 1.341 */ @Deprecated - public void addDependency(Collection upstream, Job downstream) { - for (Job p : upstream) + public void addDependency(Collection upstream, AbstractProject downstream) { + for (AbstractProject p : upstream) addDependency(p,downstream); } /** * Lists up {@link DependencyDeclarer} from the collection and let them builds dependencies. */ - public void addDependencyDeclarers(Job upstream, Collection possibleDependecyDeclarers) { + public void addDependencyDeclarers(AbstractProject upstream, Collection possibleDependecyDeclarers) { for (Object o : possibleDependecyDeclarers) { if (o instanceof DependencyDeclarer) { DependencyDeclarer dd = (DependencyDeclarer) o; @@ -284,15 +283,15 @@ public void addDependencyDeclarers(Job upstream, Collection possibleDependecy * A non-direct dependency is a path of dependency "edge"s from the source to the destination, * where the length is greater than 1. */ - public boolean hasIndirectDependencies(Job src, Job dst) { - Set visited = new HashSet(); - Stack queue = new Stack(); + public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) { + Set visited = new HashSet(); + Stack queue = new Stack(); queue.addAll(getDownstream(src)); queue.remove(dst); while(!queue.isEmpty()) { - Job p = queue.pop(); + AbstractProject p = queue.pop(); if(p==dst) return true; if(visited.add(p)) @@ -305,27 +304,27 @@ public boolean hasIndirectDependencies(Job src, Job dst) { /** * Gets all the direct and indirect upstream dependencies of the given project. */ - public Set getTransitiveUpstream(Job src) { + public Set getTransitiveUpstream(AbstractProject src) { return getTransitive(backward,src,true); } /** * Gets all the direct and indirect downstream dependencies of the given project. */ - public Set getTransitiveDownstream(Job src) { + public Set getTransitiveDownstream(AbstractProject src) { return getTransitive(forward,src,false); } - private Set getTransitive(Map> direction, Job src, boolean up) { - Set visited = new HashSet(); - Stack queue = new Stack(); + private Set getTransitive(Map> direction, AbstractProject src, boolean up) { + Set visited = new HashSet(); + Stack queue = new Stack(); queue.add(src); while(!queue.isEmpty()) { - Job p = queue.pop(); + AbstractProject p = queue.pop(); - for (Job child : get(direction,p,up)) { + for (AbstractProject child : get(direction,p,up)) { if(visited.add(child)) queue.add(child); } @@ -334,7 +333,7 @@ private Set getTransitive(Map> direction, Job sr return visited; } - private void add(Map> map, Job key, Dependency dep) { + private void add(Map> map, AbstractProject key, Dependency dep) { List set = map.get(key); if(set==null) { set = new ArrayList(); @@ -352,8 +351,8 @@ private void add(Map> map, Job key, Dependency dep) { set.add(new DependencyGroup(dep)); } - private Map> finalize(Map> m) { - for (Entry> e : m.entrySet()) { + private Map> finalize(Map> m) { + for (Entry> e : m.entrySet()) { Collections.sort( e.getValue(), NAME_COMPARATOR ); e.setValue( Collections.unmodifiableList(e.getValue()) ); } @@ -372,7 +371,7 @@ public int compare(DependencyGroup lhs, DependencyGroup rhs) { /** * Compare two Projects based on the topological order defined by this Dependency Graph */ - public int compare(Job o1, Job o2) { + public int compare(AbstractProject o1, AbstractProject o2) { return topologicalOrder.compare(o1,o2); } @@ -384,7 +383,7 @@ public int compare(Job o1, Job o2) { * * @since 1.521 */ - public List> getTopologicallySorted() { + public List> getTopologicallySorted() { return topologicallySorted; } @@ -393,18 +392,18 @@ public List> getTopologicallySorted() { * @since 1.341 */ public static class Dependency { - private Job upstream, downstream; + private AbstractProject upstream, downstream; - public Dependency(Job upstream, Job downstream) { + public Dependency(AbstractProject upstream, AbstractProject downstream) { this.upstream = upstream; this.downstream = downstream; } - public Job getUpstreamProject() { + public AbstractProject getUpstreamProject() { return upstream; } - public Job getDownstreamProject() { + public AbstractProject getDownstreamProject() { return downstream; } @@ -421,7 +420,7 @@ public Job getDownstreamProject() { * @param actions Add Actions for the triggered build to this list; never null * @return True to trigger a build of the downstream project */ - public boolean shouldTriggerBuild(Run build, TaskListener listener, + public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { return true; } @@ -463,7 +462,7 @@ private static class DependencyGroup { DependencyGroup(Dependency first) { this.upstream = first.getUpstreamProject(); - this.downstream = first.getDownstreamProject(); + this.downstream= first.getDownstreamProject(); group.add(first); } @@ -475,13 +474,13 @@ public Set getGroup() { return group; } - private Job upstream, downstream; + private AbstractProject upstream, downstream; - public Job getUpstreamProject() { + public AbstractProject getUpstreamProject() { return upstream; } - public Job getDownstreamProject() { + public AbstractProject getDownstreamProject() { return downstream; } } diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index ed5c1ea75bc3..a3ac3fdd9d2f 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -72,14 +72,11 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedMap; -import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; @@ -182,13 +179,6 @@ public abstract class Job, RunT extends Run> properties = new CopyOnWriteList>(); - private static final Comparator REVERSE_INTEGER_COMPARATOR = new Comparator() { - public int compare(Integer o1, Integer o2) { - return o2-o1; - } - }; - - @Restricted(NoExternalUse.class) public transient RunIdMigrator runIdMigrator; @@ -342,63 +332,6 @@ public boolean isKeepDependencies() { return keepDependencies; } - - /** - * Returns the project if any of the downstream project is either - * building, waiting, pending or buildable. - *

- * This means eventually there will be an automatic triggering of - * the given project (provided that all builds went smoothly.) - */ - public Job getBuildingDownstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); - - for (Job tup : getTransitiveDownstreamProjects()) { - if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) - return tup; - } - return null; - } - - /** - * Returns the project if any of the upstream project is either - * building or is in the queue. - *

- * This means eventually there will be an automatic triggering of - * the given project (provided that all builds went smoothly.) - */ - public Job getBuildingUpstream() { - Set unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks(); - - for (Job tup : getTransitiveUpstreamProjects()) { - if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup))) - return tup; - } - return null; - } - - /** - * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link Job}. - * - *

- * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs - * that acts as a single unit. This method can be used to find the top most dominating job that - * covers such a tree. - * - * @return never null. - * @see Run#getRootBuild() - */ - public Job getRootProject() { - if (this instanceof TopLevelItem) { - return this; - } else { - ItemGroup p = this.getParent(); - if (p instanceof Job) - return ((Job) p).getRootProject(); - return this; - } - } - /** * Allocates a new buildCommand number. */ @@ -566,86 +499,9 @@ public Collection getAllJobs() { return Collections. singleton(this); } - /** - * Builds the dependency graph. No-op by default. - * @param graph - */ - protected void buildDependencyGraph(DependencyGraph graph) { - } - - /** - * Gets the other {@link Job}s that should be built - * when a build of this project is completed. - */ - @Exported - public final List getDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getDownstream(this); - } - - @Exported - public final List getUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getUpstream(this); - } - - /** - * Gets all the upstream projects including transitive upstream projects. - * - * @since 1.138 - */ - public final Set getTransitiveUpstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this); - } - - /** - * Gets all the downstream projects including transitive downstream projects. - * - * @since 1.138 - */ - public final Set getTransitiveDownstreamProjects() { - return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this); - } - - /** - * Gets the dependency relationship map between this project (as the source) - * and that project (as the sink.) - * - * @return - * can be empty but not null. build number of this project to the build - * numbers of that project. - */ - public SortedMap getRelationship(AbstractProject that) { - TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); - - checkAndRecord(that, r, this.getBuilds()); - // checkAndRecord(that, r, that.getBuilds()); - - return r; - } - - /** - * Helper method for getDownstreamRelationship. - * - * For each given build, find the build number range of the given project and put that into the map. - */ - private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) { - for (RunT build : builds) { - RangeSet rs = build.getDownstreamRelationship(that); - if(rs==null || rs.isEmpty()) - continue; - - int n = build.getNumber(); - - RangeSet value = r.get(n); - if(value==null) - r.put(n,rs); - else - value.add(rs); - } - } - /** * Adds {@link JobProperty}. - * + * * @since 1.188 */ public void addProperty(JobProperty jobProp) throws IOException { diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 2959b2fd021a..b2153447338d 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -44,10 +44,6 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.StandardOpenOption; - -import hudson.tasks.Fingerprinter; -import hudson.util.AdaptedIterator; -import hudson.util.Iterators; import jenkins.util.SystemProperties; import hudson.Util; import hudson.XmlFile; @@ -84,14 +80,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -149,11 +143,6 @@ public abstract class Run ,RunT extends Run> extends Actionable implements ExtensionPoint, Comparable, AccessControlled, PersistenceRoot, DescriptorByNameOwner, OnMaster { - /** - * Set if we want the blame information to flow from upstream to downstream build. - */ - private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); - /** * The original {@link Queue.Item#getId()} has not yet been mapped onto the {@link Run} instance. * @since 1.601 @@ -972,246 +961,6 @@ protected void dropLinks() { return nextBuild; } - /** - * Gets the dependency relationship from this build (as the source) - * and that project (as the sink.) - * - * @return - * range of build numbers that represent which downstream builds are using this build. - * The range will be empty if no build of that project matches this (or there is no {@link Fingerprinter.FingerprintAction}), but it'll never be null. - */ - public Fingerprint.RangeSet getDownstreamRelationship(Job that) { - Fingerprint.RangeSet rs = new Fingerprint.RangeSet(); - - Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class); - if (f==null) return rs; - - // look for fingerprints that point to this build as the source, and merge them all - for (Fingerprint e : f.getFingerprints().values()) { - - if (upstreamCulprits) { - // With upstreamCulprits, we allow downstream relationships - // from intermediate jobs - rs.add(e.getRangeSet(that)); - } else { - Fingerprint.BuildPtr o = e.getOriginal(); - if (o!=null && o.is(this)) - rs.add(e.getRangeSet(that)); - } - } - - return rs; - } - - /** - * Works like {@link #getDownstreamRelationship(Job)} but returns - * the actual build objects, in ascending order. - * @since 1.150 - */ - public Iterable> getDownstreamBuilds(final Job that) { - final Iterable nums = getDownstreamRelationship(that).listNumbers(); - - return new Iterable>() { - public Iterator> iterator() { - return Iterators.removeNull( - new AdaptedIterator>(nums) { - protected Run adapt(Integer item) { - return that.getBuildByNumber(item); - } - }); - } - }; - } - - /** - * Gets the dependency relationship from this build (as the sink) - * and that project (as the source.) - * - * @return - * Build number of the upstream build that feed into this build, - * or -1 if no record is available (for example if there is no {@link Fingerprinter.FingerprintAction}, even if there is an {@link Cause.UpstreamCause}). - */ - public int getUpstreamRelationship(Job that) { - Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class); - if (f==null) return -1; - - int n = -1; - - // look for fingerprints that point to the given project as the source, and merge them all - for (Fingerprint e : f.getFingerprints().values()) { - if (upstreamCulprits) { - // With upstreamCulprits, we allow upstream relationships - // from intermediate jobs - Fingerprint.RangeSet rangeset = e.getRangeSet(that); - if (!rangeset.isEmpty()) { - n = Math.max(n, rangeset.listNumbersReverse().iterator().next()); - } - } else { - Fingerprint.BuildPtr o = e.getOriginal(); - if (o!=null && o.belongsTo(that)) - n = Math.max(n,o.getNumber()); - } - } - - return n; - } - - /** - * Works like {@link #getUpstreamRelationship(Job)} but returns the - * actual build object. - * - * @return - * null if no such upstream build was found, or it was found but the - * build record is already lost. - */ - public Run getUpstreamRelationshipBuild(Job that) { - int n = getUpstreamRelationship(that); - if (n==-1) return null; - return that.getBuildByNumber(n); - } - - /** - * Gets the downstream builds of this build, which are the builds of the - * downstream projects that use artifacts of this build. - * - * @return - * For each project with fingerprinting enabled, returns the range - * of builds (which can be empty if no build uses the artifact from this build. - */ - public Map getDownstreamBuilds() { - Map r = new HashMap(); - for (Job p : getParent().getDownstreamProjects()) { - r.put(p,getDownstreamRelationship(p)); - } - return r; - } - - /** - * Gets the upstream builds of this build, which are the builds of the - * upstream projects whose artifacts feed into this build. - * @return empty if there is no {@link Fingerprinter.FingerprintAction} (even if there is an {@link Cause.UpstreamCause}) - * @see #getTransitiveUpstreamBuilds() - */ - public Map getUpstreamBuilds() { - return _getUpstreamBuilds(getParent().getUpstreamProjects()); - } - - /** - * Works like {@link #getUpstreamBuilds()} but also includes all the transitive - * dependencies as well. - */ - public Map getTransitiveUpstreamBuilds() { - return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects()); - } - - private Map _getUpstreamBuilds(Collection projects) { - Map r = new HashMap(); - for (Job p : projects) { - int n = getUpstreamRelationship(p); - if (n>=0) - r.put(p,n); - } - return r; - } - - /** - * Gets the changes in the dependency between the given build and this build. - * @return empty if there is no {@link Fingerprinter.FingerprintAction} - */ - public Map getDependencyChanges(Run from) { - if (from==null) return Collections.emptyMap(); // make it easy to call this from views - Fingerprinter.FingerprintAction n = this.getAction(Fingerprinter.FingerprintAction.class); - Fingerprinter.FingerprintAction o = from.getAction(Fingerprinter.FingerprintAction.class); - if (n==null || o==null) return Collections.emptyMap(); - - Map ndep = n.getDependencies(true); - Map odep = o.getDependencies(true); - - Map r = new HashMap(); - - for (Map.Entry entry : odep.entrySet()) { - Job p = entry.getKey(); - Integer oldNumber = entry.getValue(); - Integer newNumber = ndep.get(p); - if (newNumber!=null && oldNumber.compareTo(newNumber)<0) { - r.put(p,new Run.DependencyChange(p,oldNumber,newNumber)); - } - } - - return r; - } - - /** - * Gets the nearest ancestor {@link Run} that belongs to - * {@linkplain Job#getRootProject() the root project of getParent()} that - * dominates/governs/encompasses this build. - * - *

- * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs, - * and still in some of them, builds of child projects are related/tied to that of the parent project. - * In such a case, this method returns the governing build. - * - * @return never null. In the worst case the build dominates itself. - * @since 1.421 - * @see Job#getRootProject() - */ - public Run getRootBuild() { - return this; - } - - /** - * Represents a change in the dependency. - */ - public static class DependencyChange { - /** - * The dependency project. - */ - public final Job project; - /** - * Version of the dependency project used in the previous build. - */ - public final int fromId; - /** - * {@link Run} object for {@link #fromId}. Can be null if the log is gone. - */ - public final Run from; - /** - * Version of the dependency project used in this build. - */ - public final int toId; - - public final Run to; - - public DependencyChange(Job project, int fromId, int toId) { - this.project = project; - this.fromId = fromId; - this.toId = toId; - this.from = project.getBuildByNumber(fromId); - this.to = project.getBuildByNumber(toId); - } - - /** - * Gets the {@link Run} objects (fromId,toId]. - *

- * This method returns all such available builds in the ascending order - * of IDs, but due to log rotations, some builds may be already unavailable. - */ - public List getBuilds() { - List r = new ArrayList(); - - Run b = project.getNearestBuild(fromId); - if (b!=null && b.getNumber()==fromId) - b = b.getNextBuild(); // fromId exclusive - - while (b!=null && b.getNumber()<=toId) { - r.add(b); - b = b.getNextBuild(); - } - - return r; - } - } - /** * Returns the URL of this {@link Run}, relative to the context root of Hudson. * diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index b205e75352ab..d2b0a91f6b0d 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -245,24 +245,21 @@ public int compare(Dependency lhs, Dependency rhs) { SecurityContext orig = ACL.impersonate(auth); try { if (dep.shouldTriggerBuild(build, listener, buildActions)) { - Job j = dep.getDownstreamProject(); - if (j instanceof AbstractProject) { - AbstractProject p = (AbstractProject) j; - // Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission: - if (p.isDisabled()) { - logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p))); - continue; - } - boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run) build), buildActions.toArray(new Action[buildActions.size()])); - if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) { - String name = ModelHyperlinkNote.encodeTo(p); - if (scheduled) { - logger.println(Messages.BuildTrigger_Triggering(name)); - } else { - logger.println(Messages.BuildTrigger_InQueue(name)); - } - } // otherwise upstream users should not know that it happened + AbstractProject p = dep.getDownstreamProject(); + // Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission: + if (p.isDisabled()) { + logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p))); + continue; } + boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run)build), buildActions.toArray(new Action[buildActions.size()])); + if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) { + String name = ModelHyperlinkNote.encodeTo(p); + if (scheduled) { + logger.println(Messages.BuildTrigger_Triggering(name)); + } else { + logger.println(Messages.BuildTrigger_InQueue(name)); + } + } // otherwise upstream users should not know that it happened } } finally { SecurityContextHolder.setContext(orig); @@ -272,28 +269,24 @@ public int compare(Dependency lhs, Dependency rhs) { return true; } - public void buildDependencyGraph(Job j, DependencyGraph graph) { - if (j instanceof AbstractProject) { - AbstractProject owner = (AbstractProject) j; - - for (AbstractProject p : getChildProjects(owner)) - graph.addDependency(new Dependency(owner, p) { - @Override - public boolean shouldTriggerBuild(Run build, TaskListener listener, - List actions) { - Job downstream = getDownstreamProject(); - if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders - LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[]{Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()}); - return false; // do not even issue a warning to build log - } - if (!downstream.hasPermission(Item.BUILD)) { - listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream))); - return false; - } - return build.getResult().isBetterOrEqualTo(threshold); + public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { + for (AbstractProject p : getChildProjects(owner)) + graph.addDependency(new Dependency(owner, p) { + @Override + public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, + List actions) { + AbstractProject downstream = getDownstreamProject(); + if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders + LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()}); + return false; // do not even issue a warning to build log } - }); - } + if (!downstream.hasPermission(Item.BUILD)) { + listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream))); + return false; + } + return build.getResult().isBetterOrEqualTo(threshold); + } + }); } @Override diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java index 94024d9a9b00..fb45ef9cc2cb 100644 --- a/core/src/main/java/hudson/tasks/Fingerprinter.java +++ b/core/src/main/java/hudson/tasks/Fingerprinter.java @@ -149,7 +149,7 @@ public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } - public void buildDependencyGraph(Job owner, DependencyGraph graph) { + public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { if (enableFingerprintsInDependencyGraph) { RunList builds = owner.getBuilds(); Set seenUpstreamProjects = new HashSet(); @@ -157,12 +157,12 @@ public void buildDependencyGraph(Job owner, DependencyGraph graph) { for ( ListIterator iter = builds.listIterator(); iter.hasNext(); ) { Run build = (Run) iter.next(); for (FingerprintAction action : build.getActions(FingerprintAction.class)) { - for (Job key : action.getDependencies().keySet()) { + for (AbstractProject key : action.getDependencies().keySet()) { if (key == owner) { continue; // Avoid self references } - Job p = key; + AbstractProject p = key; // TODO is this harmful to call unconditionally, so it would apply also to MavenModule for example? if (key.getClass().getName().equals("hudson.matrix.MatrixConfiguration")) { p = key.getRootProject(); @@ -175,7 +175,7 @@ public void buildDependencyGraph(Job owner, DependencyGraph graph) { seenUpstreamProjects.add(p.getName()); graph.addDependency(new Dependency(p, owner) { @Override - public boolean shouldTriggerBuild(Run build, + public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { // Fingerprints should not trigger builds. @@ -389,7 +389,7 @@ public synchronized Map getFingerprints() { /** * Gets the dependency to other existing builds in a map. */ - public Map getDependencies() { + public Map getDependencies() { return getDependencies(false); } @@ -400,8 +400,8 @@ public Map getDependencies() { * the result, even if it doesn't exist * @since 1.430 */ - public Map getDependencies(boolean includeMissing) { - Map r = new HashMap(); + public Map getDependencies(boolean includeMissing) { + Map r = new HashMap(); for (Fingerprint fp : getFingerprints().values()) { BuildPtr bp = fp.getOriginal(); @@ -411,6 +411,11 @@ public Map getDependencies(boolean includeMissing) { try { Job job = bp.getJob(); if (job==null) continue; // project no longer exists + if (!(job instanceof AbstractProject)) { + // Ignoring this for now. In the future we may want a dependency map function not limited to AbstractProject. + // (Could be used by getDependencyChanges if pulled up from AbstractBuild into Run, for example.) + continue; + } if (job.getParent()==build.getParent()) continue; // we are the parent of the build owner, that is almost like we are the owner if(!includeMissing && job.getBuildByNumber(bp.getNumber())==null) @@ -419,7 +424,7 @@ public Map getDependencies(boolean includeMissing) { Integer existing = r.get(job); if(existing!=null && existing>bp.getNumber()) continue; // the record in the map is already up to date - r.put(job, bp.getNumber()); + r.put((AbstractProject) job, bp.getNumber()); } catch (AccessDeniedException e) { // Need to log in to access this job, so ignore continue; diff --git a/core/src/main/java/jenkins/model/DependencyDeclarer.java b/core/src/main/java/jenkins/model/DependencyDeclarer.java index deaee082aa7b..a6cb2c52c61e 100644 --- a/core/src/main/java/jenkins/model/DependencyDeclarer.java +++ b/core/src/main/java/jenkins/model/DependencyDeclarer.java @@ -25,7 +25,6 @@ import hudson.model.AbstractProject; import hudson.model.DependencyGraph; -import hudson.model.Job; import hudson.tasks.BuildWrapper; import hudson.tasks.Builder; import hudson.tasks.Publisher; @@ -51,7 +50,7 @@ public interface DependencyDeclarer { // so that this concept can be extended elsewhere, like maven projects and so on. /** - * Invoked from {@link Job#buildDependencyGraph(DependencyGraph)}. + * Invoked from {@link AbstractProject#buildDependencyGraph(DependencyGraph)}. * * @param owner * The project that owns the publishers, builders, etc. @@ -62,5 +61,5 @@ public interface DependencyDeclarer { * @param graph * The dependency graph being built. Never null. */ - void buildDependencyGraph(Job owner, DependencyGraph graph); + void buildDependencyGraph(AbstractProject owner, DependencyGraph graph); } diff --git a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java b/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java index 0a18bdba6cc2..cc9b3986995c 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java +++ b/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java @@ -95,17 +95,12 @@ public abstract class RunWithSCMMixIn & Queue.Task, if (p instanceof AbstractBuild && upstreamCulprits) { // If we have dependencies since the last successful build, add their authors to our list if (p.getPreviousNotFailedBuild() != null) { - Map depmap = - p.getDependencyChanges(p.getPreviousSuccessfulBuild()); - for (Run.DependencyChange dep : depmap.values()) { - for (Run rawRun : dep.getBuilds()) { - if (rawRun instanceof RunWithSCM) { - RunWithSCM b = (RunWithSCM) rawRun; - for (ChangeLogSet c : b.getChangeSets()) { - for (ChangeLogSet.Entry e : c) { - r.add(e.getAuthor()); - } - } + Map depmap = + ((AbstractBuild) p).getDependencyChanges((AbstractBuild)p.getPreviousSuccessfulBuild()); + for (AbstractBuild.DependencyChange dep : depmap.values()) { + for (AbstractBuild b : dep.getBuilds()) { + for (ChangeLogSet.Entry entry : b.getChangeSet()) { + r.add(entry.getAuthor()); } } } diff --git a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java index 8ca337761a95..e9be86f3dd65 100644 --- a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java +++ b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java @@ -139,10 +139,10 @@ private boolean shouldTrigger(Run upstreamBuild, TaskListener listener) { return result != null && result.isBetterOrEqualTo(threshold); } - @Override public void buildDependencyGraph(final Job downstream, DependencyGraph graph) { - for (Job upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, Job.class)) { + @Override public void buildDependencyGraph(final AbstractProject downstream, DependencyGraph graph) { + for (AbstractProject upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, AbstractProject.class)) { graph.addDependency(new DependencyGraph.Dependency(upstream, downstream) { - @Override public boolean shouldTriggerBuild(Run upstreamBuild, TaskListener listener, List actions) { + @Override public boolean shouldTriggerBuild(AbstractBuild upstreamBuild, TaskListener listener, List actions) { return shouldTrigger(upstreamBuild, listener); } }); diff --git a/test/src/test/java/hudson/model/DependencyGraphTest.java b/test/src/test/java/hudson/model/DependencyGraphTest.java index 5a98c90fd962..1bb7e134cf38 100644 --- a/test/src/test/java/hudson/model/DependencyGraphTest.java +++ b/test/src/test/java/hudson/model/DependencyGraphTest.java @@ -93,10 +93,10 @@ private TestDeclarer(Result buildResult, AbstractProject down) { super(buildResult); this.down = down; } - public void buildDependencyGraph(Job owner, DependencyGraph graph) { + public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { graph.addDependency(new DependencyGraph.Dependency(owner, down) { @Override - public boolean shouldTriggerBuild(Run build, TaskListener listener, + public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { // Trigger for ODD build number if (build.getNumber() % 2 == 1) { @@ -122,7 +122,7 @@ public void testItemReadPermission() throws Exception { // @LocalData for this test has jobs w/o anonymous Item.READ AbstractProject up = (AbstractProject) jenkins.getItem("hiddenUpstream"); assertNotNull("hiddenUpstream project not found", up); - List down = jenkins.getDependencyGraph().getDownstream(up); + List down = jenkins.getDependencyGraph().getDownstream(up); assertEquals("Should have one downstream project", 1, down.size()); } finally { SecurityContextHolder.clearContext(); @@ -150,9 +150,9 @@ public void testTopologicalSort() throws Exception { jenkins.rebuildDependencyGraph(); DependencyGraph g = jenkins.getDependencyGraph(); - List> sorted = g.getTopologicallySorted(); + List> sorted = g.getTopologicallySorted(); StringBuilder buf = new StringBuilder(); - for (Job p : sorted) { + for (AbstractProject p : sorted) { buf.append(p.getName()); } String r = buf.toString(); diff --git a/test/src/test/java/hudson/tasks/BuildTriggerTest.java b/test/src/test/java/hudson/tasks/BuildTriggerTest.java index e16b6d221c92..fda4972badaa 100644 --- a/test/src/test/java/hudson/tasks/BuildTriggerTest.java +++ b/test/src/test/java/hudson/tasks/BuildTriggerTest.java @@ -38,7 +38,6 @@ import hudson.model.DependencyGraph; import hudson.model.DependencyGraph.Dependency; import hudson.model.Item; -import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; @@ -368,7 +367,7 @@ private Dep(AbstractProject upstream, AbstractProject downstream) { } @Override - public boolean shouldTriggerBuild(Run build, TaskListener listener, List actions) { + public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { if (block) { try { Thread.sleep(5000); @@ -387,12 +386,9 @@ public SlowTrigger(String childProjects) { } @Override @SuppressWarnings("rawtypes") - public void buildDependencyGraph(Job j, DependencyGraph graph) { - if (j instanceof AbstractProject) { - AbstractProject owner = (AbstractProject) j; - for (AbstractProject ch : getChildProjects(owner)) { - graph.addDependency(new Dep(owner, ch)); - } + public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { + for (AbstractProject ch: getChildProjects(owner)) { + graph.addDependency(new Dep(owner, ch)); } } } diff --git a/test/src/test/java/hudson/tasks/FingerprinterTest.java b/test/src/test/java/hudson/tasks/FingerprinterTest.java index 680e0373d5d9..d26ce52d1ed1 100644 --- a/test/src/test/java/hudson/tasks/FingerprinterTest.java +++ b/test/src/test/java/hudson/tasks/FingerprinterTest.java @@ -102,8 +102,8 @@ public static void setUp() throws Exception { j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); assertEquals(1, downstreamProjects.size()); assertEquals(1, upstreamProjects.size()); @@ -142,9 +142,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List downstreamProjects2 = upstream2.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List downstreamProjects2 = upstream2.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); assertEquals(1, downstreamProjects.size()); assertEquals(1, downstreamProjects2.size()); @@ -165,9 +165,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen j.jenkins.rebuildDependencyGraph(); - List downstreamProjects = upstream.getDownstreamProjects(); - List upstreamProjects = downstream.getUpstreamProjects(); - List upstreamProjects2 = downstream2.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); + List upstreamProjects2 = downstream2.getUpstreamProjects(); assertEquals(2, downstreamProjects.size()); assertEquals(1, upstreamProjects.size()); @@ -189,8 +189,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen Jenkins.getInstance().rebuildDependencyGraph(); - List upstreamProjects = downstream.getUpstreamProjects(); - List downstreamProjects = upstream.getDownstreamProjects(); + List upstreamProjects = downstream.getUpstreamProjects(); + List downstreamProjects = upstream.getDownstreamProjects(); assertEquals(0, upstreamProjects.size()); assertEquals(0, downstreamProjects.size()); @@ -204,8 +204,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen Jenkins.getInstance().rebuildDependencyGraph(); - List upstreamProjects = p.getUpstreamProjects(); - List downstreamProjects = p.getDownstreamProjects(); + List upstreamProjects = p.getUpstreamProjects(); + List downstreamProjects = p.getDownstreamProjects(); assertEquals(0, upstreamProjects.size()); assertEquals(0, downstreamProjects.size()); @@ -229,9 +229,9 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen assertEquals("There should only be one FreestyleBuild", 1, builds.size()); FreeStyleBuild build = builds.iterator().next(); assertEquals(Result.SUCCESS, build.getResult()); - List downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject); + List downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject); assertTrue(downstream.contains(freestyleProject)); - List upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject); + List upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject); assertTrue(upstream.contains(matrixProject)); } From e6f606465dff9f63c17a7867062dbe6a9ffe94c6 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 10:06:51 -0400 Subject: [PATCH 08/18] Code review responses --- core/src/main/java/hudson/model/Job.java | 14 ++++----- core/src/main/java/hudson/model/View.java | 37 +++++++++++------------ core/src/main/java/hudson/scm/SCM.java | 8 +++-- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index a3ac3fdd9d2f..750fd5e412bd 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -1075,14 +1075,14 @@ public FeedItem(ChangeLogSet.Entry e, int idx) { scmNames.add(s.getDescriptor().getDisplayName()); } scmDisplayName = " " + Util.join(scmNames, ", "); + } - for (RunT r = getLastBuild(); r != null; r = r.getPreviousBuild()) { - int idx = 0; - if (r instanceof RunWithSCMMixIn.RunWithSCM) { - for (ChangeLogSet c : ((RunWithSCMMixIn.RunWithSCM) r).getChangeSets()) { - for (ChangeLogSet.Entry e : c) { - entries.add(new FeedItem(e, idx++)); - } + for (RunT r = getLastBuild(); r != null; r = r.getPreviousBuild()) { + int idx = 0; + if (r instanceof RunWithSCMMixIn.RunWithSCM) { + for (ChangeLogSet c : ((RunWithSCMMixIn.RunWithSCM) r).getChangeSets()) { + for (ChangeLogSet.Entry e : c) { + entries.add(new FeedItem(e, idx++)); } } } diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 2db0f46040bc..278f0b9f5a05 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -116,7 +116,6 @@ import java.util.logging.Logger; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static jenkins.model.Jenkins.checkGoodName; import static jenkins.scm.RunWithSCMMixIn.*; import org.kohsuke.accmod.Restricted; @@ -253,7 +252,7 @@ public String getViewName() { */ public void rename(String newName) throws Failure, FormException { if(name.equals(newName)) return; // noop - checkGoodName(newName); + Jenkins.checkGoodName(newName); if(owner.getView(newName)!=null) throw new FormException(Messages.Hudson_ViewAlreadyExists(newName),"name"); String oldName = name; @@ -730,23 +729,21 @@ private Map getUserInfo(Collection items) { Map users = new HashMap(); for (Item item : items) { for (Job job : item.getAllJobs()) { - if (job instanceof SCMTriggerItem) { - RunList> runs = job.getBuilds(); - for (Run r : runs) { - if (r instanceof RunWithSCM) { - RunWithSCM runWithSCM = (RunWithSCM) r; + RunList> runs = job.getBuilds(); + for (Run r : runs) { + if (r instanceof RunWithSCM) { + RunWithSCM runWithSCM = (RunWithSCM) r; - for (ChangeLogSet c : runWithSCM.getChangeSets()) { - for (Entry entry : c) { - User user = entry.getAuthor(); + for (ChangeLogSet c : runWithSCM.getChangeSets()) { + for (Entry entry : c) { + User user = entry.getAuthor(); - UserInfo info = users.get(user); - if (info == null) - users.put(user, new UserInfo(user, job, r.getTimestamp())); - else if (info.getLastChange().before(r.getTimestamp())) { - info.project = job; - info.lastChange = r.getTimestamp(); - } + UserInfo info = users.get(user); + if (info == null) + users.put(user, new UserInfo(user, job, r.getTimestamp())); + else if (info.getLastChange().before(r.getTimestamp())) { + info.project = job; + info.lastChange = r.getTimestamp(); } } } @@ -1055,7 +1052,7 @@ public FormValidation doCheckJobName(@QueryParameter String value) { } try { - checkGoodName(value); + Jenkins.checkGoodName(value); value = value.trim(); // why trim *after* checkGoodName? not sure, but ItemGroupMixIn.createTopLevelItem does the same Jenkins.getInstance().getProjectNamingStrategy().checkName(value); } catch (Failure e) { @@ -1308,7 +1305,7 @@ public static View create(StaplerRequest req, StaplerResponse rsp, ViewGroup own || requestContentType.startsWith("text/xml")); String name = req.getParameter("name"); - checkGoodName(name); + Jenkins.checkGoodName(name); if(owner.getView(name)!=null) throw new Failure(Messages.Hudson_ViewAlreadyExists(name)); @@ -1370,7 +1367,7 @@ public static View createViewFromXML(String name, InputStream xml) throws IOExce try (InputStream in = new BufferedInputStream(xml)) { View v = (View) Jenkins.XSTREAM.fromXML(in); if (name != null) v.name = name; - checkGoodName(v.name); + Jenkins.checkGoodName(v.name); return v; } catch(StreamException|ConversionException|Error e) {// mostly reflection errors throw new IOException("Unable to read",e); diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index 9c9a85699bff..01b0c2e2b295 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -526,8 +526,10 @@ public void postCheckout(AbstractBuild build, Launcher launcher, FilePath w * can be before/after your checkout method is invoked. So if you are going to provide information about * check out (like SVN revision number that was checked out), be prepared for the possibility that the * check out hasn't happened yet. + * + * @since FIXME */ - public void buildEnvVars(@Nonnull Run build, @Nonnull Map env) { + public void buildEnvironment(@Nonnull Run build, @Nonnull Map env) { if (build instanceof AbstractBuild) { buildEnvVars((AbstractBuild)build, env); } @@ -535,8 +537,8 @@ public void buildEnvVars(@Nonnull Run build, @Nonnull Map en @Deprecated public void buildEnvVars(AbstractBuild build, Map env) { - if (Util.isOverridden(SCM.class, getClass(), "buildEnvVars", Run.class, Map.class)) { - buildEnvVars((Run) build, env); + if (Util.isOverridden(SCM.class, getClass(), "buildEnvironment", Run.class, Map.class)) { + buildEnvironment(build, env); } // default implementation is noop. } From 39136804e53bf9f34cc91fdf3c64ab57d976eac3 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 10:28:10 -0400 Subject: [PATCH 09/18] Rework to solo RunWithSCM interface --- .../main/java/hudson/model/AbstractBuild.java | 56 ++++--------------- core/src/main/java/hudson/model/Job.java | 6 +- core/src/main/java/hudson/model/View.java | 3 +- .../{RunWithSCMMixIn.java => RunWithSCM.java} | 40 +++++-------- 4 files changed, 28 insertions(+), 77 deletions(-) rename core/src/main/java/jenkins/scm/{RunWithSCMMixIn.java => RunWithSCM.java} (79%) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 22286913354e..832cac7d692c 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -30,7 +30,7 @@ import hudson.FilePath; import hudson.Functions; import hudson.Launcher; -import jenkins.scm.RunWithSCMMixIn; +import jenkins.scm.RunWithSCM; import jenkins.util.SystemProperties; import hudson.console.ModelHyperlinkNote; import hudson.model.Fingerprint.BuildPtr; @@ -100,7 +100,7 @@ * @author Kohsuke Kawaguchi * @see AbstractProject */ -public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCMMixIn.RunWithSCM { +public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun, RunWithSCM { /** * Set if we want the blame information to flow from upstream to downstream build. @@ -181,21 +181,16 @@ public final P getProject() { return runMixIn; } + @SuppressWarnings("unchecked") // untypable @Override - public RunWithSCMMixIn getRunWithSCMMixIn() { - return new RunWithSCMMixIn() { - @SuppressWarnings("unchecked") // untypable - @Override protected R asRun() { - return (R) AbstractBuild.this; - } - - @Override - @Nonnull public List> getChangeSets() { - ChangeLogSet cs = getChangeSet(); - return cs.isEmptySet() ? Collections.>emptyList() : Collections.>singletonList(cs); - } + public R asRun() { + return (R) AbstractBuild.this; + } - }; + @Override + @Nonnull public List> getChangeSets() { + ChangeLogSet cs = getChangeSet(); + return cs.isEmptySet() ? Collections.emptyList() : Collections.singletonList(cs); } @Override protected final BuildReference createReference() { @@ -336,37 +331,11 @@ public FilePath[] getModuleRoots() { return getParent().getScm().getModuleRoots(ws, this); } - /** - * List of users who committed a change since the last non-broken build till now. - * - *

- * This list at least always include people who made changes in this build, but - * if the previous build was a failure it also includes the culprit list from there. - * - * @return - * can be empty but never null. - */ - @Override - @Exported - @Nonnull public Set getCulprits() { - return getRunWithSCMMixIn().getCulprits(); - } - @Override @CheckForNull public Set getCulpritIds() { return culprits; } - /** - * Returns true if this user has made a commit to this build. - * - * @since 1.191 - */ - @Override - public boolean hasParticipant(User user) { - return getRunWithSCMMixIn().hasParticipant(user); - } - /** * Gets the version of Hudson that was used to build this job. * @@ -857,11 +826,6 @@ public Collection getBuildFingerprints() { return cs; } - @Override - @Nonnull public List> getChangeSets() { - return getRunWithSCMMixIn().getChangeSets(); - } - /** * Returns true if the changelog is already computed. */ diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 750fd5e412bd..14caf63d59bb 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -91,7 +91,7 @@ import jenkins.model.ProjectNamingStrategy; import jenkins.model.RunIdMigrator; import jenkins.model.lazy.LazyBuildMixIn; -import jenkins.scm.RunWithSCMMixIn; +import jenkins.scm.RunWithSCM; import jenkins.security.HexStringConfidentialKey; import jenkins.triggers.SCMTriggerItem; import net.sf.json.JSONException; @@ -1079,8 +1079,8 @@ public FeedItem(ChangeLogSet.Entry e, int idx) { for (RunT r = getLastBuild(); r != null; r = r.getPreviousBuild()) { int idx = 0; - if (r instanceof RunWithSCMMixIn.RunWithSCM) { - for (ChangeLogSet c : ((RunWithSCMMixIn.RunWithSCM) r).getChangeSets()) { + if (r instanceof RunWithSCM) { + for (ChangeLogSet c : ((RunWithSCM) r).getChangeSets()) { for (ChangeLogSet.Entry e : c) { entries.add(new FeedItem(e, idx++)); } diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 278f0b9f5a05..68dd95299f99 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -63,6 +63,7 @@ import jenkins.model.item_category.Categories; import jenkins.model.item_category.Category; import jenkins.model.item_category.ItemCategory; +import jenkins.scm.RunWithSCM; import jenkins.triggers.SCMTriggerItem; import jenkins.util.ProgressiveRendering; import jenkins.util.xml.XMLUtils; @@ -116,7 +117,7 @@ import java.util.logging.Logger; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static jenkins.scm.RunWithSCMMixIn.*; +import static jenkins.scm.RunWithSCM.*; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; diff --git a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java b/core/src/main/java/jenkins/scm/RunWithSCM.java similarity index 79% rename from core/src/main/java/jenkins/scm/RunWithSCMMixIn.java rename to core/src/main/java/jenkins/scm/RunWithSCM.java index cc9b3986995c..4ffa28843a01 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCMMixIn.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -33,7 +33,6 @@ import hudson.model.User; import hudson.scm.ChangeLogSet; import hudson.util.AdaptedIterator; -import jenkins.triggers.SCMTriggerItem; import jenkins.util.SystemProperties; import org.kohsuke.stapler.export.Exported; @@ -51,16 +50,16 @@ /** * @since FIXME */ -public abstract class RunWithSCMMixIn & Queue.Task, - RunT extends Run & RunWithSCMMixIn.RunWithSCM & Queue.Executable> { +public interface RunWithSCM & Queue.Task, + RunT extends Run & RunWithSCM & Queue.Executable> { /** * Set if we want the blame information to flow from upstream to downstream build. */ - private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); + boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); - protected abstract RunT asRun(); + RunT asRun(); - public abstract List> getChangeSets(); + List> getChangeSets(); /** * List of users who committed a change since the last non-broken build till now. @@ -73,8 +72,8 @@ public abstract class RunWithSCMMixIn & Queue.Task, * can be empty but never null. */ @Exported - @Nonnull public Set getCulprits() { - if (asRun().getCulpritIds() == null) { + @Nonnull default Set getCulprits() { + if (getCulpritIds() == null) { Set r = new HashSet(); RunT p = asRun().getPreviousCompletedBuild(); if (p != null && asRun().isBuilding()) { @@ -112,7 +111,7 @@ public abstract class RunWithSCMMixIn & Queue.Task, return new AbstractSet() { public Iterator iterator() { - return new AdaptedIterator(asRun().getCulpritIds().iterator()) { + return new AdaptedIterator(getCulpritIds().iterator()) { protected User adapt(String id) { return User.get(id); } @@ -120,7 +119,7 @@ protected User adapt(String id) { } public int size() { - return asRun().getCulpritIds().size(); + return getCulpritIds().size(); } }; } @@ -128,7 +127,7 @@ public int size() { /** * Returns true if this user has made a commit to this build. */ - public boolean hasParticipant(User user) { + default boolean hasParticipant(User user) { for (ChangeLogSet c : getChangeSets()) { for (ChangeLogSet.Entry e : c) try { @@ -141,21 +140,8 @@ public boolean hasParticipant(User user) { return false; } - public interface RunWithSCM & Queue.Task, - RunT extends Run & RunWithSCM & Queue.Executable> { - @Nonnull - List> getChangeSets(); + @CheckForNull + Set getCulpritIds(); - @Nonnull - Set getCulprits(); - - @CheckForNull - Set getCulpritIds(); - - RunWithSCMMixIn getRunWithSCMMixIn(); - - boolean hasParticipant(User user); - } - - private static final Logger LOGGER = Logger.getLogger(RunWithSCMMixIn.class.getName()); + Logger LOGGER = Logger.getLogger(RunWithSCM.class.getName()); } From 977f922136e8bcdad42ad1448df45a36b2e64f68 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 11:14:03 -0400 Subject: [PATCH 10/18] Refactor for supporting on-demand calculation of culprits --- .../main/java/hudson/model/AbstractBuild.java | 29 +++++++ .../src/main/java/jenkins/scm/RunWithSCM.java | 75 +++++++------------ 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 832cac7d692c..d58bcbdf4b69 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -336,6 +336,35 @@ public FilePath[] getModuleRoots() { return culprits; } + @Override + public boolean shouldCalculateCulprits() { + return getCulpritIds() == null; + } + + @Override + @Nonnull + public Set calculateCulprits() { + Set c = RunWithSCM.super.calculateCulprits(); + + AbstractBuild p = getPreviousCompletedBuild(); + if (upstreamCulprits) { + // If we have dependencies since the last successful build, add their authors to our list + if (p.getPreviousNotFailedBuild() != null) { + Map depmap = + p.getDependencyChanges(p.getPreviousSuccessfulBuild()); + for (AbstractBuild.DependencyChange dep : depmap.values()) { + for (AbstractBuild b : dep.getBuilds()) { + for (ChangeLogSet.Entry entry : b.getChangeSet()) { + c.add(entry.getAuthor()); + } + } + } + } + } + + return c; + } + /** * Gets the version of Hudson that was used to build this job. * diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 4ffa28843a01..0a2679800e9c 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -24,8 +24,6 @@ package jenkins.scm; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; import hudson.model.Job; import hudson.model.Queue; import hudson.model.Result; @@ -42,7 +40,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,15 +49,16 @@ */ public interface RunWithSCM & Queue.Task, RunT extends Run & RunWithSCM & Queue.Executable> { - /** - * Set if we want the blame information to flow from upstream to downstream build. - */ - boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits"); RunT asRun(); List> getChangeSets(); + @CheckForNull + Set getCulpritIds(); + + boolean shouldCalculateCulprits(); + /** * List of users who committed a change since the last non-broken build till now. * @@ -73,40 +71,8 @@ public interface RunWithSCM & Queue.Task, */ @Exported @Nonnull default Set getCulprits() { - if (getCulpritIds() == null) { - Set r = new HashSet(); - RunT p = asRun().getPreviousCompletedBuild(); - if (p != null && asRun().isBuilding()) { - Result pr = p.getResult(); - if (pr != null && pr.isWorseThan(Result.SUCCESS)) { - // we are still building, so this is just the current latest information, - // but we seems to be failing so far, so inherit culprits from the previous build. - // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record - // this information - r.addAll(p.getCulprits()); - } - } - for (ChangeLogSet c : getChangeSets()) { - for (ChangeLogSet.Entry e : c) - r.add(e.getAuthor()); - } - - if (p instanceof AbstractBuild && upstreamCulprits) { - // If we have dependencies since the last successful build, add their authors to our list - if (p.getPreviousNotFailedBuild() != null) { - Map depmap = - ((AbstractBuild) p).getDependencyChanges((AbstractBuild)p.getPreviousSuccessfulBuild()); - for (AbstractBuild.DependencyChange dep : depmap.values()) { - for (AbstractBuild b : dep.getBuilds()) { - for (ChangeLogSet.Entry entry : b.getChangeSet()) { - r.add(entry.getAuthor()); - } - } - } - } - } - - return r; + if (shouldCalculateCulprits()) { + return calculateCulprits(); } return new AbstractSet() { @@ -124,6 +90,27 @@ public int size() { }; } + default Set calculateCulprits() { + Set r = new HashSet(); + RunT p = asRun().getPreviousCompletedBuild(); + if (p != null && asRun().isBuilding()) { + Result pr = p.getResult(); + if (pr != null && pr.isWorseThan(Result.SUCCESS)) { + // we are still building, so this is just the current latest information, + // but we seems to be failing so far, so inherit culprits from the previous build. + // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record + // this information + r.addAll(p.getCulprits()); + } + } + for (ChangeLogSet c : getChangeSets()) { + for (ChangeLogSet.Entry e : c) + r.add(e.getAuthor()); + } + + return r; + } + /** * Returns true if this user has made a commit to this build. */ @@ -134,14 +121,10 @@ default boolean hasParticipant(User user) { if (e.getAuthor() == user) return true; } catch (RuntimeException re) { + Logger LOGGER = Logger.getLogger(RunWithSCM.class.getName()); LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + asRun().getParent().getDisplayName() + ", " + asRun().getDisplayName(), re); } } return false; } - - @CheckForNull - Set getCulpritIds(); - - Logger LOGGER = Logger.getLogger(RunWithSCM.class.getName()); } From 28dce4c30572a11c3e85acb349fc5ceddb0491ba Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 13:21:20 -0400 Subject: [PATCH 11/18] Get rid of asRun(). --- core/src/main/java/hudson/model/AbstractBuild.java | 6 ------ core/src/main/java/jenkins/scm/RunWithSCM.java | 10 +++++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index d58bcbdf4b69..5fd3ef400692 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -181,12 +181,6 @@ public final P getProject() { return runMixIn; } - @SuppressWarnings("unchecked") // untypable - @Override - public R asRun() { - return (R) AbstractBuild.this; - } - @Override @Nonnull public List> getChangeSets() { ChangeLogSet cs = getChangeSet(); diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 0a2679800e9c..e56d636138e4 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -50,8 +50,6 @@ public interface RunWithSCM & Queue.Task, RunT extends Run & RunWithSCM & Queue.Executable> { - RunT asRun(); - List> getChangeSets(); @CheckForNull @@ -90,10 +88,11 @@ public int size() { }; } + @SuppressWarnings("unchecked") default Set calculateCulprits() { Set r = new HashSet(); - RunT p = asRun().getPreviousCompletedBuild(); - if (p != null && asRun().isBuilding()) { + RunT p = ((RunT)this).getPreviousCompletedBuild(); + if (p != null && ((RunT)this).isBuilding()) { Result pr = p.getResult(); if (pr != null && pr.isWorseThan(Result.SUCCESS)) { // we are still building, so this is just the current latest information, @@ -114,6 +113,7 @@ default Set calculateCulprits() { /** * Returns true if this user has made a commit to this build. */ + @SuppressWarnings("unchecked") default boolean hasParticipant(User user) { for (ChangeLogSet c : getChangeSets()) { for (ChangeLogSet.Entry e : c) @@ -122,7 +122,7 @@ default boolean hasParticipant(User user) { return true; } catch (RuntimeException re) { Logger LOGGER = Logger.getLogger(RunWithSCM.class.getName()); - LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + asRun().getParent().getDisplayName() + ", " + asRun().getDisplayName(), re); + LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + ((RunT)this).getParent().getDisplayName() + ", " + ((RunT)this).getDisplayName(), re); } } return false; From 31bc41739bb3bf3aa7c7583becb2ea71a89911ec Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 14:41:01 -0400 Subject: [PATCH 12/18] Review comments. --- .../main/java/hudson/model/AbstractBuild.java | 12 ++--- core/src/main/java/hudson/model/View.java | 22 ++++----- core/src/main/java/hudson/scm/SCM.java | 8 ++-- .../src/main/java/jenkins/scm/RunWithSCM.java | 46 +++++++++++++++---- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java index 5fd3ef400692..168505f29a2f 100644 --- a/core/src/main/java/hudson/model/AbstractBuild.java +++ b/core/src/main/java/hudson/model/AbstractBuild.java @@ -181,12 +181,6 @@ public final P getProject() { return runMixIn; } - @Override - @Nonnull public List> getChangeSets() { - ChangeLogSet cs = getChangeSet(); - return cs.isEmptySet() ? Collections.emptyList() : Collections.singletonList(cs); - } - @Override protected final BuildReference createReference() { return getRunMixIn().createReference(); } @@ -849,6 +843,12 @@ public Collection getBuildFingerprints() { return cs; } + @Override + @Nonnull public List> getChangeSets() { + ChangeLogSet cs = getChangeSet(); + return cs.isEmptySet() ? Collections.emptyList() : Collections.singletonList(cs); + } + /** * Returns true if the changelog is already computed. */ diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 68dd95299f99..ac15a8569adf 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -773,18 +773,16 @@ public Api getApi() { public static boolean isApplicable(Collection items) { for (Item item : items) { for (Job job : item.getAllJobs()) { - if (job instanceof SCMTriggerItem) { - RunList> runs = job.getBuilds(); - - for (Run r : runs) { - if (r instanceof RunWithSCM) { - RunWithSCM runWithSCM = (RunWithSCM) r; - for (ChangeLogSet c : runWithSCM.getChangeSets()) { - for (Entry entry : c) { - User user = entry.getAuthor(); - if (user != null) - return true; - } + RunList> runs = job.getBuilds(); + + for (Run r : runs) { + if (r instanceof RunWithSCM) { + RunWithSCM runWithSCM = (RunWithSCM) r; + for (ChangeLogSet c : runWithSCM.getChangeSets()) { + for (Entry entry : c) { + User user = entry.getAuthor(); + if (user != null) + return true; } } } diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index 01b0c2e2b295..4c9f7b0ec860 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -522,10 +522,10 @@ public void postCheckout(AbstractBuild build, Launcher launcher, FilePath w * (for example, SVN revision number.) * *

- * This method is invoked whenever someone does {@link Run#getEnvironment(TaskListener)}, which - * can be before/after your checkout method is invoked. So if you are going to provide information about - * check out (like SVN revision number that was checked out), be prepared for the possibility that the - * check out hasn't happened yet. + * This method is invoked whenever someone does {@link AbstractBuild#getEnvironment(TaskListener)}, via + * {@link #buildEnvVars(AbstractBuild, Map)}, whichvcan be before/after your checkout method is invoked. So if you + * are going to provide information aboutvcheck out (like SVN revision number that was checked out), be prepared + * for the possibility that thevcheck out hasn't happened yet. * * @since FIXME */ diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index e56d636138e4..7797f8c88d1c 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2014 Jesse Glick. + * Copyright 2017 CloudBees, inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,7 @@ package jenkins.scm; +import com.google.common.collect.ImmutableSet; import hudson.model.Job; import hudson.model.Queue; import hudson.model.Result; @@ -31,7 +32,6 @@ import hudson.model.User; import hudson.scm.ChangeLogSet; import hudson.util.AdaptedIterator; -import jenkins.util.SystemProperties; import org.kohsuke.stapler.export.Exported; import javax.annotation.CheckForNull; @@ -50,11 +50,27 @@ public interface RunWithSCM & Queue.Task, RunT extends Run & RunWithSCM & Queue.Executable> { + /** + * Gets all {@link ChangeLogSet}s currently associated with this item. + * + * @return A possibly empty list of {@link ChangeLogSet}s. + */ + @Nonnull List> getChangeSets(); + /** + * Gets the ids for all {@link User}s included in {@link #getChangeSets()} for this item. + * + * @return A possibly null set of user IDs. + */ @CheckForNull Set getCulpritIds(); + /** + * Determines whether culprits should be recalcuated or the existing {@link #getCulpritIds()} should be used instead. + * + * @return True if culprits should be recalcuated, false otherwise. + */ boolean shouldCalculateCulprits(); /** @@ -74,8 +90,10 @@ public interface RunWithSCM & Queue.Task, } return new AbstractSet() { + private Set culpritIds = ImmutableSet.copyOf(getCulpritIds()); + public Iterator iterator() { - return new AdaptedIterator(getCulpritIds().iterator()) { + return new AdaptedIterator(culpritIds.iterator()) { protected User adapt(String id) { return User.get(id); } @@ -83,14 +101,21 @@ protected User adapt(String id) { } public int size() { - return getCulpritIds().size(); + return culpritIds.size(); } }; } + /** + * Internal method used for actually calculating the culprits from scratch. Called by {@link #getCulprits()} and + * overrides of {@link #getCulprits()}. Does not persist culprits information. + * + * @return a non-null {@link Set} of {@link User}s associated with this item. + */ @SuppressWarnings("unchecked") + @Nonnull default Set calculateCulprits() { - Set r = new HashSet(); + Set r = new HashSet<>(); RunT p = ((RunT)this).getPreviousCompletedBuild(); if (p != null && ((RunT)this).isBuilding()) { Result pr = p.getResult(); @@ -103,8 +128,9 @@ default Set calculateCulprits() { } } for (ChangeLogSet c : getChangeSets()) { - for (ChangeLogSet.Entry e : c) + for (ChangeLogSet.Entry e : c) { r.add(e.getAuthor()); + } } return r; @@ -116,14 +142,16 @@ default Set calculateCulprits() { @SuppressWarnings("unchecked") default boolean hasParticipant(User user) { for (ChangeLogSet c : getChangeSets()) { - for (ChangeLogSet.Entry e : c) + for (ChangeLogSet.Entry e : c) { try { - if (e.getAuthor() == user) + if (e.getAuthor() == user) { return true; + } } catch (RuntimeException re) { Logger LOGGER = Logger.getLogger(RunWithSCM.class.getName()); - LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + ((RunT)this).getParent().getDisplayName() + ", " + ((RunT)this).getDisplayName(), re); + LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + ((RunT) this).getParent().getDisplayName() + ", " + ((RunT) this).getDisplayName(), re); } + } } return false; } From 66460f95b0437e5e2de0aed80ef2790ff85d991a Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 15:23:57 -0400 Subject: [PATCH 13/18] Get rid of isBuilding() check on previous build's culprits. --- core/src/main/java/jenkins/scm/RunWithSCM.java | 4 +--- .../test/java/hudson/model/AbstractBuildTest.java | 15 ++++++++++++++- .../test/java/hudson/tasks/LogRotatorTest.java | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 7797f8c88d1c..67615412f407 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -117,13 +117,11 @@ public int size() { default Set calculateCulprits() { Set r = new HashSet<>(); RunT p = ((RunT)this).getPreviousCompletedBuild(); - if (p != null && ((RunT)this).isBuilding()) { + if (p != null) { Result pr = p.getResult(); if (pr != null && pr.isWorseThan(Result.SUCCESS)) { // we are still building, so this is just the current latest information, // but we seems to be failing so far, so inherit culprits from the previous build. - // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record - // this information r.addAll(p.getCulprits()); } } diff --git a/test/src/test/java/hudson/model/AbstractBuildTest.java b/test/src/test/java/hudson/model/AbstractBuildTest.java index 55a9326b9c3b..4d96f971fa5a 100644 --- a/test/src/test/java/hudson/model/AbstractBuildTest.java +++ b/test/src/test/java/hudson/model/AbstractBuildTest.java @@ -34,8 +34,12 @@ import java.util.Collections; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.TimeUnit; + import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; + +import hudson.tasks.LogRotatorTest; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.CaptureEnvironmentBuilder; @@ -132,6 +136,8 @@ public void culprits() throws Exception { FakeChangeLogSCM scm = new FakeChangeLogSCM(); p.setScm(scm); + LogRotatorTest.StallBuilder sync = new LogRotatorTest.StallBuilder(); + // 1st build, successful, no culprits scm.addChange().withAuthor("alice"); FreeStyleBuild b = j.buildAndAssertSuccess(p); @@ -144,8 +150,15 @@ public void culprits() throws Exception { assertCulprits(b, "bob"); // 3rd build. bob continues to be in culprit + p.getBuildersList().add(sync); scm.addChange().withAuthor("charlie"); - b = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + b = p.scheduleBuild2(0).waitForStart(); + sync.waitFor(b.getNumber(), 1, TimeUnit.SECONDS); + + // Verify that we can get culprits while running. + assertCulprits(b, "bob", "charlie"); + sync.release(b.getNumber()); + j.assertBuildStatus(Result.FAILURE, j.waitForCompletion(b)); assertCulprits(b, "bob", "charlie"); // 4th build, unstable. culprit list should continue diff --git a/test/src/test/java/hudson/tasks/LogRotatorTest.java b/test/src/test/java/hudson/tasks/LogRotatorTest.java index d45259e23c3c..1990b3bc826d 100644 --- a/test/src/test/java/hudson/tasks/LogRotatorTest.java +++ b/test/src/test/java/hudson/tasks/LogRotatorTest.java @@ -224,7 +224,7 @@ public Descriptor getDescriptor() { } } - static class StallBuilder extends TestBuilder { + public static class StallBuilder extends TestBuilder { private int syncBuildNumber; From 99baf7a6105bc6dce4319741aaa80addb9c4b253 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 15:34:46 -0400 Subject: [PATCH 14/18] Forgot a bit of javadoc --- core/src/main/java/jenkins/scm/RunWithSCM.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 67615412f407..c16e52dca041 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -31,6 +31,7 @@ import hudson.model.Run; import hudson.model.User; import hudson.scm.ChangeLogSet; +import hudson.scm.SCM; import hudson.util.AdaptedIterator; import org.kohsuke.stapler.export.Exported; @@ -45,6 +46,8 @@ import java.util.logging.Logger; /** + * Allows a {@link Run} to provide {@link SCM}-related methods, such as providing changesets and culprits. + * * @since FIXME */ public interface RunWithSCM & Queue.Task, From aa5ff5410f03db553c68617d2b3296e2dcac3788 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 15:35:58 -0400 Subject: [PATCH 15/18] Forgot to remove SCMTriggerItem everywhere in View --- core/src/main/java/hudson/model/View.java | 65 +++++++++++------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index ac15a8569adf..db669cdb2a5d 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -64,7 +64,6 @@ import jenkins.model.item_category.Category; import jenkins.model.item_category.ItemCategory; import jenkins.scm.RunWithSCM; -import jenkins.triggers.SCMTriggerItem; import jenkins.util.ProgressiveRendering; import jenkins.util.xml.XMLUtils; @@ -829,44 +828,42 @@ public AsynchPeople(View parent) { int itemCount = 0; for (Item item : items) { for (Job job : item.getAllJobs()) { - if (job instanceof SCMTriggerItem) { - RunList> builds = job.getBuilds(); - int buildCount = 0; - for (Run r : builds) { - if (canceled()) { - return; - } - if (!(r instanceof RunWithSCM)) { - continue; - } + RunList> builds = job.getBuilds(); + int buildCount = 0; + for (Run r : builds) { + if (canceled()) { + return; + } + if (!(r instanceof RunWithSCM)) { + continue; + } - RunWithSCM runWithSCM = (RunWithSCM) r; - for (ChangeLogSet c : runWithSCM.getChangeSets()) { - for (ChangeLogSet.Entry entry : c) { - User user = entry.getAuthor(); - UserInfo info = users.get(user); - if (info == null) { - UserInfo userInfo = new UserInfo(user, job, r.getTimestamp()); - userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize); - synchronized (this) { - users.put(user, userInfo); - modified.add(user); - } - } else if (info.getLastChange().before(r.getTimestamp())) { - synchronized (this) { - info.project = job; - info.lastChange = r.getTimestamp(); - modified.add(user); - } + RunWithSCM runWithSCM = (RunWithSCM) r; + for (ChangeLogSet c : runWithSCM.getChangeSets()) { + for (ChangeLogSet.Entry entry : c) { + User user = entry.getAuthor(); + UserInfo info = users.get(user); + if (info == null) { + UserInfo userInfo = new UserInfo(user, job, r.getTimestamp()); + userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize); + synchronized (this) { + users.put(user, userInfo); + modified.add(user); + } + } else if (info.getLastChange().before(r.getTimestamp())) { + synchronized (this) { + info.project = job; + info.lastChange = r.getTimestamp(); + modified.add(user); } } } - // TODO consider also adding the user of the UserCause when applicable - buildCount++; - // TODO this defeats lazy-loading. Should rather do a breadth-first search, as in hudson.plugins.view.dashboard.builds.LatestBuilds - // (though currently there is no quick implementation of RunMap.size() ~ idOnDisk.size(), which would be needed for proper progress) - progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1)); } + // TODO consider also adding the user of the UserCause when applicable + buildCount++; + // TODO this defeats lazy-loading. Should rather do a breadth-first search, as in hudson.plugins.view.dashboard.builds.LatestBuilds + // (though currently there is no quick implementation of RunMap.size() ~ idOnDisk.size(), which would be needed for proper progress) + progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1)); } } itemCount++; From 71a46145a62b7da36b36ad5d4c359beada8de6ad Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 2 May 2017 15:51:56 -0400 Subject: [PATCH 16/18] Javadoc cleanup --- core/src/main/java/hudson/scm/SCM.java | 6 +++--- core/src/main/java/jenkins/scm/RunWithSCM.java | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index 4c9f7b0ec860..73711dd4cbd0 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -523,9 +523,9 @@ public void postCheckout(AbstractBuild build, Launcher launcher, FilePath w * *

* This method is invoked whenever someone does {@link AbstractBuild#getEnvironment(TaskListener)}, via - * {@link #buildEnvVars(AbstractBuild, Map)}, whichvcan be before/after your checkout method is invoked. So if you - * are going to provide information aboutvcheck out (like SVN revision number that was checked out), be prepared - * for the possibility that thevcheck out hasn't happened yet. + * {@link #buildEnvVars(AbstractBuild, Map)}, which can be before/after your checkout method is invoked. So if you + * are going to provide information about check out (like SVN revision number that was checked out), be prepared + * for the possibility that the check out hasn't happened yet. * * @since FIXME */ diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index c16e52dca041..1189e56de910 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -47,7 +47,7 @@ /** * Allows a {@link Run} to provide {@link SCM}-related methods, such as providing changesets and culprits. - * + * * @since FIXME */ public interface RunWithSCM & Queue.Task, @@ -64,7 +64,8 @@ public interface RunWithSCM & Queue.Task, /** * Gets the ids for all {@link User}s included in {@link #getChangeSets()} for this item. * - * @return A possibly null set of user IDs. + * @return A set of user IDs, or null if this was the first time the method was called or the build is still running + * for a {@link RunWithSCM} instance with no culprits. */ @CheckForNull Set getCulpritIds(); @@ -110,7 +111,7 @@ public int size() { } /** - * Internal method used for actually calculating the culprits from scratch. Called by {@link #getCulprits()} and + * Method used for actually calculating the culprits from scratch. Called by {@link #getCulprits()} and * overrides of {@link #getCulprits()}. Does not persist culprits information. * * @return a non-null {@link Set} of {@link User}s associated with this item. From b6e1ae5537d27dda1dc0544e7060f792fcef81a8 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Mon, 8 May 2017 09:24:55 -0400 Subject: [PATCH 17/18] Adding a @since TODO for Job.doRssChangelog --- core/src/main/java/hudson/model/Job.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 14caf63d59bb..0575724d9971 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -1050,6 +1050,8 @@ public PermalinkList getPermalinks() { /** * RSS feed for changes in this project. + * + * @since TODO */ public void doRssChangelog(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { class FeedItem { From 914963c22317e7d72cf7e3e7d9ed8ab57709ccb0 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Mon, 8 May 2017 12:39:09 -0400 Subject: [PATCH 18/18] Get rid of superfluous Queue.Task/Executable --- core/src/main/java/jenkins/scm/RunWithSCM.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/scm/RunWithSCM.java b/core/src/main/java/jenkins/scm/RunWithSCM.java index 1189e56de910..551716503cf9 100644 --- a/core/src/main/java/jenkins/scm/RunWithSCM.java +++ b/core/src/main/java/jenkins/scm/RunWithSCM.java @@ -50,8 +50,8 @@ * * @since FIXME */ -public interface RunWithSCM & Queue.Task, - RunT extends Run & RunWithSCM & Queue.Executable> { +public interface RunWithSCM, + RunT extends Run & RunWithSCM> { /** * Gets all {@link ChangeLogSet}s currently associated with this item.