File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\scm\SCM.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, InfraDNA, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.scm;
25
26 import hudson.AbortException;
27 import hudson.ExtensionPoint;
28 import hudson.FilePath;
29 import hudson.Launcher;
30 import hudson.DescriptorExtensionList;
31 import hudson.Extension;
32 import hudson.Util;
33 import hudson.security.PermissionGroup;
34 import hudson.security.Permission;
35 import hudson.security.PermissionScope;
36 import hudson.tasks.Builder;
37 import hudson.model.AbstractBuild;
38 import hudson.model.AbstractProject;
39 import hudson.model.BuildListener;
40 import hudson.model.Describable;
41 import hudson.model.TaskListener;
42 import hudson.model.Node;
43 import hudson.model.WorkspaceCleanupThread;
44 import jenkins.model.Jenkins;
45 import hudson.model.Descriptor;
46 import hudson.model.Api;
47 import hudson.model.Action;
48 import hudson.model.AbstractProject.AbstractProjectDescriptor;
49
50 import java.io.File;
51 import java.io.FileWriter;
52 import java.io.IOException;
53 import java.util.Map;
54 import java.util.List;
55 import java.util.ArrayList;
56
57 import org.kohsuke.stapler.export.Exported;
58 import org.kohsuke.stapler.export.ExportedBean;
59
60 /**
61 * Captures the configuration information in it.
62 *
63 * <p>
64 * To register a custom {@link SCM} implementation from a plugin,
65 * put {@link Extension} on your {@link SCMDescriptor}.
66 *
67 * <p>
68 * Use the "project-changes" view to render change list to be displayed
69 * at the project level. The default implementation simply aggregates
70 * change lists from builds, but your SCM can provide different views.
71 * The view gets the "builds" variable which is a list of builds that are
72 * selected for the display.
73 *
74 * <p>
75 * If you are interested in writing a subclass in a plugin,
76 * also take a look at <a href="http://wiki.jenkins-ci.org/display/JENKINS/Writing+an+SCM+plugin">
77 * "Writing an SCM plugin"</a> wiki article.
78 *
79 * @author Kohsuke Kawaguchi
80 */
81 @ExportedBean
82 public abstract class SCM implements Describable<SCM>, ExtensionPoint {
83 /**
84 * Stores {@link AutoBrowserHolder}. Lazily created.
85 */
86 private transient AutoBrowserHolder autoBrowserHolder;
87
88 /**
89 * Expose {@link SCM} to the remote API.
90 */
91 public Api getApi() {
92 return new Api(this);
93 }
94
95 /**
96 * Returns the {@link RepositoryBrowser} for files
97 * controlled by this {@link SCM}.
98 *
99 * @return
100 * null to indicate that there's no explicitly configured browser
101 * for this SCM instance.
102 *
103 * @see #getEffectiveBrowser()
104 */
105 public RepositoryBrowser<?> getBrowser() {
106 return null;
107 }
108
109 /**
110 * Type of this SCM.
111 *
112 * Exposed so that the client of the remote API can tell what SCM this is.
113 */
114 @Exported
115 public String getType() {
116 return getClass().getName();
117 }
118
119 /**
120 * Returns the applicable {@link RepositoryBrowser} for files
121 * controlled by this {@link SCM}.
122 *
123 * <p>
124 * This method attempts to find applicable browser
125 * from other job configurations.
126 */
127 @Exported(name="browser")
128 public final RepositoryBrowser<?> getEffectiveBrowser() {
129 RepositoryBrowser<?> b = getBrowser();
130 if(b!=null)
131 return b;
132 if(autoBrowserHolder==null)
133 autoBrowserHolder = new AutoBrowserHolder(this);
134 return autoBrowserHolder.get();
135
136 }
137
138 /**
139 * Returns true if this SCM supports
140 * {@link #poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState) poling}.
141 *
142 * @since 1.105
143 */
144 public boolean supportsPolling() {
145 return true;
146 }
147
148 /**
149 * Returns true if this SCM requires a checked out workspace for doing polling.
150 *
151 * <p>
152 * This flag affects the behavior of Hudson when a job lost its workspace
153 * (typically due to a slave outage.) If this method returns false and
154 * polling is configured, then that would immediately trigger a new build.
155 *
156 * <p>
157 * This flag also affects the mutual exclusion control between builds and polling.
158 * If this methods returns false, polling will continue asynchronously even
159 * when a build is in progress, but otherwise the polling activity is blocked
160 * if a build is currently using a workspace.
161 *
162 * <p>
163 * The default implementation returns true.
164 *
165 * <p>
166 * See issue #1348 for more discussion of this feature.
167 *
168 * @since 1.196
169 */
170 public boolean requiresWorkspaceForPolling() {
171 return true;
172 }
173
174 /**
175 * Called before a workspace is deleted on the given node, to provide SCM an opportunity to perform clean up.
176 *
177 * <p>
178 * Hudson periodically scans through all the slaves and removes old workspaces that are deemed unnecessary.
179 * This behavior is implemented in {@link WorkspaceCleanupThread}, and it is necessary to control the
180 * disk consumption on slaves. If we don't do this, in a long run, all the slaves will have workspaces
181 * for all the projects, which will be prohibitive in big Hudson.
182 *
183 * <p>
184 * However, some SCM implementations require that the server be made aware of deletion of the local workspace,
185 * and this method provides an opportunity for SCMs to perform such a clean-up act.
186 *
187 * <p>
188 * This call back is invoked after Hudson determines that a workspace is unnecessary, but before the actual
189 * recursive directory deletion happens.
190 *
191 * <p>
192 * Note that this method does not guarantee that such a clean up will happen. For example, slaves can be
193 * taken offline by being physically removed from the network, and in such a case there's no opportunity
194 * to perform this clean up.
195 *
196 * <p>
197 * This method is also invoked when the project is deleted.
198 *
199 * @param project
200 * The project that owns this {@link SCM}. This is always the same object for a particular instance
201 * of {@link SCM}. Just passed in here so that {@link SCM} itself doesn't have to remember the value.
202 * @param workspace
203 * The workspace which is about to be deleted. Never null. This can be a remote file path.
204 * @param node
205 * The node that hosts the workspace. SCM can use this information to determine the course of action.
206 *
207 * @return
208 * true if {@link SCM} is OK to let Hudson proceed with deleting the workspace.
209 * False to veto the workspace deletion.
210 *
211 * @since 1.246
212 */
213 public boolean processWorkspaceBeforeDeletion(AbstractProject<?,?> project, FilePath workspace, Node node) throws IOException, InterruptedException {
214 return true;
215 }
216
217 /**
218 * Checks if there has been any changes to this module in the repository.
219 *
220 * TODO: we need to figure out a better way to communicate an error back,
221 * so that we won't keep retrying the same node (for example a slave might be down.)
222 *
223 * <p>
224 * If the SCM doesn't implement polling, have the {@link #supportsPolling()} method
225 * return false.
226 *
227 * @param project
228 * The project to check for updates
229 * @param launcher
230 * Abstraction of the machine where the polling will take place. If SCM declares
231 * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null.
232 * @param workspace
233 * The workspace directory that contains baseline files. If SCM declares
234 * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null.
235 * @param listener
236 * Logs during the polling should be sent here.
237 *
238 * @return true
239 * if the change is detected.
240 *
241 * @throws InterruptedException
242 * interruption is usually caused by the user aborting the computation.
243 * this exception should be simply propagated all the way up.
244 *
245 * @see #supportsPolling()
246 *
247 * @deprecated as of 1.345
248 * Override {@link #calcRevisionsFromBuild(AbstractBuild, Launcher, TaskListener)} and
249 * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} for implementation.
250 *
251 * The implementation is now separated in two pieces, one that computes the revision of the current workspace,
252 * and the other that computes the revision of the remote repository.
253 *
254 * Call {@link #poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} for use instead.
255 */
256 public boolean pollChanges(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException {
257 // up until 1.336, this method was abstract, so everyone should have overridden this method
258 // without calling super.pollChanges. So the compatibility implementation is purely for
259 // new implementations that doesn't override this method.
260
261 // not sure if this can be implemented any better
262 return false;
263 }
264
265 /**
266 * Calculates the {@link SCMRevisionState} that represents the state of the workspace of the given build.
267 *
268 * <p>
269 * The returned object is then fed into the
270 * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} method
271 * as the baseline {@link SCMRevisionState} to determine if the build is necessary.
272 *
273 * <p>
274 * This method is called after source code is checked out for the given build (that is, after
275 * {@link SCM#checkout(AbstractBuild, Launcher, FilePath, BuildListener, File)} has finished successfully.)
276 *
277 * <p>
278 * The obtained object is added to the build as an {@link Action} for later retrieval. As an optimization,
279 * {@link SCM} implementation can choose to compute {@link SCMRevisionState} and add it as an action
280 * during check out, in which case this method will not called.
281 *
282 * @param build
283 * The calculated {@link SCMRevisionState} is for the files checked out in this build. Never null.
284 * If {@link #requiresWorkspaceForPolling()} returns true, Hudson makes sure that the workspace of this
285 * build is available and accessible by the callee.
286 * @param launcher
287 * Abstraction of the machine where the polling will take place. If SCM declares
288 * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace},
289 * this parameter is null. Otherwise never null.
290 * @param listener
291 * Logs during the polling should be sent here.
292 *
293 * @return can be null.
294 *
295 * @throws InterruptedException
296 * interruption is usually caused by the user aborting the computation.
297 * this exception should be simply propagated all the way up.
298 */
299 public abstract SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException;
300
301 /**
302 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
303 * on BugParade for more details.
304 */
305 public SCMRevisionState _calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
306 return calcRevisionsFromBuild(build, launcher, listener);
307 }
308
309 /**
310 * Compares the current state of the remote repository against the given baseline {@link SCMRevisionState}.
311 *
312 * <p>
313 * Conceptually, the act of polling is to take two states of the repository and to compare them to see
314 * if there's any difference. In practice, however, comparing two arbitrary repository states is an expensive
315 * operation, so in this abstraction, we chose to mix (1) the act of building up a repository state and
316 * (2) the act of comparing it with the earlier state, so that SCM implementations can implement this
317 * more easily.
318 *
319 * <p>
320 * Multiple invocations of this method may happen over time to make sure that the remote repository
321 * is "quiet" before Hudson schedules a new build.
322 *
323 * @param project
324 * The project to check for updates
325 * @param launcher
326 * Abstraction of the machine where the polling will take place. If SCM declares
327 * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null.
328 * @param workspace
329 * The workspace directory that contains baseline files. If SCM declares
330 * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null.
331 * @param listener
332 * Logs during the polling should be sent here.
333 * @param baseline
334 * The baseline of the comparison. This object is the return value from earlier
335 * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} or
336 * {@link #calcRevisionsFromBuild(AbstractBuild, Launcher, TaskListener)}.
337 *
338 * @return
339 * This method returns multiple values that are bundled together into the {@link PollingResult} value type.
340 * {@link PollingResult#baseline} should be the value of the baseline parameter, {@link PollingResult#remote}
341 * is the current state of the remote repository (this object only needs to be understandable to the future
342 * invocations of this method),
343 * and {@link PollingResult#change} that indicates the degree of changes found during the comparison.
344 *
345 * @throws InterruptedException
346 * interruption is usually caused by the user aborting the computation.
347 * this exception should be simply propagated all the way up.
348 */
349 protected abstract PollingResult compareRemoteRevisionWith(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException;
350
351 /**
352 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
353 * on BugParade for more details.
354 */
355 private PollingResult _compareRemoteRevisionWith(AbstractProject<?, ?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline2) throws IOException, InterruptedException {
356 return compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2);
357 }
358
359 /**
360 * Convenience method for the caller to handle the backward compatibility between pre 1.345 SCMs.
361 */
362 public final PollingResult poll(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException {
363 if (is1_346OrLater()) {
364 // This is to work around HUDSON-5827 in a general way.
365 // don't let the SCM.compareRemoteRevisionWith(...) see SCMRevisionState that it didn't produce.
366 SCMRevisionState baseline2;
367 if (baseline!=SCMRevisionState.NONE) {
368 baseline2 = baseline;
369 } else {
370 baseline2 = _calcRevisionsFromBuild(project.getLastBuild(), launcher, listener);
371 }
372
373 return _compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2);
374 } else {
375 return pollChanges(project,launcher,workspace,listener) ? PollingResult.SIGNIFICANT : PollingResult.NO_CHANGES;
376 }
377 }
378
379 private boolean is1_346OrLater() {
380 for (Class<?> c = getClass(); c != SCM.class; c = c.getSuperclass()) {
381 try {
382 c.getDeclaredMethod("compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class);
383 return true;
384 } catch (NoSuchMethodException e) { }
385 }
386 return false;
387 }
388
389 /**
390 * Obtains a fresh workspace of the module(s) into the specified directory
391 * of the specified machine.
392 *
393 * <p>
394 * The "update" operation can be performed instead of a fresh checkout if
395 * feasible.
396 *
397 * <p>
398 * This operation should also capture the information necessary to tag the workspace later.
399 *
400 * @param launcher
401 * Abstracts away the machine that the files will be checked out.
402 * @param workspace
403 * a directory to check out the source code. May contain left-over
404 * from the previous build.
405 * @param changelogFile
406 * Upon a successful return, this file should capture the changelog.
407 * When there's no change, this file should contain an empty entry.
408 * See {@link #createEmptyChangeLog(File, BuildListener, String)}.
409 * @return
410 * false if the operation fails. The error should be reported to the listener.
411 * Otherwise return the changes included in this update (if this was an update.)
412 * <p>
413 * Using the return value to indicate success/failure should
414 * be considered deprecated, and implementations are encouraged
415 * to throw {@link AbortException} to indicate a failure.
416 *
417 * @throws InterruptedException
418 * interruption is usually caused by the user aborting the build.
419 * this exception will cause the build to be aborted.
420 */
421 public abstract boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException;
422
423 /**
424 * Adds environmental variables for the builds to the given map.
425 *
426 * <p>
427 * This can be used to propagate information from SCM to builds
428 * (for example, SVN revision number.)
429 *
430 * <p>
431 * This method is invoked whenever someone does {@link AbstractBuild#getEnvironment(TaskListener)}, which
432 * can be before/after your checkout method is invoked. So if you are going to provide information about
433 * check out (like SVN revision number that was checked out), be prepared for the possibility that the
434 * check out hasn't happened yet.
435 */
436 public void buildEnvVars(AbstractBuild<?,?> build, Map<String, String> env) {
437 // default implementation is noop.
438 }
439
440 /**
441 * Gets the top directory of the checked out module.
442 *
443 * <p>
444 * Often SCMs have to create a directory inside a workspace, which
445 * creates directory layout like this:
446 *
447 * <pre>
448 * workspace <- workspace root
449 * +- xyz <- directory checked out by SCM
450 * +- CVS
451 * +- build.xml <- user file
452 * </pre>
453 *
454 * <p>
455 * Many builders, like Ant or Maven, works off the specific user file
456 * at the top of the checked out module (in the above case, that would
457 * be <tt>xyz/build.xml</tt>), yet the builder doesn't know the "xyz"
458 * part; that comes from SCM.
459 *
460 * <p>
461 * Collaboration between {@link Builder} and {@link SCM} allows
462 * Hudson to find build.xml without asking the user to enter "xyz" again.
463 *
464 * <p>
465 * This method is for this purpose. It takes the workspace
466 * root as a parameter, and expected to return the directory
467 * that was checked out from SCM.
468 *
469 * <p>
470 * If this SCM is configured to create a directory, try to
471 * return that directory so that builders can work seamlessly.
472 *
473 * <p>
474 * If SCM doesn't need to create any directory inside workspace,
475 * or in any other tricky cases, it should revert to the default
476 * implementation, which is to just return the parameter.
477 *
478 * @param workspace
479 * The workspace root directory.
480 * @param build
481 * The build for which the module root is desired.
482 * This parameter is null when existing legacy code calls deprecated {@link #getModuleRoot(FilePath)}.
483 * Handle this situation gracefully if your can, but otherwise you can just fail with an exception, too.
484 *
485 * @since 1.382
486 */
487 public FilePath getModuleRoot(FilePath workspace, AbstractBuild build) {
488 // For backwards compatibility, call the one argument version of the method.
489 return getModuleRoot(workspace);
490 }
491
492 /**
493 * @deprecated since 1.382
494 * Use/override {@link #getModuleRoot(FilePath, AbstractBuild)} instead.
495 */
496 public FilePath getModuleRoot(FilePath workspace) {
497 if (Util.isOverridden(SCM.class,getClass(),"getModuleRoot", FilePath.class,AbstractBuild.class))
498 // if the subtype already implements newer getModuleRoot(FilePath,AbstractBuild), call that.
499 return getModuleRoot(workspace,null);
500
501 return workspace;
502 }
503
504 /**
505 * Gets the top directories of all the checked out modules.
506 *
507 * <p>
508 * Some SCMs support checking out multiple modules inside a workspace, which
509 * creates directory layout like this:
510 *
511 * <pre>
512 * workspace <- workspace root
513 * +- xyz <- directory checked out by SCM
514 * +- .svn
515 * +- build.xml <- user file
516 * +- abc <- second module from different SCM root
517 * +- .svn
518 * +- build.xml <- user file
519 * </pre>
520 *
521 * This method takes the workspace root as a parameter, and is expected to return
522 * all the module roots that were checked out from SCM.
523 *
524 * <p>
525 * For normal SCMs, the array will be of length <code>1</code> and it's contents
526 * will be identical to calling {@link #getModuleRoot(FilePath, AbstractBuild)}.
527 *
528 * @param workspace The workspace root directory
529 * @param build
530 * The build for which the module roots are desired.
531 * This parameter is null when existing legacy code calls deprecated {@link #getModuleRoot(FilePath)}.
532 * Handle this situation gracefully if your can, but otherwise you can just fail with an exception, too.
533 *
534 * @return An array of all module roots.
535 * @since 1.382
536 */
537 public FilePath[] getModuleRoots(FilePath workspace, AbstractBuild build) {
538 if (Util.isOverridden(SCM.class,getClass(),"getModuleRoots", FilePath.class))
539 // if the subtype derives legacy getModuleRoots(FilePath), delegate to it
540 return getModuleRoots(workspace);
541
542 // otherwise the default implementation
543 return new FilePath[]{getModuleRoot(workspace,build)};
544 }
545
546 /**
547 * @deprecated as of 1.382.
548 * Use/derive from {@link #getModuleRoots(FilePath, AbstractBuild)} instead.
549 */
550 public FilePath[] getModuleRoots(FilePath workspace) {
551 if (Util.isOverridden(SCM.class,getClass(),"getModuleRoots", FilePath.class, AbstractBuild.class))
552 // if the subtype already derives newer getModuleRoots(FilePath,AbstractBuild), delegate to it
553 return getModuleRoots(workspace,null);
554
555 // otherwise the default implementation
556 return new FilePath[] { getModuleRoot(workspace), };
557 }
558
559 /**
560 * The returned object will be used to parse <tt>changelog.xml</tt>.
561 */
562 public abstract ChangeLogParser createChangeLogParser();
563
564 public SCMDescriptor<?> getDescriptor() {
565 return (SCMDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass());
566 }
567
568 //
569 // convenience methods
570 //
571
572 protected final boolean createEmptyChangeLog(File changelogFile, BuildListener listener, String rootTag) {
573 try {
574 FileWriter w = new FileWriter(changelogFile);
575 w.write("<"+rootTag +"/>");
576 w.close();
577 return true;
578 } catch (IOException e) {
579 e.printStackTrace(listener.error(e.getMessage()));
580 return false;
581 }
582 }
583
584 protected final String nullify(String s) {
585 if(s==null) return null;
586 if(s.trim().length()==0) return null;
587 return s;
588 }
589
590 public static final PermissionGroup PERMISSIONS = new PermissionGroup(SCM.class, Messages._SCM_Permissions_Title());
591 /**
592 * Permission to create new tags.
593 * @since 1.171
594 */
595 public static final Permission TAG = new Permission(PERMISSIONS,"Tag",Messages._SCM_TagPermission_Description(),Permission.CREATE, PermissionScope.ITEM);
596
597 /**
598 * Returns all the registered {@link SCMDescriptor}s.
599 */
600 public static DescriptorExtensionList<SCM,SCMDescriptor<?>> all() {
601 return Jenkins.getInstance().<SCM,SCMDescriptor<?>>getDescriptorList(SCM.class);
602 }
603
604 /**
605 * Returns the list of {@link SCMDescriptor}s that are applicable to the given project.
606 */
607 public static List<SCMDescriptor<?>> _for(final AbstractProject project) {
608 if(project==null) return all();
609
610 final Descriptor pd = Jenkins.getInstance().getDescriptor((Class) project.getClass());
611 List<SCMDescriptor<?>> r = new ArrayList<SCMDescriptor<?>>();
612 for (SCMDescriptor<?> scmDescriptor : all()) {
613 if(!scmDescriptor.isApplicable(project)) continue;
614
615 if (pd instanceof AbstractProjectDescriptor) {
616 AbstractProjectDescriptor apd = (AbstractProjectDescriptor) pd;
617 if(!apd.isApplicable(scmDescriptor)) continue;
618 }
619
620 r.add(scmDescriptor);
621 }
622
623 return r;
624 }
625 }
626