/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, InfraDNA, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.scm; import hudson.AbortException; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.ExtensionPoint; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Api; import hudson.model.BuildListener; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Job; import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.TopLevelItemDescriptor; import hudson.model.WorkspaceCleanupThread; import hudson.security.Permission; import hudson.security.PermissionGroup; import hudson.security.PermissionScope; import hudson.tasks.Builder; import hudson.util.IOUtils; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; /** * Captures the configuration information in it. * * <p> * To register a custom {@link SCM} implementation from a plugin, * put {@link Extension} on your {@link SCMDescriptor}. * * <p> * Use the "project-changes" view to render change list to be displayed * at the project level. The default implementation simply aggregates * change lists from builds, but your SCM can provide different views. * The view gets the "builds" variable which is a list of builds that are * selected for the display. * * <p> * If you are interested in writing a subclass in a plugin, * also take a look at <a href="http://wiki.jenkins-ci.org/display/JENKINS/Writing+an+SCM+plugin"> * "Writing an SCM plugin"</a> wiki article. * * @author Kohsuke Kawaguchi */ @ExportedBean public abstract class SCM implements Describable<SCM>, ExtensionPoint { /** JENKINS-35098: discouraged */ @SuppressWarnings("FieldMayBeFinal") private static boolean useAutoBrowserHolder = SystemProperties.getBoolean(SCM.class.getName() + ".useAutoBrowserHolder"); /** * Stores {@link AutoBrowserHolder}. Lazily created. * @deprecated Unused by default. */ private transient AutoBrowserHolder autoBrowserHolder; /** * Expose {@link SCM} to the remote API. */ public Api getApi() { return new Api(this); } /** * Returns the {@link RepositoryBrowser} for files * controlled by this {@link SCM}. * * @return * null to indicate that there's no explicitly configured browser * for this SCM instance. * * @see #getEffectiveBrowser() */ public @CheckForNull RepositoryBrowser<?> getBrowser() { return null; } /** * Type of this SCM. * * Exposed so that the client of the remote API can tell what SCM this is. */ @Exported public String getType() { return getClass().getName(); } /** * Returns the applicable {@link RepositoryBrowser} for files * controlled by this {@link SCM}. * @see #guessBrowser */ @SuppressWarnings("deprecation") @Exported(name="browser") public final @CheckForNull RepositoryBrowser<?> getEffectiveBrowser() { RepositoryBrowser<?> b = getBrowser(); if(b!=null) return b; if (useAutoBrowserHolder) { if (autoBrowserHolder == null) { autoBrowserHolder = new AutoBrowserHolder(this); } return autoBrowserHolder.get(); } else { return guessBrowser(); } } /** * Returns true if this SCM supports * {@link #poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState) poling}. * * @since 1.105 */ public boolean supportsPolling() { return true; } /** * Returns true if this SCM requires a checked out workspace for doing polling. * * <p> * This flag affects the behavior of Hudson when a job lost its workspace * (typically due to a agent outage.) If this method returns false and * polling is configured, then that would immediately trigger a new build. * * <p> * This flag also affects the mutual exclusion control between builds and polling. * If this methods returns false, polling will continue asynchronously even * when a build is in progress, but otherwise the polling activity is blocked * if a build is currently using a workspace. * * <p> * The default implementation returns true. * * <p> * See issue #1348 for more discussion of this feature. * * @since 1.196 */ public boolean requiresWorkspaceForPolling() { return true; } /** * Called before a workspace is deleted on the given node, to provide SCM an opportunity to perform clean up. * * <p> * Hudson periodically scans through all the agents and removes old workspaces that are deemed unnecessary. * This behavior is implemented in {@link WorkspaceCleanupThread}, and it is necessary to control the * disk consumption on agents. If we don't do this, in a long run, all the agents will have workspaces * for all the projects, which will be prohibitive in big Hudson. * * <p> * However, some SCM implementations require that the server be made aware of deletion of the local workspace, * and this method provides an opportunity for SCMs to perform such a clean-up act. * * <p> * This call back is invoked after Hudson determines that a workspace is unnecessary, but before the actual * recursive directory deletion happens. * * <p> * Note that this method does not guarantee that such a clean up will happen. For example, agents can be * taken offline by being physically removed from the network, and in such a case there's no opportunity * to perform this clean up. * * <p> * This method is also invoked when the project is deleted. * * @param project * The project that owns this {@link SCM}. This is always the same object for a particular instance * of {@link SCM}. Just passed in here so that {@link SCM} itself doesn't have to remember the value. * @param workspace * The workspace which is about to be deleted. This can be a remote file path. * @param node * The node that hosts the workspace. SCM can use this information to determine the course of action. * * @return * true if {@link SCM} is OK to let Hudson proceed with deleting the workspace. * False to veto the workspace deletion. * * @since 1.568 */ public boolean processWorkspaceBeforeDeletion(@Nonnull Job<?,?> project, @Nonnull FilePath workspace, @Nonnull Node node) throws IOException, InterruptedException { if (project instanceof AbstractProject) { return processWorkspaceBeforeDeletion((AbstractProject) project, workspace, node); } else { return true; } } @Deprecated public boolean processWorkspaceBeforeDeletion(AbstractProject<?,?> project, FilePath workspace, Node node) throws IOException, InterruptedException { if (Util.isOverridden(SCM.class, getClass(), "processWorkspaceBeforeDeletion", Job.class, FilePath.class, Node.class)) { return processWorkspaceBeforeDeletion((Job) project, workspace, node); } else { return true; } } /** * Checks if there has been any changes to this module in the repository. * * TODO: we need to figure out a better way to communicate an error back, * so that we won't keep retrying the same node (for example an agent might be down.) * * <p> * If the SCM doesn't implement polling, have the {@link #supportsPolling()} method * return false. * * @param project * The project to check for updates * @param launcher * Abstraction of the machine where the polling will take place. If SCM declares * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null. * @param workspace * The workspace directory that contains baseline files. If SCM declares * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null. * @param listener * Logs during the polling should be sent here. * * @return true * if the change is detected. * * @throws InterruptedException * interruption is usually caused by the user aborting the computation. * this exception should be simply propagated all the way up. * * @see #supportsPolling() * * @deprecated as of 1.345 * Override {@link #calcRevisionsFromBuild(AbstractBuild, Launcher, TaskListener)} and * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} for implementation. * * The implementation is now separated in two pieces, one that computes the revision of the current workspace, * and the other that computes the revision of the remote repository. * * Call {@link #poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} for use instead. */ @Deprecated public boolean pollChanges(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { // up until 1.336, this method was abstract, so everyone should have overridden this method // without calling super.pollChanges. So the compatibility implementation is purely for // new implementations that doesn't override this method. throw new AbstractMethodError("you must override compareRemoteRevisionWith"); } /** * Calculates the {@link SCMRevisionState} that represents the state of the workspace of the given build. * * <p> * The returned object is then fed into the * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} method * as the baseline {@link SCMRevisionState} to determine if the build is necessary. * * <p> * This method is called after source code is checked out for the given build (that is, after * {@link SCM#checkout(Run, Launcher, FilePath, TaskListener, File)} has finished successfully.) * * <p> * The obtained object is added to the build as an {@link Action} for later retrieval. As an optimization, * {@link SCM} implementation can choose to compute {@link SCMRevisionState} and add it as an action * during check out, in which case this method will not called. * * @param build * The calculated {@link SCMRevisionState} is for the files checked out in this build. * If {@link #requiresWorkspaceForPolling()} returns true, Hudson makes sure that the workspace of this * build is available and accessible by the callee. * @param workspace the location of the checkout; normally not null, since this will normally be called immediately after checkout, * though could be null if data is being loaded from a very old version of Jenkins and the SCM declares that it does not require a workspace for polling * @param launcher * Abstraction of the machine where the polling will take place. Nullness matches that of {@code workspace}. * @param listener * Logs during the polling should be sent here. * * @throws InterruptedException * interruption is usually caused by the user aborting the computation. * this exception should be simply propagated all the way up. * @since 1.568 */ public @CheckForNull SCMRevisionState calcRevisionsFromBuild(@Nonnull Run<?,?> build, @Nullable FilePath workspace, @Nullable Launcher launcher, @Nonnull TaskListener listener) throws IOException, InterruptedException { if (build instanceof AbstractBuild && Util.isOverridden(SCM.class, getClass(), "calcRevisionsFromBuild", AbstractBuild.class, Launcher.class, TaskListener.class)) { return calcRevisionsFromBuild((AbstractBuild) build, launcher, listener); } else { throw new AbstractMethodError("you must override the new calcRevisionsFromBuild overload"); } } @Deprecated public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { return calcRevisionsFromBuild(build, launcher != null ? build.getWorkspace() : null, launcher, listener); } @Deprecated public SCMRevisionState _calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { return calcRevisionsFromBuild(build, launcher, listener); } /** * Compares the current state of the remote repository against the given baseline {@link SCMRevisionState}. * * <p> * Conceptually, the act of polling is to take two states of the repository and to compare them to see * if there's any difference. In practice, however, comparing two arbitrary repository states is an expensive * operation, so in this abstraction, we chose to mix (1) the act of building up a repository state and * (2) the act of comparing it with the earlier state, so that SCM implementations can implement this * more easily. * * <p> * Multiple invocations of this method may happen over time to make sure that the remote repository * is "quiet" before Hudson schedules a new build. * * @param project * The project to check for updates * @param launcher * Abstraction of the machine where the polling will take place. If SCM declares * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null. * @param workspace * The workspace directory that contains baseline files. If SCM declares * that {@linkplain #requiresWorkspaceForPolling() the polling doesn't require a workspace}, this parameter is null. * @param listener * Logs during the polling should be sent here. * @param baseline * The baseline of the comparison. This object is the return value from earlier * {@link #compareRemoteRevisionWith(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)} or * {@link #calcRevisionsFromBuild(AbstractBuild, Launcher, TaskListener)}. * * @return * This method returns multiple values that are bundled together into the {@link PollingResult} value type. * {@link PollingResult#baseline} should be the value of the baseline parameter, {@link PollingResult#remote} * is the current state of the remote repository (this object only needs to be understandable to the future * invocations of this method), * and {@link PollingResult#change} that indicates the degree of changes found during the comparison. * * @throws InterruptedException * interruption is usually caused by the user aborting the computation. * this exception should be simply propagated all the way up. * @since 1.568 */ public PollingResult compareRemoteRevisionWith(@Nonnull Job<?,?> project, @Nullable Launcher launcher, @Nullable FilePath workspace, @Nonnull TaskListener listener, @Nonnull SCMRevisionState baseline) throws IOException, InterruptedException { if (project instanceof AbstractProject && Util.isOverridden(SCM.class, getClass(), "compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class)) { return compareRemoteRevisionWith((AbstractProject) project, launcher, workspace, listener, baseline); } else { throw new AbstractMethodError("you must override the new overload of compareRemoteRevisionWith"); } } @Deprecated protected PollingResult compareRemoteRevisionWith(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException { return compareRemoteRevisionWith((Job) project, launcher, workspace, listener, baseline); } /** * Convenience method for the caller to handle the backward compatibility between pre 1.345 SCMs. */ public final PollingResult poll(AbstractProject<?,?> project, Launcher launcher, FilePath workspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException { if (is1_346OrLater()) { // This is to work around HUDSON-5827 in a general way. // don't let the SCM.compareRemoteRevisionWith(...) see SCMRevisionState that it didn't produce. SCMRevisionState baseline2; if (baseline!=SCMRevisionState.NONE) { baseline2 = baseline; } else { baseline2 = calcRevisionsFromBuild(project.getLastBuild(), launcher, listener); } return compareRemoteRevisionWith(project, launcher, workspace, listener, baseline2); } else { return pollChanges(project,launcher,workspace,listener) ? PollingResult.SIGNIFICANT : PollingResult.NO_CHANGES; } } private boolean is1_346OrLater() { for (Class<?> c = getClass(); c != SCM.class; c = c.getSuperclass()) { try { c.getDeclaredMethod("compareRemoteRevisionWith", AbstractProject.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class); return true; } catch (NoSuchMethodException e) { try { c.getDeclaredMethod("compareRemoteRevisionWith", Job.class, Launcher.class, FilePath.class, TaskListener.class, SCMRevisionState.class); return true; } catch (NoSuchMethodException e2) {} } } return false; } /** * Should create a key by which this SCM configuration might be distinguished from others in the same project. * Should be invariable across builds but otherwise as distinctive as possible. * <p>Could include information such as the relative paths used in {@link #getModuleRoots(FilePath, AbstractBuild)}, * and/or configured repository URLs and branch names, and/or labels set for this purpose by the user. * <p>The result may be used for various purposes, but it may be long and/or include URL-unsafe characters, * so to use in a URL path component you may need to first wrap it in {@link Util#getDigestOf(String)} or otherwise encode it. * @return by default, just {@link #getType} * @since 1.568 */ public @Nonnull String getKey() { return getType(); } /** * Obtains a fresh workspace of the module(s) into the specified directory * of the specified machine. * * <p> * The "update" operation can be performed instead of a fresh checkout if * feasible. * * <p> * This operation should also capture the information necessary to tag the workspace later. * * @param launcher * Abstracts away the machine that the files will be checked out. * @param workspace * a directory to check out the source code. May contain left-over * from the previous build. * @param changelogFile * Upon a successful return, this file should capture the changelog. * When there's no change, this file should contain an empty entry. * See {@link #createEmptyChangeLog(File, TaskListener, String)}. * May be null, in which case no changelog was requested. * @param baseline version from the previous build to use for changelog creation, if requested and available * @throws InterruptedException * interruption is usually caused by the user aborting the build. * this exception will cause the build to be aborted. * @throws AbortException in case of a routine failure * @since 1.568 */ public void checkout(@Nonnull Run<?,?> build, @Nonnull Launcher launcher, @Nonnull FilePath workspace, @Nonnull TaskListener listener, @CheckForNull File changelogFile, @CheckForNull SCMRevisionState baseline) throws IOException, InterruptedException { if (build instanceof AbstractBuild && listener instanceof BuildListener && Util.isOverridden(SCM.class, getClass(), "checkout", AbstractBuild.class, Launcher.class, FilePath.class, BuildListener.class, File.class)) { if (changelogFile == null) { changelogFile = File.createTempFile("changelog", ".xml"); try { if (!checkout((AbstractBuild) build, launcher, workspace, (BuildListener) listener, changelogFile)) { throw new AbortException(); } } finally { Util.deleteFile(changelogFile); } } else { if (!checkout((AbstractBuild) build, launcher, workspace, (BuildListener) listener, changelogFile)) { throw new AbortException(); } } } else { throw new AbstractMethodError("you must override the new overload of checkout"); } } @Deprecated public boolean checkout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener, @Nonnull File changelogFile) throws IOException, InterruptedException { AbstractBuild<?,?> prev = build.getPreviousBuild(); checkout((Run) build, launcher, workspace, listener, changelogFile, prev != null ? prev.getAction(SCMRevisionState.class) : null); return true; } /** * Get a chance to do operations after the workspace i checked out and the changelog is written. * @since 1.568 */ public void postCheckout(@Nonnull Run<?,?> build, @Nonnull Launcher launcher, @Nonnull FilePath workspace, @Nonnull TaskListener listener) throws IOException, InterruptedException { if (build instanceof AbstractBuild && listener instanceof BuildListener) { postCheckout((AbstractBuild) build, launcher, workspace, (BuildListener) listener); } } @Deprecated public void postCheckout(AbstractBuild<?,?> build, Launcher launcher, FilePath workspace, BuildListener listener) throws IOException, InterruptedException { if (Util.isOverridden(SCM.class, getClass(), "postCheckout", Run.class, Launcher.class, FilePath.class, TaskListener.class)) { postCheckout((Run) build, launcher, workspace, listener); } /* Default implementation is noop */ } /** * Adds environmental variables for the builds to the given map. * * <p> * This can be used to propagate information from SCM to builds * (for example, SVN revision number.) * * <p> * This method is invoked whenever someone does {@link AbstractBuild#getEnvironment(TaskListener)}, which * can be before/after your checkout method is invoked. So if you are going to provide information about * check out (like SVN revision number that was checked out), be prepared for the possibility that the * check out hasn't happened yet. */ // TODO is an equivalent for Run needed? public void buildEnvVars(AbstractBuild<?,?> build, Map<String, String> env) { // default implementation is noop. } /** * Gets the top directory of the checked out module. * * <p> * Often SCMs have to create a directory inside a workspace, which * creates directory layout like this: * * <pre> * workspace <- workspace root * +- xyz <- directory checked out by SCM * +- CVS * +- build.xml <- user file * </pre> * * <p> * Many builders, like Ant or Maven, works off the specific user file * at the top of the checked out module (in the above case, that would * be <tt>xyz/build.xml</tt>), yet the builder doesn't know the "xyz" * part; that comes from SCM. * * <p> * Collaboration between {@link Builder} and {@link SCM} allows * Hudson to find build.xml without asking the user to enter "xyz" again. * * <p> * This method is for this purpose. It takes the workspace * root as a parameter, and expected to return the directory * that was checked out from SCM. * * <p> * If this SCM is configured to create a directory, try to * return that directory so that builders can work seamlessly. * * <p> * If SCM doesn't need to create any directory inside workspace, * or in any other tricky cases, it should revert to the default * implementation, which is to just return the parameter. * * @param workspace * The workspace root directory. * @param build * The build for which the module root is desired. * This parameter is null when existing legacy code calls deprecated {@link #getModuleRoot(FilePath)}. * Handle this situation gracefully if your can, but otherwise you can just fail with an exception, too. * * @since 1.382 */ // TODO perhaps deprecate all replace with a single getModuleRoots(FilePath, Run) public FilePath getModuleRoot(FilePath workspace, AbstractBuild build) { // For backwards compatibility, call the one argument version of the method. return getModuleRoot(workspace); } /** * @deprecated since 1.382 * Use/override {@link #getModuleRoot(FilePath, AbstractBuild)} instead. */ @Deprecated public FilePath getModuleRoot(FilePath workspace) { if (Util.isOverridden(SCM.class,getClass(),"getModuleRoot", FilePath.class,AbstractBuild.class)) // if the subtype already implements newer getModuleRoot(FilePath,AbstractBuild), call that. return getModuleRoot(workspace,null); return workspace; } /** * Gets the top directories of all the checked out modules. * * <p> * Some SCMs support checking out multiple modules inside a workspace, which * creates directory layout like this: * * <pre> * workspace <- workspace root * +- xyz <- directory checked out by SCM * +- .svn * +- build.xml <- user file * +- abc <- second module from different SCM root * +- .svn * +- build.xml <- user file * </pre> * * This method takes the workspace root as a parameter, and is expected to return * all the module roots that were checked out from SCM. * * <p> * For normal SCMs, the array will be of length <code>1</code> and it's contents * will be identical to calling {@link #getModuleRoot(FilePath, AbstractBuild)}. * * @param workspace The workspace root directory * @param build * The build for which the module roots are desired. * This parameter is null when existing legacy code calls deprecated {@link #getModuleRoot(FilePath)}. * Handle this situation gracefully if your can, but otherwise you can just fail with an exception, too. * * @return An array of all module roots. * @since 1.382 */ public FilePath[] getModuleRoots(FilePath workspace, AbstractBuild build) { if (Util.isOverridden(SCM.class,getClass(),"getModuleRoots", FilePath.class)) // if the subtype derives legacy getModuleRoots(FilePath), delegate to it return getModuleRoots(workspace); // otherwise the default implementation return new FilePath[]{getModuleRoot(workspace,build)}; } /** * @deprecated as of 1.382. * Use/derive from {@link #getModuleRoots(FilePath, AbstractBuild)} instead. */ @Deprecated public FilePath[] getModuleRoots(FilePath workspace) { if (Util.isOverridden(SCM.class,getClass(),"getModuleRoots", FilePath.class, AbstractBuild.class)) // if the subtype already derives newer getModuleRoots(FilePath,AbstractBuild), delegate to it return getModuleRoots(workspace,null); // otherwise the default implementation return new FilePath[] { getModuleRoot(workspace), }; } /** * The returned object will be used to parse <tt>changelog.xml</tt>. */ public abstract ChangeLogParser createChangeLogParser(); public SCMDescriptor<?> getDescriptor() { return (SCMDescriptor) Jenkins.getInstance().getDescriptorOrDie(getClass()); } // // convenience methods // @Deprecated protected final boolean createEmptyChangeLog(File changelogFile, BuildListener listener, String rootTag) { try { createEmptyChangeLog(changelogFile, (TaskListener) listener, rootTag); return true; } catch (IOException e) { e.printStackTrace(listener.error(e.getMessage())); return false; } } /** * @since 1.568 */ protected final void createEmptyChangeLog(@Nonnull File changelogFile, @Nonnull TaskListener listener, @Nonnull String rootTag) throws IOException { FileWriter w = null; try { w = new FileWriter(changelogFile); w.write("<"+rootTag +"/>"); w.close(); } finally { IOUtils.closeQuietly(w); } } protected final String nullify(String s) { if(s==null) return null; if(s.trim().length()==0) return null; return s; } public static final PermissionGroup PERMISSIONS = new PermissionGroup(SCM.class, Messages._SCM_Permissions_Title()); /** * Permission to create new tags. * @since 1.171 */ public static final Permission TAG = new Permission(PERMISSIONS,"Tag",Messages._SCM_TagPermission_Description(),Permission.CREATE, PermissionScope.ITEM); /** * Returns all the registered {@link SCMDescriptor}s. */ public static DescriptorExtensionList<SCM,SCMDescriptor<?>> all() { return Jenkins.getInstance().<SCM,SCMDescriptor<?>>getDescriptorList(SCM.class); } /** * Determines which kinds of SCMs are applicable to a given project. * @param project a project on which we might be configuring SCM, or null if unknown * @return all descriptors which {@link SCMDescriptor#isApplicable(Job)} to it, also filtered by {@link TopLevelItemDescriptor#isApplicable}; * or simply {@link #all} if there is no project * @since 1.568 */ public static List<SCMDescriptor<?>> _for(@CheckForNull final Job project) { if(project==null) return all(); final Descriptor pd = Jenkins.getInstance().getDescriptor((Class) project.getClass()); List<SCMDescriptor<?>> r = new ArrayList<SCMDescriptor<?>>(); for (SCMDescriptor<?> scmDescriptor : all()) { if(!scmDescriptor.isApplicable(project)) continue; if (pd instanceof TopLevelItemDescriptor) { TopLevelItemDescriptor apd = (TopLevelItemDescriptor) pd; if(!apd.isApplicable(scmDescriptor)) continue; } r.add(scmDescriptor); } return r; } @Deprecated public static List<SCMDescriptor<?>> _for(final AbstractProject project) { return _for((Job) project); } /** * Try to guess how a repository browser should be configured, based on URLs and the like. * Used when {@link #getBrowser} has not been explicitly configured. * @return a reasonable default value for {@link #getEffectiveBrowser}, or null * @since 1.568 */ public @CheckForNull RepositoryBrowser<?> guessBrowser() { return null; } }