jenkins.plugins.git.GitSCMTelescope.java Source code

Java tutorial

Introduction

Here is the source code for jenkins.plugins.git.GitSCMTelescope.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2017 Stephen Connolly
 *
 * 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 jenkins.plugins.git;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import hudson.model.Item;
import hudson.model.Queue;
import hudson.model.queue.Tasks;
import hudson.plugins.git.BranchSpec;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.UserRemoteConfig;
import hudson.scm.SCM;
import hudson.security.ACL;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.mixin.TagSCMHead;
import org.eclipse.jgit.lib.Constants;
import org.jenkinsci.plugins.gitclient.GitClient;

/**
 * An implementation of this extension point allows {@link AbstractGitSCMSource} to examine a repository from a distance
 * without requiring a local checkout.
 *
 * @since 3.6.1
 */
public abstract class GitSCMTelescope extends SCMFileSystem.Builder {
    //TODO JENKINS-51134 DiscoverOtherRefsTrait

    /**
     * Returns the {@link GitSCMTelescope} to use for the specified {@link GitSCM} or {@code null} if none match.
     * @param source the {@link GitSCM}.
     * @return the {@link GitSCMTelescope} to use for the specified {@link GitSCM} or {@code null}
     */
    @CheckForNull
    public static GitSCMTelescope of(@NonNull GitSCM source) {
        for (SCMFileSystem.Builder b : ExtensionList.lookup(SCMFileSystem.Builder.class)) {
            if (b instanceof GitSCMTelescope && b.supports(source)) {
                return (GitSCMTelescope) b;
            }
            if (b instanceof GitSCMFileSystem.BuilderImpl) {
                // telescopes must come before the fallback GitSCMFileSystem.BuilderImpl otherwise they would
                // not prevent a local checkout
                break;
            }
        }
        return null;
    }

    /**
     * Returns the {@link GitSCMTelescope} to use for the specified {@link AbstractGitSCMSource} or {@code null} if
     * none match.
     *
     * @param source the {@link AbstractGitSCMSource}.
     * @return the {@link GitSCMTelescope} to use for the specified {@link AbstractGitSCMSource} or {@code null}
     */
    @CheckForNull
    public static GitSCMTelescope of(@NonNull AbstractGitSCMSource source) {
        for (SCMFileSystem.Builder b : ExtensionList.lookup(SCMFileSystem.Builder.class)) {
            if (b instanceof GitSCMTelescope && b.supports(source)) {
                return (GitSCMTelescope) b;
            }
            if (GitSCMFileSystem.BuilderImpl.class.equals(b.getClass())) {
                // telescopes must come before the fallback GitSCMFileSystem.BuilderImpl otherwise they would
                // not prevent a local checkout
                break;
            }
        }
        return null;
    }

    /**
     * Checks if this {@link jenkins.scm.api.SCMFileSystem.Builder} supports the repository at the supplied remote URL.
     * <strong>NOTE:</strong> returning {@code true} mandates that {@link #build(Item, SCM, SCMRevision)} and
     * {@link #build(SCMSource, SCMHead, SCMRevision)} must return non-{@code null} when they are configured
     * with the corresponding repository URL.
     *
     * @param remote the repository URL.
     * @return {@code true} if and only if the remote URL is supported by this {@link GitSCMTelescope}.
     */
    public abstract boolean supports(@NonNull String remote);

    /**
     * Checks if the supplied credentials are valid against the specified repository URL.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} to validate anonymous connection.
     * @throws IOException          if the operation failed due to an IO error or invalid credentials.
     * @throws InterruptedException if the operation was interrupted.
     */
    public abstract void validate(@NonNull String remote, @CheckForNull StandardCredentials credentials)
            throws IOException, InterruptedException;

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean supports(@NonNull SCM source) {
        if (source instanceof GitSCM) {
            // we only support the GitSCM if the branch is completely unambiguous
            GitSCM git = (GitSCM) source;
            List<UserRemoteConfig> configs = git.getUserRemoteConfigs();
            List<BranchSpec> branches = git.getBranches();
            return configs.size() == 1 && supports(configs.get(0).getUrl()) && branches.size() == 1
                    && !branches.get(0).getName().contains("*");
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean supports(@NonNull SCMSource source) {
        return source instanceof AbstractGitSCMSource && source.getOwner() != null
                && supports(((AbstractGitSCMSource) source).getRemote());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head,
            @CheckForNull SCMRevision rev) throws IOException, InterruptedException {
        SCMSourceOwner owner = source.getOwner();
        if (source instanceof AbstractGitSCMSource && owner != null
                && supports(((AbstractGitSCMSource) source).getRemote())) {
            AbstractGitSCMSource git = (AbstractGitSCMSource) source;
            String remote = git.getRemote();
            StandardUsernameCredentials credentials = git.getCredentials();
            validate(remote, credentials);
            return build(remote, credentials, head, rev);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, SCMRevision rev)
            throws IOException, InterruptedException {
        if (scm instanceof GitSCM) {
            // we only support the GitSCM if the branch is completely unambiguous
            GitSCM git = (GitSCM) scm;
            List<UserRemoteConfig> configs = git.getUserRemoteConfigs();
            List<BranchSpec> branches = git.getBranches();
            if (configs.size() == 1 && supports(configs.get(0).getUrl()) && branches.size() == 1
                    && !branches.get(0).getName().contains("*")) {
                UserRemoteConfig config = configs.get(0);
                StandardCredentials credentials;
                String credentialsId = config.getCredentialsId();
                String remote = config.getUrl();
                if (credentialsId != null) {
                    List<StandardUsernameCredentials> urlCredentials = CredentialsProvider.lookupCredentials(
                            StandardUsernameCredentials.class, owner,
                            owner instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) owner)
                                    : ACL.SYSTEM,
                            URIRequirementBuilder.fromUri(remote).build());
                    credentials = CredentialsMatchers.firstOrNull(urlCredentials, CredentialsMatchers
                            .allOf(CredentialsMatchers.withId(credentialsId), GitClient.CREDENTIALS_MATCHER));
                } else {
                    credentials = null;
                }
                validate(remote, credentials);
                SCMHead head;
                if (rev == null) {
                    String name = branches.get(0).getName();
                    if (name.startsWith(Constants.R_TAGS)) {
                        head = new GitTagSCMHead(name.substring(Constants.R_TAGS.length()),
                                getTimestamp(remote, credentials, name));
                    } else if (name.startsWith(Constants.R_HEADS)) {
                        head = new GitBranchSCMHead(name.substring(Constants.R_HEADS.length()));
                    } else {
                        if (name.startsWith(config.getName() + "/")) {
                            head = new GitBranchSCMHead(name.substring(config.getName().length() + 1));
                        } else {
                            head = new GitBranchSCMHead(name);
                        }
                    }
                } else {
                    head = rev.getHead();
                }
                return build(remote, credentials, head, rev);
            }
        }
        return null;
    }

    /**
     * Given a {@link SCM} this should try to build a corresponding {@link SCMFileSystem} instance that
     * reflects the content at the specified {@link SCMRevision}. If the {@link SCM} is supported but not
     * for a fixed revision, best effort is acceptable as the most capable {@link SCMFileSystem} will be returned
     * to the caller.
     *
     * @param remote      the repository URL
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @param head        the specified {@link SCMHead}
     * @param rev         the specified {@link SCMRevision}.
     * @return the corresponding {@link SCMFileSystem} or {@code null} if this builder cannot create a {@link
     * SCMFileSystem} for the specified repository URL.
     * @throws IOException          if the attempt to create a {@link SCMFileSystem} failed due to an IO error
     *                              (such as the remote system being unavailable)
     * @throws InterruptedException if the attempt to create a {@link SCMFileSystem} was interrupted.
     */
    @CheckForNull
    protected abstract SCMFileSystem build(@NonNull String remote, @CheckForNull StandardCredentials credentials,
            @NonNull SCMHead head, @CheckForNull SCMRevision rev) throws IOException, InterruptedException;

    /**
     * Retrieves the timestamp of the specified reference or object hash.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @param refOrHash   the reference or hash. If this is a reference then it will start with {@link Constants#R_REFS}
     *                    If this is a hash, it may be a full hash or a short hash.
     * @return the timestamp.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    public abstract long getTimestamp(@NonNull String remote, @CheckForNull StandardCredentials credentials,
            @NonNull String refOrHash) throws IOException, InterruptedException;

    /**
     * Retrieves the current revision of the specified reference or object hash.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @param refOrHash   the reference or hash. If this is a reference then it will start with {@link Constants#R_REFS}
     *                    If this is a hash, it may be a full hash or a short hash.
     * @return the revision or {@code null} if the reference or hash does not exist.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    @CheckForNull
    public abstract SCMRevision getRevision(@NonNull String remote, @CheckForNull StandardCredentials credentials,
            @NonNull String refOrHash) throws IOException, InterruptedException;

    /**
     * Retrieves the timestamp of the specified reference or object hash.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @param head        the head.
     * @return the timestamp.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    public long getTimestamp(@NonNull String remote, @CheckForNull StandardCredentials credentials,
            @NonNull SCMHead head) throws IOException, InterruptedException {
        if ((head instanceof TagSCMHead)) {
            return getTimestamp(remote, credentials, Constants.R_TAGS + head.getName());
        } else {
            return getTimestamp(remote, credentials, Constants.R_HEADS + head.getName());
        }
    }

    /**
     * Retrieves the current revision of the specified head.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @param head        the head.
     * @return the revision or {@code null} if the head does not exist.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    @CheckForNull
    public SCMRevision getRevision(@NonNull String remote, @CheckForNull StandardCredentials credentials,
            @NonNull SCMHead head) throws IOException, InterruptedException {
        if ((head instanceof TagSCMHead)) {
            return getRevision(remote, credentials, Constants.R_TAGS + head.getName());
        } else {
            return getRevision(remote, credentials, Constants.R_HEADS + head.getName());
        }
    }

    /**
     * Retrieves the current revisions of the specified repository.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @return the revisions.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    public final Iterable<SCMRevision> getRevisions(@NonNull String remote,
            @CheckForNull StandardCredentials credentials) throws IOException, InterruptedException {
        return getRevisions(remote, credentials, EnumSet.allOf(ReferenceType.class));
    }

    /**
     * Retrieves the current revisions of the specified repository.
     *
     * @param remote         the repository URL.
     * @param credentials    the credentials or {@code null} for an anonymous connection.
     * @param referenceTypes the types of reference to retrieve revisions of.
     * @return the revisions.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    public abstract Iterable<SCMRevision> getRevisions(@NonNull String remote,
            @CheckForNull StandardCredentials credentials, @NonNull Set<ReferenceType> referenceTypes)
            throws IOException, InterruptedException;

    /**
     * Retrieves the default target of the specified repository.
     *
     * @param remote      the repository URL.
     * @param credentials the credentials or {@code null} for an anonymous connection.
     * @return the default target of the repository.
     * @throws IOException          if the operation failed due to an IO error.
     * @throws InterruptedException if the operation was interrupted.
     */
    public abstract String getDefaultTarget(@NonNull String remote, @CheckForNull StandardCredentials credentials)
            throws IOException, InterruptedException;

    /**
     * The potential types of reference supported by a {@link GitSCMTelescope}.
     */
    public enum ReferenceType {
        /**
         * A regular reference.
         */
        HEAD,
        /**
         * A tag reference.
         */
        TAG;
    }
}