AbstractProject.java 86.9 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5
 * Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot,
6
 * Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts,
7 8
 * id:cactusman, Yahoo! Inc., Andrew Bayer, Manufacture Francaise des Pneumatiques
 * Michelin, Romain Seguy
K
kohsuke 已提交
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 * 
 * 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.
 */
28 29
package hudson.model;

30
import antlr.ANTLRException;
31
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
K
kohsuke 已提交
32
import hudson.AbortException;
33
import hudson.CopyOnWrite;
34 35 36
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
37
import hudson.FeedAdapter;
38
import hudson.FilePath;
39
import hudson.Functions;
40
import hudson.Launcher;
41
import hudson.Util;
42
import hudson.cli.declarative.CLIMethod;
43
import hudson.cli.declarative.CLIResolver;
M
mdonohue 已提交
44
import hudson.model.Cause.LegacyCodeCause;
45
import hudson.model.Cause.RemoteCause;
46
import hudson.model.Cause.UserIdCause;
47 48
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
49
import hudson.model.PermalinkProjectAction.Permalink;
K
kohsuke 已提交
50
import hudson.model.Queue.Executable;
51
import hudson.model.Queue.Task;
52
import hudson.model.RunMap.Constructor;
53 54
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
55
import hudson.model.listeners.ItemListener;
56
import hudson.model.listeners.SCMPollListener;
57
import hudson.model.queue.CauseOfBlockage;
58 59 60
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.ScheduleResult;
import hudson.model.queue.SubTask;
61
import hudson.model.queue.SubTaskContributor;
62
import hudson.node_monitors.DiskSpaceMonitor;
K
kohsuke 已提交
63
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
64
import hudson.scm.ChangeLogSet.Entry;
65
import hudson.scm.NullSCM;
66
import hudson.scm.PollingResult;
67 68

import static hudson.scm.PollingResult.*;
69
import hudson.scm.SCM;
70
import hudson.scm.SCMRevisionState;
71
import hudson.scm.SCMS;
J
jbq 已提交
72
import hudson.search.SearchIndexBuilder;
73
import hudson.security.ACL;
K
kohsuke 已提交
74
import hudson.security.Permission;
75
import hudson.slaves.WorkspaceList;
76
import hudson.tasks.BuildStep;
77
import hudson.tasks.BuildStepDescriptor;
J
jbq 已提交
78
import hudson.tasks.BuildTrigger;
79
import hudson.tasks.BuildWrapperDescriptor;
80
import hudson.tasks.Publisher;
K
kohsuke 已提交
81
import hudson.triggers.SCMTrigger;
82
import hudson.triggers.Trigger;
83
import hudson.triggers.TriggerDescriptor;
84 85
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
86
import hudson.util.DescribableList;
S
 
shinodkm 已提交
87
import hudson.util.FormValidation;
K
Kohsuke Kawaguchi 已提交
88
import hudson.util.TimeUnit2;
K
kohsuke 已提交
89 90
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.*;
117
import jenkins.model.Jenkins;
118
import jenkins.model.JenkinsLocationConfiguration;
119
import jenkins.model.ModelObjectWithChildren;
K
Kohsuke Kawaguchi 已提交
120
import jenkins.model.Uptime;
121
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
122 123 124
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
125
import jenkins.util.TimeDuration;
K
kohsuke 已提交
126
import net.sf.json.JSONObject;
127
import org.acegisecurity.Authentication;
128 129
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
K
Kohsuke Kawaguchi 已提交
130
import org.jenkinsci.bytecode.AdaptField;
131
import org.kohsuke.accmod.Restricted;
132
import org.kohsuke.accmod.restrictions.DoNotUse;
133
import org.kohsuke.accmod.restrictions.NoExternalUse;
134 135
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
136
import org.kohsuke.stapler.Ancestor;
137
import org.kohsuke.stapler.AncestorInPath;
138 139 140
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
141
import org.kohsuke.stapler.HttpResponses;
142
import org.kohsuke.stapler.QueryParameter;
K
kohsuke 已提交
143 144 145
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
146
import org.kohsuke.stapler.interceptor.RequirePOST;
147 148 149

/**
 * Base implementation of {@link Job}s that build software.
150
 *
151
 * For now this is primarily the common part of {@link Project} and MavenModule.
152
 *
153 154 155
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
C
Christoph Kutzinski 已提交
156
@SuppressWarnings("rawtypes")
157
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem, ModelObjectWithChildren {
158

159
    /**
160 161 162
     * {@link SCM} associated with the project.
     * To allow derived classes to link {@link SCM} config to elsewhere,
     * access to this variable should always go through {@link #getScm()}.
163
     */
K
kohsuke 已提交
164
    private volatile SCM scm = new NullSCM();
165

166 167 168 169 170
    /**
     * Controls how the checkout is done.
     */
    private volatile SCMCheckoutStrategy scmCheckoutStrategy;

171 172 173 174 175
    /**
     * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
     */
    private volatile transient SCMRevisionState pollingBaseline = null;

176 177
    /**
     * All the builds keyed by their build number.
178 179 180
     *
     * External code should use {@link #getBuildByNumber(int)} or {@link #getLastBuild()} and traverse via
     * {@link Run#getPreviousBuild()}
181
     */
182
    @Restricted(NoExternalUse.class)
183 184
    @SuppressWarnings("deprecation") // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called
    protected transient RunMap<R> builds = new RunMap<R>();
185 186 187 188

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
189
    private volatile Integer quietPeriod = null;
S
 
shinodkm 已提交
190 191
    
    /**
192
     * The retry count. Null to delegate to the system default.
S
 
shinodkm 已提交
193
     */
194
    private volatile Integer scmCheckoutRetryCount = null;
195 196

    /**
197 198 199 200 201 202 203
     * If this project is configured to be only built on a certain label,
     * this value will be set to that label.
     *
     * For historical reasons, this is called 'assignedNode'. Also for
     * a historical reason, null to indicate the affinity
     * with the master node.
     *
204
     * @see #canRoam
205 206 207 208 209
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
210
     *
211
     * <p>
212 213
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
214
     */
K
kohsuke 已提交
215
    private volatile boolean canRoam;
216 217 218 219

    /**
     * True to suspend new builds.
     */
K
kohsuke 已提交
220
    protected volatile boolean disabled;
221

222 223 224 225 226 227
    /**
     * True to keep builds of this project in queue when downstream projects are
     * building. False by default to keep from breaking existing behavior.
     */
    protected volatile boolean blockBuildWhenDownstreamBuilding = false;

228 229 230 231 232 233
    /**
     * True to keep builds of this project in queue when upstream projects are
     * building. False by default to keep from breaking existing behavior.
     */
    protected volatile boolean blockBuildWhenUpstreamBuilding = false;

234
    /**
235 236 237
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
238
     * <p>
239
     * Can't store {@link JDK} directly because {@link Jenkins} and {@link Project}
240 241
     * are saved independently.
     *
242
     * @see Jenkins#getJDK(String)
243
     */
K
kohsuke 已提交
244
    private volatile String jdk;
245

K
kohsuke 已提交
246
    private volatile BuildAuthorizationToken authToken = null;
247

248 249 250
    /**
     * List of all {@link Trigger}s for this project.
     */
K
Kohsuke Kawaguchi 已提交
251
    @AdaptField(was=List.class)
K
Kohsuke Kawaguchi 已提交
252
    protected volatile DescribableList<Trigger<?>,TriggerDescriptor> triggers = new DescribableList<Trigger<?>,TriggerDescriptor>(this);
K
Kohsuke Kawaguchi 已提交
253
    private static final AtomicReferenceFieldUpdater<AbstractProject,DescribableList> triggersUpdater
K
bug fix  
Kohsuke Kawaguchi 已提交
254
            = AtomicReferenceFieldUpdater.newUpdater(AbstractProject.class,DescribableList.class,"triggers");
255

256 257
    /**
     * {@link Action}s contributed from subsidiary objects associated with
258 259 260 261
     * {@link AbstractProject}, such as from triggers, builders, publishers, etc.
     *
     * We don't want to persist them separately, and these actions
     * come and go as configuration change, so it's kept separate.
262
     */
263 264
    @CopyOnWrite
    protected transient volatile List<Action> transientActions = new Vector<Action>();
265

K
kohsuke 已提交
266 267
    private boolean concurrentBuild;

268 269 270
    /**
     * See {@link #setCustomWorkspace(String)}.
     *
271
     * @since 1.410
272 273 274
     */
    private String customWorkspace;
    
275
    protected AbstractProject(ItemGroup parent, String name) {
276
        super(parent,name);
277

278
        if(Jenkins.getInstance()!=null && !Jenkins.getInstance().getNodes().isEmpty()) {
279
            // if a new job is configured with Hudson that already has slave nodes
280 281 282
            // make it roamable by default
            canRoam = true;
        }
283 284
    }

285 286 287 288 289 290
    @Override
    public synchronized void save() throws IOException {
        super.save();
        updateTransientActions();
    }

291 292 293
    @Override
    public void onCreatedFromScratch() {
        super.onCreatedFromScratch();
J
Johno Crawford 已提交
294
        builds = createBuildRunMap();
295 296
        // solicit initial contributions, especially from TransientProjectActionFactory
        updateTransientActions();
297 298
    }

299
    @Override
300
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
301
        super.onLoad(parent, name);
302

J
Johno Crawford 已提交
303
        RunMap<R> builds = createBuildRunMap();
K
Kohsuke Kawaguchi 已提交
304 305 306

        RunMap<R> currentBuilds = this.builds;

307
        if (currentBuilds==null && parent!=null) {
K
Kohsuke Kawaguchi 已提交
308 309
            // are we overwriting what currently exist?
            // this is primarily when Jenkins is getting reloaded
310 311 312 313 314 315 316
            Item current;
            try {
                current = parent.getItem(name);
            } catch (RuntimeException x) {
                LOGGER.log(Level.WARNING, "failed to look up " + name + " in " + parent, x);
                current = null;
            }
K
Kohsuke Kawaguchi 已提交
317 318
            if (current!=null && current.getClass()==getClass()) {
                currentBuilds = ((AbstractProject)current).builds;
K
Oops  
Kohsuke Kawaguchi 已提交
319
            }
K
Kohsuke Kawaguchi 已提交
320 321
        }
        if (currentBuilds !=null) {
322
            // if we are reloading, keep all those that are still building intact
K
Kohsuke Kawaguchi 已提交
323
            for (R r : currentBuilds.getLoadedBuilds().values()) {
324 325 326 327 328
                if (r.isBuilding())
                    builds.put(r);
            }
        }
        this.builds = builds;
K
Kohsuke Kawaguchi 已提交
329
        triggers().setOwner(this);
330 331 332
        for (Trigger t : triggers()) {
            t.start(this, Items.currentlyUpdatingByXml());
        }
333 334
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
335

336 337
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
338
        updateTransientActions();
339 340
    }

J
Johno Crawford 已提交
341 342 343 344 345 346 347 348
    private RunMap<R> createBuildRunMap() {
        return new RunMap<R>(getBuildDir(), new Constructor<R>() {
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });
    }

K
Kohsuke Kawaguchi 已提交
349 350
    @WithBridgeMethods(List.class)
    protected DescribableList<Trigger<?>,TriggerDescriptor> triggers() {
351
        if (triggers == null) {
K
Kohsuke Kawaguchi 已提交
352
            triggersUpdater.compareAndSet(this,null,new DescribableList<Trigger<?>,TriggerDescriptor>(this));
353 354 355 356
        }
        return triggers;
    }

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
    @Override
    public EnvVars getEnvironment(Node node, TaskListener listener) throws IOException, InterruptedException {
        EnvVars env =  super.getEnvironment(node, listener);

        JDK jdk = getJDK();
        if (jdk != null) {
            if (node != null) { // just in case were not in a build
                jdk = jdk.forNode(node, listener);
            }
            jdk.buildEnvVars(env);
        }

        return env;
    }

372
    @Override
373
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
374 375
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
376
        FilePath ws = getWorkspace();
377
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
378 379 380 381
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
382
        }
K
kohsuke 已提交
383 384 385
        super.performDelete();
    }

K
kohsuke 已提交
386 387
    /**
     * Does this project perform concurrent builds?
K
kohsuke 已提交
388
     * @since 1.319
K
kohsuke 已提交
389
     */
K
kohsuke 已提交
390
    @Exported
K
kohsuke 已提交
391
    public boolean isConcurrentBuild() {
392
        return concurrentBuild;
K
kohsuke 已提交
393 394 395 396 397 398 399
    }

    public void setConcurrentBuild(boolean b) throws IOException {
        concurrentBuild = b;
        save();
    }

400
    /**
401 402
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
403
     */
404
    public Label getAssignedLabel() {
405
        if(canRoam)
406 407
            return null;

408
        if(assignedNode==null)
409 410
            return Jenkins.getInstance().getSelfLabel();
        return Jenkins.getInstance().getLabel(assignedNode);
411 412
    }

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    /**
     * Set of labels relevant to this job.
     *
     * This method is used to determine what slaves are relevant to jobs, for example by {@link View}s.
     * It does not affect the scheduling. This information is informational and the best-effort basis.
     *
     * @since 1.456
     * @return
     *      Minimally it should contain {@link #getAssignedLabel()}. The set can contain null element
     *      to correspond to the null return value from {@link #getAssignedLabel()}.
     */
    public Set<Label> getRelevantLabels() {
        return Collections.singleton(getAssignedLabel());
    }

428 429 430 431
    /**
     * Gets the textual representation of the assigned label as it was entered by the user.
     */
    public String getAssignedLabelString() {
432 433 434 435 436 437 438 439
        if (canRoam || assignedNode==null)    return null;
        try {
            LabelExpression.parseExpression(assignedNode);
            return assignedNode;
        } catch (ANTLRException e) {
            // must be old label or host name that includes whitespace or other unsafe chars
            return LabelAtom.escape(assignedNode);
        }
440 441
    }

K
kohsuke 已提交
442 443 444 445 446 447 448 449 450
    /**
     * Sets the assigned label.
     */
    public void setAssignedLabel(Label l) throws IOException {
        if(l==null) {
            canRoam = true;
            assignedNode = null;
        } else {
            canRoam = false;
451
            if(l== Jenkins.getInstance().getSelfLabel())  assignedNode = null;
452
            else                                        assignedNode = l.getExpression();
K
kohsuke 已提交
453 454 455 456
        }
        save();
    }

457 458 459 460 461 462 463
    /**
     * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
     */
    public void setAssignedNode(Node l) throws IOException {
        setAssignedLabel(l.getSelfLabel());
    }

464
    /**
465 466
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
467 468 469
     */
    @Override
    public String getPronoun() {
K
Kohsuke Kawaguchi 已提交
470
        return AlternativeUiTextProvider.get(PRONOUN, this,Messages.AbstractProject_Pronoun());
471 472
    }

473 474 475 476 477 478
    /**
     * Gets the human readable display name to be rendered in the "Build Now" link.
     *
     * @since 1.401
     */
    public String getBuildNowText() {
479
        return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, isParameterized() ? Messages.AbstractProject_build_with_parameters() : Messages.AbstractProject_BuildNow());
480 481
    }

482
    /**
483
     * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}.
484
     *
485 486 487 488 489 490 491
     * <p>
     * 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()
492
     */
493 494
    public AbstractProject<?,?> getRootProject() {
        if (this instanceof TopLevelItem) {
495 496
            return this;
        } else {
497 498 499 500
            ItemGroup p = this.getParent();
            if (p instanceof AbstractProject)
                return ((AbstractProject) p).getRootProject();
            return this;
501 502 503
        }
    }

504 505
    /**
     * Gets the directory where the module is checked out.
506 507 508
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
K
kohsuke 已提交
509
     * @deprecated as of 1.319
K
kohsuke 已提交
510 511 512 513 514
     *      To support concurrent builds of the same project, this method is moved to {@link AbstractBuild}.
     *      For backward compatibility, this method returns the right {@link AbstractBuild#getWorkspace()} if called
     *      from {@link Executor}, and otherwise the workspace of the last build.
     *
     *      <p>
515
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
K
kohsuke 已提交
516 517 518 519
     *      If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
     *      use {@link #getSomeWorkspace()}
     */
    public final FilePath getWorkspace() {
520 521 522 523 524 525 526 527 528 529 530 531
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getWorkspace() : null;

    }
    
    /**
     * Various deprecated methods in this class all need the 'current' build.  This method returns
     * the build suitable for that purpose.
     * 
     * @return An AbstractBuild for deprecated methods to use.
     */
    private AbstractBuild getBuildForDeprecatedMethods() {
K
kohsuke 已提交
532 533 534 535 536 537
        Executor e = Executor.currentExecutor();
        if(e!=null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if(b.getProject()==this)
538
                    return b;
K
kohsuke 已提交
539 540 541
            }
        }
        R lb = getLastBuild();
542
        if(lb!=null)    return lb;
K
kohsuke 已提交
543 544 545 546 547 548 549 550 551 552 553 554 555
        return null;
    }

    /**
     * Gets a workspace for some build of this project.
     *
     * <p>
     * This is useful for obtaining a workspace for the purpose of form field validation, where exactly
     * which build the workspace belonged is less important. The implementation makes a cursory effort
     * to find some workspace.
     *
     * @return
     *      null if there's no available workspace.
K
kohsuke 已提交
556
     * @since 1.319
557
     */
K
kohsuke 已提交
558
    public final FilePath getSomeWorkspace() {
559
        R b = getSomeBuildWithWorkspace();
560 561 562 563 564 565
        if (b!=null) return b.getWorkspace();
        for (WorkspaceBrowser browser : Jenkins.getInstance().getExtensionList(WorkspaceBrowser.class)) {
            FilePath f = browser.getWorkspace(this);
            if (f != null) return f;
        }
        return null;
566 567 568 569 570 571 572 573
    }

    /**
     * Gets some build that has a live workspace.
     *
     * @return null if no such build exists.
     */
    public final R getSomeBuildWithWorkspace() {
K
kohsuke 已提交
574 575 576
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
577
            if (ws!=null)   return b;
K
kohsuke 已提交
578 579 580
        }
        return null;
    }
581 582 583 584 585 586 587 588 589
    
    private R getSomeBuildWithExistingWorkspace() throws IOException, InterruptedException {
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
            if (ws!=null && ws.exists())   return b;
        }
        return null;
    }
590

591 592 593
    /**
     * Returns the root directory of the checked-out module.
     * <p>
594 595
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
596
     *
K
kohsuke 已提交
597
     * @deprecated as of 1.319
K
kohsuke 已提交
598
     *      See {@link #getWorkspace()} for a migration strategy.
599 600
     */
    public FilePath getModuleRoot() {
601 602
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoot() : null;
603 604
    }

S
stephenconnolly 已提交
605 606 607 608 609 610
    /**
     * Returns the root directories of all checked-out modules.
     * <p>
     * Some SCMs support checking out multiple modules into the same workspace.
     * In these cases, the returned array will have a length greater than one.
     * @return The roots of all modules checked out from the SCM.
K
kohsuke 已提交
611
     *
K
kohsuke 已提交
612
     * @deprecated as of 1.319
K
kohsuke 已提交
613
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
614 615
     */
    public FilePath[] getModuleRoots() {
616 617
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoots() : null;
S
stephenconnolly 已提交
618 619
    }

620
    public int getQuietPeriod() {
621
        return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod();
622
    }
623

624
    public SCMCheckoutStrategy getScmCheckoutStrategy() {
625 626 627
        return scmCheckoutStrategy == null ? new DefaultSCMCheckoutStrategyImpl() : scmCheckoutStrategy;
    }

628
    public void setScmCheckoutStrategy(SCMCheckoutStrategy scmCheckoutStrategy) throws IOException {
629 630 631 632 633
        this.scmCheckoutStrategy = scmCheckoutStrategy;
        save();
    }


634
    public int getScmCheckoutRetryCount() {
635
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
636
    }
637 638 639

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
640
        return quietPeriod!=null;
641
    }
K
kohsuke 已提交
642 643 644 645

    /**
     * Sets the custom quiet period of this project, or revert to the global default if null is given. 
     */
646
    public void setQuietPeriod(Integer seconds) throws IOException {
K
kohsuke 已提交
647 648 649
        this.quietPeriod = seconds;
        save();
    }
S
 
shinodkm 已提交
650
    
651
    public boolean hasCustomScmCheckoutRetryCount(){
652
        return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
653
    }
654

655
    @Override
656
    public boolean isBuildable() {
J
jpederzolli 已提交
657
        return !isDisabled() && !isHoldOffBuildUntilSave();
658 659
    }

660
    /**
661 662
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
663 664 665 666 667
     */
    public boolean isConfigurable() {
        return true;
    }

668 669 670 671 672 673 674 675 676
    public boolean blockBuildWhenDownstreamBuilding() {
        return blockBuildWhenDownstreamBuilding;
    }

    public void setBlockBuildWhenDownstreamBuilding(boolean b) throws IOException {
        blockBuildWhenDownstreamBuilding = b;
        save();
    }

677
    public boolean blockBuildWhenUpstreamBuilding() {
678
        return blockBuildWhenUpstreamBuilding;
679 680
    }

681
    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
682 683
        blockBuildWhenUpstreamBuilding = b;
        save();
684 685
    }

686 687 688
    public boolean isDisabled() {
        return disabled;
    }
S
 
shinodkm 已提交
689 690 691 692 693
    
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
694 695 696 697 698 699 700
        // retry count is optional so this is ok
        if(value == null || value.trim().equals(""))
            return FormValidation.ok();
        if (!value.matches("[0-9]*")) {
            return FormValidation.error("Invalid retry count");
        } 
        return FormValidation.ok();
S
 
shinodkm 已提交
701
    }
702

703 704 705 706
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
707
        if(disabled==b)     return; // noop
708
        this.disabled = b;
K
bug fix  
kohsuke 已提交
709
        if(b)
710
            Jenkins.getInstance().getQueue().cancel(this);
R
rednuht 已提交
711
        
712
        save();
R
rednuht 已提交
713
        ItemListener.fireOnUpdated(this);
714 715
    }

716 717
    /**
     * Specifies whether this project may be disabled by the user.
718 719
     * By default, it can be only if this is a {@link TopLevelItem};
     * would be false for matrix configurations, etc.
720 721 722 723
     * @return true if the GUI should allow {@link #doDisable} and the like
     * @since 1.475
     */
    public boolean supportsMakeDisabled() {
724
        return this instanceof TopLevelItem;
725 726
    }

727 728 729 730 731 732 733 734
    public void disable() throws IOException {
        makeDisabled(true);
    }

    public void enable() throws IOException {
        makeDisabled(false);
    }

K
kohsuke 已提交
735 736
    @Override
    public BallColor getIconColor() {
737
        if(isDisabled())
738
            return BallColor.DISABLED;
K
kohsuke 已提交
739 740 741
        else
            return super.getIconColor();
    }
742

743 744 745 746 747 748 749
    /**
     * effectively deprecated. Since using updateTransientActions correctly
     * under concurrent environment requires a lock that can too easily cause deadlocks.
     *
     * <p>
     * Override {@link #createTransientActions()} instead.
     */
750
    protected void updateTransientActions() {
751 752 753 754
        transientActions = createTransientActions();
    }

    protected List<Action> createTransientActions() {
755
        Vector<Action> ta = new Vector<Action>();
756

K
Kohsuke Kawaguchi 已提交
757
        for (JobProperty<? super P> p : Util.fixNull(properties))
758
            ta.addAll(p.getJobActions((P)this));
759

760 761
        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
            ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
762
        return ta;
763 764
    }

765
    /**
766 767
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
768
     * <p>
769 770
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
771
     */
772
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
773

K
kohsuke 已提交
774 775 776 777 778 779
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

780
    public List<ProminentProjectAction> getProminentActions() {
781
        return getActions(ProminentProjectAction.class);
782 783
    }

784
    @Override
785
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
786
        super.doConfigSubmit(req,rsp);
787

788 789
        updateTransientActions();

790
        Set<AbstractProject> upstream = Collections.emptySet();
791
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
792
            upstream = new HashSet<AbstractProject>(Items.fromNameList(getParent(),req.getParameter("upstreamProjects"),AbstractProject.class));
793 794
        }

795
        convertUpstreamBuildTrigger(upstream);
796 797

        // notify the queue as the project might be now tied to different node
798
        Jenkins.getInstance().getQueue().scheduleMaintenance();
799 800

        // this is to reflect the upstream build adjustments done above
801
        Jenkins.getInstance().rebuildDependencyGraphAsync();
802 803
    }

804 805 806
    /**
     * Reflect the submission of the pseudo 'upstream build trigger'.
     */
807
    /* package */ void convertUpstreamBuildTrigger(Set<AbstractProject> upstream) throws IOException {
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864

        SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM);
        try {
            for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
                // Don't consider child projects such as MatrixConfiguration:
                if (!p.isConfigurable()) continue;
                boolean isUpstream = upstream.contains(p);
                synchronized(p) {
                    // does 'p' include us in its BuildTrigger?
                    DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
                    BuildTrigger trigger = pl.get(BuildTrigger.class);
                    List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects(p);
                    if(isUpstream) {
                        if(!newChildProjects.contains(this))
                            newChildProjects.add(this);
                    } else {
                        newChildProjects.remove(this);
                    }

                    if(newChildProjects.isEmpty()) {
                        pl.remove(BuildTrigger.class);
                    } else {
                        // here, we just need to replace the old one with the new one,
                        // but there was a regression (we don't know when it started) that put multiple BuildTriggers
                        // into the list. For us not to lose the data, we need to merge them all.
                        List<BuildTrigger> existingList = pl.getAll(BuildTrigger.class);
                        BuildTrigger existing;
                        switch (existingList.size()) {
                        case 0:
                            existing = null;
                            break;
                        case 1:
                            existing = existingList.get(0);
                            break;
                        default:
                            pl.removeAll(BuildTrigger.class);
                            Set<AbstractProject> combinedChildren = new HashSet<AbstractProject>();
                            for (BuildTrigger bt : existingList)
                                combinedChildren.addAll(bt.getChildProjects(p));
                            existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),existingList.get(0).getThreshold());
                            pl.add(existing);
                            break;
                        }

                        if(existing!=null && existing.hasSame(p,newChildProjects))
                            continue;   // no need to touch
                        pl.replace(new BuildTrigger(newChildProjects,
                            existing==null? Result.SUCCESS:existing.getThreshold()));
                    }
                }
            }
        } finally {
            SecurityContextHolder.setContext(saveCtx);
        }
    }

    /**
M
mdonohue 已提交
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
	 * @deprecated
	 *    Use {@link #scheduleBuild(Cause)}.  Since 1.283
	 */
    public boolean scheduleBuild() {
    	return scheduleBuild(new LegacyCodeCause());
    }
    
	/**
	 * @deprecated
	 *    Use {@link #scheduleBuild(int, Cause)}.  Since 1.283
	 */
    public boolean scheduleBuild(int quietPeriod) {
    	return scheduleBuild(quietPeriod, new LegacyCodeCause());
    }
    
880 881
    /**
     * Schedules a build of this project.
882 883
     *
     * @return
K
Kohsuke Kawaguchi 已提交
884 885
     *      true if the project is added to the queue.
     *      false if the task was rejected from the queue (such as when the system is being shut down.)
886
     */
M
mdonohue 已提交
887 888
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
889 890
    }

M
mdonohue 已提交
891
    public boolean scheduleBuild(int quietPeriod, Cause c) {
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
        return scheduleBuild(quietPeriod, c, new Action[0]);
    }

    /**
     * Schedules a build.
     *
     * Important: the actions should be persistable without outside references (e.g. don't store
     * references to this project). To provide parameters for a parameterized project, add a ParametersAction. If
     * no ParametersAction is provided for such a project, one will be created with the default parameter values.
     *
     * @param quietPeriod the quiet period to observer
     * @param c the cause for this build which should be recorded
     * @param actions a list of Actions that will be added to the build
     * @return whether the build was actually scheduled
     */
    public boolean scheduleBuild(int quietPeriod, Cause c, Action... actions) {
908 909 910 911 912 913
        return scheduleBuild2(quietPeriod,c,actions)!=null;
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
914 915 916
     *
     * @param actions
     *      For the convenience of the caller, this array can contain null, and those will be silently ignored.
917
     */
918 919
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
K
kohsuke 已提交
920 921 922 923 924 925 926 927 928 929 930
        return scheduleBuild2(quietPeriod,c,Arrays.asList(actions));
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * @param actions
     *      For the convenience of the caller, this collection can contain null, and those will be silently ignored.
     * @since 1.383
     */
C
Christoph Kutzinski 已提交
931
    @SuppressWarnings("unchecked")
932 933
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
934
        if (!isBuildable())
935
            return null;
936

K
kohsuke 已提交
937
        List<Action> queueActions = new ArrayList<Action>(actions);
938 939 940 941
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
942 943 944 945
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

K
Kohsuke Kawaguchi 已提交
946 947 948
        ScheduleResult i = Jenkins.getInstance().getQueue().schedule2(this, quietPeriod, queueActions);
        if(i.isAccepted())
            return (QueueTaskFuture)i.getItem().getFuture();
949
        return null;
950 951 952 953 954 955
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
956 957 958
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
959 960 961 962 963 964 965 966 967 968 969 970 971
        if(paramDefProp == null)
            return defValues;
        
        /* Scan for all parameter with an associated default values */
        for(ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions())
        {
           ParameterValue defaultValue  = paramDefinition.getDefaultParameterValue();
            
            if(defaultValue != null)
                defValues.add(defaultValue);           
        }
        
        return defValues;
K
kohsuke 已提交
972 973
    }

974
    /**
975 976 977 978
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
979
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
980
     * as deprecated.
981
     */
C
Christoph Kutzinski 已提交
982
    @SuppressWarnings("deprecation")
983 984
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod) {
985
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
M
mdonohue 已提交
986 987
    }
    
K
kohsuke 已提交
988
    /**
989 990
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
991
     */
992 993
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c) {
994 995 996
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

997 998 999 1000
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
1001
        if(isDisabled())    return false;
1002
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
1003
        if(scmt==null)      return false;
1004 1005 1006 1007
        scmt.run();
        return true;
    }

1008 1009 1010 1011 1012
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
1013
        return Jenkins.getInstance().getQueue().contains(this);
1014 1015
    }

K
kohsuke 已提交
1016 1017
    @Override
    public Queue.Item getQueueItem() {
1018
        return Jenkins.getInstance().getQueue().getItem(this);
K
kohsuke 已提交
1019 1020
    }

K
kohsuke 已提交
1021 1022 1023
    /**
     * Gets the JDK that this project is configured with, or null.
     */
1024
    public JDK getJDK() {
1025
        return Jenkins.getInstance().getJDK(jdk);
1026 1027 1028 1029 1030
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
1031
    public void setJDK(JDK jdk) throws IOException {
1032 1033 1034 1035
        this.jdk = jdk.getName();
        save();
    }

1036 1037
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
1038 1039
    }

1040
    @Override
1041
    public RunMap<R> _getRuns() {
J
Jesse Glick 已提交
1042 1043 1044
        if (builds == null) {
            throw new IllegalStateException("no run map created yet for " + this);
        }
1045
        assert builds.baseDirInitialized() : "neither onCreatedFromScratch nor onLoad called on " + this + " yet";
1046
        return builds;
1047 1048
    }

1049
    @Override
1050
    public void removeRun(R run) {
1051 1052 1053
        if (!this.builds.remove(run)) {
            LOGGER.log(Level.WARNING, "{0} did not contain {1} to begin with", new Object[] {this, run});
        }
1054 1055
    }

1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getBuild(String id) {
        return builds.getById(id);
    }

    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getBuildByNumber(int n) {
        return builds.getByNumber(n);
    }

    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getFirstBuild() {
        return builds.oldestBuild();
    }

    @Override
1087
    public @CheckForNull R getLastBuild() {
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
        return builds.newestBuild();
    }

    @Override
    public R getNearestBuild(int n) {
        return builds.search(n, Direction.ASC);
    }

    @Override
    public R getNearestOldBuild(int n) {
        return builds.search(n, Direction.DESC);
    }

1101 1102 1103 1104 1105
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
1106
    // keep track of the previous time we started a build
1107
    private transient long lastBuildStartTime;
H
huybrechts 已提交
1108
    
1109 1110 1111
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
    protected synchronized R newBuild() throws IOException {
    	// make sure we don't start two builds in the same second
    	// so the build directories will be different too
    	long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
    	if (timeSinceLast < 1000) {
    		try {
				Thread.sleep(1000 - timeSinceLast);
			} catch (InterruptedException e) {
			}
    	}
    	lastBuildStartTime = System.currentTimeMillis();
1123
        try {
1124
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
1125 1126 1127 1128 1129 1130 1131
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
1132
            throw handleInvocationTargetException(e);
1133 1134 1135 1136
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
1137

1138
    private IOException handleInvocationTargetException(InvocationTargetException e) {
1139
        Throwable t = e.getTargetException();
1140 1141 1142
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
1143 1144 1145
        throw new Error(t);
    }

1146 1147 1148
    /**
     * Loads an existing build record from disk.
     */
1149 1150
    protected R loadBuild(File dir) throws IOException {
        try {
1151
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
1152 1153 1154 1155 1156
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
1157
            throw handleInvocationTargetException(e);
1158 1159 1160 1161
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
1162

K
kohsuke 已提交
1163 1164
    /**
     * {@inheritDoc}
1165
     *
K
kohsuke 已提交
1166 1167
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
1168
     * {@link BuildStep}s and others who want to add a project action
1169
     * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
1170 1171
     *
     * @see TransientProjectActionFactory
K
kohsuke 已提交
1172
     */
1173
    @SuppressWarnings("deprecation")
1174
    @Override
1175
    public List<Action> getActions() {
1176 1177 1178
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
1179
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
1180
        return Collections.unmodifiableList(actions);
1181 1182
    }

1183 1184
    /**
     * Gets the {@link Node} where this project was last built on.
1185 1186 1187 1188
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
1189 1190 1191 1192
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
1193
        if(b==null)
1194 1195 1196 1197 1198
            return null;
        else
            return b.getBuiltOn();
    }

1199 1200 1201 1202
    public Object getSameNodeConstraint() {
        return this; // in this way, any member that wants to run with the main guy can nominate the project itself 
    }

1203 1204 1205 1206
    public final Task getOwnerTask() {
        return this;
    }

1207 1208 1209 1210
    @Nonnull
    public Authentication getDefaultAuthentication() {
        // backward compatible behaviour.
        return ACL.SYSTEM;
1211 1212
    }

1213
    /**
1214
     * {@inheritDoc}
1215
     *
1216
     * <p>
1217
     * A project must be blocked if its own previous build is in progress,
1218 1219
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
1220
     */
1221
    public boolean isBuildBlocked() {
1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
        return getCauseOfBlockage()!=null;
    }

    public String getWhyBlocked() {
        CauseOfBlockage cb = getCauseOfBlockage();
        return cb!=null ? cb.getShortDescription() : null;
    }

    /**
     * Blocked because the previous build is already in progress.
     */
    public static class BecauseOfBuildInProgress extends CauseOfBlockage {
        private final AbstractBuild<?,?> build;

        public BecauseOfBuildInProgress(AbstractBuild<?, ?> build) {
            this.build = build;
1238 1239
        }

1240
        @Override
1241 1242 1243 1244 1245 1246 1247 1248
        public String getShortDescription() {
            Executor e = build.getExecutor();
            String eta = "";
            if (e != null)
                eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
            int lbn = build.getNumber();
            return Messages.AbstractProject_BuildInProgress(lbn, eta);
        }
1249
    }
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265
    
    /**
     * 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 BecauseOfDownstreamBuildInProgress(AbstractProject<?,?> up) {
            this.up = up;
        }

        @Override
        public String getShortDescription() {
            return Messages.AbstractProject_DownstreamBuildInProgress(up.getName());
        }
    }
1266

1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
    /**
     * 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 BecauseOfUpstreamBuildInProgress(AbstractProject<?,?> up) {
            this.up = up;
        }

1277
        @Override
1278 1279 1280
        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
1281 1282
    }

1283
    public CauseOfBlockage getCauseOfBlockage() {
1284 1285
        // Block builds until they are done with post-production
        if (isLogUpdated() && !isConcurrentBuild())
1286
            return new BecauseOfBuildInProgress(getLastBuild());
1287 1288 1289 1290
        if (blockBuildWhenDownstreamBuilding()) {
            AbstractProject<?,?> bup = getBuildingDownstream();
            if (bup!=null)
                return new BecauseOfDownstreamBuildInProgress(bup);
1291 1292
        }
        if (blockBuildWhenUpstreamBuilding()) {
1293 1294 1295 1296 1297 1298
            AbstractProject<?,?> bup = getBuildingUpstream();
            if (bup!=null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }
1299

1300
    /**
1301 1302
     * Returns the project if any of the downstream project is either
     * building, waiting, pending or buildable.
1303 1304 1305 1306
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
1307
    public AbstractProject getBuildingDownstream() {
1308
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1309

1310
        for (AbstractProject tup : getTransitiveDownstreamProjects()) {
1311
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1312 1313 1314 1315 1316
                return tup;
        }
        return null;
    }

1317
    /**
1318
     * Returns the project if any of the upstream project is either
1319 1320 1321 1322 1323
     * building or is in the queue.
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
1324
    public AbstractProject getBuildingUpstream() {
1325
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1326

1327
        for (AbstractProject tup : getTransitiveUpstreamProjects()) {
1328
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1329 1330 1331
                return tup;
        }
        return null;
1332 1333
    }

1334 1335
    public List<SubTask> getSubTasks() {
        List<SubTask> r = new ArrayList<SubTask>();
1336
        r.add(this);
1337 1338 1339
        for (SubTaskContributor euc : SubTaskContributor.all())
            r.addAll(euc.forProject(this));
        for (JobProperty<? super P> p : properties)
1340
            r.addAll(p.getSubTasks());
1341
        return r;
1342 1343
    }

1344
    public R createExecutable() throws IOException {
1345
        if(isDisabled())    return null;
1346
        return newBuild();
1347 1348
    }

1349 1350 1351 1352
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
1353 1354 1355 1356
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

1357 1358
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
1359
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
1360
     *
K
kohsuke 已提交
1361
     * @deprecated as of 1.319
K
kohsuke 已提交
1362 1363 1364 1365 1366 1367 1368
     *      Projects no longer have a fixed workspace, ands builds will find an available workspace via
     *      {@link WorkspaceList} for each build (furthermore, that happens after a build is started.)
     *      So a {@link Resource} representation for a workspace at the project level no longer makes sense.
     *
     *      <p>
     *      If you need to lock a workspace while you do some computation, see the source code of
     *      {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
1369 1370
     */
    public Resource getWorkspaceResource() {
1371
        return new Resource(getFullDisplayName()+" workspace");
1372 1373 1374 1375 1376 1377
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
1378
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
1379
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
1380 1381 1382 1383 1384 1385 1386 1387 1388 1389
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
1390 1391
     * Set of child resource activities of the build of this project (override in child projects).
     * @return The set of child resource activities of the build of this project.
1392 1393
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
1394
        return Collections.emptySet();
1395 1396
    }

1397
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
1398
        SCM scm = getScm();
1399 1400
        if(scm==null)
            return true;    // no SCM
1401

1402
        FilePath workspace = build.getWorkspace();
1403 1404 1405 1406
        try {
            workspace.mkdirs();
        } catch (IOException e) {
            // Can't create workspace dir - Is slave disk full ?
1407
            new DiskSpaceMonitor().markNodeOfflineIfDiskspaceIsTooLow(build.getBuiltOn().toComputer());
1408 1409
            throw e;
        }
1410 1411
        
        boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
C
Christoph Kutzinski 已提交
1412 1413 1414
        if (r) {
            // Only calcRevisionsFromBuild if checkout was successful. Note that modern SCM implementations
            // won't reach this line anyway, as they throw AbortExceptions on checkout failure.
1415 1416
            calcPollingBaseline(build, launcher, listener);
        }
1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
        return r;
    }

    /**
     * Pushes the baseline up to the newly checked out revision.
     */
    private void calcPollingBaseline(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
        SCMRevisionState baseline = build.getAction(SCMRevisionState.class);
        if (baseline==null) {
            try {
1427
                baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
1428 1429 1430 1431 1432 1433 1434 1435 1436
            } catch (AbstractMethodError e) {
                baseline = SCMRevisionState.NONE; // pre-1.345 SCM implementations, which doesn't use the baseline in polling
            }
            if (baseline!=null)
                build.addAction(baseline);
        }
        pollingBaseline = baseline;
    }

1437 1438
    /**
     * Checks if there's any update in SCM, and returns true if any is found.
1439
     *
1440 1441
     * @deprecated as of 1.346
     *      Use {@link #poll(TaskListener)} instead.
1442
     */
1443
    public boolean pollSCMChanges( TaskListener listener ) {
1444 1445 1446 1447 1448 1449 1450
        return poll(listener).hasChanges();
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * <p>
1451 1452
     * The implementation is responsible for ensuring mutual exclusion between polling and builds
     * if necessary.
1453 1454 1455 1456
     *
     * @since 1.345
     */
    public PollingResult poll( TaskListener listener ) {
1457
        SCM scm = getScm();
1458
        if (scm==null) {
K
i18n  
kohsuke 已提交
1459
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
1460
            return NO_CHANGES;
1461
        }
1462
        if (!isBuildable()) {
K
i18n  
kohsuke 已提交
1463
            listener.getLogger().println(Messages.AbstractProject_Disabled());
1464 1465 1466 1467 1468
            return NO_CHANGES;
        }

        R lb = getLastBuild();
        if (lb==null) {
1469
            listener.getLogger().println(Messages.AbstractProject_NoBuilds());
1470
            return isInQueue() ? NO_CHANGES : BUILD_NOW;
1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
        }

        if (pollingBaseline==null) {
            R success = getLastSuccessfulBuild(); // if we have a persisted baseline, we'll find it by this
            for (R r=lb; r!=null; r=r.getPreviousBuild()) {
                SCMRevisionState s = r.getAction(SCMRevisionState.class);
                if (s!=null) {
                    pollingBaseline = s;
                    break;
                }
                if (r==success) break;  // searched far enough
            }
            // NOTE-NO-BASELINE:
            // if we don't have baseline yet, it means the data is built by old Hudson that doesn't set the baseline
            // as action, so we need to compute it. This happens later.
1486 1487 1488
        }

        try {
K
Kohsuke Kawaguchi 已提交
1489
            SCMPollListener.fireBeforePolling(this, listener);
1490
            PollingResult r = _poll(listener, scm);
K
Kohsuke Kawaguchi 已提交
1491 1492
            SCMPollListener.firePollingSuccess(this,listener, r);
            return r;
1493
        } catch (AbortException e) {
1494
            listener.getLogger().println(e.getMessage());
K
i18n  
kohsuke 已提交
1495
            listener.fatalError(Messages.AbstractProject_Aborted());
1496
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
K
Kohsuke Kawaguchi 已提交
1497
            SCMPollListener.firePollingFailed(this, listener,e);
1498
            return NO_CHANGES;
1499 1500
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
K
Kohsuke Kawaguchi 已提交
1501
            SCMPollListener.firePollingFailed(this, listener,e);
1502
            return NO_CHANGES;
1503
        } catch (InterruptedException e) {
1504
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
K
Kohsuke Kawaguchi 已提交
1505
            SCMPollListener.firePollingFailed(this, listener,e);
1506
            return NO_CHANGES;
K
Kohsuke Kawaguchi 已提交
1507 1508 1509 1510 1511 1512
        } catch (RuntimeException e) {
            SCMPollListener.firePollingFailed(this, listener,e);
            throw e;
        } catch (Error e) {
            SCMPollListener.firePollingFailed(this, listener,e);
            throw e;
1513 1514
        }
    }
K
Kohsuke Kawaguchi 已提交
1515 1516 1517 1518

    /**
     * {@link #poll(TaskListener)} method without the try/catch block that does listener notification and .
     */
1519
    private PollingResult _poll(TaskListener listener, SCM scm) throws IOException, InterruptedException {
K
Kohsuke Kawaguchi 已提交
1520
        if (scm.requiresWorkspaceForPolling()) {
1521 1522 1523 1524
            R b = getSomeBuildWithExistingWorkspace();
            if (b == null) b = getLastBuild();
            // lock the workspace for the given build
            FilePath ws=b.getWorkspace();
K
Kohsuke Kawaguchi 已提交
1525

1526
            WorkspaceOfflineReason workspaceOfflineReason = workspaceOffline( b );
1527
            if ( workspaceOfflineReason != null ) {
1528 1529 1530 1531
                // workspace offline
                for (WorkspaceBrowser browser : Jenkins.getInstance().getExtensionList(WorkspaceBrowser.class)) {
                    ws = browser.getWorkspace(this);
                    if (ws != null) {
1532
                        return pollWithWorkspace(listener, scm, b, ws, browser.getWorkspaceList());
1533 1534 1535
                    }
                }

K
Kohsuke Kawaguchi 已提交
1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548
                // At this point we start thinking about triggering a build just to get a workspace,
                // because otherwise there's no way we can detect changes.
                // However, first there are some conditions in which we do not want to do so.

                // 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) {
                    listener.getLogger().print(Messages.AbstractProject_AwaitingWorkspaceToComeOnline(remaining/1000));
                    listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
                    return NO_CHANGES;
                }

K
Kohsuke Kawaguchi 已提交
1549 1550 1551 1552
                Label label = getAssignedLabel();
                if (label != null && label.isSelfLabel()) {
                    // if the build is fixed on a node, then attempting a build will do us
                    // no good. We should just wait for the slave to come back.
1553 1554
                    listener.getLogger().print(Messages.AbstractProject_NoWorkspace());
                    listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
K
Kohsuke Kawaguchi 已提交
1555 1556
                    return NO_CHANGES;
                }
K
Kohsuke Kawaguchi 已提交
1557

K
Kohsuke Kawaguchi 已提交
1558 1559 1560 1561 1562 1563 1564
                listener.getLogger().println( ws==null
                    ? Messages.AbstractProject_WorkspaceOffline()
                    : Messages.AbstractProject_NoWorkspace());
                if (isInQueue()) {
                    listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
                    return NO_CHANGES;
                }
K
Kohsuke Kawaguchi 已提交
1565 1566 1567 1568 1569

                // build now, or nothing will ever be built
                listener.getLogger().print(Messages.AbstractProject_NewBuildForWorkspace());
                listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
                return BUILD_NOW;
K
Kohsuke Kawaguchi 已提交
1570
            } else {
1571 1572
                WorkspaceList l = b.getBuiltOn().toComputer().getWorkspaceList();
                return pollWithWorkspace(listener, scm, b, ws, l);
1573

K
Kohsuke Kawaguchi 已提交
1574 1575 1576 1577 1578 1579
            }
        } else {
            // polling without workspace
            LOGGER.fine("Polling SCM changes of " + getName());

            if (pollingBaseline==null) // see NOTE-NO-BASELINE above
1580
                calcPollingBaseline(getLastBuild(),null,listener);
K
Kohsuke Kawaguchi 已提交
1581 1582 1583 1584 1585 1586
            PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
            pollingBaseline = r.remote;
            return r;
        }
    }

1587 1588 1589 1590 1591 1592 1593 1594
    private PollingResult pollWithWorkspace(TaskListener listener, SCM scm, R lb, FilePath ws, WorkspaceList l) throws InterruptedException, IOException {
        // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
        // this prevents multiple workspaces of the same job --- the behavior of Hudson < 1.319.
        //
        // OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
        // so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
        // by having multiple workspaces
        WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
1595 1596
        Node node = lb.getBuiltOn();
        Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(node,listener));
1597
        try {
1598
            listener.getLogger().println("Polling SCM changes on " + node.getSelfLabel().getName());
1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609
            LOGGER.fine("Polling SCM changes of " + getName());
            if (pollingBaseline==null) // see NOTE-NO-BASELINE above
                calcPollingBaseline(lb,launcher,listener);
            PollingResult r = scm.poll(this, launcher, ws, listener, pollingBaseline);
            pollingBaseline = r.remote;
            return r;
        } finally {
            lease.release();
        }
    }

1610 1611 1612 1613 1614 1615 1616
    enum WorkspaceOfflineReason {
        nonexisting_workspace,
        builton_node_gone,
        builton_node_no_executors
    }

    private WorkspaceOfflineReason workspaceOffline(R build) throws IOException, InterruptedException {
1617 1618
        FilePath ws = build.getWorkspace();
        if (ws==null || !ws.exists()) {
1619
            return WorkspaceOfflineReason.nonexisting_workspace;
1620 1621 1622 1623
        }
        
        Node builtOn = build.getBuiltOn();
        if (builtOn == null) { // node built-on doesn't exist anymore
1624
            return WorkspaceOfflineReason.builton_node_gone;
1625 1626 1627
        }
        
        if (builtOn.toComputer() == null) { // node still exists, but has 0 executors - o.s.l.t.
1628
            return WorkspaceOfflineReason.builton_node_no_executors;
1629
        }
1630 1631

        return null;
1632
    }
1633

1634 1635
    /**
     * Returns true if this user has made a commit to this project.
1636
     *
1637 1638 1639
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
1640 1641
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
1642 1643 1644 1645
                return true;
        return false;
    }

1646
    @Exported
1647 1648 1649 1650
    public SCM getScm() {
        return scm;
    }

1651
    public void setScm(SCM scm) throws IOException {
1652
        this.scm = scm;
1653
        save();
1654 1655
    }

1656 1657 1658
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
1659
    public void addTrigger(Trigger<?> trigger) throws IOException {
1660
        addToList(trigger,triggers());
1661 1662
    }

1663
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1664
        removeFromList(trigger,triggers());
1665 1666
    }

1667 1668
    protected final synchronized <T extends Describable<T>>
    void addToList( T item, List<T> collection ) throws IOException {
1669 1670
        //No support to replace item in position, remove then add
        removeFromList(item.getDescriptor(), collection);
1671 1672
        collection.add(item);
        save();
1673
        updateTransientActions();
1674 1675
    }

1676 1677
    protected final synchronized <T extends Describable<T>>
    void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
1678 1679 1680 1681
        final Iterator<T> iCollection = collection.iterator();
        while(iCollection.hasNext()) {
            final T next = iCollection.next();
            if(next.getDescriptor()==item) {
1682
                // found it
1683
                iCollection.remove();
1684
                save();
1685
                updateTransientActions();
1686 1687 1688 1689 1690
                return;
            }
        }
    }

C
Christoph Kutzinski 已提交
1691
    @SuppressWarnings("unchecked")
K
Kohsuke Kawaguchi 已提交
1692 1693
    public Map<TriggerDescriptor,Trigger<?>> getTriggers() {
        return triggers().toMap();
1694 1695
    }

1696
    /**
1697
     * Gets the specific trigger, or null if the propert is not configured for this job.
1698 1699
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
1700
        for (Trigger p : triggers()) {
1701
            if(clazz.isInstance(p))
1702 1703 1704 1705 1706
                return clazz.cast(p);
        }
        return null;
    }

1707 1708 1709 1710 1711
//
//
// fingerprint related
//
//
1712 1713 1714 1715 1716 1717
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1718 1719
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1720
     */
K
kohsuke 已提交
1721
    @Exported
1722
    public final List<AbstractProject> getDownstreamProjects() {
1723
        return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
1724
    }
1725

K
kohsuke 已提交
1726
    @Exported
1727
    public final List<AbstractProject> getUpstreamProjects() {
1728
        return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1729 1730
    }

K
kohsuke 已提交
1731
    /**
1732 1733 1734 1735
     * Returns only those upstream projects that defines {@link BuildTrigger} to this project.
     * This is a subset of {@link #getUpstreamProjects()}
     *
     * @return A List of upstream projects that has a {@link BuildTrigger} to this project.
K
kohsuke 已提交
1736 1737 1738
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1739 1740
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1741
            if (buildTrigger != null)
1742
                if (buildTrigger.getChildProjects(ap).contains(this))
1743
                    result.add(ap);
1744
        }        
K
kohsuke 已提交
1745
        return result;
1746 1747
    }    
    
K
kohsuke 已提交
1748 1749
    /**
     * Gets all the upstream projects including transitive upstream projects.
1750
     *
K
kohsuke 已提交
1751 1752 1753
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1754
        return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1755 1756 1757
    }

    /**
1758 1759
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1760 1761 1762
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1763
        return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1764 1765 1766 1767 1768
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1769 1770 1771 1772
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1773 1774
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1775
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1776 1777 1778 1779 1780 1781 1782 1783 1784

        checkAndRecord(that, r, this.getBuilds());
        // checkAndRecord(that, r, that.getBuilds());

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1785 1786
     *
     * For each given build, find the build number range of the given project and put that into the map.
1787
     */
1788
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1789 1790
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1791
            if(rs==null || rs.isEmpty())
1792 1793 1794 1795 1796
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1797 1798
            if(value==null)
                r.put(n,rs);
1799 1800 1801 1802 1803
            else
                value.add(rs);
        }
    }

1804 1805 1806 1807 1808 1809
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

1810
    @Override
K
kohsuke 已提交
1811 1812
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1813
        if(isBuildable() && hasPermission(Jenkins.ADMINISTER))
1814
            sib.add("build","build");
K
kohsuke 已提交
1815 1816 1817
        return sib;
    }

1818 1819
    @Override
    protected HistoryWidget createHistoryWidget() {
1820
        return new BuildHistoryWidget<R>(this,builds,HISTORY_ADAPTER);
1821
    }
1822
    
K
kohsuke 已提交
1823
    public boolean isParameterized() {
1824
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1825
    }
1826

1827 1828 1829 1830 1831
//
//
// actions
//
//
1832 1833 1834
    /**
     * Schedules a new build command.
     */
1835 1836
    public void doBuild( StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay ) throws IOException, ServletException {
        if (delay==null)    delay=new TimeDuration(getQuietPeriod());
1837

K
kohsuke 已提交
1838 1839
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
J
Jesse Glick 已提交
1840 1841 1842 1843 1844 1845 1846 1847
        if (pp != null && !req.getMethod().equals("POST")) {
            // show the parameter entry form.
            req.getView(pp, "index.jelly").forward(req, rsp);
            return;
        }

        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

K
kohsuke 已提交
1848
        if (pp != null) {
1849
            pp._doBuild(req,rsp,delay);
K
kohsuke 已提交
1850 1851 1852
            return;
        }

1853 1854 1855
        if (!isBuildable())
            throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));

K
Kohsuke Kawaguchi 已提交
1856 1857 1858
        ScheduleResult r = Jenkins.getInstance().getQueue().schedule2(this, delay.getTime(), getBuildCause(req));
        if (r.isAccepted()) {
            rsp.sendRedirect(SC_CREATED,req.getContextPath()+'/'+r.getItem().getUrl());
1859 1860
        } else
            rsp.sendRedirect(".");
1861 1862
    }

1863 1864 1865 1866 1867 1868
    /** @deprecated use {@link #doBuild(StaplerRequest, StaplerResponse, TimeDuration)} */
    @Deprecated
    public void doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        doBuild(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
    }

1869 1870 1871 1872
    /**
     * Computes the build cause, using RemoteCause or UserCause as appropriate.
     */
    /*package*/ CauseAction getBuildCause(StaplerRequest req) {
1873 1874
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1875 1876
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1877
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1878
        } else {
1879
            cause = new UserIdCause();
1880
        }
1881
        return new CauseAction(cause);
1882 1883 1884 1885
    }

    /**
     * Computes the delay by taking the default value and the override in the request parameter into the account.
1886
     *
1887
     * @deprecated as of 1.489
1888
     *      Inject {@link TimeDuration}.
1889 1890
     */
    public int getDelay(StaplerRequest req) throws ServletException {
1891
        String delay = req.getParameter("delay");
1892 1893 1894 1895 1896 1897 1898 1899 1900
        if (delay==null)    return getQuietPeriod();

        try {
            // TODO: more unit handling
            if(delay.endsWith("sec"))   delay=delay.substring(0,delay.length()-3);
            if(delay.endsWith("secs"))  delay=delay.substring(0,delay.length()-4);
            return Integer.parseInt(delay);
        } catch (NumberFormatException e) {
            throw new ServletException("Invalid delay parameter value: "+delay);
1901
        }
1902
    }
1903

1904 1905 1906 1907
    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
1908
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
1909 1910 1911 1912
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1913
            pp.buildWithParameters(req,rsp,delay);
1914 1915 1916 1917 1918
        } else {
        	throw new IllegalStateException("This build is not parameterized!");
        }
    	
    }
1919

1920 1921 1922 1923 1924 1925
    /** @deprecated use {@link #doBuildWithParameters(StaplerRequest, StaplerResponse, TimeDuration)} */
    @Deprecated
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        doBuildWithParameters(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
    }

1926 1927 1928
    /**
     * Schedules a new SCM polling command.
     */
1929
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1930 1931
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
J
Jesse Glick 已提交
1932
        rsp.sendRedirect(".");
1933 1934 1935 1936 1937
    }

    /**
     * Cancels a scheduled build.
     */
J
Jesse Glick 已提交
1938
    @RequirePOST
1939
    public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1940
        checkPermission(ABORT);
1941

1942
        Jenkins.getInstance().getQueue().cancel(this);
1943 1944 1945
        rsp.forwardToPreviousPage(req);
    }

1946 1947 1948 1949
    /**
     * Deletes this project.
     */
    @Override
1950
    @RequirePOST
1951 1952 1953 1954
    public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
        delete();
        if (req == null || rsp == null)
            return;
1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968
        List<Ancestor> ancestors = req.getAncestors();
        ListIterator<Ancestor> it = ancestors.listIterator(ancestors.size());
        String url = getParent().getUrl(); // fallback but we ought to get to Jenkins.instance at the root
        while (it.hasPrevious()) {
            Object a = it.previous().getObject();
            if (a instanceof View) {
                url = ((View) a).getUrl();
                break;
            } else if (a instanceof ViewGroup) {
                url = ((ViewGroup) a).getUrl();
                break;
            }
        }
        rsp.sendRedirect2(req.getContextPath() + '/' + url);
1969 1970
    }
    
1971
    @Override
1972 1973
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1974
        JSONObject json = req.getSubmittedForm();
1975

1976
        makeDisabled(req.getParameter("disable")!=null);
1977 1978

        jdk = req.getParameter("jdk");
1979
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1980 1981 1982 1983
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }
1984 1985
        if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
            scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
S
 
shinodkm 已提交
1986
        } else {
1987
            scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1988
        }
1989
        blockBuildWhenDownstreamBuilding = req.getParameter("blockBuildWhenDownstreamBuilding")!=null;
1990 1991
        blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null;

1992
        if(req.hasParameter("customWorkspace")) {
1993
            customWorkspace = Util.fixEmptyAndTrim(req.getParameter("customWorkspace.directory"));
1994 1995 1996
        } else {
            customWorkspace = null;
        }
1997 1998 1999 2000 2001 2002 2003

        if (json.has("scmCheckoutStrategy"))
            scmCheckoutStrategy = req.bindJSON(SCMCheckoutStrategy.class,
                json.getJSONObject("scmCheckoutStrategy"));
        else
            scmCheckoutStrategy = null;

2004
        
2005
        if(req.getParameter("hasSlaveAffinity")!=null) {
2006
            assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString"));
2007 2008 2009
        } else {
            assignedNode = null;
        }
2010
        canRoam = assignedNode==null;
2011

2012
        concurrentBuild = req.getSubmittedForm().has("concurrentBuild");
K
kohsuke 已提交
2013

2014
        authToken = BuildAuthorizationToken.create(req);
2015

K
kohsuke 已提交
2016
        setScm(SCMS.parseSCM(req,this));
2017

2018
        for (Trigger t : triggers())
2019
            t.stop();
K
Kohsuke Kawaguchi 已提交
2020 2021
        triggers.replaceBy(buildDescribable(req, Trigger.for_(this)));
        for (Trigger t : triggers())
2022
            t.start(this,true);
J
Jesse Glick 已提交
2023 2024 2025 2026 2027 2028 2029

        for (Publisher _t : Descriptor.newInstancesFromHeteroList(req, json, "publisher", Jenkins.getInstance().getExtensionList(BuildTrigger.DescriptorImpl.class))) {
            BuildTrigger t = (BuildTrigger) _t;
            for (AbstractProject downstream : t.getChildProjects(this)) {
                downstream.checkPermission(BUILD);
            }
        }
2030 2031
    }

K
kohsuke 已提交
2032 2033 2034 2035 2036 2037 2038 2039 2040
    /**
     * @deprecated
     *      As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
     */
    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, ServletException {
        return buildDescribable(req,descriptors);
    }

    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors)
2041
        throws FormException, ServletException {
2042

2043
        JSONObject data = req.getSubmittedForm();
2044
        List<T> r = new Vector<T>();
2045
        for (Descriptor<T> d : descriptors) {
2046 2047 2048
            String safeName = d.getJsonSafeClassName();
            if (req.getParameter(safeName) != null) {
                T instance = d.newInstance(req, data.getJSONObject(safeName));
2049
                r.add(instance);
2050 2051
            }
        }
2052
        return r;
2053 2054
    }

2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065
    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
        ContextMenu menu = new ContextMenu();
        for (Permalink p : getPermalinks()) {
            if (p.resolve(this)!=null)
                menu.add(p.getId(),p.getDisplayName());
        }
        return menu;
    }

2066 2067 2068
    /**
     * Serves the workspace files.
     */
2069
    public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
2070
        checkPermission(Item.WORKSPACE);
K
kohsuke 已提交
2071
        FilePath ws = getSomeWorkspace();
2072
        if ((ws == null) || (!ws.exists())) {
2073
            // if there's no workspace, report a nice error message
2074 2075 2076 2077
            // Would be good if when asked for *plain*, do something else!
            // (E.g. return 404, or send empty doc.)
            // Not critical; client can just check if content type is not text/plain,
            // which also serves to detect old versions of Hudson.
2078
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
2079
            return null;
2080
        } else {
2081
            return new DirectoryBrowserSupport(this, ws, getDisplayName()+" workspace", "folder.png", true);
2082 2083
        }
    }
2084

2085 2086 2087
    /**
     * Wipes out the workspace.
     */
2088
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
2089
        checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
2090 2091 2092 2093
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b!=null ? b.getWorkspace() : null;
        if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
2094 2095 2096
            for (WorkspaceListener wl : WorkspaceListener.all()) {
                wl.afterDelete(this);
            }
2097 2098 2099 2100
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
2101
        }
2102 2103
    }

2104
    @CLIMethod(name="disable-job")
2105
    @RequirePOST
2106
    public HttpResponse doDisable() throws IOException, ServletException {
2107 2108
        checkPermission(CONFIGURE);
        makeDisabled(true);
2109
        return new HttpRedirect(".");
2110 2111
    }

2112
    @CLIMethod(name="enable-job")
2113
    @RequirePOST
2114
    public HttpResponse doEnable() throws IOException, ServletException {
2115 2116
        checkPermission(CONFIGURE);
        makeDisabled(false);
2117
        return new HttpRedirect(".");
2118
    }
2119
    
2120

K
kohsuke 已提交
2121 2122 2123
    /**
     * RSS feed for changes in this project.
     */
2124
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
2125 2126 2127 2128 2129 2130 2131 2132 2133
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

            public FeedItem(Entry e, int idx) {
                this.e = e;
                this.idx = idx;
            }

2134
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
2135 2136 2137 2138 2139 2140
                return e.getParent().build;
            }
        }

        List<FeedItem> entries = new ArrayList<FeedItem>();

2141 2142 2143 2144
        for(R r=getLastBuild(); r!=null; r=r.getPreviousBuild()) {
            int idx=0;
            for( ChangeLogSet.Entry e : r.getChangeSet())
                entries.add(new FeedItem(e,idx++));
K
kohsuke 已提交
2145 2146
        }

2147 2148 2149 2150 2151 2152 2153
        RSS.forwardToRss(
            getDisplayName()+' '+getScm().getDescriptor().getDisplayName()+" changes",
            getUrl()+"changes",
            entries, new FeedAdapter<FeedItem>() {
                public String getEntryTitle(FeedItem item) {
                    return "#"+item.getBuild().number+' '+item.e.getMsg()+" ("+item.e.getAuthor()+")";
                }
K
kohsuke 已提交
2154

2155 2156 2157
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
2158

2159 2160 2161
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
2162

2163 2164 2165 2166 2167 2168
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
2169

2170 2171 2172
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
2173

2174
                public String getEntryAuthor(FeedItem entry) {
2175
                    return JenkinsLocationConfiguration.get().getAdminAddress();
2176 2177 2178
                }
            },
            req, rsp );
K
kohsuke 已提交
2179 2180
    }

2181 2182 2183 2184 2185 2186 2187
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
2188
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
2189
         * from showing up on their configuration screen. This is often useful when you are building
2190 2191
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
2192 2193
         *
         * <p>
2194 2195 2196 2197
         * Some {@link Descriptor}s define their own schemes for controlling applicability
         * (such as {@link BuildStepDescriptor#isApplicable(Class)}),
         * This method works like AND in conjunction with them;
         * Both this method and that method need to return true in order for a given {@link Descriptor}
2198 2199 2200 2201
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
2202 2203
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
2204 2205
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
2206
         */
K
kohsuke 已提交
2207
        @Override
2208
        public boolean isApplicable(Descriptor descriptor) {
2209 2210
            return true;
        }
2211

2212 2213
        public FormValidation doCheckAssignedLabelString(@AncestorInPath AbstractProject<?,?> project,
                                                         @QueryParameter String value) {
2214 2215
            if (Util.fixEmpty(value)==null)
                return FormValidation.ok(); // nothing typed yet
2216 2217 2218
            try {
                Label.parseExpression(value);
            } catch (ANTLRException e) {
S
Seiji Sogabe 已提交
2219 2220
                return FormValidation.error(e,
                        Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
2221
            }
2222 2223
            Jenkins j = Jenkins.getInstance();
            Label l = j.getLabel(value);
2224 2225 2226 2227 2228 2229 2230
            if (l.isEmpty()) {
                for (LabelAtom a : l.listAtoms()) {
                    if (a.isEmpty()) {
                        LabelAtom nearest = LabelAtom.findNearest(a.getName());
                        return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch_DidYouMean(a.getName(),nearest.getDisplayName()));
                    }
                }
S
Seiji Sogabe 已提交
2231
                return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
2232
            }
S
Javadoc  
Stephen Connolly 已提交
2233
            if (project != null) {
2234
                for (AbstractProject.LabelValidator v : j
S
Javadoc  
Stephen Connolly 已提交
2235 2236 2237 2238 2239
                        .getExtensionList(AbstractProject.LabelValidator.class)) {
                    FormValidation result = v.check(project, l);
                    if (!FormValidation.Kind.OK.equals(result.kind)) {
                        return result;
                    }
2240 2241
                }
            }
2242 2243 2244
            return FormValidation.okWithMarkup(Messages.AbstractProject_LabelLink(
                    j.getRootUrl(), l.getUrl(), l.getNodes().size() + l.getClouds().size()
            ));
2245
        }
2246

2247
        public FormValidation doCheckCustomWorkspace(@QueryParameter(value="customWorkspace.directory") String customWorkspace){
2248
        	if(Util.fixEmptyAndTrim(customWorkspace)==null)
S
Seiji Sogabe 已提交
2249
        		return FormValidation.error(Messages.AbstractProject_CustomWorkspaceEmpty());
2250 2251 2252 2253
        	else
        		return FormValidation.ok();
        }
        
2254 2255
        public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) {
            AutoCompletionCandidates candidates = new AutoCompletionCandidates();
2256
            List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267
            for (Job job: jobs) {
                if (job.getFullName().startsWith(value)) {
                    if (job.hasPermission(Item.READ)) {
                        candidates.add(job.getFullName());
                    }
                }
            }
            return candidates;
        }

        public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
2268
            AutoCompletionCandidates c = new AutoCompletionCandidates();
2269
            Set<Label> labels = Jenkins.getInstance().getLabels();
2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281
            List<String> queries = new AutoCompleteSeeder(value).getSeeds();

            for (String term : queries) {
                for (Label l : labels) {
                    if (l.getName().startsWith(term)) {
                        c.add(l.getName());
                    }
                }
            }
            return c;
        }

2282
        public List<SCMCheckoutStrategyDescriptor> getApplicableSCMCheckoutStrategyDescriptors(AbstractProject p) {
2283 2284 2285
            return SCMCheckoutStrategyDescriptor._for(p);
        }

2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297
        /**
        * Utility class for taking the current input value and computing a list
        * of potential terms to match against the list of defined labels.
         */
        static class AutoCompleteSeeder {
            private String source;

            AutoCompleteSeeder(String source) {
                this.source = source;
            }

            List<String> getSeeds() {
C
Christoph Kutzinski 已提交
2298
                ArrayList<String> terms = new ArrayList<String>();
2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325
                boolean trailingQuote = source.endsWith("\"");
                boolean leadingQuote = source.startsWith("\"");
                boolean trailingSpace = source.endsWith(" ");

                if (trailingQuote || (trailingSpace && !leadingQuote)) {
                    terms.add("");
                } else {
                    if (leadingQuote) {
                        int quote = source.lastIndexOf('"');
                        if (quote == 0) {
                            terms.add(source.substring(1));
                        } else {
                            terms.add("");
                        }
                    } else {
                        int space = source.lastIndexOf(' ');
                        if (space > -1) {
                            terms.add(source.substring(space+1));
                        } else {
                            terms.add(source);
                        }
                    }
                }

                return terms;
            }
        }
2326 2327
    }

2328
    /**
2329
     * Finds a {@link AbstractProject} that has the name closest to the given name.
2330
     * @see Items#findNearest
2331 2332
     */
    public static AbstractProject findNearest(String name) {
J
Jesse Glick 已提交
2333
        return findNearest(name,Jenkins.getInstance());
2334 2335 2336 2337 2338 2339
    }

    /**
     * Finds a {@link AbstractProject} whose name (when referenced from the specified context) is closest to the given name.
     *
     * @since 1.419
2340
     * @see Items#findNearest
2341 2342
     */
    public static AbstractProject findNearest(String name, ItemGroup context) {
2343
        return Items.findNearest(AbstractProject.class, name, context);
2344
    }
2345 2346 2347

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
2348
            return o2-o1;
2349 2350
        }
    };
2351

2352
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
2353

2354
    /**
2355
     * Permission to abort a build
2356
     */
2357
    public static final Permission ABORT = CANCEL;
2358

K
Kohsuke Kawaguchi 已提交
2359 2360 2361
    /**
     * Replaceable "Build Now" text.
     */
2362 2363
    public static final Message<AbstractProject> BUILD_NOW_TEXT = new Message<AbstractProject>();

2364 2365 2366 2367 2368 2369
    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractProject resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
2370
        AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
2371 2372 2373 2374
        if (item==null)
            throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
        return item;
    }
2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393

    public String getCustomWorkspace() {
        return customWorkspace;
    }

    /**
     * User-specified workspace directory, or null if it's up to Jenkins.
     *
     * <p>
     * Normally a project uses the workspace location assigned by its parent container,
     * but sometimes people have builds that have hard-coded paths.
     *
     * <p>
     * This is not {@link File} because it may have to hold a path representation on another OS.
     *
     * <p>
     * If this path is relative, it's resolved against {@link Node#getRootPath()} on the node where this workspace
     * is prepared. 
     *
2394
     * @since 1.410
2395 2396
     */
    public void setCustomWorkspace(String customWorkspace) throws IOException {
2397
        this.customWorkspace= Util.fixEmptyAndTrim(customWorkspace);
2398 2399
        save();
    }
2400 2401 2402

    /**
     * Plugins may want to contribute additional restrictions on the use of specific labels for specific projects.
S
Javadoc  
Stephen Connolly 已提交
2403 2404 2405
     * This extension point allows such restrictions.
     *
     * @since 1.540
2406 2407
     */
    public static abstract class LabelValidator implements ExtensionPoint {
S
Javadoc  
Stephen Connolly 已提交
2408 2409 2410 2411 2412 2413 2414 2415 2416
        /**
         * Check the use of the label within the specified context.
         *
         * @param project the project that wants to restrict itself to the specified label.
         * @param label   the label that the project wants to restrict itself to.
         * @return the {@link FormValidation} result.
         */
        @Nonnull
        public abstract FormValidation check(@Nonnull AbstractProject<?, ?> project, @Nonnull Label label);
2417
    }
S
Javadoc  
Stephen Connolly 已提交
2418

2419 2420 2421 2422 2423 2424 2425 2426 2427 2428
    @Restricted(DoNotUse.class)
    @Extension public static final class ItemListenerImpl extends ItemListener {
        @Override public void onLocationChanged(Item item, String oldFullName, String newFullName) {
            if (item instanceof AbstractProject) {
                AbstractProject p = (AbstractProject) item;
                p.builds.updateBaseDir(p.getBuildDir());
            }
        }
    }

2429
}