提交 38e81a69 编写于 作者: J Jesse Glick

Merge branch 'SCM-Job'

......@@ -120,15 +120,15 @@ public class WorkspaceSnapshotSCM extends SCM {
return new Snapshot(snapshot,b);
}
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
@Override public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
return null;
}
protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
@Override protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
return PollingResult.NO_CHANGES;
}
public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
@Override public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
try {
resolve().restoreTo(workspace,listener);
return true;
......@@ -139,11 +139,11 @@ public class WorkspaceSnapshotSCM extends SCM {
}
}
public ChangeLogParser createChangeLogParser() {
@Override public ChangeLogParser createChangeLogParser() {
return null;
}
public SCMDescriptor<?> getDescriptor() {
@Override public SCMDescriptor<?> getDescriptor() {
return null;
}
}
......@@ -91,6 +91,8 @@ import static java.util.logging.Level.WARNING;
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.
......@@ -617,14 +619,22 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
build.scm = NullChangeLogParser.INSTANCE;
try {
if (project.checkout(build, launcher,listener,new File(build.getRootDir(),"changelog.xml"))) {
File changeLogFile = new File(build.getRootDir(), "changelog.xml");
if (project.checkout(build, launcher,listener, changeLogFile)) {
// check out succeeded
SCM scm = project.getScm();
for (SCMListener l : SCMListener.all()) {
try {
l.onCheckout(build, scm, build.getWorkspace(), listener, changeLogFile, project.pollingBaseline);
} catch (Exception e) {
throw new IOException(e);
}
}
build.scm = scm.createChangeLogParser();
build.changeSet = new WeakReference<ChangeLogSet<? extends Entry>>(build.calcChangeSet());
for (SCMListener l : Jenkins.getInstance().getSCMListeners())
for (SCMListener l : SCMListener.all())
try {
l.onChangeLogParsed(build,listener,build.getChangeSet());
} catch (Exception e) {
......@@ -862,6 +872,12 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
return cs;
}
@Restricted(DoNotUse.class) // for project-changes.jelly
public List<ChangeLogSet<? extends ChangeLogSet.Entry>> getChangeSets() {
ChangeLogSet<? extends Entry> cs = getChangeSet();
return cs.isEmptySet() ? Collections.<ChangeLogSet<? extends ChangeLogSet.Entry>>emptyList() : Collections.<ChangeLogSet<? extends ChangeLogSet.Entry>>singletonList(cs);
}
/**
* Returns true if the changelog is already computed.
*/
......
......@@ -159,7 +159,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
*/
private volatile transient SCMRevisionState pollingBaseline = null;
volatile transient SCMRevisionState pollingBaseline = null;
private transient LazyBuildMixIn<P,R> buildMixIn;
......@@ -1380,7 +1380,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
// give time for slaves to come online if we are right after reconnection (JENKINS-8408)
long running = Jenkins.getInstance().getInjector().getInstance(Uptime.class).getUptime();
long remaining = TimeUnit2.MINUTES.toMillis(10)-running;
if (remaining>0) {
if (remaining>0 && /* this logic breaks tests of polling */!Functions.getIsUnitTest()) {
listener.getLogger().print(Messages.AbstractProject_AwaitingWorkspaceToComeOnline(remaining/1000));
listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
return NO_CHANGES;
......
......@@ -34,6 +34,7 @@ import jenkins.model.Jenkins;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import hudson.Util;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
......@@ -69,21 +70,40 @@ public abstract class Cause {
public abstract String getShortDescription();
/**
* Called when the cause is registered to {@link AbstractBuild}.
*
* @param build
* never null
* @since 1.376
* Called when the cause is registered.
* @since TODO
*/
public void onAddedTo(AbstractBuild build) {}
public void onAddedTo(@Nonnull Run build) {
if (build instanceof AbstractBuild) {
onAddedTo((AbstractBuild) build);
}
}
@Deprecated
public void onAddedTo(AbstractBuild build) {
if (Util.isOverridden(Cause.class, getClass(), "onAddedTo", Run.class)) {
onAddedTo((Run) build);
}
}
/**
* Called when a build is loaded from disk.
* Useful in case the cause needs to keep a build reference;
* this ought to be {@code transient}.
* @since 1.540
* @since TODO
*/
public void onLoad(@Nonnull AbstractBuild<?,?> build) {}
public void onLoad(@Nonnull Run<?,?> build) {
if (build instanceof AbstractBuild) {
onLoad((AbstractBuild) build);
}
}
@Deprecated
public void onLoad(AbstractBuild<?,?> build) {
if (Util.isOverridden(Cause.class, getClass(), "onLoad", Run.class)) {
onLoad((Run) build);
}
}
/**
* Report a line to the listener about this cause.
......
......@@ -119,11 +119,9 @@ public class CauseAction implements FoldableAction, RunAction2 {
}
@Override public void onLoad(Run<?,?> owner) {
if (owner instanceof AbstractBuild) { // cf. onAttached
AbstractBuild<?,?> b = (AbstractBuild) owner;
for (Cause c : causes) {
if (c != null)
c.onLoad(b);
for (Cause c : causes) {
if (c != null) {
c.onLoad(owner);
}
}
}
......@@ -132,11 +130,9 @@ public class CauseAction implements FoldableAction, RunAction2 {
* When hooked up to build, notify {@link Cause}s.
*/
@Override public void onAttached(Run<?,?> owner) {
if (owner instanceof AbstractBuild) {// this should be always true but being defensive here
AbstractBuild b = (AbstractBuild) owner;
for (Cause c : causes) {
if (c != null)
c.onAddedTo(b);
for (Cause c : causes) {
if (c != null) {
c.onAddedTo(owner);
}
}
}
......
......@@ -26,6 +26,8 @@ package hudson.model;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.model.queue.QueueTaskFuture;
import hudson.scm.SCM;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrappers;
......@@ -35,6 +37,7 @@ import hudson.tasks.Publisher;
import hudson.tasks.Maven;
import hudson.tasks.Maven.ProjectWithMaven;
import hudson.tasks.Maven.MavenInstallation;
import hudson.triggers.SCMTrigger;
import hudson.triggers.Trigger;
import hudson.util.DescribableList;
import net.sf.json.JSONObject;
......@@ -43,11 +46,13 @@ import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import jenkins.triggers.SCMTriggerItem;
/**
* Buildable software project.
......@@ -55,7 +60,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
* @author Kohsuke Kawaguchi
*/
public abstract class Project<P extends Project<P,B>,B extends Build<P,B>>
extends AbstractProject<P,B> implements SCMedItem, Saveable, ProjectWithMaven, BuildableItemWithBuildWrappers {
extends AbstractProject<P,B> implements SCMTriggerItem, Saveable, ProjectWithMaven, BuildableItemWithBuildWrappers {
/**
* List of active {@link Builder}s configured for this project.
......@@ -97,6 +102,22 @@ public abstract class Project<P extends Project<P,B>,B extends Build<P,B>>
return this;
}
@Override public Item asItem() {
return this;
}
@Override public QueueTaskFuture<?> scheduleBuild2(int quietPeriod, Action... actions) {
return scheduleBuild2(quietPeriod, null, actions);
}
@Override public SCMTrigger getSCMTrigger() {
return getTrigger(SCMTrigger.class);
}
@Override public Collection<? extends SCM> getSCMs() {
return SCMTriggerItem.SCMTriggerItems.resolveMultiScmIfConfigured(getScm());
}
public List<Builder> getBuilders() {
return getBuildersList().toList();
}
......
......@@ -25,13 +25,10 @@ package hudson.model;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.triggers.SCMTrigger;
import jenkins.triggers.SCMTriggerItem;
/**
* {@link Item}s that has associated SCM.
*
* @author Kohsuke Kawaguchi
* @see SCMTrigger
* @deprecated Implement {@link SCMTriggerItem} instead.
*/
public interface SCMedItem extends BuildableItem {
/**
......
......@@ -116,6 +116,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
// TODO could also be good to add checkbox that lets users configure a workspace to never be auto-cleaned.
// TODO check instead for SCMTriggerItem:
if (item instanceof AbstractProject<?,?>) {
AbstractProject<?,?> p = (AbstractProject<?,?>) item;
Node lb = p.getLastBuiltOn();
......
......@@ -23,13 +23,26 @@
*/
package hudson.model.listeners;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.model.BuildListener;
import jenkins.model.Jenkins;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.scm.ChangeLogSet;
import hudson.scm.SCM;
import hudson.ExtensionPoint;
import hudson.scm.SCMRevisionState;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;
import jenkins.model.Jenkins;
/**
* Receives notifications about SCM activities in Hudson.
......@@ -45,6 +58,15 @@ import hudson.ExtensionPoint;
* @since 1.70
*/
public abstract class SCMListener implements ExtensionPoint {
/**
* Should be called immediately after {@link SCM#checkout(Run, Launcher, FilePath, TaskListener, File)} is called.
* @param pollingBaseline information about what actually was checked out, if that is available, and this checkout is intended to be included in the build’s polling (if it does any at all)
* @throws Exception if the checkout should be considered failed
* @since TODO
*/
public void onCheckout(Run<?,?> build, SCM scm, FilePath workspace, TaskListener listener, @CheckForNull File changelogFile, @CheckForNull SCMRevisionState pollingBaseline) throws Exception {}
/**
* Called once the changelog is determined.
*
......@@ -85,23 +107,47 @@ public abstract class SCMListener implements ExtensionPoint {
* If any exception is thrown from this method, it will be recorded
* and causes the build to fail.
*/
public void onChangeLogParsed(Run<?,?> build, SCM scm, TaskListener listener, ChangeLogSet<?> changelog) throws Exception {
if (build instanceof AbstractBuild && listener instanceof BuildListener && Util.isOverridden(SCMListener.class, getClass(), "onChangeLogParsed", AbstractBuild.class, BuildListener.class, ChangeLogSet.class)) {
onChangeLogParsed((AbstractBuild) build, (BuildListener) listener, changelog);
}
}
@Deprecated
public void onChangeLogParsed(AbstractBuild<?,?> build, BuildListener listener, ChangeLogSet<?> changelog) throws Exception {
onChangeLogParsed((Run) build, build.getProject().getScm(), listener, changelog);
}
/**
* Registers this {@link SCMListener} so that it will start receiving events.
*/
@SuppressWarnings("deprecation")
public static Collection<? extends SCMListener> all() {
Jenkins j = Jenkins.getInstance();
if (j == null) {
return Collections.emptySet();
}
List<SCMListener> r = new ArrayList<SCMListener>(j.getExtensionList(SCMListener.class));
for (SCMListener l : j.getSCMListeners()) {
r.add(l);
}
return r;
}
/** @deprecated Use {@link Extension} instead. */
@Deprecated
public final void register() {
Jenkins.getInstance().getSCMListeners().add(this);
Jenkins j = Jenkins.getInstance();
if (j != null) {
j.getSCMListeners().add(this);
}
}
/**
* Unregisters this {@link SCMListener} so that it will never receive further events.
*
* <p>
* Unless {@link SCMListener} is unregistered, it will never be a subject of GC.
*/
/** @deprecated Use {@link Extension} instead. */
@Deprecated
public final boolean unregister() {
return Jenkins.getInstance().getSCMListeners().remove(this);
Jenkins j = Jenkins.getInstance();
if (j != null) {
return j.getSCMListeners().remove(this);
} else {
return false;
}
}
}
......@@ -48,6 +48,7 @@ public abstract class SCMPollListener implements ExtensionPoint {
* @param listener
* Connected to the polling log.
*/
// TODO switch to Job
public void onBeforePolling( AbstractProject<?, ?> project, TaskListener listener ) {}
/**
......
......@@ -47,10 +47,19 @@ import jenkins.model.RunAction2;
* @author Kohsuke Kawaguchi
*/
public abstract class AbstractScmTagAction extends TaskAction implements BuildBadgeAction, RunAction2 {
private transient /*final*/ Run<?,?> run;
@Deprecated
protected transient /*final*/ AbstractBuild build;
protected AbstractScmTagAction(Run<?,?> run) {
this.run = run;
this.build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
}
@Deprecated
protected AbstractScmTagAction(AbstractBuild build) {
this.build = build;
this((Run) build);
}
public final String getUrlName() {
......@@ -65,6 +74,11 @@ public abstract class AbstractScmTagAction extends TaskAction implements BuildBa
return SCM.TAG;
}
public Run<?,?> getRun() {
return run;
}
@Deprecated
public AbstractBuild getBuild() {
return build;
}
......@@ -82,7 +96,7 @@ public abstract class AbstractScmTagAction extends TaskAction implements BuildBa
public abstract boolean isTagged();
protected ACL getACL() {
return build.getACL();
return run.getACL();
}
public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
......@@ -100,7 +114,8 @@ public abstract class AbstractScmTagAction extends TaskAction implements BuildBa
}
@Override public void onLoad(Run<?, ?> r) {
build = (AbstractBuild) r;
run = r;
build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
}
}
......@@ -48,7 +48,17 @@ final class AutoBrowserHolder {
}
public RepositoryBrowser get() {
int g = owner.getDescriptor().generation;
if (cacheGeneration == -1) {
return cache;
}
SCMDescriptor<?> d = owner.getDescriptor();
RepositoryBrowser<?> dflt = owner.guessBrowser();
if (dflt != null) {
cache = dflt;
cacheGeneration = -1;
return cache;
}
int g = d.generation;
if(g!=cacheGeneration) {
cacheGeneration = g;
cache = infer();
......
......@@ -23,17 +23,18 @@
*/
package hudson.scm;
import hudson.ExtensionPoint;
import hudson.MarkupText;
import hudson.ExtensionListView;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.util.CopyOnWriteList;
import hudson.scm.ChangeLogSet.Entry;
import hudson.ExtensionListView;
import hudson.ExtensionPoint;
import hudson.MarkupText;
import hudson.Util;
import hudson.model.AbstractBuild;
import jenkins.model.Jenkins;
import hudson.model.Run;
import hudson.scm.ChangeLogSet.Entry;
import hudson.util.CopyOnWriteList;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
/**
* Performs mark up on changelog messages to be displayed.
......@@ -76,7 +77,18 @@ public abstract class ChangeLogAnnotator implements ExtensionPoint {
* method is invoked. Never null. {@link MarkupText#getText()} on this instance
* will return the same string as {@link Entry#getMsgEscaped()}.
*/
public abstract void annotate(AbstractBuild<?,?> build, Entry change, MarkupText text );
public void annotate(Run<?,?> build, Entry change, MarkupText text) {
if (build instanceof AbstractBuild && Util.isOverridden(ChangeLogAnnotator.class, getClass(), "annotate", AbstractBuild.class, Entry.class, MarkupText.class)) {
annotate((AbstractBuild) build, change, text);
} else {
throw new AbstractMethodError("You must override the newer overload of annotate");
}
}
@Deprecated
public void annotate(AbstractBuild<?,?> build, Entry change, MarkupText text) {
annotate((Run) build, change, text);
}
/**
* Registers this annotator, so that Hudson starts using this object
......
......@@ -23,22 +23,34 @@
*/
package hudson.scm;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Build;
import hudson.model.Run;
import hudson.scm.ChangeLogSet.Entry;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import org.xml.sax.SAXException;
/**
* Encapsulates the file format of the changelog.
*
* Instances should be stateless, but
* persisted as a part of {@link Build}.
* persisted as a part of build.
*
* @author Kohsuke Kawaguchi
*/
public abstract class ChangeLogParser {
public abstract ChangeLogSet<? extends Entry> parse(AbstractBuild build, File changelogFile) throws IOException, SAXException;
public ChangeLogSet<? extends Entry> parse(Run build, RepositoryBrowser<?> browser, File changelogFile) throws IOException, SAXException {
if (build instanceof AbstractBuild && Util.isOverridden(ChangeLogParser.class, getClass(), "parse", AbstractBuild.class, File.class)) {
return parse((AbstractBuild) build, changelogFile);
} else {
throw new AbstractMethodError("You must override the newer overload of parse");
}
}
@Deprecated
public ChangeLogSet<? extends Entry> parse(AbstractBuild build, File changelogFile) throws IOException, SAXException {
return parse((Run) build, build.getProject().getScm().getEffectiveBrowser(), changelogFile);
}
}
......@@ -26,6 +26,7 @@ package hudson.scm;
import hudson.MarkupText;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.model.User;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
......@@ -54,12 +55,36 @@ import java.util.logging.Logger;
public abstract class ChangeLogSet<T extends ChangeLogSet.Entry> implements Iterable<T> {
/**
* {@link AbstractBuild} whose change log this object represents.
* Build whose change log this object represents.
*/
private final Run<?,?> run;
@Deprecated
public final AbstractBuild<?,?> build;
private final RepositoryBrowser</* ideally T */?> browser;
protected ChangeLogSet(Run<?,?> run, RepositoryBrowser<?> browser) {
this.run = run;
build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
this.browser = browser;
}
@Deprecated
protected ChangeLogSet(AbstractBuild<?, ?> build) {
this.build = build;
this((Run) build, browserFromBuild(build));
}
private static RepositoryBrowser<?> browserFromBuild(AbstractBuild<?,?> build) {
if (build == null) { // not generally allowed, but sometimes done in unit tests
return null;
}
return build.getParent().getScm().getEffectiveBrowser();
}
public Run<?,?> getRun() {
return run;
}
public RepositoryBrowser<?> getBrowser() {
return browser;
}
/**
......@@ -92,10 +117,15 @@ public abstract class ChangeLogSet<T extends ChangeLogSet.Entry> implements Iter
/**
* Constant instance that represents no changes.
*/
public static ChangeLogSet<? extends ChangeLogSet.Entry> createEmpty(AbstractBuild build) {
public static ChangeLogSet<? extends ChangeLogSet.Entry> createEmpty(Run build) {
return new EmptyChangeLogSet(build);
}
@Deprecated
public static ChangeLogSet<? extends ChangeLogSet.Entry> createEmpty(AbstractBuild build) {
return createEmpty((Run) build);
}
@ExportedBean(defaultVisibility=999)
public static abstract class Entry {
private ChangeLogSet parent;
......@@ -209,7 +239,7 @@ public abstract class ChangeLogSet<T extends ChangeLogSet.Entry> implements Iter
MarkupText markup = new MarkupText(getMsg());
for (ChangeLogAnnotator a : ChangeLogAnnotator.all())
try {
a.annotate(parent.build,this,markup);
a.annotate(parent.run, this, markup);
} catch(Exception e) {
LOGGER.info("ChangeLogAnnotator " + a.toString() + " failed to annotate message '" + getMsg() + "'; " + e.getMessage());
} catch(Error e) {
......
package hudson.scm;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
......@@ -11,8 +13,12 @@ import java.util.Iterator;
* @author Kohsuke Kawaguchi
*/
final class EmptyChangeLogSet extends ChangeLogSet<ChangeLogSet.Entry> {
/*package*/ EmptyChangeLogSet(AbstractBuild<?, ?> build) {
super(build);
/*package*/ EmptyChangeLogSet(Run<?, ?> build) {
super(build, new RepositoryBrowser<ChangeLogSet.Entry>() {
@Override public URL getChangeSetLink(ChangeLogSet.Entry changeSet) throws IOException {
return null;
}
});
}
@Override
......
......@@ -23,7 +23,7 @@
*/
package hudson.scm;
import hudson.model.AbstractBuild;
import hudson.model.Run;
import org.xml.sax.SAXException;
import java.io.File;
......@@ -37,7 +37,7 @@ public class NullChangeLogParser extends ChangeLogParser {
public static final NullChangeLogParser INSTANCE = new NullChangeLogParser();
public ChangeLogSet<? extends ChangeLogSet.Entry> parse(AbstractBuild build, File changelogFile) throws IOException, SAXException {
@Override public ChangeLogSet<? extends ChangeLogSet.Entry> parse(Run build, RepositoryBrowser<?> browser, File changelogFile) throws IOException, SAXException {
return ChangeLogSet.createEmpty(build);
}
......
......@@ -23,18 +23,16 @@
*/
package hudson.scm;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.TaskListener;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import java.io.File;
import java.io.IOException;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
/**
* No {@link SCM}.
......@@ -42,19 +40,21 @@ import java.io.IOException;
* @author Kohsuke Kawaguchi
*/
public class NullSCM extends SCM {
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
@Override public SCMRevisionState calcRevisionsFromBuild(Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
return null;
}
protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
@Override public PollingResult compareRemoteRevisionWith(Job<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
return PollingResult.NO_CHANGES;
}
public boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath remoteDir, BuildListener listener, File changeLogFile) throws IOException, InterruptedException {
return createEmptyChangeLog(changeLogFile, listener, "log");
@Override public void checkout(Run<?,?> build, Launcher launcher, FilePath remoteDir, TaskListener listener, File changeLogFile) throws IOException, InterruptedException {
if (changeLogFile != null) {
createEmptyChangeLog(changeLogFile, listener, "log");
}
}
public ChangeLogParser createChangeLogParser() {
@Override public ChangeLogParser createChangeLogParser() {
return NullChangeLogParser.INSTANCE;
}
......@@ -64,7 +64,7 @@ public class NullSCM extends SCM {
super(null);
}
public String getDisplayName() {
@Override public String getDisplayName() {
return Messages.NullSCM_DisplayName();
}
......
......@@ -24,37 +24,39 @@
package hudson.scm;
import hudson.AbortException;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.Launcher;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.Util;
import hudson.security.PermissionGroup;
import hudson.security.Permission;
import hudson.security.PermissionScope;
import hudson.tasks.Builder;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Api;
import hudson.model.BuildListener;
import hudson.model.Describable;
import hudson.model.TaskListener;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItemDescriptor;
import hudson.model.WorkspaceCleanupThread;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.tasks.Builder;
import hudson.util.IOUtils;
import jenkins.model.Jenkins;
import hudson.model.Descriptor;
import hudson.model.Api;
import hudson.model.Action;
import hudson.model.AbstractProject.AbstractProjectDescriptor;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
......@@ -103,7 +105,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
*
* @see #getEffectiveBrowser()
*/
public RepositoryBrowser<?> getBrowser() {
public @CheckForNull RepositoryBrowser<?> getBrowser() {
return null;
}
......@@ -120,13 +122,11 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
/**
* Returns the applicable {@link RepositoryBrowser} for files
* controlled by this {@link SCM}.
*
* <p>
* This method attempts to find applicable browser
* from other job configurations.
* @see #guessBrowser
* @see SCMDescriptor#isBrowserReusable
*/
@Exported(name="browser")
public final RepositoryBrowser<?> getEffectiveBrowser() {
public final @CheckForNull RepositoryBrowser<?> getEffectiveBrowser() {
RepositoryBrowser<?> b = getBrowser();
if(b!=null)
return b;
......@@ -211,8 +211,21 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
*
* @since 1.246
*/
public boolean processWorkspaceBeforeDeletion(Job<?,?> project, FilePath workspace, Node node) throws IOException, InterruptedException {
if (project instanceof AbstractProject) {
return processWorkspaceBeforeDeletion((AbstractProject) project, workspace, node);
} else {
return true;
}
}
@Deprecated
public boolean processWorkspaceBeforeDeletion(AbstractProject<?,?> project, FilePath workspace, Node node) throws IOException, InterruptedException {
return true;
if (Util.isOverridden(SCM.class, getClass(), "processWorkspaceBeforeDeletion", Job.class, FilePath.class, Node.class)) {
return processWorkspaceBeforeDeletion((Job) project, workspace, node);
} else {
return true;
}
}
/**
......@@ -258,9 +271,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
// up until 1.336, this method was abstract, so everyone should have overridden this method
// without calling super.pollChanges. So the compatibility implementation is purely for
// new implementations that doesn't override this method.
// not sure if this can be implemented any better
return false;
throw new AbstractMethodError("you must override compareRemoteRevisionWith");
}
/**
......@@ -273,7 +284,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
*
* <p>
* This method is called after source code is checked out for the given build (that is, after
* {@link SCM#checkout(AbstractBuild, Launcher, FilePath, BuildListener, File)} has finished successfully.)
* {@link SCM#checkout(Run, Launcher, FilePath, TaskListener, File)} has finished successfully.)
*
* <p>
* The obtained object is added to the build as an {@link Action} for later retrieval. As an optimization,
......@@ -284,6 +295,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
* The calculated {@link SCMRevisionState} is for the files checked out in this build. Never null.
* If {@link #requiresWorkspaceForPolling()} returns true, Hudson makes sure that the workspace of this
* build is available and accessible by the callee.
* @param workspace the location of the checkout
* @param launcher
* Abstraction of the machine where the polling will take place. If SCM declares
* that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace},
......@@ -297,12 +309,24 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
* interruption is usually caused by the user aborting the computation.
* this exception should be simply propagated all the way up.
*/
public abstract SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException;
public SCMRevisionState calcRevisionsFromBuild(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
if (build instanceof AbstractBuild && Util.isOverridden(SCM.class, getClass(), "calcRevisionsFromBuild", AbstractBuild.class, Launcher.class, TaskListener.class)) {
return calcRevisionsFromBuild((AbstractBuild) build, launcher, listener);
} else {
throw new AbstractMethodError("you must override the new calcRevisionsFromBuild overload");
}
}
@Deprecated
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
return calcRevisionsFromBuild(build, build.getWorkspace(), launcher, listener);
}
/**
* A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
* on BugParade for more details.
*/
@Deprecated
public SCMRevisionState _calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
return calcRevisionsFromBuild(build, launcher, listener);
}
......@@ -347,14 +371,17 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
* interruption is usually caused by the user aborting the computation.
* this exception should be simply propagated all the way up.
*/
protected abstract PollingResult compareRemoteRevisionWith(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException;
public PollingResult compareRemoteRevisionWith(Job<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
if (project instanceof AbstractProject && Util.isOverridden(SCM.class, getClass(), "compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class)) {
return compareRemoteRevisionWith((AbstractProject) project, launcher, workspace, listener, baseline);
} else {
throw new AbstractMethodError("you must override the new overload of compareRemoteRevisionWith");
}
}
/**
* A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
* on BugParade for more details.
*/
private PollingResult _compareRemoteRevisionWith(AbstractProject<?, ?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline2) throws IOException, InterruptedException {
return compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2);
@Deprecated
protected PollingResult compareRemoteRevisionWith(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
return compareRemoteRevisionWith((Job) project, launcher, workspace, listener, baseline);
}
/**
......@@ -371,7 +398,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
baseline2 = _calcRevisionsFromBuild(project.getLastBuild(), launcher, listener);
}
return _compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2);
return compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2);
} else {
return pollChanges(project,launcher,workspace,listener) ? PollingResult.SIGNIFICANT : PollingResult.NO_CHANGES;
}
......@@ -382,7 +409,12 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
try {
c.getDeclaredMethod("compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class);
return true;
} catch (NoSuchMethodException e) { }
} catch (NoSuchMethodException e) {
try {
c.getDeclaredMethod("compareRemoteRevisionWith", Job.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class);
return true;
} catch (NoSuchMethodException e2) {}
}
}
return false;
}
......@@ -406,26 +438,56 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
* @param changelogFile
* Upon a successful return, this file should capture the changelog.
* When there's no change, this file should contain an empty entry.
* See {@link #createEmptyChangeLog(File, BuildListener, String)}.
* @return
* false if the operation fails. The error should be reported to the listener.
* Otherwise return the changes included in this update (if this was an update.)
* <p>
* Using the return value to indicate success/failure should
* be considered deprecated, and implementations are encouraged
* to throw {@link AbortException} to indicate a failure.
* See {@link #createEmptyChangeLog(File, TaskListener, String)}.
* May be null, in which case no changelog was requested.
*
* @throws InterruptedException
* interruption is usually caused by the user aborting the build.
* this exception will cause the build to be aborted.
* @throws AbortException in case of a routine failure
*/
public abstract boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException;
public void checkout(Run<?,?> build, Launcher launcher, FilePath workspace, TaskListener listener, @CheckForNull File changelogFile) throws IOException, InterruptedException {
if (build instanceof AbstractBuild && listener instanceof BuildListener && Util.isOverridden(SCM.class, getClass(), "checkout", AbstractBuild.class, Launcher.class, FilePath.class, BuildListener.class, File.class)) {
if (changelogFile == null) {
changelogFile = File.createTempFile("changelog", ".xml");
try {
if (!checkout((AbstractBuild) build, launcher, workspace, (BuildListener) listener, changelogFile)) {
throw new AbortException();
}
} finally {
Util.deleteFile(changelogFile);
}
} else {
if (!checkout((AbstractBuild) build, launcher, workspace, (BuildListener) listener, changelogFile)) {
throw new AbortException();
}
}
} else {
throw new AbstractMethodError("you must override the new overload of checkout");
}
}
@Deprecated
public boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener, @Nonnull File changelogFile) throws IOException, InterruptedException {
checkout((Run) build, launcher, workspace, listener, changelogFile);
return true;
}
/**
* Get a chance to do operations after the workspace i checked out and the changelog is written.
* @since 1.534, 1.532.1
*/
public void postCheckout(Run<?,?> build, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
if (build instanceof AbstractBuild && listener instanceof BuildListener) {
postCheckout((AbstractBuild) build, launcher, workspace, (BuildListener) listener);
}
}
@Deprecated
public void postCheckout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener) throws IOException, InterruptedException {
if (Util.isOverridden(SCM.class, getClass(), "postCheckout", Run.class, Launcher.class, FilePath.class, TaskListener.class)) {
postCheckout((Run) build, launcher, workspace, listener);
}
/* Default implementation is noop */
}
......@@ -442,6 +504,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
* 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(AbstractBuild<?,?> build, Map<String, String> env) {
// default implementation is noop.
}
......@@ -493,6 +556,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
*
* @since 1.382
*/
// TODO perhaps deprecate all replace with a single getModuleRoots(FilePath, Run)
public FilePath getModuleRoot(FilePath workspace, AbstractBuild build) {
// For backwards compatibility, call the one argument version of the method.
return getModuleRoot(workspace);
......@@ -578,16 +642,23 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
// convenience methods
//
@Deprecated
protected final boolean createEmptyChangeLog(File changelogFile, BuildListener listener, String rootTag) {
FileWriter w = null;
try {
w = new FileWriter(changelogFile);
w.write("<"+rootTag +"/>");
w.close();
createEmptyChangeLog(changelogFile, (TaskListener) listener, rootTag);
return true;
} catch (IOException e) {
e.printStackTrace(listener.error(e.getMessage()));
return false;
}
}
protected final void createEmptyChangeLog(File changelogFile, TaskListener listener, String rootTag) throws IOException {
FileWriter w = null;
try {
w = new FileWriter(changelogFile);
w.write("<"+rootTag +"/>");
w.close();
} finally {
IOUtils.closeQuietly(w);
}
......@@ -616,7 +687,7 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
/**
* Returns the list of {@link SCMDescriptor}s that are applicable to the given project.
*/
public static List<SCMDescriptor<?>> _for(final AbstractProject project) {
public static List<SCMDescriptor<?>> _for(final Job project) {
if(project==null) return all();
final Descriptor pd = Jenkins.getInstance().getDescriptor((Class) project.getClass());
......@@ -624,8 +695,8 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
for (SCMDescriptor<?> scmDescriptor : all()) {
if(!scmDescriptor.isApplicable(project)) continue;
if (pd instanceof AbstractProjectDescriptor) {
AbstractProjectDescriptor apd = (AbstractProjectDescriptor) pd;
if (pd instanceof TopLevelItemDescriptor) {
TopLevelItemDescriptor apd = (TopLevelItemDescriptor) pd;
if(!apd.isApplicable(scmDescriptor)) continue;
}
......@@ -634,4 +705,20 @@ public abstract class SCM implements Describable<SCM>, ExtensionPoint {
return r;
}
@Deprecated
public static List<SCMDescriptor<?>> _for(final AbstractProject project) {
return _for((Job) project);
}
/**
* Try to guess how a repository browser should be configured, based on URLs and the like.
* Used when {@link #getBrowser} has not been explicitly configured.
* @return a reasonable default value for {@link #getEffectiveBrowser}, or null
* @since TODO
*/
public @CheckForNull RepositoryBrowser<?> guessBrowser() {
return null;
}
}
......@@ -23,14 +23,15 @@
*/
package hudson.scm;
import hudson.model.Descriptor;
import hudson.Util;
import hudson.model.AbstractProject;
import java.util.List;
import hudson.model.Descriptor;
import hudson.model.Job;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.logging.Logger;
import java.util.List;
import static java.util.logging.Level.WARNING;
import java.lang.reflect.Field;
import java.util.logging.Logger;
/**
* {@link Descriptor} for {@link SCM}.
......@@ -49,7 +50,7 @@ public abstract class SCMDescriptor<T extends SCM> extends Descriptor<SCM> {
/**
* Incremented every time a new {@link SCM} instance is created from this descriptor.
* This is used to invalidate cache. Due to the lack of synchronization and serialization,
* This is used to invalidate cache of {@link SCM#getEffectiveBrowser}. Due to the lack of synchronization and serialization,
* this field doesn't really count the # of instances created to date,
* but it's good enough for the cache invalidation.
*/
......@@ -97,7 +98,7 @@ public abstract class SCMDescriptor<T extends SCM> extends Descriptor<SCM> {
* <p>
* Implementing this method allows Hudson to reuse {@link RepositoryBrowser}
* configured for one project to be used for other "compatible" projects.
*
* <p>{@link SCM#guessBrowser} is more robust since it does not require another project.
* @return
* true if the two given SCM configurations are similar enough
* that they can reuse {@link RepositoryBrowser} between them.
......@@ -111,12 +112,25 @@ public abstract class SCMDescriptor<T extends SCM> extends Descriptor<SCM> {
*
* <p>
* When this method returns false, this {@link SCM} will not appear in the configuration screen
* for the given project. The default method always return true.
* for the given project. The default is true for {@link AbstractProject} but false for {@link Job}.
*
* @since 1.294
*/
public boolean isApplicable(Job project) {
if (project instanceof AbstractProject) {
return isApplicable((AbstractProject) project);
} else {
return false;
}
}
@Deprecated
public boolean isApplicable(AbstractProject project) {
return true;
if (Util.isOverridden(SCMDescriptor.class, getClass(), "isApplicable", Job.class)) {
return isApplicable((Job) project);
} else {
return true;
}
}
/**
......
......@@ -24,56 +24,57 @@
package hudson.triggers;
import antlr.ANTLRException;
import hudson.Util;
import hudson.Extension;
import hudson.Util;
import hudson.console.AnnotatedLargeText;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.AdministrativeMonitor;
import hudson.model.Cause;
import hudson.util.IOUtils;
import jenkins.model.Jenkins;
import hudson.model.CauseAction;
import hudson.model.Item;
import hudson.model.Project;
import hudson.model.SCMedItem;
import hudson.model.AdministrativeMonitor;
import hudson.model.Run;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.util.FlushProofOutputStream;
import hudson.util.FormValidation;
import hudson.util.IOUtils;
import hudson.util.NamingThreadFactory;
import hudson.util.SequentialExecutionQueue;
import hudson.util.StreamTaskListener;
import hudson.util.TimeUnit2;
import hudson.util.SequentialExecutionQueue;
import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.XMLOutput;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import static java.util.logging.Level.WARNING;
import java.util.logging.Logger;
import java.text.DateFormat;
import java.util.concurrent.ThreadFactory;
import jenkins.model.Jenkins;
import jenkins.model.RunAction2;
import jenkins.triggers.SCMTriggerItem;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.jelly.XMLOutput;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import static java.util.logging.Level.*;
import jenkins.model.RunAction2;
/**
* {@link Trigger} that checks for SCM updates periodically.
*
......@@ -85,7 +86,7 @@ import jenkins.model.RunAction2;
*
* @author Kohsuke Kawaguchi
*/
public class SCMTrigger extends Trigger<SCMedItem> {
public class SCMTrigger extends Trigger<Item> {
private boolean ignorePostCommitHooks;
......@@ -195,7 +196,7 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
public boolean isApplicable(Item item) {
return item instanceof SCMedItem;
return SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(item) != null;
}
public ExecutorService getExecutor() {
......@@ -225,11 +226,9 @@ public class SCMTrigger extends Trigger<SCMedItem> {
return Util.filter(queue.getInProgress(),Runner.class);
}
/**
* Gets the snapshot of {@link SCMedItem}s that are being polled at this very moment.
*/
public List<SCMedItem> getItemsBeingPolled() {
List<SCMedItem> r = new ArrayList<SCMedItem>();
// originally List<SCMedItem> but known to be used only for logging, in which case the instances are not actually cast to SCMedItem anyway
public List<SCMTriggerItem> getItemsBeingPolled() {
List<SCMTriggerItem> r = new ArrayList<SCMTriggerItem>();
for (Runner i : getRunners())
r.add(i.getTarget());
return r;
......@@ -268,6 +267,7 @@ public class SCMTrigger extends Trigger<SCMedItem> {
// unless you have a fair number of projects, this option is likely pointless.
// so let's hide this option for new users to avoid confusing them
// unless it was already changed
// TODO switch to check for SCMTriggerItem
return Jenkins.getInstance().getAllItems(AbstractProject.class).size() > 10
|| getPollingThreadCount() != 0;
}
......@@ -311,23 +311,35 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
/**
* Associated with {@link AbstractBuild} to show the polling log
* Associated with {@link Run} to show the polling log
* that triggered that build.
*
* @since 1.376
*/
public static class BuildAction implements RunAction2 {
private transient /*final*/ Run<?,?> run;
@Deprecated
public transient /*final*/ AbstractBuild build;
public BuildAction(Run<?,?> run) {
this.run = run;
build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
}
@Deprecated
public BuildAction(AbstractBuild build) {
this.build = build;
this((Run) build);
}
public Run<?,?> getRun() {
return run;
}
/**
* Polling log that triggered the build.
*/
public File getPollingLogFile() {
return new File(build.getRootDir(),"polling.log");
return new File(run.getRootDir(),"polling.log");
}
public String getIconFileName() {
......@@ -373,16 +385,22 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
@Override public void onLoad(Run<?, ?> r) {
build = (AbstractBuild) r;
run = r;
build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
}
}
/**
* Action object for {@link Project}. Used to display the last polling log.
* Action object for job. Used to display the last polling log.
*/
public final class SCMAction implements Action {
public AbstractProject<?,?> getOwner() {
return job.asProject();
Item item = getItem();
return item instanceof AbstractProject ? ((AbstractProject) item) : null;
}
public Item getItem() {
return job().asItem();
}
public String getIconFileName() {
......@@ -390,7 +408,11 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
public String getDisplayName() {
return Messages.SCMTrigger_getDisplayName(job.getScm().getDescriptor().getDisplayName());
Set<SCMDescriptor<?>> descriptors = new HashSet<SCMDescriptor<?>>();
for (SCM scm : job().getSCMs()) {
descriptors.add(scm.getDescriptor());
}
return descriptors.size() == 1 ? Messages.SCMTrigger_getDisplayName(descriptors.iterator().next().getDisplayName()) : Messages.SCMTrigger_BuildAction_DisplayName();
}
public String getUrlName() {
......@@ -446,8 +468,8 @@ public class SCMTrigger extends Trigger<SCMedItem> {
/**
* For which {@link Item} are we polling?
*/
public SCMedItem getTarget() {
return job;
public SCMTriggerItem getTarget() {
return job();
}
/**
......@@ -474,7 +496,7 @@ public class SCMTrigger extends Trigger<SCMedItem> {
PrintStream logger = listener.getLogger();
long start = System.currentTimeMillis();
logger.println("Started on "+ DateFormat.getDateTimeInstance().format(new Date()));
boolean result = job.poll(listener).hasChanges();
boolean result = job().poll(listener).hasChanges();
logger.println("Done. Took "+ Util.getTimeSpanString(System.currentTimeMillis()-start));
if(result)
logger.println("Changes found");
......@@ -504,7 +526,7 @@ public class SCMTrigger extends Trigger<SCMedItem> {
try {
startTime = System.currentTimeMillis();
if(runPolling()) {
AbstractProject p = job.asProject();
SCMTriggerItem p = job();
String name = " #"+p.getNextBuildNumber();
SCMTriggerCause cause;
try {
......@@ -513,7 +535,10 @@ public class SCMTrigger extends Trigger<SCMedItem> {
LOGGER.log(WARNING, "Failed to parse the polling log",e);
cause = new SCMTriggerCause();
}
if(p.scheduleBuild(p.getQuietPeriod(), cause, additionalActions)) {
Action[] queueActions = new Action[additionalActions.length + 1];
queueActions[0] = new CauseAction(cause);
System.arraycopy(additionalActions, 0, queueActions, 1, additionalActions.length);
if (p.scheduleBuild2(p.getQuietPeriod(), queueActions) != null) {
LOGGER.info("SCM changes detected in "+ job.getName()+". Triggering "+name);
} else {
LOGGER.info("SCM changes detected in "+ job.getName()+". Job is already in the queue");
......@@ -524,15 +549,12 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
}
private SCMedItem job() {
return job;
}
// as per the requirement of SequentialExecutionQueue, value equality is necessary
@Override
public boolean equals(Object that) {
return that instanceof Runner && job()==((Runner)that).job();
return that instanceof Runner && job == ((Runner) that)._job();
}
private Item _job() {return job;}
@Override
public int hashCode() {
......@@ -540,6 +562,11 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
}
@SuppressWarnings("deprecation")
private SCMTriggerItem job() {
return SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(job);
}
public static class SCMTriggerCause extends Cause {
/**
* Only used while ths cause is in the queue.
......@@ -565,7 +592,7 @@ public class SCMTrigger extends Trigger<SCMedItem> {
}
@Override
public void onAddedTo(AbstractBuild build) {
public void onAddedTo(Run build) {
try {
BuildAction a = new BuildAction(build);
FileUtils.writeStringToFile(a.getPollingLogFile(),pollingLog);
......
......@@ -1220,9 +1220,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
updateComputerList(AUTOMATIC_SLAVE_LAUNCH);
}
/**
* Gets all the installed {@link SCMListener}s.
*/
/** @deprecated Use {@link SCMListener#all} instead. */
@Deprecated
public CopyOnWriteList<SCMListener> getSCMListeners() {
return scmListeners;
}
......
/*
* 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.triggers;
import hudson.model.Action;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.TaskListener;
import hudson.model.queue.QueueTaskFuture;
import hudson.scm.NullSCM;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.triggers.SCMTrigger;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.ParameterizedJobMixIn;
/**
* The item type accepted by {@link SCMTrigger}.
* @since TODO
*/
public interface SCMTriggerItem {
/** Should be {@code this}. */
Item asItem();
/** @see Job#getNextBuildNumber */
int getNextBuildNumber();
/** @see jenkins.model.ParameterizedJobMixIn.ParameterizedJob#getQuietPeriod */
int getQuietPeriod();
/** @see ParameterizedJobMixIn#scheduleBuild2 */
@CheckForNull QueueTaskFuture<?> scheduleBuild2(int quietPeriod, Action... actions);
/**
* Checks if there is any update in SCM.
*
* <p>
* The implementation is responsible for ensuring mutual exclusion between polling and builds
* if necessary.
*/
@Nonnull PollingResult poll(@Nonnull TaskListener listener);
@CheckForNull SCMTrigger getSCMTrigger();
/**
* Obtains all active SCMs.
* May be used for informational purposes, or to determine whether to initiate polling.
* @return a possibly empty collection
*/
@Nonnull Collection<? extends SCM> getSCMs();
/**
* Utilities.
*/
class SCMTriggerItems {
/**
* See whether an item can be coerced to {@link SCMTriggerItem}.
* @param item any item
* @return itself, if a {@link SCMTriggerItem}, or an adapter, if an {@link hudson.model.SCMedItem}, else null
* @since TODO
*/
@SuppressWarnings("deprecation")
public static @CheckForNull SCMTriggerItem asSCMTriggerItem(Item item) {
if (item instanceof SCMTriggerItem) {
return (SCMTriggerItem) item;
} else if (item instanceof hudson.model.SCMedItem) {
return new Bridge((hudson.model.SCMedItem) item);
} else {
return null;
}
}
private static final class Bridge implements SCMTriggerItem {
private final hudson.model.SCMedItem delegate;
Bridge(hudson.model.SCMedItem delegate) {
this.delegate = delegate;
}
@Override public Item asItem() {
return delegate.asProject();
}
@Override public int getNextBuildNumber() {
return delegate.asProject().getNextBuildNumber();
}
@Override public int getQuietPeriod() {
return delegate.asProject().getQuietPeriod();
}
@Override public QueueTaskFuture<?> scheduleBuild2(int quietPeriod, Action... actions) {
return delegate.asProject().scheduleBuild2(quietPeriod, null, actions);
}
@Override public PollingResult poll(TaskListener listener) {
return delegate.poll(listener);
}
@Override public SCMTrigger getSCMTrigger() {
return delegate.asProject().getTrigger(SCMTrigger.class);
}
@Override public Collection<? extends SCM> getSCMs() {
return resolveMultiScmIfConfigured(delegate.asProject().getScm());
}
}
public static @Nonnull Collection<? extends SCM> resolveMultiScmIfConfigured(@CheckForNull SCM scm) {
if (scm == null || scm instanceof NullSCM) {
return Collections.emptySet();
} else if (scm.getClass().getName().equals("org.jenkinsci.plugins.multiplescms.MultiSCM")) {
try {
return (Collection<? extends SCM>) scm.getClass().getMethod("getConfiguredSCMs").invoke(scm);
} catch (Exception x) {
Logger.getLogger(SCMTriggerItem.class.getName()).log(Level.WARNING, null, x);
return Collections.singleton(scm);
}
} else {
return Collections.singleton(scm);
}
}
private SCMTriggerItems() {}
}
}
......@@ -32,7 +32,7 @@ THE SOFTWARE.
<l:main-panel>
<j:set var="from" value="${request.getParameter('from')}"/>
<j:set var="to" value="${request.getParameter('to')}"/>
<j:set var="browser" value="${it.scm.effectiveBrowser}"/>
<j:set var="browser" value="${it.scm.effectiveBrowser}"/> <!-- for compatibility; newer project-changes.jelly override this -->
<h1>
${%Changes}
......
......@@ -41,13 +41,14 @@ THE SOFTWARE.
<j:otherwise>
<j:set var="hadChanges" value="${false}"/>
<j:forEach var="b" items="${builds}">
<j:if test="${b.changeSet.iterator().hasNext()}">
<j:forEach var="changeSet" items="${b.changeSets}">
<j:set var="browser" value="${changeSet.browser}"/>
<j:set var="hadChanges" value="${true}"/>
<h2><a href="${b.number}/changes">${b.displayName}
(<i:formatDate value="${b.timestamp.time}" type="both" dateStyle="medium" timeStyle="medium"/>)</a></h2>
<ol>
<j:forEach var="c" items="${b.changeSet.iterator()}" varStatus="loop">
<j:forEach var="c" items="${changeSet.iterator()}" varStatus="loop">
<li value="${c.revision.class.name == 'java.lang.Integer' ? c.revision : null}">
<j:out value="${c.msgAnnotated}"/>
......@@ -61,13 +62,14 @@ THE SOFTWARE.
<a href="${cslink}">${browser.descriptor.displayName}</a>
</j:when>
<j:otherwise>
<!-- This anchor is meaningless if there is >1 ChangeLogSet/index.jelly for the build; an SCM should rather use an actual commit ID for the anchor: -->
<a href="${b.number}/changes#detail${loop.index}">${%detail}</a>
</j:otherwise>
</j:choose>
</li>
</j:forEach>
</ol>
</j:if>
</j:forEach>
</j:forEach>
<j:if test="${!hadChanges}">
${%No changes in any of the builds.}
......
......@@ -27,8 +27,8 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<st:compress xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="${it.build.parent.displayName} #${it.build.number} ${%Polling Log}" norefresh="true">
<st:include it="${it.build}" page="sidepanel.jelly" />
<l:layout title="${it.run.parent.displayName} #${it.run.number} ${%Polling Log}" norefresh="true">
<st:include it="${it.run}" page="sidepanel.jelly" />
<l:main-panel>
<h1>${%Polling Log}</h1>
<l:rightspace>
......
......@@ -25,7 +25,7 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout>
<st:include it="${it.owner}" page="sidepanel.jelly" />
<st:include it="${it.item}" page="sidepanel.jelly" optional="true"/>
<l:main-panel>
<h1>${%title(it.displayName)}</h1>
<j:set var="log" value="${it.log}" />
......
......@@ -33,6 +33,8 @@ import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.NullSCM;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import org.xml.sax.SAXException;
import java.io.File;
......@@ -74,6 +76,14 @@ public class FakeChangeLogSCM extends NullSCM {
return new FakeChangeLogParser();
}
@Override public SCMDescriptor<?> getDescriptor() {
return new SCMDescriptor<SCM>(null) {
@Override public String getDisplayName() {
return "";
}
};
}
public static class ChangelogAction extends InvisibleAction {
private final List<EntryImpl> entries;
......
......@@ -35,6 +35,8 @@ import hudson.tasks.Publisher
import hudson.tasks.Recorder;
import hudson.tasks.Shell;
import hudson.scm.NullSCM;
import hudson.scm.SCM
import hudson.scm.SCMDescriptor
import hudson.Launcher;
import hudson.FilePath;
import hudson.Functions;
......@@ -192,6 +194,13 @@ public class AbstractProjectTest extends HudsonTestCase {
@Override public boolean requiresWorkspaceForPolling() {
return true;
}
@Override public SCMDescriptor<?> getDescriptor() {
return new SCMDescriptor<SCM>(null) {
@Override public String getDisplayName() {
return "";
}
};
}
};
Thread t = new Thread() {
@Override public void run() {
......
......@@ -61,6 +61,7 @@ import hudson.FilePath;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.EnvVars;
import hudson.model.labels.LabelAtom;
import hudson.scm.SCMDescriptor;
import hudson.slaves.Cloud;
import hudson.slaves.DumbSlave;
import hudson.slaves.NodeProvisioner;
......@@ -807,6 +808,13 @@ public class ProjectTest {
public boolean requiresWorkspaceForPolling(){
return true;
}
@Override public SCMDescriptor<?> getDescriptor() {
return new SCMDescriptor<SCM>(null) {
@Override public String getDisplayName() {
return "";
}
};
}
@Override
protected PollingResult compareRemoteRevisionWith(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册