Java tutorial
/* * The MIT License * * Copyright (c) 2017 CloudBees, 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 jenkins.plugins.git; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.model.TaskListener; import hudson.plugins.git.GitSCM; import hudson.plugins.git.GitTool; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceCriteria; import jenkins.scm.api.trait.SCMSourceContext; import jenkins.scm.api.trait.SCMSourceTrait; import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.RefSpec; /** * The {@link SCMSourceContext} for a {@link AbstractGitSCMSource}. * * @param <C> the type of {@link GitSCMSourceContext} so that the {@link #withTrait(SCMSourceTrait)} etc methods can * be chained easily by subclasses. * @param <R> the type of {@link GitSCMSourceRequest} produced by {@link #newRequest(SCMSource, TaskListener)}. * @since 3.4.0 */ public class GitSCMSourceContext<C extends GitSCMSourceContext<C, R>, R extends GitSCMSourceRequest> extends SCMSourceContext<C, R> { /** * {@code true} if the {@link GitSCMSourceRequest} will need information about branches. */ private boolean wantBranches; /** * {@code true} if the {@link GitSCMSourceRequest} will need information about tags. */ private boolean wantTags; /** * {@code true} if the {@link GitSCMSourceRequest} needs to be prune aware. */ private boolean pruneRefs; /** * A list of other references to discover and search */ private Set<RefNameMapping> refNameMappings; /** * The name of the {@link GitTool} to use or {@code null} to use the default. */ @CheckForNull private String gitTool; /** * Should push notifications be ignored. */ private boolean ignoreOnPushNotifications; /** * The ref specs to apply to the {@link GitSCM}. */ @NonNull private List<String> refSpecs = new ArrayList<>(); /** * The remote name. */ @NonNull private String remoteName = AbstractGitSCMSource.DEFAULT_REMOTE_NAME; /** * Constructor. * * @param criteria (optional) criteria. * @param observer the {@link SCMHeadObserver}. */ public GitSCMSourceContext(@CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer) { super(criteria, observer); } /** * Returns {@code true} if the {@link GitSCMSourceRequest} will need information about branches. * * @return {@code true} if the {@link GitSCMSourceRequest} will need information about branches. */ public final boolean wantBranches() { return wantBranches; } /** * Returns {@code true} if the {@link GitSCMSourceRequest} will need information about tags. * * @return {@code true} if the {@link GitSCMSourceRequest} will need information about tags. */ public final boolean wantTags() { return wantTags; } /** * Returns {@code true} if the {@link GitSCMSourceRequest} needs to be prune aware. * * @return {@code true} if the {@link GitSCMSourceRequest} needs to be prune aware. */ public final boolean pruneRefs() { return pruneRefs; } /** * Returns {@code true} if the {@link GitSCMSourceRequest} will need information about other refs. * * @return {@code true} if the {@link GitSCMSourceRequest} will need information about other refs. */ public final boolean wantOtherRefs() { return refNameMappings != null && !refNameMappings.isEmpty(); } @NonNull public Collection<RefNameMapping> getRefNameMappings() { if (refNameMappings == null) { return Collections.emptySet(); } else { return Collections.unmodifiableSet(refNameMappings); } } /** * Returns the name of the {@link GitTool} to use or {@code null} to use the default. * * @return the name of the {@link GitTool} to use or {@code null} to use the default. */ @CheckForNull public final String gitTool() { return gitTool; } /** * Returns {@code true} if push notifications should be ignored. * * @return {@code true} if push notifications should be ignored. */ public final boolean ignoreOnPushNotifications() { return ignoreOnPushNotifications; } /** * Returns the list of ref specs to use. * * @return the list of ref specs to use. */ @NonNull public final List<String> refSpecs() { if (refSpecs.isEmpty()) { return Collections.singletonList(AbstractGitSCMSource.REF_SPEC_DEFAULT); } return Collections.unmodifiableList(refSpecs); } /** * Returns the name to give the remote. * * @return the name to give the remote. */ @NonNull public final String remoteName() { return remoteName; } /** * Adds a requirement for branch details to any {@link GitSCMSourceRequest} for this context. * * @param include {@code true} to add the requirement or {@code false} to leave the requirement as is (makes * simpler with method chaining) * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public C wantBranches(boolean include) { wantBranches = wantBranches || include; return (C) this; } /** * Adds a requirement for tag details to any {@link GitSCMSourceRequest} for this context. * * @param include {@code true} to add the requirement or {@code false} to leave the requirement as is (makes * simpler with method chaining) * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public C wantTags(boolean include) { wantTags = wantTags || include; return (C) this; } /** * Adds a requirement for git ref pruning to any {@link GitSCMSourceRequest} for this context. * * @param include {@code true} to add the requirement or {@code false} to leave the requirement as is (makes * simpler with method chaining) * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public C pruneRefs(boolean include) { pruneRefs = pruneRefs || include; return (C) this; } /** * Adds a requirement for details of additional refs to any {@link GitSCMSourceRequest} for this context. * * @param other The specification for that other ref * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public C wantOtherRef(RefNameMapping other) { if (refNameMappings == null) { refNameMappings = new TreeSet<>(); } refNameMappings.add(other); return (C) this; } /** * Configures the {@link GitTool#getName()} to use. * * @param gitTool the {@link GitTool#getName()} or {@code null} to use the system default. * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public final C withGitTool(String gitTool) { this.gitTool = gitTool; return (C) this; } /** * Configures whether push notifications should be ignored. * * @param ignoreOnPushNotifications {@code true} to ignore push notifications. * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public final C withIgnoreOnPushNotifications(boolean ignoreOnPushNotifications) { this.ignoreOnPushNotifications = ignoreOnPushNotifications; return (C) this; } /** * Adds the specified ref spec. If no ref specs were previously defined then the supplied ref spec will replace * {@link AbstractGitSCMSource#REF_SPEC_DEFAULT}. The ref spec is expected to be processed for substitution of * {@link AbstractGitSCMSource#REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR} by {@link AbstractGitSCMSource#getRemote()} * before use. * * @param refSpec the ref spec template to add. * @return {@code this} for method chaining. * @see #withoutRefSpecs() */ @SuppressWarnings("unchecked") @NonNull public final C withRefSpec(@NonNull String refSpec) { this.refSpecs.add(refSpec); return (C) this; } /** * Adds the specified ref specs. If no ref specs were previously defined then the supplied ref specs will replace * {@link AbstractGitSCMSource#REF_SPEC_DEFAULT}. The ref spec is expected to be processed for substitution of * {@link AbstractGitSCMSource#REF_SPEC_REMOTE_NAME_PLACEHOLDER_STR} by {@link AbstractGitSCMSource#getRemote()} * before use. * * @param refSpecs the ref spec templates to add. * @return {@code this} for method chaining. * @see #withoutRefSpecs() */ @SuppressWarnings("unchecked") @NonNull public final C withRefSpecs(List<String> refSpecs) { this.refSpecs.addAll(refSpecs); return (C) this; } /** * Clears the specified ref specs. If no ref specs are subsequently defined then * {@link AbstractGitSCMSource#REF_SPEC_DEFAULT} will be used as the ref spec template. * * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public final C withoutRefSpecs() { this.refSpecs.clear(); return (C) this; } /** * Configures the remote name to use for the git repository. * * @param remoteName the remote name to use for the git repository ({@code null} or the empty string are * equivalent to passing {@link AbstractGitSCMSource#DEFAULT_REMOTE_NAME}). * @return {@code this} for method chaining. */ @SuppressWarnings("unchecked") @NonNull public final C withRemoteName(String remoteName) { this.remoteName = StringUtils.defaultIfBlank(remoteName, AbstractGitSCMSource.DEFAULT_REMOTE_NAME); return (C) this; } /** * Converts the ref spec templates into {@link RefSpec} instances. * * @return the list of {@link RefSpec} instances. */ @NonNull public final List<RefSpec> asRefSpecs() { List<RefSpec> result = new ArrayList<>(Math.max(refSpecs.size(), 1)); if (wantOtherRefs() && wantBranches()) { //If wantOtherRefs() there will be a refspec in the list not added manually by a user //So if also wantBranches() we need to add the default respec for branches so we actually fetch them result.add(new RefSpec("+" + Constants.R_HEADS + "*:" + Constants.R_REMOTES + remoteName() + "/*")); } for (String template : refSpecs()) { result.add(new RefSpec( template.replaceAll(AbstractGitSCMSource.REF_SPEC_REMOTE_NAME_PLACEHOLDER, remoteName()))); } return result; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @NonNull @Override public R newRequest(@NonNull SCMSource source, TaskListener listener) { return (R) new GitSCMSourceRequest(source, this, listener); } public static final class RefNameMapping implements Comparable<RefNameMapping> { private final String ref; private final String name; private transient Pattern refPattern; public RefNameMapping(@NonNull String ref, @NonNull String name) { this.ref = ref; this.name = name; } @NonNull public String getRef() { return ref; } @NonNull public String getName() { return name; } Pattern refAsPattern() { if (refPattern == null) { refPattern = Pattern.compile(Constants.R_REFS + ref.replace("*", "(.+)")); } return refPattern; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } RefNameMapping that = (RefNameMapping) o; return Objects.equals(ref, that.ref) && Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(ref, name); } @Override public int compareTo(RefNameMapping o) { return Integer.compare(this.hashCode(), o != null ? o.hashCode() : 0); } public boolean matches(String revision, String remoteName, String remoteRev) { final Matcher matcher = refAsPattern().matcher(remoteName); if (matcher.matches()) { //TODO support multiple capture groups? if (matcher.groupCount() > 0) { //Group 0 apparently not in this count according to javadoc String resolvedName = name.replace("@{1}", matcher.group(1)); return resolvedName.equals(revision); } else { return name.equals(revision); } } return false; } public boolean matches(String remoteName) { final Matcher matcher = refAsPattern().matcher(remoteName); return matcher.matches(); } public String getName(String remoteName) { final Matcher matcher = refAsPattern().matcher(remoteName); if (matcher.matches()) { if (matcher.groupCount() > 0) { //Group 0 apparently not in this count according to javadoc return name.replace("@{1}", matcher.group(1)); } else if (!name.contains("@{1}")) { return name; } } return null; } } }