diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index 90a64f2c2edbe23ef5f45d993d6d1841a5e2478c..ed971a965361456c2d817c07c16ea0d9f5b172be 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -23,6 +23,10 @@ */ package hudson.model; +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; @@ -53,14 +57,16 @@ public abstract class Cause { public static class UpstreamCause extends Cause { private String upstreamProject, upstreamUrl; private int upstreamBuild; - private Cause upstreamCause; + @Deprecated + private transient Cause upstreamCause; + private List upstreamCauses = new ArrayList(); public UpstreamCause(AbstractBuild up) { upstreamBuild = up.getNumber(); upstreamProject = up.getProject().getName(); upstreamUrl = up.getProject().getUrl(); CauseAction ca = up.getAction(CauseAction.class); - upstreamCause = ca == null ? null : ca.getCause(); + upstreamCauses = ca == null ? null : ca.getCauses(); } public String getUpstreamProject() { @@ -79,6 +85,15 @@ public abstract class Cause { public String getShortDescription() { return Messages.Cause_UpstreamCause_ShortDescription(upstreamProject, upstreamBuild); } + + private Object readResolve() { + if(upstreamCause != null) { + if(upstreamCauses == null) upstreamCauses = new ArrayList(); + upstreamCauses.add(upstreamCause); + upstreamCause=null; + } + return this; + } } public static class UserCause extends Cause { diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java index 5100de90ed20279bb0ddfe1bfc8a168d1fa45d2d..df8465e410904fd089a25ac5eac2b1a67ccc0835 100644 --- a/core/src/main/java/hudson/model/CauseAction.java +++ b/core/src/main/java/hudson/model/CauseAction.java @@ -23,24 +23,33 @@ */ package hudson.model; +import hudson.model.Queue.Task; + +import java.util.ArrayList; +import java.util.List; + import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; @ExportedBean -public class CauseAction implements Action { - private Cause cause; +public class CauseAction implements FoldableAction { + @Deprecated + // there can be multiple causes, so this is deprecated + private transient Cause cause; + + private List causes = new ArrayList(); @Exported(visibility=2) - public Cause getCause() { - return cause; + public List getCauses() { + return causes; } - - public String getShortDescription() { - return cause.getShortDescription(); + + public CauseAction(Cause c) { + this.causes.add(c); } - public CauseAction(Cause c) { - this.cause = c; + public CauseAction(CauseAction ca) { + this.causes.addAll(ca.causes); } public String getDisplayName() { @@ -55,4 +64,24 @@ public class CauseAction implements Action { public String getUrlName() { return "cause"; } + + public void foldIntoExisting(Task t, List actions) { + for(Action action : actions) { + if(action instanceof CauseAction) { + this.causes.addAll(((CauseAction)action).causes); + return; + } + } + // no CauseAction found, so add a copy of this one + actions.add(new CauseAction(this)); + } + + private Object readResolve() { + // if we are being read in from an older version + if(cause != null) { + if(causes == null) causes=new ArrayList(); + causes.add(cause); + } + return this; + } } diff --git a/core/src/main/java/hudson/model/FoldableAction.java b/core/src/main/java/hudson/model/FoldableAction.java new file mode 100644 index 0000000000000000000000000000000000000000..175056135c6e84c9ef0455b8804987cc5b25bbce --- /dev/null +++ b/core/src/main/java/hudson/model/FoldableAction.java @@ -0,0 +1,15 @@ +package hudson.model; + +import hudson.model.Queue.Task; + +import java.util.List; + +/** + * @author mdonohue + * An action interface that allows action data to be folded together. + * This is useful for combining any distinct values from a build determined to + * be a duplicate of a build already in the build queue. + */ +public interface FoldableAction extends Action { + public void foldIntoExisting(Task t, List actions); +} diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index f0555afc2af4a585778d84c7fa2b9b8594e208b4..7ed8f910a0ad0132b13c65e7c6fd3c450218990e 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -292,71 +292,87 @@ public class Queue extends ResourceController implements Saveable { public synchronized boolean add(AbstractProject p, int quietPeriod) { return add((Task) p, quietPeriod); } - + /** * Schedules an execution of a task. * * @param quietPeriod Number of seconds that the task will be placed in queue. * Useful when the same task is likely scheduled for multiple * times. + * @return true if the project 'p' is actually added to the queue. + * false if the queue contained it and therefore the add() + * was noop, or just changed the due date of the task. * @since 1.114 */ - public synchronized boolean add(Task p, int quietPeriod, List actions) { - Item item = getItem(p); - Calendar due = new GregorianCalendar(); - due.add(Calendar.SECOND, quietPeriod); - if (item != null) { - - boolean shouldSchedule = false; - for (Action action: item.getActions()) { - if (action instanceof QueueAction) - shouldSchedule |= ((QueueAction) action).shouldSchedule(actions); - } - for (Action action: actions) { - if (action instanceof QueueAction) { - shouldSchedule |= ((QueueAction) action).shouldSchedule(item.getActions()); - } - } - if (shouldSchedule) { - //TODO rework the if-s to only have this once - LOGGER.fine(p.getFullDisplayName() + " added to queue"); - - // put the item in the queue - waitingList.add(new WaitingItem(due,p,actions)); - } else { - if (!(item instanceof WaitingItem)) - // already in the blocked or buildable stage - // no need to requeue - return false; - - WaitingItem wi = (WaitingItem) item; - - if(quietPeriod<=0) { - // the user really wants to build now, and they mean NOW. - // so let's pull in the timestamp if we can. - if (wi.timestamp.before(due)) - return false; - } else { - // otherwise we do the normal quiet period implementation - if (wi.timestamp.after(due)) - return false; - // quiet period timer reset. start the period over again - } - - // waitingList is sorted, so when we change a timestamp we need to maintain order - waitingList.remove(wi); - wi.timestamp = due; - waitingList.add(wi); - } - } else { - LOGGER.fine(p.getFullDisplayName() + " added to queue"); - - // put the item in the queue - waitingList.add(new WaitingItem(due,p,actions)); + private synchronized boolean add(Task p, int quietPeriod, List actions) { + boolean taskConsumed=false; + List items = getItems(p); + Calendar due = new GregorianCalendar(); + due.add(Calendar.SECOND, quietPeriod); + + List duplicatesInQueue = new ArrayList(); + for(Item item : items) { + boolean shouldScheduleItem = false; + for (Action action: item.getActions()) { + if (action instanceof QueueAction) + shouldScheduleItem |= ((QueueAction) action).shouldSchedule(actions); + } + for (Action action: actions) { + if (action instanceof QueueAction) { + shouldScheduleItem |= ((QueueAction) action).shouldSchedule(item.getActions()); + } + } + if(!shouldScheduleItem) { + duplicatesInQueue.add(item); + } + } + if (duplicatesInQueue.size() == 0) { + LOGGER.fine(p.getFullDisplayName() + " added to queue"); + + // put the item in the queue + waitingList.add(new WaitingItem(due,p,actions)); + taskConsumed=true; + } else { + // the requested build is already queued, so will not be added + List waitingDuplicates = new ArrayList(); + for(Item item : duplicatesInQueue) { + for(Action a : actions) { + if(a instanceof FoldableAction) { + ((FoldableAction)a).foldIntoExisting(item.task, item.getActions()); + } + } + if ((item instanceof WaitingItem)) + waitingDuplicates.add((WaitingItem)item); + } + if(duplicatesInQueue.size() == 0) { + // all duplicates in the queue are already in the blocked or + // buildable stage no need to requeue + return false; + } + // TODO: avoid calling scheduleMaintenance() if none of the waiting items + // actually change + for(WaitingItem wi : waitingDuplicates) { + if(quietPeriod<=0) { + // the user really wants to build now, and they mean NOW. + // so let's pull in the timestamp if we can. + if (wi.timestamp.before(due)) + continue; + } else { + // otherwise we do the normal quiet period implementation + if (wi.timestamp.after(due)) + continue; + // quiet period timer reset. start the period over again + } - } - scheduleMaintenance(); // let an executor know that a new item is in the queue. - return true; + // waitingList is sorted, so when we change a timestamp we need to maintain order + waitingList.remove(wi); + wi.timestamp = due; + waitingList.add(wi); + } + + } + scheduleMaintenance(); // let an executor know that a new item is in the queue. + return taskConsumed; } public synchronized boolean add(Task p, int quietPeriod) { diff --git a/core/src/main/resources/hudson/model/CauseAction/summary.jelly b/core/src/main/resources/hudson/model/CauseAction/summary.jelly index b080c01bb84c4fd6848d7ebfa2836df44dae65f2..923a95e5b3148c373ab6b5e838b81a9a6d93bd4e 100644 --- a/core/src/main/resources/hudson/model/CauseAction/summary.jelly +++ b/core/src/main/resources/hudson/model/CauseAction/summary.jelly @@ -23,7 +23,9 @@ THE SOFTWARE. --> - - - + + +

+
+