<commit_before>/*
 * The MIT License
 *
 * Copyright (c) 2013-2017, CloudBees, Inc., <NAME>, Amadeus IT Group.
 *
 * 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.CredentialsNameProvider;
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 edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.EnvVars;
import hudson.Extension;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.Item;
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.GitTool;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.browser.GitRepositoryBrowser;
import hudson.plugins.git.extensions.GitSCMExtension;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildChooser;
import hudson.plugins.git.util.BuildChooserContext;
import hudson.plugins.git.util.BuildChooserDescriptor;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;
import hudson.security.ACL;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import jenkins.model.Jenkins;
import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait;
import jenkins.plugins.git.traits.GitSCMExtensionTrait;
import jenkins.plugins.git.traits.GitToolSCMSourceTrait;
import jenkins.plugins.git.traits.RefSpecsSCMSourceTrait;
import jenkins.plugins.git.traits.RemoteNameSCMSourceTrait;
import jenkins.scm.api.SCMFile;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadCategory;
import jenkins.scm.api.SCMHeadEvent;
import jenkins.scm.api.SCMHeadObserver;
import jenkins.scm.api.SCMProbe;
import jenkins.scm.api.SCMProbeStat;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceCriteria;
import jenkins.scm.api.SCMSourceEvent;
import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.metadata.PrimaryInstanceMetadataAction;
import jenkins.scm.api.trait.SCMSourceRequest;
import jenkins.scm.api.trait.SCMSourceTrait;
import jenkins.scm.api.trait.SCMTrait;
import jenkins.scm.impl.TagSCMHeadCategory;
import jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait;
import jenkins.scm.impl.trait.WildcardSCMSourceFilterTrait;
import net.jcip.annotations.GuardedBy;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.jenkinsci.plugins.gitclient.FetchCommand;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
 * Base class for {@link SCMSource} implementations that produce {@link GitSCM} implementations.
 *
 * @since 2.0
 */
public abstract class AbstractGitSCMSource extends SCMSource {

    /**
     * The default remote name to use when configuring the ref specs to use with fetch operations.
     *
     * @since 3.4.0
     */
    public static final String DEFAULT_REMOTE_NAME = "origin";
    /**
     * The placeholder to use in ref spec templates in order to correctly ensure that the ref spec remote name
     * matches the remote name.
     * <p>
     * The template uses {@code @{...}} as that is an illegal sequence in a remote name
     *
     * @see <a href="https://github.com/git/git/blob/027a3b943b444a3e3a76f9a89803fc10245b858f/refs.c#L61-L68">git
     * source code rules on ref spec names</a>
     * @since 3.4.0
     */
    public static final String REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR = "@{remote}";
    /**
     * The regex for {@link #REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR}.
     *
     * @since 3.4.0
     */
    public static final String REF_SPEC_REMOTE_NAME_PLACEHOLDER = "(?i)"+Pattern.quote(REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR);
    /**
     * The default ref spec template.
     *
     * @since 3.4.0
     */
    public static final String REF_SPEC_DEFAULT =
            "+refs/heads/*:refs/remotes/" + REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR + "/*";

    /**
     * Keep one lock per cache directory. Lazy populated, but never purge, except on restart.
     */
    private static final ConcurrentMap<String, Lock> cacheLocks = new ConcurrentHashMap<>();

    private static final Logger LOGGER = Logger.getLogger(AbstractGitSCMSource.class.getName());

    public AbstractGitSCMSource() {
    }
    
    @Deprecated
    public AbstractGitSCMSource(String id) {
        setId(id);
    }

    @CheckForNull
    public abstract String getCredentialsId();

    /**
     * @return Git remote URL
     */
    public abstract String getRemote();

    /**
     * @deprecated use {@link WildcardSCMSourceFilterTrait}
     * @return the includes.
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getIncludes() {
        WildcardSCMHeadFilterTrait trait = SCMTrait.find(getTraits(), WildcardSCMHeadFilterTrait.class);
        return trait != null ? trait.getIncludes() : "*";
    }

    /**
     * @return the excludes.
     * @deprecated use {@link WildcardSCMSourceFilterTrait}
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getExcludes() {
        WildcardSCMHeadFilterTrait trait = SCMTrait.find(getTraits(), WildcardSCMHeadFilterTrait.class);
        return trait != null ? trait.getExcludes() : "";
    }

    /**
     * Gets {@link GitRepositoryBrowser} to be used with this SCMSource.
     * @return Repository browser or {@code null} if the default tool should be used.
     * @since 2.5.1
     * @deprecated use {@link GitBrowserSCMSourceTrait}
     */
    @CheckForNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public GitRepositoryBrowser getBrowser() {
        GitBrowserSCMSourceTrait trait = SCMTrait.find(getTraits(), GitBrowserSCMSourceTrait.class);
        return trait != null ? trait.getBrowser() : null;
    }

    /**
     * Gets Git tool to be used for this SCM Source.
     * @return Git Tool or {@code null} if the default tool should be used.
     * @since 2.5.1
     * @deprecated use {@link GitToolSCMSourceTrait}
     */
    @CheckForNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getGitTool() {
        GitToolSCMSourceTrait trait = SCMTrait.find(getTraits(), GitToolSCMSourceTrait.class);
        return trait != null ? trait.getGitTool() : null;
    }

    /**
     * Gets list of extensions, which should be used with this branch source.
     * @return List of Extensions to be used. May be empty
     * @since 2.5.1
     * @deprecated use corresponding {@link GitSCMExtensionTrait} (and if there isn't one then likely the
     * {@link GitSCMExtension} is not appropriate to use in the context of a {@link SCMSource})
     */
    @NonNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public List<GitSCMExtension> getExtensions() {
        List<GitSCMExtension> extensions = new ArrayList<>();
        for (SCMSourceTrait t : getTraits()) {
            if (t instanceof GitSCMExtensionTrait) {
                extensions.add(((GitSCMExtensionTrait) t).getExtension());
            }
        }
        return Collections.unmodifiableList(extensions);
    }

    /**
     * Returns the {@link SCMSourceTrait} instances for this {@link AbstractGitSCMSource}.
     * @return the {@link SCMSourceTrait} instances
     * @since 3.4.0
     */
    @NonNull
    public List<SCMSourceTrait> getTraits() {
        // Always return empty list (we expect subclasses to override)
        return Collections.emptyList();
    }

    /**
     * @deprecated use {@link RemoteNameSCMSourceTrait}
     * @return the remote name.
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getRemoteName() {
        RemoteNameSCMSourceTrait trait = SCMTrait.find(getTraits(), RemoteNameSCMSourceTrait.class);
        return trait != null ? trait.getRemoteName() : DEFAULT_REMOTE_NAME;
    }

    /**
     * Resolves the {@link GitTool}.
     * @return the {@link GitTool}.
     * @deprecated use {@link #resolveGitTool(String)}.
     */
    @CheckForNull
    @Deprecated
    @Restricted(DoNotUse.class)
    @RestrictedSince("3.4.0")
    protected GitTool resolveGitTool() {
        return resolveGitTool(getGitTool());
    }

    /**
     * Resolves the {@link GitTool}.
     * @param gitTool the {@link GitTool#getName()} to resolve.
     * @return the {@link GitTool}
     * @since 3.4.0
     */
    @CheckForNull
    protected GitTool resolveGitTool(String gitTool) {
        return StringUtils.isBlank(gitTool)
                ? GitTool.getDefaultInstallation()
                : Jenkins.getActiveInstance()
                        .getDescriptorByType(GitTool.DescriptorImpl.class)
                        .getInstallation(gitTool);
    }

    private interface Retriever<T> {
        T run(GitClient client, String remoteName) throws IOException, InterruptedException;
    }

    @NonNull
    private <T, C extends GitSCMSourceContext<C, R>, R extends GitSCMSourceRequest> T doRetrieve(Retriever<T> retriever,
                                                                                                 @NonNull C context,
                                                                                                 @NonNull TaskListener listener,
                                                                                                 boolean prune)
            throws IOException, InterruptedException {
        String cacheEntry = getCacheEntry();
        Lock cacheLock = getCacheLock(cacheEntry);
        cacheLock.lock();
        try {
            File cacheDir = getCacheDir(cacheEntry);
            Git git = Git.with(listener, new EnvVars(EnvVars.masterEnvVars)).in(cacheDir);
            GitTool tool = resolveGitTool(context.gitTool());
            if (tool != null) {
                git.using(tool.getGitExe());
            }
            GitClient client = git.getClient();
            client.addDefaultCredentials(getCredentials());
            if (!client.hasGitRepo()) {
                listener.getLogger().println("Creating git repository in " + cacheDir);
                client.init();
            }
            String remoteName = context.remoteName();
            listener.getLogger().println("Setting " + remoteName + " to " + getRemote());
            client.setRemoteUrl(remoteName, getRemote());
            listener.getLogger().println((prune ? "Fetching & pruning " : "Fetching ") + remoteName + "...");
            FetchCommand fetch = client.fetch_();
            if (prune) {
                fetch = fetch.prune();
            }
            URIish remoteURI = null;
            try {
                remoteURI = new URIish(remoteName);
            } catch (URISyntaxException ex) {
                listener.getLogger().println("URI syntax exception for '" + remoteName + "' " + ex);
            }
            fetch.from(remoteURI, context.asRefSpecs()).execute();
            return retriever.run(client, remoteName);
        } finally {
            cacheLock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    @CheckForNull
    @Override
    protected SCMRevision retrieve(@NonNull final SCMHead head, @NonNull final TaskListener listener)
            throws IOException, InterruptedException {
        GitSCMSourceContext context = new GitSCMSourceContext<>(null, SCMHeadObserver.none()).withTraits(getTraits());
        GitSCMTelescope telescope = GitSCMTelescope.of(this);
        if (telescope != null) {
            String remote = getRemote();
            StandardUsernameCredentials credentials = getCredentials();
            telescope.validate(remote, credentials);
            return telescope.getRevision(remote, credentials, head);
        }
        if (head instanceof GitSCMHeadMixin) {
            //Since it is a specific SCMHead we are after, that we know the refspec of
            //we can save a bit of time and bandwidth by just fetching that refspec
            context = context.withoutRefSpecs().withRefSpec(((GitSCMHeadMixin) head).getRef());
        }
        return doRetrieve(new Retriever<SCMRevision>() {
                              @Override
                              public SCMRevision run(GitClient client, String remoteName) throws IOException, InterruptedException {
                                  if (head instanceof GitTagSCMHead) {
                                      try {
                                          ObjectId objectId = client.revParse(Constants.R_TAGS + head.getName());
                                          return new GitTagSCMRevision((GitTagSCMHead) head, objectId.name());
                                      } catch (GitException e) {
                                          // tag does not exist
                                          return null;
                                      }
                                  } else if (head instanceof GitBranchSCMHead) {
                                      for (Branch b : client.getRemoteBranches()) {
                                          String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
                                          if (branchName.equals(head.getName())) {
                                              return new GitBranchSCMRevision((GitBranchSCMHead)head, b.getSHA1String());
                                          }
                                      }
                                  } else if (head instanceof GitRefSCMHead) {
                                      try {
                                          ObjectId objectId = client.revParse(((GitRefSCMHead) head).getRef());
                                          return new GitRefSCMRevision((GitRefSCMHead)head, objectId.name());
                                      } catch (GitException e) {
                                          // ref could not be found
                                          return null;
                                      }
                                  } else {
                                      //listener.getLogger().println("Entering default git retrieve code path");
                                      for (Branch b : client.getRemoteBranches()) {
                                          String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
                                          if (branchName.equals(head.getName())) {
                                              return new SCMRevisionImpl(head, b.getSHA1String());
                                          }
                                      }
                                  }
                                  return null;
                              }
                          },<commit_msg>[JENKINS-48061] Make the comment an actual comment<commit_after>/*
 * The MIT License
 *
 * Copyright (c) 2013-2017, CloudBees, Inc., <NAME>, Amadeus IT Group.
 *
 * 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.CredentialsNameProvider;
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 edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.EnvVars;
import hudson.Extension;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.Item;
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.GitTool;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.browser.GitRepositoryBrowser;
import hudson.plugins.git.extensions.GitSCMExtension;
import hudson.plugins.git.util.Build;
import hudson.plugins.git.util.BuildChooser;
import hudson.plugins.git.util.BuildChooserContext;
import hudson.plugins.git.util.BuildChooserDescriptor;
import hudson.plugins.git.util.BuildData;
import hudson.scm.SCM;
import hudson.security.ACL;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import jenkins.model.Jenkins;
import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait;
import jenkins.plugins.git.traits.GitSCMExtensionTrait;
import jenkins.plugins.git.traits.GitToolSCMSourceTrait;
import jenkins.plugins.git.traits.RefSpecsSCMSourceTrait;
import jenkins.plugins.git.traits.RemoteNameSCMSourceTrait;
import jenkins.scm.api.SCMFile;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadCategory;
import jenkins.scm.api.SCMHeadEvent;
import jenkins.scm.api.SCMHeadObserver;
import jenkins.scm.api.SCMProbe;
import jenkins.scm.api.SCMProbeStat;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceCriteria;
import jenkins.scm.api.SCMSourceEvent;
import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.metadata.PrimaryInstanceMetadataAction;
import jenkins.scm.api.trait.SCMSourceRequest;
import jenkins.scm.api.trait.SCMSourceTrait;
import jenkins.scm.api.trait.SCMTrait;
import jenkins.scm.impl.TagSCMHeadCategory;
import jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait;
import jenkins.scm.impl.trait.WildcardSCMSourceFilterTrait;
import net.jcip.annotations.GuardedBy;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.jenkinsci.plugins.gitclient.FetchCommand;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
 * Base class for {@link SCMSource} implementations that produce {@link GitSCM} implementations.
 *
 * @since 2.0
 */
public abstract class AbstractGitSCMSource extends SCMSource {

    /**
     * The default remote name to use when configuring the ref specs to use with fetch operations.
     *
     * @since 3.4.0
     */
    public static final String DEFAULT_REMOTE_NAME = "origin";
    /**
     * The placeholder to use in ref spec templates in order to correctly ensure that the ref spec remote name
     * matches the remote name.
     * <p>
     * The template uses {@code @{...}} as that is an illegal sequence in a remote name
     *
     * @see <a href="https://github.com/git/git/blob/027a3b943b444a3e3a76f9a89803fc10245b858f/refs.c#L61-L68">git
     * source code rules on ref spec names</a>
     * @since 3.4.0
     */
    public static final String REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR = "@{remote}";
    /**
     * The regex for {@link #REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR}.
     *
     * @since 3.4.0
     */
    public static final String REF_SPEC_REMOTE_NAME_PLACEHOLDER = "(?i)"+Pattern.quote(REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR);
    /**
     * The default ref spec template.
     *
     * @since 3.4.0
     */
    public static final String REF_SPEC_DEFAULT =
            "+refs/heads/*:refs/remotes/" + REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR + "/*";

    /**
     * Keep one lock per cache directory. Lazy populated, but never purge, except on restart.
     */
    private static final ConcurrentMap<String, Lock> cacheLocks = new ConcurrentHashMap<>();

    private static final Logger LOGGER = Logger.getLogger(AbstractGitSCMSource.class.getName());

    public AbstractGitSCMSource() {
    }
    
    @Deprecated
    public AbstractGitSCMSource(String id) {
        setId(id);
    }

    @CheckForNull
    public abstract String getCredentialsId();

    /**
     * @return Git remote URL
     */
    public abstract String getRemote();

    /**
     * @deprecated use {@link WildcardSCMSourceFilterTrait}
     * @return the includes.
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getIncludes() {
        WildcardSCMHeadFilterTrait trait = SCMTrait.find(getTraits(), WildcardSCMHeadFilterTrait.class);
        return trait != null ? trait.getIncludes() : "*";
    }

    /**
     * @return the excludes.
     * @deprecated use {@link WildcardSCMSourceFilterTrait}
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getExcludes() {
        WildcardSCMHeadFilterTrait trait = SCMTrait.find(getTraits(), WildcardSCMHeadFilterTrait.class);
        return trait != null ? trait.getExcludes() : "";
    }

    /**
     * Gets {@link GitRepositoryBrowser} to be used with this SCMSource.
     * @return Repository browser or {@code null} if the default tool should be used.
     * @since 2.5.1
     * @deprecated use {@link GitBrowserSCMSourceTrait}
     */
    @CheckForNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public GitRepositoryBrowser getBrowser() {
        GitBrowserSCMSourceTrait trait = SCMTrait.find(getTraits(), GitBrowserSCMSourceTrait.class);
        return trait != null ? trait.getBrowser() : null;
    }

    /**
     * Gets Git tool to be used for this SCM Source.
     * @return Git Tool or {@code null} if the default tool should be used.
     * @since 2.5.1
     * @deprecated use {@link GitToolSCMSourceTrait}
     */
    @CheckForNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getGitTool() {
        GitToolSCMSourceTrait trait = SCMTrait.find(getTraits(), GitToolSCMSourceTrait.class);
        return trait != null ? trait.getGitTool() : null;
    }

    /**
     * Gets list of extensions, which should be used with this branch source.
     * @return List of Extensions to be used. May be empty
     * @since 2.5.1
     * @deprecated use corresponding {@link GitSCMExtensionTrait} (and if there isn't one then likely the
     * {@link GitSCMExtension} is not appropriate to use in the context of a {@link SCMSource})
     */
    @NonNull
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public List<GitSCMExtension> getExtensions() {
        List<GitSCMExtension> extensions = new ArrayList<>();
        for (SCMSourceTrait t : getTraits()) {
            if (t instanceof GitSCMExtensionTrait) {
                extensions.add(((GitSCMExtensionTrait) t).getExtension());
            }
        }
        return Collections.unmodifiableList(extensions);
    }

    /**
     * Returns the {@link SCMSourceTrait} instances for this {@link AbstractGitSCMSource}.
     * @return the {@link SCMSourceTrait} instances
     * @since 3.4.0
     */
    @NonNull
    public List<SCMSourceTrait> getTraits() {
        // Always return empty list (we expect subclasses to override)
        return Collections.emptyList();
    }

    /**
     * @deprecated use {@link RemoteNameSCMSourceTrait}
     * @return the remote name.
     */
    @Deprecated
    @Restricted(NoExternalUse.class)
    @RestrictedSince("3.4.0")
    public String getRemoteName() {
        RemoteNameSCMSourceTrait trait = SCMTrait.find(getTraits(), RemoteNameSCMSourceTrait.class);
        return trait != null ? trait.getRemoteName() : DEFAULT_REMOTE_NAME;
    }

    /**
     * Resolves the {@link GitTool}.
     * @return the {@link GitTool}.
     * @deprecated use {@link #resolveGitTool(String)}.
     */
    @CheckForNull
    @Deprecated
    @Restricted(DoNotUse.class)
    @RestrictedSince("3.4.0")
    protected GitTool resolveGitTool() {
        return resolveGitTool(getGitTool());
    }

    /**
     * Resolves the {@link GitTool}.
     * @param gitTool the {@link GitTool#getName()} to resolve.
     * @return the {@link GitTool}
     * @since 3.4.0
     */
    @CheckForNull
    protected GitTool resolveGitTool(String gitTool) {
        return StringUtils.isBlank(gitTool)
                ? GitTool.getDefaultInstallation()
                : Jenkins.getActiveInstance()
                        .getDescriptorByType(GitTool.DescriptorImpl.class)
                        .getInstallation(gitTool);
    }

    private interface Retriever<T> {
        T run(GitClient client, String remoteName) throws IOException, InterruptedException;
    }

    @NonNull
    private <T, C extends GitSCMSourceContext<C, R>, R extends GitSCMSourceRequest> T doRetrieve(Retriever<T> retriever,
                                                                                                 @NonNull C context,
                                                                                                 @NonNull TaskListener listener,
                                                                                                 boolean prune)
            throws IOException, InterruptedException {
        String cacheEntry = getCacheEntry();
        Lock cacheLock = getCacheLock(cacheEntry);
        cacheLock.lock();
        try {
            File cacheDir = getCacheDir(cacheEntry);
            Git git = Git.with(listener, new EnvVars(EnvVars.masterEnvVars)).in(cacheDir);
            GitTool tool = resolveGitTool(context.gitTool());
            if (tool != null) {
                git.using(tool.getGitExe());
            }
            GitClient client = git.getClient();
            client.addDefaultCredentials(getCredentials());
            if (!client.hasGitRepo()) {
                listener.getLogger().println("Creating git repository in " + cacheDir);
                client.init();
            }
            String remoteName = context.remoteName();
            listener.getLogger().println("Setting " + remoteName + " to " + getRemote());
            client.setRemoteUrl(remoteName, getRemote());
            listener.getLogger().println((prune ? "Fetching & pruning " : "Fetching ") + remoteName + "...");
            FetchCommand fetch = client.fetch_();
            if (prune) {
                fetch = fetch.prune();
            }
            URIish remoteURI = null;
            try {
                remoteURI = new URIish(remoteName);
            } catch (URISyntaxException ex) {
                listener.getLogger().println("URI syntax exception for '" + remoteName + "' " + ex);
            }
            fetch.from(remoteURI, context.asRefSpecs()).execute();
            return retriever.run(client, remoteName);
        } finally {
            cacheLock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    @CheckForNull
    @Override
    protected SCMRevision retrieve(@NonNull final SCMHead head, @NonNull final TaskListener listener)
            throws IOException, InterruptedException {
        GitSCMSourceContext context = new GitSCMSourceContext<>(null, SCMHeadObserver.none()).withTraits(getTraits());
        GitSCMTelescope telescope = GitSCMTelescope.of(this);
        if (telescope != null) {
            String remote = getRemote();
            StandardUsernameCredentials credentials = getCredentials();
            telescope.validate(remote, credentials);
            return telescope.getRevision(remote, credentials, head);
        }
        if (head instanceof GitSCMHeadMixin) {
            //Since it is a specific SCMHead we are after, that we know the refspec of
            //we can save a bit of time and bandwidth by just fetching that refspec
            context = context.withoutRefSpecs().withRefSpec(((GitSCMHeadMixin) head).getRef());
        }
        return doRetrieve(new Retriever<SCMRevision>() {
                              @Override
                              public SCMRevision run(GitClient client, String remoteName) throws IOException, InterruptedException {
                                  if (head instanceof GitTagSCMHead) {
                                      try {
                                          ObjectId objectId = client.revParse(Constants.R_TAGS + head.getName());
                                          return new GitTagSCMRevision((GitTagSCMHead) head, objectId.name());
                                      } catch (GitException e) {
                                          // tag does not exist
                                          return null;
                                      }
                                  } else if (head instanceof GitBranchSCMHead) {
                                      for (Branch b : client.getRemoteBranches()) {
                                          String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
                                          if (branchName.equals(head.getName())) {
                                              return new GitBranchSCMRevision((GitBranchSCMHead)head, b.getSHA1String());
                                          }
                                      }
                                  } else if (head instanceof GitRefSCMHead) {
                                      try {
                                          ObjectId objectId = client.revParse(((GitRefSCMHead) head).getRef());
                                          return new GitRefSCMRevision((GitRefSCMHead)head, objectId.name());
                                      } catch (GitException e) {
                                          // ref could not be found
                                          return null;
                                      }
                                  } else {
                                      //Entering default/legacy git retrieve code path
                                      for (Branch b : client.getRemoteBranches()) {
                                          String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
                                          if (branchName.equals(head.getName())) {
                                              return new SCMRevisionImpl(head, b.getSHA1String());
                                          }
                                      }
                                  }
                                  return null;
                              }
                          },
{'fraction_non_alphanumeric': 0.09430093133206117, 'fraction_numerical': 0.005382928426988693, 'mean_word_length': 2.336057007125891, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 35, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 124, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '415198', 'n_tokens_mistral': 9941, 'n_tokens_neox': 9560, 'n_words': 2294}