Skip to content

Commit

Permalink
[JENKINS-43507] Allow adding additional remotes to the GitSCMBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc committed Jun 12, 2017
1 parent ee664b5 commit 6396748
Show file tree
Hide file tree
Showing 14 changed files with 866 additions and 96 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -30,7 +30,7 @@
<concurrency>1C</concurrency>
<findbugs.failOnError>false</findbugs.failOnError>
<workflow.version>1.14.2</workflow.version>
<scm-api-plugin.version>2.2.0-20170504.132618-8</scm-api-plugin.version>
<scm-api-plugin.version>2.2.0-20170612.200107-11</scm-api-plugin.version>
</properties>

<build>
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/hudson/plugins/git/GitSCM.java
Expand Up @@ -1139,7 +1139,8 @@ public void checkout(Run<?, ?> build, Launcher launcher, FilePath workspace, Tas

LocalBranch lb = getExtensions().get(LocalBranch.class);
if (lb != null) {
if (lb.getLocalBranch() == null || lb.getLocalBranch().equals("**")) {
String lbn = lb.getLocalBranch();
if (lbn == null || lbn.equals("**")) {
// local branch is configured with empty value or "**" so use remote branch name for checkout
localBranchName = deriveLocalBranchName(remoteBranchName);
}
Expand Down
Expand Up @@ -214,7 +214,7 @@ public String getExcludes() {
@RestrictedSince("3.4.0")
public GitRepositoryBrowser getBrowser() {
for (SCMSourceTrait trait : getTraits()) {
if (trait instanceof GitToolSCMSourceTrait) {
if (trait instanceof GitBrowserSCMSourceTrait) {
return ((GitBrowserSCMSourceTrait) trait).getBrowser();
}
}
Expand Down
196 changes: 181 additions & 15 deletions src/main/java/jenkins/plugins/git/GitSCMBuilder.java
Expand Up @@ -39,9 +39,14 @@
import hudson.plugins.git.extensions.impl.BuildChooserSetting;
import hudson.scm.SCM;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.trait.SCMBuilder;
Expand All @@ -52,7 +57,7 @@
* The {@link SCMBuilder} base class for {@link AbstractGitSCMSource}.
*
* @param <B> the concrete type of {@link GitSCMBuilder} so that subclasses can chain correctly in their
* {@link #withHead(SCMHead)} etc methods.
* {@link #withHead(SCMHead)} etc methods.
* @since 3.4.0
*/
public class GitSCMBuilder<B extends GitSCMBuilder<B>> extends SCMBuilder<B, GitSCM> {
Expand Down Expand Up @@ -93,6 +98,11 @@ public class GitSCMBuilder<B extends GitSCMBuilder<B>> extends SCMBuilder<B, Git
*/
@NonNull
private String remote;
/**
* Any additional remotes keyed by their remote name.
*/
@NonNull
private final Map<String, AdditionalRemote> additionalRemotes = new TreeMap<>();

/**
* Constructor.
Expand Down Expand Up @@ -189,6 +199,42 @@ public final String remoteName() {
return remoteName;
}

/**
* Gets the (possibly empty) additional remote names.
*
* @return the (possibly empty) additional remote names.
*/
@NonNull
public final Set<String> additionalRemoteNames() {
return Collections.unmodifiableSet(additionalRemotes.keySet());
}

/**
* Gets the remote URL of the git repository for the specified remote name.
*
* @param remoteName the additional remote name.
* @return the remote URL of the named additional remote or {@code null} if the supplied name is not in
* {@link #additionalRemoteNames()}
*/
@CheckForNull
public final String additionalRemote(String remoteName) {
AdditionalRemote additionalRemote = additionalRemotes.get(remoteName);
return additionalRemote == null ? null : additionalRemote.remote();
}

/**
* Gets the ref specs to use for the git repository of the specified remote name.
*
* @param remoteName the additional remote name.
* @return the ref specs for the named additional remote or {@code null} if the supplied name is not in
* {@link #additionalRemoteNames()}
*/
@CheckForNull
public final List<String> additionalRemoteRefSpecs(String remoteName) {
AdditionalRemote additionalRemote = additionalRemotes.get(remoteName);
return additionalRemote == null ? null : additionalRemote.refSpecs();
}

/**
* Configures the {@link GitRepositoryBrowser} to use.
*
Expand Down Expand Up @@ -245,13 +291,9 @@ public final B withExtension(@CheckForNull GitSCMExtension extension) {
* @param extensions the {@link GitSCMExtension}s.
* @return {@code this} for method chaining.
*/
@SuppressWarnings("unchecked")
@NonNull
public final B withExtensions(GitSCMExtension... extensions) {
for (GitSCMExtension extension : extensions) {
withExtension(extension);
}
return (B) this;
return withExtensions(Arrays.asList(extensions));
}

/**
Expand Down Expand Up @@ -335,7 +377,7 @@ public final B withoutRefSpecs() {
*/
@SuppressWarnings("unchecked")
@NonNull
public final B withRemote(String remote) {
public final B withRemote(@NonNull String remote) {
this.remote = remote;
return (B) this;
}
Expand All @@ -354,18 +396,53 @@ public final B withRemoteName(@CheckForNull String remoteName) {
return (B) this;
}

/**
* Configures an additional remote. It is the responsibility of the caller to ensure that there are no conflicts
* with the eventual {@link #remote()} name.
*
* @param remoteName the name of the additional remote.
* @param remote the url of the additional remote.
* @param refSpecs the ref specs of the additional remote, if empty will default to
* {@link AbstractGitSCMSource#REF_SPEC_DEFAULT}
* @return {@code this} for method chaining.
*/
@NonNull
public final B withAdditionalRemote(@NonNull String remoteName, @NonNull String remote, String... refSpecs) {
return withAdditionalRemote(remoteName, remote, Arrays.asList(refSpecs));
}

/**
* Configures an additional remote. It is the responsibility of the caller to ensure that there are no conflicts
* with the eventual {@link #remote()} name.
*
* @param remoteName the name of the additional remote.
* @param remote the url of the additional remote.
* @param refSpecs the ref specs of the additional remote, if empty will default to
* {@link AbstractGitSCMSource#REF_SPEC_DEFAULT}
* @return {@code this} for method chaining.
*/
@SuppressWarnings("unchecked")
@NonNull
public final B withAdditionalRemote(@NonNull String remoteName, @NonNull String remote, List<String> refSpecs) {
this.additionalRemotes.put(remoteName, new AdditionalRemote(remoteName, remote, refSpecs));
return (B) 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));
// de-duplicate effective ref-specs after substitution of placeholder
Set<String> refSpecs = new LinkedHashSet<>(Math.max(this.refSpecs.size(), 1));
for (String template : refSpecs()) {
result.add(new RefSpec(
template.replaceAll(AbstractGitSCMSource.REF_SPEC_REMOTE_NAME_PLACEHOLDER, remoteName())
));
refSpecs.add(template.replaceAll(AbstractGitSCMSource.REF_SPEC_REMOTE_NAME_PLACEHOLDER, remoteName()));
}
List<RefSpec> result = new ArrayList<>(refSpecs.size());
for (String refSpec : refSpecs) {
result.add(new RefSpec(refSpec));
}
return result;
}
Expand All @@ -378,13 +455,17 @@ public final List<RefSpec> asRefSpecs() {
@NonNull
public final List<UserRemoteConfig> asRemoteConfigs() {
List<RefSpec> refSpecs = asRefSpecs();
List<UserRemoteConfig> result = new ArrayList<>(refSpecs.size());
List<UserRemoteConfig> result = new ArrayList<>(refSpecs.size() + additionalRemotes.size());
String remote = remote();
for (RefSpec refSpec : refSpecs) {
result.add(new UserRemoteConfig(remote, remoteName(), refSpec.toString(), credentialsId()));
}
for (AdditionalRemote r : additionalRemotes.values()) {
for (RefSpec refSpec : r.asRefSpecs()) {
result.add(new UserRemoteConfig(r.remote(), r.remoteName(), refSpec.toString(), credentialsId()));
}
}
return result;

}

/**
Expand All @@ -394,15 +475,16 @@ public final List<UserRemoteConfig> asRemoteConfigs() {
@Override
public GitSCM build() {
List<GitSCMExtension> extensions = new ArrayList<>(extensions());
if (revision() instanceof AbstractGitSCMSource.SCMRevisionImpl) {
SCMRevision revision = revision();
if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) {
// remove any conflicting BuildChooserSetting if present
for (Iterator<GitSCMExtension> iterator = extensions.iterator(); iterator.hasNext(); ) {
if (iterator.next() instanceof BuildChooserSetting) {
iterator.remove();
}
}
extensions.add(new BuildChooserSetting(new AbstractGitSCMSource.SpecificRevisionBuildChooser(
(AbstractGitSCMSource.SCMRevisionImpl) revision())));
(AbstractGitSCMSource.SCMRevisionImpl) revision)));
}
return new GitSCM(
asRemoteConfigs(),
Expand All @@ -411,4 +493,88 @@ public GitSCM build() {
browser(), gitTool(),
extensions);
}

/**
* Internal value class to manage additional remote configuration.
*/
private static final class AdditionalRemote {
/**
* The name of the remote.
*/
@NonNull
private final String name;
/**
* The url of the remote.
*/
@NonNull
private final String url;
/**
* The ref spec templates of the remote.
*/
@NonNull
private final List<String> refSpecs;

/**
* Constructor.
*
* @param name the name of the remote.
* @param url the url of the remote.
* @param refSpecs the ref specs of the remote.
*/
public AdditionalRemote(@NonNull String name, @NonNull String url, @NonNull List<String> refSpecs) {
this.name = name;
this.url = url;
this.refSpecs = new ArrayList<>(
refSpecs.isEmpty()
? Collections.singletonList(AbstractGitSCMSource.REF_SPEC_DEFAULT)
: refSpecs
);
}

/**
* Gets the name of the remote.
*
* @return the name of the remote.
*/
public String remoteName() {
return name;
}

/**
* Gets the url of the remote.
*
* @return the url of the remote.
*/
public String remote() {
return url;
}

/**
* Gets the ref specs of the remote.
*
* @return the ref specs of the remote.
*/
public List<String> refSpecs() {
return Collections.unmodifiableList(refSpecs);
}

/**
* Converts the ref spec templates into {@link RefSpec} instances.
*
* @return the list of {@link RefSpec} instances.
*/
@NonNull
public final List<RefSpec> asRefSpecs() {
// de-duplicate effective ref-specs after substitution of placeholder
Set<String> refSpecs = new LinkedHashSet<>(Math.max(this.refSpecs.size(), 1));
for (String template : refSpecs()) {
refSpecs.add(template.replaceAll(AbstractGitSCMSource.REF_SPEC_REMOTE_NAME_PLACEHOLDER, remoteName()));
}
List<RefSpec> result = new ArrayList<>(refSpecs.size());
for (String refSpec : refSpecs) {
result.add(new RefSpec(refSpec));
}
return result;
}
}
}
32 changes: 25 additions & 7 deletions src/main/java/jenkins/plugins/git/GitSCMSource.java
Expand Up @@ -82,6 +82,9 @@
import jenkins.scm.api.trait.SCMHeadPrefilter;
import jenkins.scm.api.trait.SCMSourceTrait;
import jenkins.scm.api.trait.SCMSourceTraitDescriptor;
import jenkins.scm.impl.form.NamedArrayList;
import jenkins.scm.impl.trait.Discovery;
import jenkins.scm.impl.trait.Selection;
import jenkins.scm.impl.trait.WildcardSCMHeadFilterTrait;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
Expand All @@ -101,7 +104,7 @@
import org.kohsuke.stapler.StaplerResponse;

/**
* @author Stephen Connolly
* A {@link SCMSource} that discovers branches in a git repository.
*/
public class GitSCMSource extends AbstractGitSCMSource {
private static final String DEFAULT_INCLUDES = "*";
Expand All @@ -112,7 +115,8 @@ public class GitSCMSource extends AbstractGitSCMSource {

private final String remote;

private final String credentialsId;
@CheckForNull
private transient String credentialsId;

@Deprecated
private transient String remoteName;
Expand Down Expand Up @@ -146,10 +150,14 @@ public class GitSCMSource extends AbstractGitSCMSource {
private List<SCMSourceTrait> traits = new ArrayList<>();

@DataBoundConstructor
public GitSCMSource(String id, String remote, String credentialsId) {
public GitSCMSource(String id, String remote) {
super(id);
this.remote = remote;
this.credentialsId = credentialsId;
}

@DataBoundSetter
public void setCredentialsId(@CheckForNull String credentialsId) {
this.credentialsId = credentialsId;
}

@DataBoundSetter
Expand Down Expand Up @@ -497,11 +505,21 @@ public ListBoxModel doFillGitToolItems() {
return getSCMDescriptor().doFillGitToolItems();
}

public List<SCMSourceTraitDescriptor> getTraitDescriptors() {
return SCMSourceTrait._for(this, GitSCMSourceContext.class, GitSCMBuilder.class);
public List<NamedArrayList<? extends SCMSourceTraitDescriptor>> getTraitsDescriptorLists() {
List<NamedArrayList<? extends SCMSourceTraitDescriptor>> result = new ArrayList<>();
List<SCMSourceTraitDescriptor> descriptors =
SCMSourceTrait._for(this, GitSCMSourceContext.class, GitSCMBuilder.class);
NamedArrayList.select(descriptors, "Within Repository",
NamedArrayList.anyOf(
NamedArrayList.withAnnotation(Selection.class),
NamedArrayList.withAnnotation(Discovery.class)
),
true, result);
NamedArrayList.select(descriptors, "Additional", null, true, result);
return result;
}

public List<SCMSourceTrait> getTraitDefaults() {
public List<SCMSourceTrait> getTraitsDefaults() {
return Collections.emptyList();
}
}
Expand Down

0 comments on commit 6396748

Please sign in to comment.