AbstractProject.java 70.2 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
 * id:cactusman, Yahoo! Inc., Andrew Bayer
K
kohsuke 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 * 
 * 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.
 */
27 28
package hudson.model;

29
import java.util.regex.Pattern;
30
import antlr.ANTLRException;
K
kohsuke 已提交
31
import hudson.AbortException;
32
import hudson.CopyOnWrite;
33
import hudson.FeedAdapter;
34
import hudson.FilePath;
35
import hudson.Launcher;
36
import hudson.Util;
37
import hudson.cli.declarative.CLIMethod;
38
import hudson.cli.declarative.CLIResolver;
39
import hudson.diagnosis.OldDataMonitor;
M
mdonohue 已提交
40
import hudson.model.Cause.LegacyCodeCause;
41
import hudson.model.Cause.RemoteCause;
42
import hudson.model.Cause.UserCause;
43 44
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
K
kohsuke 已提交
45
import hudson.model.Queue.Executable;
46
import hudson.model.Queue.Task;
47
import hudson.model.queue.SubTask;
48 49
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
50 51
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
52
import hudson.model.queue.CauseOfBlockage;
53
import hudson.model.queue.SubTaskContributor;
K
kohsuke 已提交
54
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
55
import hudson.scm.ChangeLogSet.Entry;
56
import hudson.scm.NullSCM;
57
import hudson.scm.PollingResult;
58
import hudson.scm.SCM;
59
import hudson.scm.SCMRevisionState;
60
import hudson.scm.SCMS;
J
jbq 已提交
61
import hudson.search.SearchIndexBuilder;
K
kohsuke 已提交
62
import hudson.security.Permission;
63
import hudson.slaves.WorkspaceList;
64
import hudson.tasks.BuildStep;
65
import hudson.tasks.BuildStepDescriptor;
J
jbq 已提交
66
import hudson.tasks.BuildTrigger;
67
import hudson.tasks.BuildWrapperDescriptor;
68
import hudson.tasks.Mailer;
69
import hudson.tasks.Publisher;
K
kohsuke 已提交
70
import hudson.triggers.SCMTrigger;
71
import hudson.triggers.Trigger;
72
import hudson.triggers.TriggerDescriptor;
73 74
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
75
import hudson.util.DescribableList;
76
import hudson.util.EditDistance;
S
 
shinodkm 已提交
77
import hudson.util.FormValidation;
K
kohsuke 已提交
78 79 80
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import net.sf.json.JSONObject;
81 82
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
83 84 85
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
86
import org.kohsuke.stapler.HttpResponses;
87
import org.kohsuke.stapler.QueryParameter;
K
kohsuke 已提交
88 89 90
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
91

K
kohsuke 已提交
92
import javax.servlet.ServletException;
93
import java.io.File;
94
import java.io.IOException;
K
kohsuke 已提交
95
import java.lang.reflect.InvocationTargetException;
K
kohsuke 已提交
96
import java.util.ArrayList;
97
import java.util.Arrays;
98
import java.util.Calendar;
99
import java.util.Collection;
J
jbq 已提交
100
import java.util.Collections;
101
import java.util.Comparator;
J
jbq 已提交
102
import java.util.HashSet;
103 104
import java.util.List;
import java.util.Map;
J
jbq 已提交
105
import java.util.Set;
106 107 108
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
K
kohsuke 已提交
109
import java.util.concurrent.Future;
110 111
import java.util.logging.Level;
import java.util.logging.Logger;
112

113 114 115
import static hudson.scm.PollingResult.*;
import static javax.servlet.http.HttpServletResponse.*;

116 117
/**
 * Base implementation of {@link Job}s that build software.
118
 *
119
 * For now this is primarily the common part of {@link Project} and MavenModule.
120
 *
121 122 123
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
124
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
125

126
    /**
127 128 129
     * {@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()}.
130
     */
K
kohsuke 已提交
131
    private volatile SCM scm = new NullSCM();
132

133 134 135 136 137
    /**
     * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
     */
    private volatile transient SCMRevisionState pollingBaseline = null;

138 139 140
    /**
     * All the builds keyed by their build number.
     */
141
    protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
142 143 144 145

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
146
    private volatile Integer quietPeriod = null;
S
 
shinodkm 已提交
147 148
    
    /**
149
     * The retry count. Null to delegate to the system default.
S
 
shinodkm 已提交
150
     */
151
    private volatile Integer scmCheckoutRetryCount = null;
152 153

    /**
154 155 156 157 158 159 160
     * 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.
     *
161
     * @see #canRoam
162 163 164 165 166
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
167
     *
168
     * <p>
169 170
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
171
     */
K
kohsuke 已提交
172
    private volatile boolean canRoam;
173 174 175 176

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

179 180 181 182 183 184
    /**
     * 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;

185 186 187 188 189 190
    /**
     * 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;

191
    /**
192 193 194
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
195
     * <p>
196
     * Can't store {@link JDK} directly because {@link Jenkins} and {@link Project}
197 198
     * are saved independently.
     *
199
     * @see Jenkins#getJDK(String)
200
     */
K
kohsuke 已提交
201
    private volatile String jdk;
202

K
kohsuke 已提交
203
    private volatile BuildAuthorizationToken authToken = null;
204

205 206 207
    /**
     * List of all {@link Trigger}s for this project.
     */
208
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
209

210 211
    /**
     * {@link Action}s contributed from subsidiary objects associated with
212 213 214 215
     * {@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.
216
     */
217 218
    @CopyOnWrite
    protected transient volatile List<Action> transientActions = new Vector<Action>();
219

K
kohsuke 已提交
220 221
    private boolean concurrentBuild;

222 223 224
    /**
     * See {@link #setCustomWorkspace(String)}.
     *
225
     * @since 1.410
226 227 228
     */
    private String customWorkspace;
    
229
    protected AbstractProject(ItemGroup parent, String name) {
230
        super(parent,name);
231

232
        if(!Jenkins.getInstance().getNodes().isEmpty()) {
233
            // if a new job is configured with Hudson that already has slave nodes
234 235 236
            // make it roamable by default
            canRoam = true;
        }
237 238 239 240 241 242 243
    }

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

246
    @Override
247
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
248
        super.onLoad(parent, name);
249 250

        this.builds = new RunMap<R>();
251
        this.builds.load(this,new Constructor<R>() {
252 253 254 255 256
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

257
        if(triggers==null) {
258
            // it didn't exist in < 1.28
259
            triggers = new Vector<Trigger<?>>();
260 261
            OldDataMonitor.report(this, "1.28");
        }
262
        for (Trigger t : triggers)
263
            t.start(this,false);
264 265
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
266

267 268
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
269
        updateTransientActions();
270 271
    }

272
    @Override
273
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
274 275
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
276
        FilePath ws = getWorkspace();
277
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
278 279 280 281
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
282
        }
K
kohsuke 已提交
283 284 285
        super.performDelete();
    }

K
kohsuke 已提交
286 287
    /**
     * Does this project perform concurrent builds?
K
kohsuke 已提交
288
     * @since 1.319
K
kohsuke 已提交
289
     */
K
kohsuke 已提交
290
    @Exported
K
kohsuke 已提交
291
    public boolean isConcurrentBuild() {
292
        return Jenkins.CONCURRENT_BUILD && concurrentBuild;
K
kohsuke 已提交
293 294 295 296 297 298 299
    }

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

300
    /**
301 302
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
303
     */
304
    public Label getAssignedLabel() {
305
        if(canRoam)
306 307
            return null;

308
        if(assignedNode==null)
309 310
            return Jenkins.getInstance().getSelfLabel();
        return Jenkins.getInstance().getLabel(assignedNode);
311 312
    }

313 314 315 316
    /**
     * Gets the textual representation of the assigned label as it was entered by the user.
     */
    public String getAssignedLabelString() {
317 318 319 320 321 322 323 324
        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);
        }
325 326
    }

K
kohsuke 已提交
327 328 329 330 331 332 333 334 335
    /**
     * Sets the assigned label.
     */
    public void setAssignedLabel(Label l) throws IOException {
        if(l==null) {
            canRoam = true;
            assignedNode = null;
        } else {
            canRoam = false;
336
            if(l== Jenkins.getInstance().getSelfLabel())  assignedNode = null;
337
            else                                        assignedNode = l.getExpression();
K
kohsuke 已提交
338 339 340 341
        }
        save();
    }

342 343 344 345 346 347 348
    /**
     * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
     */
    public void setAssignedNode(Node l) throws IOException {
        setAssignedLabel(l.getSelfLabel());
    }

349
    /**
350 351
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
352 353 354
     */
    @Override
    public String getPronoun() {
K
Kohsuke Kawaguchi 已提交
355
        return AlternativeUiTextProvider.get(PRONOUN, this,Messages.AbstractProject_Pronoun());
356 357
    }

358 359 360 361 362 363 364 365 366
    /**
     * 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());
    }

367 368 369 370 371
    /**
     * Returns the root project value.
     *
     * @return the root project value.
     */
372
    public AbstractProject getRootProject() {
373
        if (this.getParent() instanceof Jenkins) {
374 375 376 377 378 379
            return this;
        } else {
            return ((AbstractProject) this.getParent()).getRootProject();
        }
    }

380 381
    /**
     * Gets the directory where the module is checked out.
382 383 384
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
K
kohsuke 已提交
385
     * @deprecated as of 1.319
K
kohsuke 已提交
386 387 388 389 390
     *      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>
391
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
K
kohsuke 已提交
392 393 394 395
     *      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() {
396 397 398 399 400 401 402 403 404 405 406 407
        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 已提交
408 409 410 411 412 413
        Executor e = Executor.currentExecutor();
        if(e!=null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if(b.getProject()==this)
414
                    return b;
K
kohsuke 已提交
415 416 417
            }
        }
        R lb = getLastBuild();
418
        if(lb!=null)    return lb;
K
kohsuke 已提交
419 420 421 422 423 424 425 426 427 428 429 430 431
        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 已提交
432
     * @since 1.319
433
     */
K
kohsuke 已提交
434
    public final FilePath getSomeWorkspace() {
435 436 437 438 439 440 441 442 443 444
        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 已提交
445 446 447
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
448
            if (ws!=null)   return b;
K
kohsuke 已提交
449 450 451
        }
        return null;
    }
452

453 454 455
    /**
     * Returns the root directory of the checked-out module.
     * <p>
456 457
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
458
     *
K
kohsuke 已提交
459
     * @deprecated as of 1.319
K
kohsuke 已提交
460
     *      See {@link #getWorkspace()} for a migration strategy.
461 462
     */
    public FilePath getModuleRoot() {
463 464
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoot() : null;
465 466
    }

S
stephenconnolly 已提交
467 468 469 470 471 472
    /**
     * 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 已提交
473
     *
K
kohsuke 已提交
474
     * @deprecated as of 1.319
K
kohsuke 已提交
475
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
476 477
     */
    public FilePath[] getModuleRoots() {
478 479
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoots() : null;
S
stephenconnolly 已提交
480 481
    }

482
    public int getQuietPeriod() {
483
        return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod();
484
    }
S
 
shinodkm 已提交
485
    
486
    public int getScmCheckoutRetryCount() {
487
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
488
    }
489 490 491

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
492
        return quietPeriod!=null;
493
    }
K
kohsuke 已提交
494 495 496 497

    /**
     * Sets the custom quiet period of this project, or revert to the global default if null is given. 
     */
498
    public void setQuietPeriod(Integer seconds) throws IOException {
K
kohsuke 已提交
499 500 501
        this.quietPeriod = seconds;
        save();
    }
S
 
shinodkm 已提交
502
    
503
    public boolean hasCustomScmCheckoutRetryCount(){
504
        return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
505
    }
506

507
    @Override
508
    public boolean isBuildable() {
J
jpederzolli 已提交
509
        return !isDisabled() && !isHoldOffBuildUntilSave();
510 511
    }

512
    /**
513 514
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
515 516 517 518 519
     */
    public boolean isConfigurable() {
        return true;
    }

520 521 522 523 524 525 526 527 528
    public boolean blockBuildWhenDownstreamBuilding() {
        return blockBuildWhenDownstreamBuilding;
    }

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

529
    public boolean blockBuildWhenUpstreamBuilding() {
530
        return blockBuildWhenUpstreamBuilding;
531 532
    }

533
    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
534 535
        blockBuildWhenUpstreamBuilding = b;
        save();
536 537
    }

538 539 540
    public boolean isDisabled() {
        return disabled;
    }
S
 
shinodkm 已提交
541 542 543 544 545
    
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
546 547 548 549 550 551 552
        // 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 已提交
553
    }
554

555 556 557 558
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
559
        if(disabled==b)     return; // noop
560
        this.disabled = b;
K
bug fix  
kohsuke 已提交
561
        if(b)
562
            Jenkins.getInstance().getQueue().cancel(this);
563 564 565
        save();
    }

566 567 568 569 570 571 572 573
    public void disable() throws IOException {
        makeDisabled(true);
    }

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

K
kohsuke 已提交
574 575
    @Override
    public BallColor getIconColor() {
576
        if(isDisabled())
577
            return BallColor.DISABLED;
K
kohsuke 已提交
578 579 580
        else
            return super.getIconColor();
    }
581

582 583 584 585 586 587 588
    /**
     * effectively deprecated. Since using updateTransientActions correctly
     * under concurrent environment requires a lock that can too easily cause deadlocks.
     *
     * <p>
     * Override {@link #createTransientActions()} instead.
     */
589
    protected void updateTransientActions() {
590 591 592 593
        transientActions = createTransientActions();
    }

    protected List<Action> createTransientActions() {
594
        Vector<Action> ta = new Vector<Action>();
595

596 597
        for (JobProperty<? super P> p : properties)
            ta.addAll(p.getJobActions((P)this));
598

599 600
        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
            ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
601
        return ta;
602 603
    }

604
    /**
605 606
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
607
     * <p>
608 609
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
610
     */
611
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
612

K
kohsuke 已提交
613 614 615 616 617 618
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

619 620 621 622
    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
623
            if(action instanceof ProminentProjectAction)
624 625 626 627 628
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

629
    @Override
630
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
631
        super.doConfigSubmit(req,rsp);
632

633 634
        updateTransientActions();

635
        Set<AbstractProject> upstream = Collections.emptySet();
636
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
637
            upstream = new HashSet<AbstractProject>(Items.fromNameList(getParent(),req.getParameter("upstreamProjects"),AbstractProject.class));
638 639 640
        }

        // dependency setting might have been changed by the user, so rebuild.
641
        Jenkins.getInstance().rebuildDependencyGraph();
642 643 644 645 646

        // 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

647
        for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
648 649
            // Don't consider child projects such as MatrixConfiguration:
            if (!p.isConfigurable()) continue;
650
            boolean isUpstream = upstream.contains(p);
651 652 653
            synchronized(p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
654
                BuildTrigger trigger = pl.get(BuildTrigger.class);
655
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects(p);
656 657
                if(isUpstream) {
                    if(!newChildProjects.contains(this))
658 659 660 661 662
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

663
                if(newChildProjects.isEmpty()) {
664
                    pl.remove(BuildTrigger.class);
665
                } else {
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
                    // 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)
683
                            combinedChildren.addAll(bt.getChildProjects(p));
684 685 686 687 688
                        existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),existingList.get(0).getThreshold());
                        pl.add(existing);
                        break;
                    }

689
                    if(existing!=null && existing.hasSame(p,newChildProjects))
690
                        continue;   // no need to touch
691
                    pl.replace(new BuildTrigger(newChildProjects,
692
                        existing==null?Result.SUCCESS:existing.getThreshold()));
693 694 695 696 697
                }
            }
        }

        // notify the queue as the project might be now tied to different node
698
        Jenkins.getInstance().getQueue().scheduleMaintenance();
699 700

        // this is to reflect the upstream build adjustments done above
701
        Jenkins.getInstance().rebuildDependencyGraph();
702 703
    }

M
mdonohue 已提交
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
	/**
	 * @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());
    }
    
720 721
    /**
     * Schedules a build of this project.
722 723 724 725 726
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
727
     */
M
mdonohue 已提交
728 729
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
730 731
    }

M
mdonohue 已提交
732
    public boolean scheduleBuild(int quietPeriod, Cause c) {
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
        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) {
749 750 751 752 753 754
        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.
755 756 757
     *
     * @param actions
     *      For the convenience of the caller, this array can contain null, and those will be silently ignored.
758 759
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
K
kohsuke 已提交
760 761 762 763 764 765 766 767 768 769 770 771
        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
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
772
        if (!isBuildable())
773
            return null;
774

K
kohsuke 已提交
775
        List<Action> queueActions = new ArrayList<Action>(actions);
776 777 778 779
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
780 781 782 783
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

784
        WaitingItem i = Jenkins.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
785 786 787
        if(i!=null)
            return (Future)i.getFuture();
        return null;
788 789 790 791 792 793
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
794 795 796
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
797 798 799 800 801 802 803 804 805 806 807 808 809
        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 已提交
810 811
    }

812
    /**
813 814 815 816
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
817
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
818
     * as deprecated.
819
     */
M
mdonohue 已提交
820
    public Future<R> scheduleBuild2(int quietPeriod) {
821
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
M
mdonohue 已提交
822 823
    }
    
K
kohsuke 已提交
824
    /**
825 826
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
827
     */
K
kohsuke 已提交
828
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
829 830 831
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

832 833 834 835
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
836
        if(isDisabled())    return false;
837
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
838
        if(scmt==null)      return false;
839 840 841 842
        scmt.run();
        return true;
    }

843 844 845 846 847
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
848
        return Jenkins.getInstance().getQueue().contains(this);
849 850
    }

K
kohsuke 已提交
851 852
    @Override
    public Queue.Item getQueueItem() {
853
        return Jenkins.getInstance().getQueue().getItem(this);
K
kohsuke 已提交
854 855
    }

K
kohsuke 已提交
856 857 858
    /**
     * Gets the JDK that this project is configured with, or null.
     */
859
    public JDK getJDK() {
860
        return Jenkins.getInstance().getJDK(jdk);
861 862 863 864 865
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
866
    public void setJDK(JDK jdk) throws IOException {
867 868 869 870
        this.jdk = jdk.getName();
        save();
    }

871 872
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
873 874
    }

875
    @Override
876 877 878 879
    public SortedMap<Integer, ? extends R> _getRuns() {
        return builds.getView();
    }

880
    @Override
881 882 883 884
    public void removeRun(R run) {
        this.builds.remove(run);
    }

885 886 887 888 889
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
890
    // keep track of the previous time we started a build
891
    private transient long lastBuildStartTime;
H
huybrechts 已提交
892
    
893 894 895
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
896 897 898 899 900 901 902 903 904 905 906
    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();
907
        try {
908
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
909 910 911 912 913 914 915
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
916
            throw handleInvocationTargetException(e);
917 918 919 920
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
921

922
    private IOException handleInvocationTargetException(InvocationTargetException e) {
923
        Throwable t = e.getTargetException();
924 925 926
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
927 928 929
        throw new Error(t);
    }

930 931 932
    /**
     * Loads an existing build record from disk.
     */
933 934
    protected R loadBuild(File dir) throws IOException {
        try {
935
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
936 937 938 939 940
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
941
            throw handleInvocationTargetException(e);
942 943 944 945
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
946

K
kohsuke 已提交
947 948
    /**
     * {@inheritDoc}
949
     *
K
kohsuke 已提交
950 951
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
952
     * {@link BuildStep}s and others who want to add a project action
953
     * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
954 955
     *
     * @see TransientProjectActionFactory
K
kohsuke 已提交
956
     */
957
    @Override
958 959 960 961
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
962
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
963
        return Collections.unmodifiableList(actions);
964 965
    }

966 967
    /**
     * Gets the {@link Node} where this project was last built on.
968 969 970 971
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
972 973 974 975
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
976
        if(b==null)
977 978 979 980 981
            return null;
        else
            return b.getBuiltOn();
    }

982 983 984 985
    public Object getSameNodeConstraint() {
        return this; // in this way, any member that wants to run with the main guy can nominate the project itself 
    }

986 987 988 989
    public final Task getOwnerTask() {
        return this;
    }

990
    /**
991
     * {@inheritDoc}
992
     *
993
     * <p>
994
     * A project must be blocked if its own previous build is in progress,
995 996
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
997
     */
998
    public boolean isBuildBlocked() {
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        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;
1015 1016
        }

1017
        @Override
1018 1019 1020 1021 1022 1023 1024 1025
        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);
        }
1026
    }
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    
    /**
     * 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());
        }
    }
1043

1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
    /**
     * 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;
        }

1054
        @Override
1055 1056 1057
        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
1058 1059
    }

1060 1061 1062
    public CauseOfBlockage getCauseOfBlockage() {
        if (isBuilding() && !isConcurrentBuild())
            return new BecauseOfBuildInProgress(getLastBuild());
1063 1064 1065 1066
        if (blockBuildWhenDownstreamBuilding()) {
            AbstractProject<?,?> bup = getBuildingDownstream();
            if (bup!=null)
                return new BecauseOfDownstreamBuildInProgress(bup);
1067 1068
        }
        if (blockBuildWhenUpstreamBuilding()) {
1069 1070 1071 1072 1073 1074
            AbstractProject<?,?> bup = getBuildingUpstream();
            if (bup!=null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }
1075

1076
    /**
1077 1078
     * Returns the project if any of the downstream project is either
     * building, waiting, pending or buildable.
1079 1080 1081 1082 1083
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
    protected AbstractProject getBuildingDownstream() {
1084
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1085

1086
        for (AbstractProject tup : Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this)) {
1087
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1088 1089 1090 1091 1092
                return tup;
        }
        return null;
    }

1093
    /**
1094
     * Returns the project if any of the upstream project is either
1095 1096 1097 1098 1099 1100
     * 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.)
     */
    protected AbstractProject getBuildingUpstream() {
1101
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1102

1103
        for (AbstractProject tup : Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this)) {
1104
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1105 1106 1107
                return tup;
        }
        return null;
1108 1109
    }

1110 1111
    public List<SubTask> getSubTasks() {
        List<SubTask> r = new ArrayList<SubTask>();
1112
        r.add(this);
1113 1114 1115
        for (SubTaskContributor euc : SubTaskContributor.all())
            r.addAll(euc.forProject(this));
        for (JobProperty<? super P> p : properties)
1116
            r.addAll(p.getSubTasks());
1117
        return r;
1118 1119
    }

1120
    public R createExecutable() throws IOException {
1121
        if(isDisabled())    return null;
1122
        return newBuild();
1123 1124
    }

1125 1126 1127 1128
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
1129 1130 1131 1132
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

1133 1134
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
1135
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
1136
     *
K
kohsuke 已提交
1137
     * @deprecated as of 1.319
K
kohsuke 已提交
1138 1139 1140 1141 1142 1143 1144
     *      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}.
1145 1146
     */
    public Resource getWorkspaceResource() {
1147
        return new Resource(getFullDisplayName()+" workspace");
1148 1149 1150 1151 1152 1153
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
1154
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
1155
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
1166 1167
     * 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.
1168 1169
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
1170
        return Collections.emptySet();
1171 1172
    }

1173
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
1174
        SCM scm = getScm();
1175 1176
        if(scm==null)
            return true;    // no SCM
1177

1178 1179
        FilePath workspace = build.getWorkspace();
        workspace.mkdirs();
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
        
        boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
        calcPollingBaseline(build, launcher, listener);
        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 {
1193
                baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206
            } 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;
    }

    /**
     * For reasons I don't understand, if I inline this method, AbstractMethodError escapes try/catch block.
     */
    private SCMRevisionState safeCalcRevisionsFromBuild(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
1207
        return getScm()._calcRevisionsFromBuild(build, launcher, listener);
1208 1209 1210 1211
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
1212
     *
1213 1214
     * @deprecated as of 1.346
     *      Use {@link #poll(TaskListener)} instead.
1215
     */
1216
    public boolean pollSCMChanges( TaskListener listener ) {
1217 1218 1219 1220 1221 1222 1223
        return poll(listener).hasChanges();
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * <p>
1224 1225
     * The implementation is responsible for ensuring mutual exclusion between polling and builds
     * if necessary.
1226 1227 1228 1229
     *
     * @since 1.345
     */
    public PollingResult poll( TaskListener listener ) {
1230
        SCM scm = getScm();
1231
        if (scm==null) {
K
i18n  
kohsuke 已提交
1232
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
1233
            return NO_CHANGES;
1234
        }
1235
        if (isDisabled()) {
K
i18n  
kohsuke 已提交
1236
            listener.getLogger().println(Messages.AbstractProject_Disabled());
1237 1238 1239 1240 1241
            return NO_CHANGES;
        }

        R lb = getLastBuild();
        if (lb==null) {
1242
            listener.getLogger().println(Messages.AbstractProject_NoBuilds());
1243
            return isInQueue() ? NO_CHANGES : BUILD_NOW;
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
        }

        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.
1259 1260 1261
        }

        try {
1262
            if (scm.requiresWorkspaceForPolling()) {
K
kohsuke 已提交
1263
                // lock the workspace of the last build
1264
                FilePath ws=lb.getWorkspace();
K
kohsuke 已提交
1265

1266
                if (workspaceOffline(lb)) {
K
kohsuke 已提交
1267 1268 1269 1270 1271 1272
                    // 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());
1273
                        return NO_CHANGES;
K
kohsuke 已提交
1274
                    }
1275 1276 1277
                    listener.getLogger().println( ws==null
                        ? Messages.AbstractProject_WorkspaceOffline()
                        : Messages.AbstractProject_NoWorkspace());
1278 1279 1280 1281 1282 1283 1284
                    if (isInQueue()) {
                        listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
                        return NO_CHANGES;
                    } else {
                        listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
                        return BUILD_NOW;
                    }
K
kohsuke 已提交
1285 1286
                } else {
                    WorkspaceList l = lb.getBuiltOn().toComputer().getWorkspaceList();
1287
                    // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
1288 1289 1290 1291 1292
                    // 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
1293
                    WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
1294
                    Launcher launcher = ws.createLauncher(listener);
K
kohsuke 已提交
1295 1296
                    try {
                        LOGGER.fine("Polling SCM changes of " + getName());
1297 1298 1299 1300 1301
                        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 已提交
1302
                    } finally {
1303
                        lease.release();
K
kohsuke 已提交
1304
                    }
K
kohsuke 已提交
1305
                }
K
kohsuke 已提交
1306 1307 1308
            } else {
                // polling without workspace
                LOGGER.fine("Polling SCM changes of " + getName());
1309 1310 1311 1312 1313 1314

                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 已提交
1315
            }
1316
        } catch (AbortException e) {
1317
            listener.getLogger().println(e.getMessage());
K
i18n  
kohsuke 已提交
1318
            listener.fatalError(Messages.AbstractProject_Aborted());
1319
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
1320
            return NO_CHANGES;
1321 1322
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
1323
            return NO_CHANGES;
1324
        } catch (InterruptedException e) {
1325
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
1326
            return NO_CHANGES;
1327 1328
        }
    }
1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346
    
    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;
    }
1347

1348 1349
    /**
     * Returns true if this user has made a commit to this project.
1350
     *
1351 1352 1353
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
1354 1355
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
1356 1357 1358 1359
                return true;
        return false;
    }

1360
    @Exported
1361 1362 1363 1364
    public SCM getScm() {
        return scm;
    }

1365
    public void setScm(SCM scm) throws IOException {
1366
        this.scm = scm;
1367
        save();
1368 1369
    }

1370 1371 1372
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
1373
    public void addTrigger(Trigger<?> trigger) throws IOException {
1374
        addToList(trigger,triggers);
1375 1376
    }

1377
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1378
        removeFromList(trigger,triggers);
1379 1380
    }

1381 1382 1383 1384
    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()) {
1385
                // replace
1386
                collection.set(i,item);
1387 1388 1389 1390 1391 1392 1393
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
1394
        updateTransientActions();
1395 1396
    }

1397 1398 1399 1400
    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) {
1401 1402 1403
                // found it
                collection.remove(i);
                save();
1404
                updateTransientActions();
1405 1406 1407 1408 1409
                return;
            }
        }
    }

1410 1411
    public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
        return (Map)Descriptor.toMap(triggers);
1412 1413
    }

1414
    /**
1415
     * Gets the specific trigger, or null if the propert is not configured for this job.
1416 1417 1418
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : triggers) {
1419
            if(clazz.isInstance(p))
1420 1421 1422 1423 1424
                return clazz.cast(p);
        }
        return null;
    }

1425 1426 1427 1428 1429
//
//
// fingerprint related
//
//
1430 1431 1432 1433 1434 1435
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1436 1437
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1438
     */
K
kohsuke 已提交
1439
    @Exported
1440
    public final List<AbstractProject> getDownstreamProjects() {
1441
        return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
1442
    }
1443

K
kohsuke 已提交
1444
    @Exported
1445
    public final List<AbstractProject> getUpstreamProjects() {
1446
        return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1447 1448
    }

K
kohsuke 已提交
1449
    /**
1450 1451 1452 1453
     * 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 已提交
1454 1455 1456
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1457 1458
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1459
            if (buildTrigger != null)
1460
                if (buildTrigger.getChildProjects(ap).contains(this))
1461
                    result.add(ap);
1462
        }        
K
kohsuke 已提交
1463
        return result;
1464 1465
    }    
    
K
kohsuke 已提交
1466 1467
    /**
     * Gets all the upstream projects including transitive upstream projects.
1468
     *
K
kohsuke 已提交
1469 1470 1471
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1472
        return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1473 1474 1475
    }

    /**
1476 1477
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1478 1479 1480
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1481
        return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1482 1483 1484 1485 1486
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1487 1488 1489 1490
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1491 1492
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1493
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1494 1495 1496 1497 1498 1499 1500 1501 1502

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

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1503 1504
     *
     * For each given build, find the build number range of the given project and put that into the map.
1505
     */
1506
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1507 1508
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1509
            if(rs==null || rs.isEmpty())
1510 1511 1512 1513 1514
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1515 1516
            if(value==null)
                r.put(n,rs);
1517 1518 1519 1520 1521
            else
                value.add(rs);
        }
    }

1522 1523 1524 1525 1526 1527
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

1528
    @Override
K
kohsuke 已提交
1529 1530
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1531
        if(isBuildable() && hasPermission(Jenkins.ADMINISTER))
1532
            sib.add("build","build");
K
kohsuke 已提交
1533 1534 1535
        return sib;
    }

1536 1537
    @Override
    protected HistoryWidget createHistoryWidget() {
1538
        return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
1539
    }
1540
    
K
kohsuke 已提交
1541
    public boolean isParameterized() {
1542
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1543
    }
1544

1545 1546 1547 1548 1549
//
//
// actions
//
//
1550 1551 1552
    /**
     * Schedules a new build command.
     */
1553
    public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1554
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1555

K
kohsuke 已提交
1556 1557 1558
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1559
            pp._doBuild(req,rsp);
K
kohsuke 已提交
1560 1561 1562
            return;
        }

1563 1564 1565
        if (!isBuildable())
            throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));

1566
        Jenkins.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req));
1567 1568 1569 1570 1571 1572 1573
        rsp.forwardToPreviousPage(req);
    }

    /**
     * Computes the build cause, using RemoteCause or UserCause as appropriate.
     */
    /*package*/ CauseAction getBuildCause(StaplerRequest req) {
1574 1575
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1576 1577
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1578
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1579 1580 1581
        } else {
            cause = new UserCause();
        }
1582
        return new CauseAction(cause);
1583 1584 1585 1586 1587 1588
    }

    /**
     * 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 {
1589
        String delay = req.getParameter("delay");
1590 1591 1592 1593 1594 1595 1596 1597 1598
        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);
1599
        }
1600
    }
1601

1602 1603 1604 1605
    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
1606
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1607 1608 1609 1610 1611 1612 1613 1614 1615 1616
        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!");
        }
    	
    }
1617 1618 1619 1620

    /**
     * Schedules a new SCM polling command.
     */
1621
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1622 1623 1624
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
1625 1626 1627 1628 1629
    }

    /**
     * Cancels a scheduled build.
     */
1630
    public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1631
        checkPermission(BUILD);
1632

1633
        Jenkins.getInstance().getQueue().cancel(this);
1634 1635 1636
        rsp.forwardToPreviousPage(req);
    }

1637
    @Override
1638 1639
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1640

1641
        makeDisabled(req.getParameter("disable")!=null);
1642 1643

        jdk = req.getParameter("jdk");
1644
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1645 1646 1647 1648
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }
1649 1650
        if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
            scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
S
 
shinodkm 已提交
1651
        } else {
1652
            scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1653
        }
1654
        blockBuildWhenDownstreamBuilding = req.getParameter("blockBuildWhenDownstreamBuilding")!=null;
1655 1656
        blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null;

1657 1658 1659 1660 1661 1662
        if(req.hasParameter("customWorkspace")) {
            customWorkspace = req.getParameter("customWorkspace.directory");
        } else {
            customWorkspace = null;
        }
        
1663
        if(req.getParameter("hasSlaveAffinity")!=null) {
1664
            assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString"));
1665 1666 1667
        } else {
            assignedNode = null;
        }
1668
        canRoam = assignedNode==null;
1669

1670
        concurrentBuild = req.getSubmittedForm().has("concurrentBuild");
K
kohsuke 已提交
1671

1672
        authToken = BuildAuthorizationToken.create(req);
1673

K
kohsuke 已提交
1674
        setScm(SCMS.parseSCM(req,this));
1675 1676 1677

        for (Trigger t : triggers)
            t.stop();
1678
        triggers = buildDescribable(req, Trigger.for_(this));
1679
        for (Trigger t : triggers)
1680
            t.start(this,true);
1681 1682
    }

K
kohsuke 已提交
1683 1684 1685 1686 1687 1688 1689 1690 1691
    /**
     * @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)
1692
        throws FormException, ServletException {
1693

1694
        JSONObject data = req.getSubmittedForm();
1695
        List<T> r = new Vector<T>();
1696
        for (Descriptor<T> d : descriptors) {
1697 1698 1699
            String safeName = d.getJsonSafeClassName();
            if (req.getParameter(safeName) != null) {
                T instance = d.newInstance(req, data.getJSONObject(safeName));
1700
                r.add(instance);
1701 1702
            }
        }
1703
        return r;
1704 1705 1706 1707 1708
    }

    /**
     * Serves the workspace files.
     */
1709
    public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1710
        checkPermission(AbstractProject.WORKSPACE);
K
kohsuke 已提交
1711
        FilePath ws = getSomeWorkspace();
1712
        if ((ws == null) || (!ws.exists())) {
1713
            // if there's no workspace, report a nice error message
1714 1715 1716 1717
            // 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.
1718
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1719
            return null;
1720
        } else {
1721
            return new DirectoryBrowserSupport(this, ws, getDisplayName()+" workspace", "folder.png", true);
1722 1723
        }
    }
1724

1725 1726 1727
    /**
     * Wipes out the workspace.
     */
1728
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1729
        checkPermission(BUILD);
1730 1731 1732 1733
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b!=null ? b.getWorkspace() : null;
        if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
1734 1735 1736 1737
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1738
        }
1739 1740
    }

1741
    @CLIMethod(name="disable-job")
1742
    public HttpResponse doDisable() throws IOException, ServletException {
1743 1744 1745
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(true);
1746
        return new HttpRedirect(".");
1747 1748
    }

1749
    @CLIMethod(name="enable-job")
1750
    public HttpResponse doEnable() throws IOException, ServletException {
1751
        requirePOST();
1752 1753
        checkPermission(CONFIGURE);
        makeDisabled(false);
1754
        return new HttpRedirect(".");
1755 1756
    }

K
kohsuke 已提交
1757 1758 1759
    /**
     * RSS feed for changes in this project.
     */
1760
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1761 1762 1763 1764 1765 1766 1767 1768 1769
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

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

1770
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
1771 1772 1773 1774 1775 1776
                return e.getParent().build;
            }
        }

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

1777 1778 1779 1780
        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 已提交
1781 1782
        }

1783 1784 1785 1786 1787 1788 1789
        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 已提交
1790

1791 1792 1793
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
1794

1795 1796 1797
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
1798

1799 1800 1801 1802 1803 1804
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
1805

1806 1807 1808
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
1809

1810
                public String getEntryAuthor(FeedItem entry) {
1811
                    return Mailer.descriptor().getAdminAddress();
1812 1813 1814
                }
            },
            req, rsp );
K
kohsuke 已提交
1815 1816
    }

1817 1818 1819 1820 1821 1822 1823
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
1824
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
1825
         * from showing up on their configuration screen. This is often useful when you are building
1826 1827
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
1828 1829
         *
         * <p>
1830 1831 1832 1833
         * 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}
1834 1835 1836 1837
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
1838 1839
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
1840 1841
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
1842
         */
K
kohsuke 已提交
1843
        @Override
1844
        public boolean isApplicable(Descriptor descriptor) {
1845 1846
            return true;
        }
1847 1848

        public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
1849 1850
            if (Util.fixEmpty(value)==null)
                return FormValidation.ok(); // nothing typed yet
1851 1852 1853
            try {
                Label.parseExpression(value);
            } catch (ANTLRException e) {
S
Seiji Sogabe 已提交
1854 1855
                return FormValidation.error(e,
                        Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
1856 1857
            }
            // TODO: if there's an atom in the expression that is empty, report it
1858
            if (Jenkins.getInstance().getLabel(value).isEmpty())
S
Seiji Sogabe 已提交
1859
                return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
1860 1861
            return FormValidation.ok();
        }
1862

1863 1864
        public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) {
            AutoCompletionCandidates candidates = new AutoCompletionCandidates();
1865
            List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876
            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) {
1877
            AutoCompletionCandidates c = new AutoCompletionCandidates();
1878
            Set<Label> labels = Jenkins.getInstance().getLabels();
1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931
            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;
        }

        /**
        * 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;
            private Pattern quoteMatcher = Pattern.compile("(\\\"?)(.+?)(\\\"?+)(\\s*)");

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

            List<String> getSeeds() {
                ArrayList<String> terms = new ArrayList();
                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;
            }
        }
1932 1933
    }

1934
    /**
1935
     * Finds a {@link AbstractProject} that has the name closest to the given name.
1936 1937
     */
    public static AbstractProject findNearest(String name) {
1938
        List<AbstractProject> projects = Jenkins.getInstance().getItems(AbstractProject.class);
1939
        String[] names = new String[projects.size()];
1940
        for( int i=0; i<projects.size(); i++ )
1941 1942 1943
            names[i] = projects.get(i).getName();

        String nearest = EditDistance.findNearest(name, names);
1944
        return (AbstractProject) Jenkins.getInstance().getItem(nearest);
1945
    }
1946 1947 1948

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
1949
            return o2-o1;
1950 1951
        }
    };
1952

1953
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
1954

1955
    /**
1956
     * Permission to abort a build. For now, let's make it the same as {@link #BUILD}
1957 1958
     */
    public static final Permission ABORT = BUILD;
1959

K
Kohsuke Kawaguchi 已提交
1960 1961 1962
    /**
     * Replaceable "Build Now" text.
     */
1963 1964
    public static final Message<AbstractProject> BUILD_NOW_TEXT = new Message<AbstractProject>();

1965 1966 1967 1968 1969 1970
    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractProject resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
1971
        AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
1972 1973 1974 1975
        if (item==null)
            throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
        return item;
    }
1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994

    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. 
     *
1995
     * @since 1.410
1996 1997 1998 1999 2000
     */
    public void setCustomWorkspace(String customWorkspace) throws IOException {
        this.customWorkspace= customWorkspace;
        save();
    }
2001
}