AbstractProject.java 74.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 hudson.Functions;
31
import antlr.ANTLRException;
K
kohsuke 已提交
32
import hudson.AbortException;
33
import hudson.CopyOnWrite;
34
import hudson.FeedAdapter;
35
import hudson.FilePath;
36
import hudson.Launcher;
37
import hudson.Util;
38
import hudson.cli.declarative.CLIMethod;
39
import hudson.cli.declarative.CLIResolver;
40
import hudson.diagnosis.OldDataMonitor;
M
mdonohue 已提交
41
import hudson.model.Cause.LegacyCodeCause;
42
import hudson.model.Cause.RemoteCause;
43
import hudson.model.Cause.UserIdCause;
44 45
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
K
kohsuke 已提交
46
import hudson.model.Queue.Executable;
47
import hudson.model.Queue.Task;
48
import hudson.model.listeners.SCMListener;
49
import hudson.model.queue.SubTask;
50 51
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
52 53
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
54
import hudson.model.listeners.SCMPollListener;
55
import hudson.model.queue.CauseOfBlockage;
56
import hudson.model.queue.SubTaskContributor;
K
kohsuke 已提交
57
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
58
import hudson.scm.ChangeLogSet.Entry;
59
import hudson.scm.NullChangeLogParser;
60
import hudson.scm.NullSCM;
61
import hudson.scm.PollingResult;
62
import hudson.scm.SCM;
63
import hudson.scm.SCMRevisionState;
64
import hudson.scm.SCMS;
J
jbq 已提交
65
import hudson.search.SearchIndexBuilder;
K
kohsuke 已提交
66
import hudson.security.Permission;
67
import hudson.slaves.WorkspaceList;
68
import hudson.tasks.BuildStep;
69
import hudson.tasks.BuildStepDescriptor;
J
jbq 已提交
70
import hudson.tasks.BuildTrigger;
71
import hudson.tasks.BuildWrapperDescriptor;
72
import hudson.tasks.Mailer;
73
import hudson.tasks.Publisher;
K
kohsuke 已提交
74
import hudson.triggers.SCMTrigger;
75
import hudson.triggers.Trigger;
76
import hudson.triggers.TriggerDescriptor;
77 78
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
79
import hudson.util.DescribableList;
80
import hudson.util.EditDistance;
S
 
shinodkm 已提交
81
import hudson.util.FormValidation;
82
import hudson.util.IOException2;
K
kohsuke 已提交
83 84
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
85
import jenkins.model.Jenkins;
86 87 88
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
K
kohsuke 已提交
89
import net.sf.json.JSONObject;
90 91
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
92 93
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
94
import org.kohsuke.stapler.AncestorInPath;
95 96 97
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
98
import org.kohsuke.stapler.HttpResponses;
99
import org.kohsuke.stapler.QueryParameter;
K
kohsuke 已提交
100 101 102
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
103
import org.kohsuke.stapler.interceptor.RequirePOST;
104

K
kohsuke 已提交
105
import javax.servlet.ServletException;
106
import java.io.File;
107
import java.io.IOException;
108 109
import java.io.InterruptedIOException;
import java.lang.ref.WeakReference;
K
kohsuke 已提交
110
import java.lang.reflect.InvocationTargetException;
K
kohsuke 已提交
111
import java.util.ArrayList;
112
import java.util.Arrays;
113
import java.util.Calendar;
114
import java.util.Collection;
J
jbq 已提交
115
import java.util.Collections;
116
import java.util.Comparator;
J
jbq 已提交
117
import java.util.HashSet;
118 119
import java.util.List;
import java.util.Map;
J
jbq 已提交
120
import java.util.Set;
121 122 123
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
K
kohsuke 已提交
124
import java.util.concurrent.Future;
125 126
import java.util.logging.Level;
import java.util.logging.Logger;
127

128 129 130
import static hudson.scm.PollingResult.*;
import static javax.servlet.http.HttpServletResponse.*;

131 132
/**
 * Base implementation of {@link Job}s that build software.
133
 *
134
 * For now this is primarily the common part of {@link Project} and MavenModule.
135
 *
136 137 138
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
C
Christoph Kutzinski 已提交
139
@SuppressWarnings("rawtypes")
140
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
141

142
    /**
143 144 145
     * {@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()}.
146
     */
K
kohsuke 已提交
147
    private volatile SCM scm = new NullSCM();
148

149 150 151 152 153
    /**
     * Controls how the checkout is done.
     */
    private volatile SCMCheckoutStrategy scmCheckoutStrategy;

154 155 156 157 158
    /**
     * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
     */
    private volatile transient SCMRevisionState pollingBaseline = null;

159 160
    /**
     * All the builds keyed by their build number.
161 162 163
     *
     * External code should use {@link #getBuildByNumber(int)} or {@link #getLastBuild()} and traverse via
     * {@link Run#getPreviousBuild()}
164
     */
165
    @Restricted(NoExternalUse.class)
166
    protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
167 168 169 170

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
171
    private volatile Integer quietPeriod = null;
S
 
shinodkm 已提交
172 173
    
    /**
174
     * The retry count. Null to delegate to the system default.
S
 
shinodkm 已提交
175
     */
176
    private volatile Integer scmCheckoutRetryCount = null;
177 178

    /**
179 180 181 182 183 184 185
     * 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.
     *
186
     * @see #canRoam
187 188 189 190 191
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
192
     *
193
     * <p>
194 195
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
196
     */
K
kohsuke 已提交
197
    private volatile boolean canRoam;
198 199 200 201

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

204 205 206 207 208 209
    /**
     * 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;

210 211 212 213 214 215
    /**
     * 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;

216
    /**
217 218 219
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
220
     * <p>
221
     * Can't store {@link JDK} directly because {@link Jenkins} and {@link Project}
222 223
     * are saved independently.
     *
224
     * @see Jenkins#getJDK(String)
225
     */
K
kohsuke 已提交
226
    private volatile String jdk;
227

K
kohsuke 已提交
228
    private volatile BuildAuthorizationToken authToken = null;
229

230 231 232
    /**
     * List of all {@link Trigger}s for this project.
     */
233
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
234

235 236
    /**
     * {@link Action}s contributed from subsidiary objects associated with
237 238 239 240
     * {@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.
241
     */
242 243
    @CopyOnWrite
    protected transient volatile List<Action> transientActions = new Vector<Action>();
244

K
kohsuke 已提交
245 246
    private boolean concurrentBuild;

247 248 249
    /**
     * See {@link #setCustomWorkspace(String)}.
     *
250
     * @since 1.410
251 252 253
     */
    private String customWorkspace;
    
254
    protected AbstractProject(ItemGroup parent, String name) {
255
        super(parent,name);
256

257
        if(!Jenkins.getInstance().getNodes().isEmpty()) {
258
            // if a new job is configured with Hudson that already has slave nodes
259 260 261
            // make it roamable by default
            canRoam = true;
        }
262 263 264 265 266 267 268
    }

    @Override
    public void onCreatedFromScratch() {
        super.onCreatedFromScratch();
        // solicit initial contributions, especially from TransientProjectActionFactory
        updateTransientActions();
269 270
    }

271
    @Override
272
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
273
        super.onLoad(parent, name);
274 275

        this.builds = new RunMap<R>();
276
        this.builds.load(this,new Constructor<R>() {
277 278 279 280 281
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

282
        if(triggers==null) {
283
            // it didn't exist in < 1.28
284
            triggers = new Vector<Trigger<?>>();
285 286
            OldDataMonitor.report(this, "1.28");
        }
287
        for (Trigger t : triggers)
288
            t.start(this,false);
289 290
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
291

292 293
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
294
        updateTransientActions();
295 296
    }

297
    @Override
298
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
299 300
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
301
        FilePath ws = getWorkspace();
302
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
303 304 305 306
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
307
        }
K
kohsuke 已提交
308 309 310
        super.performDelete();
    }

K
kohsuke 已提交
311 312
    /**
     * Does this project perform concurrent builds?
K
kohsuke 已提交
313
     * @since 1.319
K
kohsuke 已提交
314
     */
K
kohsuke 已提交
315
    @Exported
K
kohsuke 已提交
316
    public boolean isConcurrentBuild() {
317
        return concurrentBuild;
K
kohsuke 已提交
318 319 320 321 322 323 324
    }

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

325
    /**
326 327
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
328
     */
329
    public Label getAssignedLabel() {
330
        if(canRoam)
331 332
            return null;

333
        if(assignedNode==null)
334 335
            return Jenkins.getInstance().getSelfLabel();
        return Jenkins.getInstance().getLabel(assignedNode);
336 337
    }

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
    /**
     * 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());
    }

353 354 355 356
    /**
     * Gets the textual representation of the assigned label as it was entered by the user.
     */
    public String getAssignedLabelString() {
357 358 359 360 361 362 363 364
        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);
        }
365 366
    }

K
kohsuke 已提交
367 368 369 370 371 372 373 374 375
    /**
     * Sets the assigned label.
     */
    public void setAssignedLabel(Label l) throws IOException {
        if(l==null) {
            canRoam = true;
            assignedNode = null;
        } else {
            canRoam = false;
376
            if(l== Jenkins.getInstance().getSelfLabel())  assignedNode = null;
377
            else                                        assignedNode = l.getExpression();
K
kohsuke 已提交
378 379 380 381
        }
        save();
    }

382 383 384 385 386 387 388
    /**
     * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
     */
    public void setAssignedNode(Node l) throws IOException {
        setAssignedLabel(l.getSelfLabel());
    }

389
    /**
390 391
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
392 393 394
     */
    @Override
    public String getPronoun() {
K
Kohsuke Kawaguchi 已提交
395
        return AlternativeUiTextProvider.get(PRONOUN, this,Messages.AbstractProject_Pronoun());
396 397
    }

398 399 400 401 402 403 404 405 406
    /**
     * Gets the human readable display name to be rendered in the "Build Now" link.
     *
     * @since 1.401
     */
    public String getBuildNowText() {
        return AlternativeUiTextProvider.get(BUILD_NOW_TEXT,this,Messages.AbstractProject_BuildNow());
    }

407
    /**
408
     * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}.
409
     *
410 411 412 413 414 415 416
     * <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()
417
     */
418 419
    public AbstractProject<?,?> getRootProject() {
        if (this instanceof TopLevelItem) {
420 421
            return this;
        } else {
422 423 424 425
            ItemGroup p = this.getParent();
            if (p instanceof AbstractProject)
                return ((AbstractProject) p).getRootProject();
            return this;
426 427 428
        }
    }

429 430
    /**
     * Gets the directory where the module is checked out.
431 432 433
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
K
kohsuke 已提交
434
     * @deprecated as of 1.319
K
kohsuke 已提交
435 436 437 438 439
     *      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>
440
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
K
kohsuke 已提交
441 442 443 444
     *      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() {
445 446 447 448 449 450 451 452 453 454 455 456
        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 已提交
457 458 459 460 461 462
        Executor e = Executor.currentExecutor();
        if(e!=null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if(b.getProject()==this)
463
                    return b;
K
kohsuke 已提交
464 465 466
            }
        }
        R lb = getLastBuild();
467
        if(lb!=null)    return lb;
K
kohsuke 已提交
468 469 470 471 472 473 474 475 476 477 478 479 480
        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 已提交
481
     * @since 1.319
482
     */
K
kohsuke 已提交
483
    public final FilePath getSomeWorkspace() {
484 485 486 487 488 489 490 491 492 493
        R b = getSomeBuildWithWorkspace();
        return b!=null ? b.getWorkspace() : null;
    }

    /**
     * Gets some build that has a live workspace.
     *
     * @return null if no such build exists.
     */
    public final R getSomeBuildWithWorkspace() {
K
kohsuke 已提交
494 495 496
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
497
            if (ws!=null)   return b;
K
kohsuke 已提交
498 499 500
        }
        return null;
    }
501

502 503 504
    /**
     * Returns the root directory of the checked-out module.
     * <p>
505 506
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
507
     *
K
kohsuke 已提交
508
     * @deprecated as of 1.319
K
kohsuke 已提交
509
     *      See {@link #getWorkspace()} for a migration strategy.
510 511
     */
    public FilePath getModuleRoot() {
512 513
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoot() : null;
514 515
    }

S
stephenconnolly 已提交
516 517 518 519 520 521
    /**
     * 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 已提交
522
     *
K
kohsuke 已提交
523
     * @deprecated as of 1.319
K
kohsuke 已提交
524
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
525 526
     */
    public FilePath[] getModuleRoots() {
527 528
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoots() : null;
S
stephenconnolly 已提交
529 530
    }

531
    public int getQuietPeriod() {
532
        return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod();
533
    }
534

535
    public SCMCheckoutStrategy getScmCheckoutStrategy() {
536 537 538
        return scmCheckoutStrategy == null ? new DefaultSCMCheckoutStrategyImpl() : scmCheckoutStrategy;
    }

539
    public void setScmCheckoutStrategy(SCMCheckoutStrategy scmCheckoutStrategy) throws IOException {
540 541 542 543 544
        this.scmCheckoutStrategy = scmCheckoutStrategy;
        save();
    }


545
    public int getScmCheckoutRetryCount() {
546
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
547
    }
548 549 550

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
551
        return quietPeriod!=null;
552
    }
K
kohsuke 已提交
553 554 555 556

    /**
     * Sets the custom quiet period of this project, or revert to the global default if null is given. 
     */
557
    public void setQuietPeriod(Integer seconds) throws IOException {
K
kohsuke 已提交
558 559 560
        this.quietPeriod = seconds;
        save();
    }
S
 
shinodkm 已提交
561
    
562
    public boolean hasCustomScmCheckoutRetryCount(){
563
        return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
564
    }
565

566
    @Override
567
    public boolean isBuildable() {
J
jpederzolli 已提交
568
        return !isDisabled() && !isHoldOffBuildUntilSave();
569 570
    }

571
    /**
572 573
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
574 575 576 577 578
     */
    public boolean isConfigurable() {
        return true;
    }

579 580 581 582 583 584 585 586 587
    public boolean blockBuildWhenDownstreamBuilding() {
        return blockBuildWhenDownstreamBuilding;
    }

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

588
    public boolean blockBuildWhenUpstreamBuilding() {
589
        return blockBuildWhenUpstreamBuilding;
590 591
    }

592
    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
593 594
        blockBuildWhenUpstreamBuilding = b;
        save();
595 596
    }

597 598 599
    public boolean isDisabled() {
        return disabled;
    }
S
 
shinodkm 已提交
600 601 602 603 604
    
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
605 606 607 608 609 610 611
        // 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 已提交
612
    }
613

614 615 616 617
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
618
        if(disabled==b)     return; // noop
619
        this.disabled = b;
K
bug fix  
kohsuke 已提交
620
        if(b)
621
            Jenkins.getInstance().getQueue().cancel(this);
622 623 624
        save();
    }

625 626 627 628 629 630 631 632
    public void disable() throws IOException {
        makeDisabled(true);
    }

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

K
kohsuke 已提交
633 634
    @Override
    public BallColor getIconColor() {
635
        if(isDisabled())
636
            return BallColor.DISABLED;
K
kohsuke 已提交
637 638 639
        else
            return super.getIconColor();
    }
640

641 642 643 644 645 646 647
    /**
     * effectively deprecated. Since using updateTransientActions correctly
     * under concurrent environment requires a lock that can too easily cause deadlocks.
     *
     * <p>
     * Override {@link #createTransientActions()} instead.
     */
648
    protected void updateTransientActions() {
649 650 651 652
        transientActions = createTransientActions();
    }

    protected List<Action> createTransientActions() {
653
        Vector<Action> ta = new Vector<Action>();
654

655 656
        for (JobProperty<? super P> p : properties)
            ta.addAll(p.getJobActions((P)this));
657

658 659
        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
            ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
660
        return ta;
661 662
    }

663
    /**
664 665
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
666
     * <p>
667 668
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
669
     */
670
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
671

K
kohsuke 已提交
672 673 674 675 676 677
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

678 679 680 681
    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
682
            if(action instanceof ProminentProjectAction)
683 684 685 686 687
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

688
    @Override
689
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
690
        super.doConfigSubmit(req,rsp);
691

692 693
        updateTransientActions();

694
        Set<AbstractProject> upstream = Collections.emptySet();
695
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
696
            upstream = new HashSet<AbstractProject>(Items.fromNameList(getParent(),req.getParameter("upstreamProjects"),AbstractProject.class));
697 698 699
        }

        // dependency setting might have been changed by the user, so rebuild.
700
        Jenkins.getInstance().rebuildDependencyGraph();
701 702 703 704 705

        // reflect the submission of the pseudo 'upstream build trriger'.
        // this needs to be done after we release the lock on 'this',
        // or otherwise we could dead-lock

706
        for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
707 708
            // Don't consider child projects such as MatrixConfiguration:
            if (!p.isConfigurable()) continue;
709
            boolean isUpstream = upstream.contains(p);
710 711 712
            synchronized(p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
713
                BuildTrigger trigger = pl.get(BuildTrigger.class);
714
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects(p);
715 716
                if(isUpstream) {
                    if(!newChildProjects.contains(this))
717 718 719 720 721
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

722
                if(newChildProjects.isEmpty()) {
723
                    pl.remove(BuildTrigger.class);
724
                } else {
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
                    // 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)
742
                            combinedChildren.addAll(bt.getChildProjects(p));
743 744 745 746 747
                        existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),existingList.get(0).getThreshold());
                        pl.add(existing);
                        break;
                    }

748
                    if(existing!=null && existing.hasSame(p,newChildProjects))
749
                        continue;   // no need to touch
750
                    pl.replace(new BuildTrigger(newChildProjects,
751
                        existing==null?Result.SUCCESS:existing.getThreshold()));
752 753 754 755 756
                }
            }
        }

        // notify the queue as the project might be now tied to different node
757
        Jenkins.getInstance().getQueue().scheduleMaintenance();
758 759

        // this is to reflect the upstream build adjustments done above
760
        Jenkins.getInstance().rebuildDependencyGraph();
761 762
    }

M
mdonohue 已提交
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
	/**
	 * @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());
    }
    
779 780
    /**
     * Schedules a build of this project.
781 782 783 784 785
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
786
     */
M
mdonohue 已提交
787 788
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
789 790
    }

M
mdonohue 已提交
791
    public boolean scheduleBuild(int quietPeriod, Cause c) {
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
        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) {
808 809 810 811 812 813
        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.
814 815 816
     *
     * @param actions
     *      For the convenience of the caller, this array can contain null, and those will be silently ignored.
817 818
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
K
kohsuke 已提交
819 820 821 822 823 824 825 826 827 828 829
        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 已提交
830
    @SuppressWarnings("unchecked")
K
kohsuke 已提交
831
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
832
        if (!isBuildable())
833
            return null;
834

K
kohsuke 已提交
835
        List<Action> queueActions = new ArrayList<Action>(actions);
836 837 838 839
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
840 841 842 843
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

844
        WaitingItem i = Jenkins.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
845
        if(i!=null)
846
            return (Future)i.getFuture();
847
        return null;
848 849 850 851 852 853
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
854 855 856
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
857 858 859 860 861 862 863 864 865 866 867 868 869
        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 已提交
870 871
    }

872
    /**
873 874 875 876
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
877
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
878
     * as deprecated.
879
     */
C
Christoph Kutzinski 已提交
880
    @SuppressWarnings("deprecation")
M
mdonohue 已提交
881
    public Future<R> scheduleBuild2(int quietPeriod) {
882
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
M
mdonohue 已提交
883 884
    }
    
K
kohsuke 已提交
885
    /**
886 887
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
888
     */
K
kohsuke 已提交
889
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
890 891 892
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

893 894 895 896
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
897
        if(isDisabled())    return false;
898
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
899
        if(scmt==null)      return false;
900 901 902 903
        scmt.run();
        return true;
    }

904 905 906 907 908
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
909
        return Jenkins.getInstance().getQueue().contains(this);
910 911
    }

K
kohsuke 已提交
912 913
    @Override
    public Queue.Item getQueueItem() {
914
        return Jenkins.getInstance().getQueue().getItem(this);
K
kohsuke 已提交
915 916
    }

K
kohsuke 已提交
917 918 919
    /**
     * Gets the JDK that this project is configured with, or null.
     */
920
    public JDK getJDK() {
921
        return Jenkins.getInstance().getJDK(jdk);
922 923 924 925 926
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
927
    public void setJDK(JDK jdk) throws IOException {
928 929 930 931
        this.jdk = jdk.getName();
        save();
    }

932 933
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
934 935
    }

936
    @Override
937 938 939 940
    public SortedMap<Integer, ? extends R> _getRuns() {
        return builds.getView();
    }

941
    @Override
942 943 944 945
    public void removeRun(R run) {
        this.builds.remove(run);
    }

946 947 948 949 950
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
951
    // keep track of the previous time we started a build
952
    private transient long lastBuildStartTime;
H
huybrechts 已提交
953
    
954 955 956
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
957 958 959 960 961 962 963 964 965 966 967
    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();
968
        try {
969
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
970 971 972 973 974 975 976
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
977
            throw handleInvocationTargetException(e);
978 979 980 981
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
982

983
    private IOException handleInvocationTargetException(InvocationTargetException e) {
984
        Throwable t = e.getTargetException();
985 986 987
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
988 989 990
        throw new Error(t);
    }

991 992 993
    /**
     * Loads an existing build record from disk.
     */
994 995
    protected R loadBuild(File dir) throws IOException {
        try {
996
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
997 998 999 1000 1001
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
1002
            throw handleInvocationTargetException(e);
1003 1004 1005 1006
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
1007

K
kohsuke 已提交
1008 1009
    /**
     * {@inheritDoc}
1010
     *
K
kohsuke 已提交
1011 1012
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
1013
     * {@link BuildStep}s and others who want to add a project action
1014
     * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
1015 1016
     *
     * @see TransientProjectActionFactory
K
kohsuke 已提交
1017
     */
1018
    @Override
1019 1020 1021 1022
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
1023
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
1024
        return Collections.unmodifiableList(actions);
1025 1026
    }

1027 1028
    /**
     * Gets the {@link Node} where this project was last built on.
1029 1030 1031 1032
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
1033 1034 1035 1036
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
1037
        if(b==null)
1038 1039 1040 1041 1042
            return null;
        else
            return b.getBuiltOn();
    }

1043 1044 1045 1046
    public Object getSameNodeConstraint() {
        return this; // in this way, any member that wants to run with the main guy can nominate the project itself 
    }

1047 1048 1049 1050
    public final Task getOwnerTask() {
        return this;
    }

1051
    /**
1052
     * {@inheritDoc}
1053
     *
1054
     * <p>
1055
     * A project must be blocked if its own previous build is in progress,
1056 1057
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
1058
     */
1059
    public boolean isBuildBlocked() {
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
        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;
1076 1077
        }

1078
        @Override
1079 1080 1081 1082 1083 1084 1085 1086
        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);
        }
1087
    }
1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
    
    /**
     * 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());
        }
    }
1104

1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
    /**
     * 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;
        }

1115
        @Override
1116 1117 1118
        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
1119 1120
    }

1121
    public CauseOfBlockage getCauseOfBlockage() {
1122 1123
        // Block builds until they are done with post-production
        if (isLogUpdated() && !isConcurrentBuild())
1124
            return new BecauseOfBuildInProgress(getLastBuild());
1125 1126 1127 1128
        if (blockBuildWhenDownstreamBuilding()) {
            AbstractProject<?,?> bup = getBuildingDownstream();
            if (bup!=null)
                return new BecauseOfDownstreamBuildInProgress(bup);
1129 1130
        }
        if (blockBuildWhenUpstreamBuilding()) {
1131 1132 1133 1134 1135 1136
            AbstractProject<?,?> bup = getBuildingUpstream();
            if (bup!=null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }
1137

1138
    /**
1139 1140
     * Returns the project if any of the downstream project is either
     * building, waiting, pending or buildable.
1141 1142 1143 1144
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
1145
    public AbstractProject getBuildingDownstream() {
1146
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1147

1148
        for (AbstractProject tup : getTransitiveDownstreamProjects()) {
1149
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1150 1151 1152 1153 1154
                return tup;
        }
        return null;
    }

1155
    /**
1156
     * Returns the project if any of the upstream project is either
1157 1158 1159 1160 1161
     * 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.)
     */
1162
    public AbstractProject getBuildingUpstream() {
1163
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1164

1165
        for (AbstractProject tup : getTransitiveUpstreamProjects()) {
1166
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1167 1168 1169
                return tup;
        }
        return null;
1170 1171
    }

1172 1173
    public List<SubTask> getSubTasks() {
        List<SubTask> r = new ArrayList<SubTask>();
1174
        r.add(this);
1175 1176 1177
        for (SubTaskContributor euc : SubTaskContributor.all())
            r.addAll(euc.forProject(this));
        for (JobProperty<? super P> p : properties)
1178
            r.addAll(p.getSubTasks());
1179
        return r;
1180 1181
    }

1182
    public R createExecutable() throws IOException {
1183
        if(isDisabled())    return null;
1184
        return newBuild();
1185 1186
    }

1187 1188 1189 1190
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
1191 1192 1193 1194
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

1195 1196
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
1197
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
1198
     *
K
kohsuke 已提交
1199
     * @deprecated as of 1.319
K
kohsuke 已提交
1200 1201 1202 1203 1204 1205 1206
     *      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}.
1207 1208
     */
    public Resource getWorkspaceResource() {
1209
        return new Resource(getFullDisplayName()+" workspace");
1210 1211 1212 1213 1214 1215
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
1216
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
1217
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
1228 1229
     * 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.
1230 1231
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
1232
        return Collections.emptySet();
1233 1234
    }

1235
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
1236
        SCM scm = getScm();
1237 1238
        if(scm==null)
            return true;    // no SCM
1239

1240 1241
        FilePath workspace = build.getWorkspace();
        workspace.mkdirs();
1242 1243
        
        boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
C
Christoph Kutzinski 已提交
1244 1245 1246
        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.
1247 1248
            calcPollingBaseline(build, launcher, listener);
        }
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
        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 {
1259
                baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
1260 1261 1262 1263 1264 1265 1266 1267 1268
            } 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;
    }

1269 1270
    /**
     * Checks if there's any update in SCM, and returns true if any is found.
1271
     *
1272 1273
     * @deprecated as of 1.346
     *      Use {@link #poll(TaskListener)} instead.
1274
     */
1275
    public boolean pollSCMChanges( TaskListener listener ) {
1276 1277 1278 1279 1280 1281 1282
        return poll(listener).hasChanges();
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * <p>
1283 1284
     * The implementation is responsible for ensuring mutual exclusion between polling and builds
     * if necessary.
1285 1286 1287 1288
     *
     * @since 1.345
     */
    public PollingResult poll( TaskListener listener ) {
1289
        SCM scm = getScm();
1290
        if (scm==null) {
K
i18n  
kohsuke 已提交
1291
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
1292
            return NO_CHANGES;
1293
        }
1294
        if (isDisabled()) {
K
i18n  
kohsuke 已提交
1295
            listener.getLogger().println(Messages.AbstractProject_Disabled());
1296 1297 1298 1299 1300
            return NO_CHANGES;
        }

        R lb = getLastBuild();
        if (lb==null) {
1301
            listener.getLogger().println(Messages.AbstractProject_NoBuilds());
1302
            return isInQueue() ? NO_CHANGES : BUILD_NOW;
1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317
        }

        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.
1318 1319 1320
        }

        try {
1321 1322 1323 1324 1325
        	try {
        		SCMPollListener.fireBeforePolling(this, listener);
        	} catch( Exception e ) {
        		/* Make sure, that the listeners do not have any impact on the actual poll */
        	}
1326
            if (scm.requiresWorkspaceForPolling()) {
K
kohsuke 已提交
1327
                // lock the workspace of the last build
1328
                FilePath ws=lb.getWorkspace();
K
kohsuke 已提交
1329

1330
                if (workspaceOffline(lb)) {
K
kohsuke 已提交
1331 1332 1333 1334 1335 1336
                    // workspace offline. build now, or nothing will ever be built
                    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.
                        listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
1337
                        return NO_CHANGES;
K
kohsuke 已提交
1338
                    }
1339 1340 1341
                    listener.getLogger().println( ws==null
                        ? Messages.AbstractProject_WorkspaceOffline()
                        : Messages.AbstractProject_NoWorkspace());
1342 1343 1344 1345 1346 1347 1348
                    if (isInQueue()) {
                        listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
                        return NO_CHANGES;
                    } else {
                        listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
                        return BUILD_NOW;
                    }
K
kohsuke 已提交
1349 1350
                } else {
                    WorkspaceList l = lb.getBuiltOn().toComputer().getWorkspaceList();
1351
                    // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
1352 1353 1354 1355 1356
                    // 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
1357
                    WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
1358
                    Launcher launcher = ws.createLauncher(listener);
K
kohsuke 已提交
1359 1360
                    try {
                        LOGGER.fine("Polling SCM changes of " + getName());
1361 1362 1363 1364 1365
                        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;
K
kohsuke 已提交
1366
                    } finally {
1367
                        lease.release();
K
kohsuke 已提交
1368
                    }
K
kohsuke 已提交
1369
                }
K
kohsuke 已提交
1370 1371 1372
            } else {
                // polling without workspace
                LOGGER.fine("Polling SCM changes of " + getName());
1373 1374 1375 1376 1377 1378

                if (pollingBaseline==null) // see NOTE-NO-BASELINE above
                    calcPollingBaseline(lb,null,listener);
                PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
                pollingBaseline = r.remote;
                return r;
K
kohsuke 已提交
1379
            }
1380
        } catch (AbortException e) {
1381
            listener.getLogger().println(e.getMessage());
K
i18n  
kohsuke 已提交
1382
            listener.fatalError(Messages.AbstractProject_Aborted());
1383
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
1384
            return NO_CHANGES;
1385 1386
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
1387
            return NO_CHANGES;
1388
        } catch (InterruptedException e) {
1389
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
1390
            return NO_CHANGES;
1391 1392 1393 1394 1395 1396 1397
        } finally {
        	/* Do this no matter what */
        	try {
        		SCMPollListener.fireAfterPolling(this, listener);
        	} catch( Exception e ) {
        		/* Make sure, that the listeners do not have any impact on the actual poll */
        	}
1398 1399
        }
    }
1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417
    
    private boolean workspaceOffline(R build) throws IOException, InterruptedException {
        FilePath ws = build.getWorkspace();
        if (ws==null || !ws.exists()) {
            return true;
        }
        
        Node builtOn = build.getBuiltOn();
        if (builtOn == null) { // node built-on doesn't exist anymore
            return true;
        }
        
        if (builtOn.toComputer() == null) { // node still exists, but has 0 executors - o.s.l.t.
            return true;
        }
        
        return false;
    }
1418

1419 1420
    /**
     * Returns true if this user has made a commit to this project.
1421
     *
1422 1423 1424
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
1425 1426
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
1427 1428 1429 1430
                return true;
        return false;
    }

1431
    @Exported
1432 1433 1434 1435
    public SCM getScm() {
        return scm;
    }

1436
    public void setScm(SCM scm) throws IOException {
1437
        this.scm = scm;
1438
        save();
1439 1440
    }

1441 1442 1443
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
1444
    public void addTrigger(Trigger<?> trigger) throws IOException {
1445
        addToList(trigger,triggers);
1446 1447
    }

1448
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1449
        removeFromList(trigger,triggers);
1450 1451
    }

1452 1453 1454 1455
    protected final synchronized <T extends Describable<T>>
    void addToList( T item, List<T> collection ) throws IOException {
        for( int i=0; i<collection.size(); i++ ) {
            if(collection.get(i).getDescriptor()==item.getDescriptor()) {
1456
                // replace
1457
                collection.set(i,item);
1458 1459 1460 1461 1462 1463 1464
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
1465
        updateTransientActions();
1466 1467
    }

1468 1469 1470 1471
    protected final synchronized <T extends Describable<T>>
    void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
        for( int i=0; i< collection.size(); i++ ) {
            if(collection.get(i).getDescriptor()==item) {
1472 1473 1474
                // found it
                collection.remove(i);
                save();
1475
                updateTransientActions();
1476 1477 1478 1479 1480
                return;
            }
        }
    }

C
Christoph Kutzinski 已提交
1481
    @SuppressWarnings("unchecked")
1482 1483
    public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
        return (Map)Descriptor.toMap(triggers);
1484 1485
    }

1486
    /**
1487
     * Gets the specific trigger, or null if the propert is not configured for this job.
1488 1489 1490
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : triggers) {
1491
            if(clazz.isInstance(p))
1492 1493 1494 1495 1496
                return clazz.cast(p);
        }
        return null;
    }

1497 1498 1499 1500 1501
//
//
// fingerprint related
//
//
1502 1503 1504 1505 1506 1507
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1508 1509
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1510
     */
K
kohsuke 已提交
1511
    @Exported
1512
    public final List<AbstractProject> getDownstreamProjects() {
1513
        return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
1514
    }
1515

K
kohsuke 已提交
1516
    @Exported
1517
    public final List<AbstractProject> getUpstreamProjects() {
1518
        return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1519 1520
    }

K
kohsuke 已提交
1521
    /**
1522 1523 1524 1525
     * 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 已提交
1526 1527 1528
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1529 1530
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1531
            if (buildTrigger != null)
1532
                if (buildTrigger.getChildProjects(ap).contains(this))
1533
                    result.add(ap);
1534
        }        
K
kohsuke 已提交
1535
        return result;
1536 1537
    }    
    
K
kohsuke 已提交
1538 1539
    /**
     * Gets all the upstream projects including transitive upstream projects.
1540
     *
K
kohsuke 已提交
1541 1542 1543
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1544
        return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1545 1546 1547
    }

    /**
1548 1549
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1550 1551 1552
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1553
        return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1554 1555 1556 1557 1558
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1559 1560 1561 1562
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1563 1564
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1565
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1566 1567 1568 1569 1570 1571 1572 1573 1574

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

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1575 1576
     *
     * For each given build, find the build number range of the given project and put that into the map.
1577
     */
1578
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1579 1580
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1581
            if(rs==null || rs.isEmpty())
1582 1583 1584 1585 1586
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1587 1588
            if(value==null)
                r.put(n,rs);
1589 1590 1591 1592 1593
            else
                value.add(rs);
        }
    }

1594 1595 1596 1597 1598 1599
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

1600
    @Override
K
kohsuke 已提交
1601 1602
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1603
        if(isBuildable() && hasPermission(Jenkins.ADMINISTER))
1604
            sib.add("build","build");
K
kohsuke 已提交
1605 1606 1607
        return sib;
    }

1608 1609
    @Override
    protected HistoryWidget createHistoryWidget() {
1610
        return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
1611
    }
1612
    
K
kohsuke 已提交
1613
    public boolean isParameterized() {
1614
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1615
    }
1616

1617 1618 1619 1620 1621
//
//
// actions
//
//
1622 1623 1624
    /**
     * Schedules a new build command.
     */
1625
    public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1626
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1627

K
kohsuke 已提交
1628 1629 1630
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1631
            pp._doBuild(req,rsp);
K
kohsuke 已提交
1632 1633 1634
            return;
        }

1635 1636 1637
        if (!isBuildable())
            throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));

1638
        Jenkins.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req));
1639 1640 1641 1642 1643 1644 1645
        rsp.forwardToPreviousPage(req);
    }

    /**
     * Computes the build cause, using RemoteCause or UserCause as appropriate.
     */
    /*package*/ CauseAction getBuildCause(StaplerRequest req) {
1646 1647
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1648 1649
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1650
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1651
        } else {
1652
            cause = new UserIdCause();
1653
        }
1654
        return new CauseAction(cause);
1655 1656 1657 1658 1659 1660
    }

    /**
     * Computes the delay by taking the default value and the override in the request parameter into the account.
     */
    public int getDelay(StaplerRequest req) throws ServletException {
1661
        String delay = req.getParameter("delay");
1662 1663 1664 1665 1666 1667 1668 1669 1670
        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);
1671
        }
1672
    }
1673

1674 1675 1676 1677
    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
1678
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1679 1680 1681 1682 1683 1684 1685 1686 1687 1688
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
            pp.buildWithParameters(req,rsp);
        } else {
        	throw new IllegalStateException("This build is not parameterized!");
        }
    	
    }
1689 1690 1691 1692

    /**
     * Schedules a new SCM polling command.
     */
1693
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1694 1695 1696
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
1697 1698 1699 1700 1701
    }

    /**
     * Cancels a scheduled build.
     */
1702
    public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1703
        checkPermission(ABORT);
1704

1705
        Jenkins.getInstance().getQueue().cancel(this);
1706 1707 1708
        rsp.forwardToPreviousPage(req);
    }

1709 1710 1711 1712
    /**
     * Deletes this project.
     */
    @Override
1713
    @RequirePOST
1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724
    public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
        delete();
        if (req == null || rsp == null)
            return;
        View view = req.findAncestorObject(View.class);
        if (view == null)
            rsp.sendRedirect2(req.getContextPath() + '/' + getParent().getUrl());
        else 
            rsp.sendRedirect2(req.getContextPath() + '/' + view.getUrl());
    }
    
1725
    @Override
1726 1727
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1728
        JSONObject json = req.getSubmittedForm();
1729

1730
        makeDisabled(req.getParameter("disable")!=null);
1731 1732

        jdk = req.getParameter("jdk");
1733
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1734 1735 1736 1737
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }
1738 1739
        if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
            scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
S
 
shinodkm 已提交
1740
        } else {
1741
            scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1742
        }
1743
        blockBuildWhenDownstreamBuilding = req.getParameter("blockBuildWhenDownstreamBuilding")!=null;
1744 1745
        blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null;

1746
        if(req.hasParameter("customWorkspace")) {
1747
            customWorkspace = Util.fixEmptyAndTrim(req.getParameter("customWorkspace.directory"));
1748 1749 1750
        } else {
            customWorkspace = null;
        }
1751 1752 1753 1754 1755 1756 1757

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

1758
        
1759
        if(req.getParameter("hasSlaveAffinity")!=null) {
1760
            assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString"));
1761 1762 1763
        } else {
            assignedNode = null;
        }
1764
        canRoam = assignedNode==null;
1765

1766
        concurrentBuild = req.getSubmittedForm().has("concurrentBuild");
K
kohsuke 已提交
1767

1768
        authToken = BuildAuthorizationToken.create(req);
1769

K
kohsuke 已提交
1770
        setScm(SCMS.parseSCM(req,this));
1771 1772 1773

        for (Trigger t : triggers)
            t.stop();
1774
        triggers = buildDescribable(req, Trigger.for_(this));
1775
        for (Trigger t : triggers)
1776
            t.start(this,true);
1777 1778
    }

K
kohsuke 已提交
1779 1780 1781 1782 1783 1784 1785 1786 1787
    /**
     * @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)
1788
        throws FormException, ServletException {
1789

1790
        JSONObject data = req.getSubmittedForm();
1791
        List<T> r = new Vector<T>();
1792
        for (Descriptor<T> d : descriptors) {
1793 1794 1795
            String safeName = d.getJsonSafeClassName();
            if (req.getParameter(safeName) != null) {
                T instance = d.newInstance(req, data.getJSONObject(safeName));
1796
                r.add(instance);
1797 1798
            }
        }
1799
        return r;
1800 1801 1802 1803 1804
    }

    /**
     * Serves the workspace files.
     */
1805
    public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1806
        checkPermission(AbstractProject.WORKSPACE);
K
kohsuke 已提交
1807
        FilePath ws = getSomeWorkspace();
1808
        if ((ws == null) || (!ws.exists())) {
1809
            // if there's no workspace, report a nice error message
1810 1811 1812 1813
            // 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.
1814
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1815
            return null;
1816
        } else {
1817
            return new DirectoryBrowserSupport(this, ws, getDisplayName()+" workspace", "folder.png", true);
1818 1819
        }
    }
1820

1821 1822 1823
    /**
     * Wipes out the workspace.
     */
1824
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1825
        checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
1826 1827 1828 1829
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b!=null ? b.getWorkspace() : null;
        if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
1830 1831 1832
            for (WorkspaceListener wl : WorkspaceListener.all()) {
                wl.afterDelete(this);
            }
1833 1834 1835 1836
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1837
        }
1838 1839
    }

1840
    @CLIMethod(name="disable-job")
1841
    @RequirePOST
1842
    public HttpResponse doDisable() throws IOException, ServletException {
1843 1844
        checkPermission(CONFIGURE);
        makeDisabled(true);
1845
        return new HttpRedirect(".");
1846 1847
    }

1848
    @CLIMethod(name="enable-job")
1849
    @RequirePOST
1850
    public HttpResponse doEnable() throws IOException, ServletException {
1851 1852
        checkPermission(CONFIGURE);
        makeDisabled(false);
1853
        return new HttpRedirect(".");
1854 1855
    }

K
kohsuke 已提交
1856 1857 1858
    /**
     * RSS feed for changes in this project.
     */
1859
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1860 1861 1862 1863 1864 1865 1866 1867 1868
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

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

1869
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
1870 1871 1872 1873 1874 1875
                return e.getParent().build;
            }
        }

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

1876 1877 1878 1879
        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 已提交
1880 1881
        }

1882 1883 1884 1885 1886 1887 1888
        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 已提交
1889

1890 1891 1892
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
1893

1894 1895 1896
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
1897

1898 1899 1900 1901 1902 1903
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
1904

1905 1906 1907
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
1908

1909
                public String getEntryAuthor(FeedItem entry) {
1910
                    return Mailer.descriptor().getAdminAddress();
1911 1912 1913
                }
            },
            req, rsp );
K
kohsuke 已提交
1914 1915
    }

1916 1917 1918 1919 1920 1921 1922
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
1923
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
1924
         * from showing up on their configuration screen. This is often useful when you are building
1925 1926
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
1927 1928
         *
         * <p>
1929 1930 1931 1932
         * 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}
1933 1934 1935 1936
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
1937 1938
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
1939 1940
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
1941
         */
K
kohsuke 已提交
1942
        @Override
1943
        public boolean isApplicable(Descriptor descriptor) {
1944 1945
            return true;
        }
1946 1947

        public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
1948 1949
            if (Util.fixEmpty(value)==null)
                return FormValidation.ok(); // nothing typed yet
1950 1951 1952
            try {
                Label.parseExpression(value);
            } catch (ANTLRException e) {
S
Seiji Sogabe 已提交
1953 1954
                return FormValidation.error(e,
                        Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
1955
            }
1956 1957 1958 1959 1960 1961 1962 1963
            Label l = Jenkins.getInstance().getLabel(value);
            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 已提交
1964
                return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
1965
            }
1966 1967
            return FormValidation.ok();
        }
1968

1969
        public FormValidation doCheckCustomWorkspace(@QueryParameter(value="customWorkspace.directory") String customWorkspace){
1970 1971 1972 1973 1974 1975
        	if(Util.fixEmptyAndTrim(customWorkspace)==null)
        		return FormValidation.error("Custom workspace is empty");
        	else
        		return FormValidation.ok();
        }
        
1976 1977
        public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) {
            AutoCompletionCandidates candidates = new AutoCompletionCandidates();
1978
            List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
            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) {
1990
            AutoCompletionCandidates c = new AutoCompletionCandidates();
1991
            Set<Label> labels = Jenkins.getInstance().getLabels();
1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003
            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;
        }

2004
        public List<SCMCheckoutStrategyDescriptor> getApplicableSCMCheckoutStrategyDescriptors(AbstractProject p) {
2005 2006 2007
            return SCMCheckoutStrategyDescriptor._for(p);
        }

2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019
        /**
        * 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 已提交
2020
                ArrayList<String> terms = new ArrayList<String>();
2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047
                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;
            }
        }
2048 2049
    }

2050
    /**
2051
     * Finds a {@link AbstractProject} that has the name closest to the given name.
2052 2053
     */
    public static AbstractProject findNearest(String name) {
2054 2055 2056 2057 2058 2059 2060 2061 2062 2063
        return findNearest(name,Hudson.getInstance());
    }

    /**
     * Finds a {@link AbstractProject} whose name (when referenced from the specified context) is closest to the given name.
     *
     * @since 1.419
     */
    public static AbstractProject findNearest(String name, ItemGroup context) {
        List<AbstractProject> projects = Hudson.getInstance().getAllItems(AbstractProject.class);
2064
        String[] names = new String[projects.size()];
2065
        for( int i=0; i<projects.size(); i++ )
2066
            names[i] = projects.get(i).getRelativeNameFrom(context);
2067 2068

        String nearest = EditDistance.findNearest(name, names);
2069
        return (AbstractProject)Jenkins.getInstance().getItem(nearest,context);
2070
    }
2071 2072 2073

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
2074
            return o2-o1;
2075 2076
        }
    };
2077

2078
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
2079

2080
    /**
2081
     * Permission to abort a build
2082
     */
2083
    public static final Permission ABORT = CANCEL;
2084

K
Kohsuke Kawaguchi 已提交
2085 2086 2087
    /**
     * Replaceable "Build Now" text.
     */
2088 2089
    public static final Message<AbstractProject> BUILD_NOW_TEXT = new Message<AbstractProject>();

2090 2091 2092 2093 2094 2095
    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractProject resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
2096
        AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
2097 2098 2099 2100
        if (item==null)
            throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
        return item;
    }
2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119

    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. 
     *
2120
     * @since 1.410
2121 2122
     */
    public void setCustomWorkspace(String customWorkspace) throws IOException {
2123
        this.customWorkspace= Util.fixEmptyAndTrim(customWorkspace);
2124 2125
        save();
    }
2126
    
2127
}