From 50189847e9f8f9581f3ab05251830b211a8e8f1b Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Wed, 29 Jul 2020 03:48:23 +0530 Subject: [PATCH 01/16] RepositoryAPI This class intends to implement an extension point provided by the Git Plugin which should return the size of a repository if the provided URL is applicable to the Github Branch Source Plugin. --- .../github_branch_source/GitHubSCMSource.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index ecdb2feb0..d3798df4d 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -80,6 +80,7 @@ import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import jenkins.plugins.git.AbstractGitSCMSource; +import jenkins.plugins.git.GitRepoSizeEstimator; import jenkins.plugins.git.GitTagSCMRevision; import jenkins.plugins.git.MergeWithGitSCMExtension; import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait; @@ -1916,6 +1917,27 @@ public void afterSave() { } } + /** + * This extension intends to perform a GET request without any credentials on the provided repository URL + * to return the size of repository. + */ + @Extension + public static class RepositoryAPI extends GitToolChooser.RepositorySizeAPI { + + @Override + public boolean isApplicableTo(String repoUrl) { + return repoUrl.contains("github"); + } + + @Override + public Long getSizeOfRepository(String repoUrl) throws Exception { + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); + GitHub github = Connector.connect(info.getApiUri(), null); + GHRepository ghRepository = github.getRepository(info.getRepoOwner() + '/' + info.getRepository()); + return (long) ghRepository.getSize(); + } + } + @Symbol("github") @Extension public static class DescriptorImpl extends SCMSourceDescriptor implements CustomDescribableModel { From b77c89ec988dc577987c472efbe52069b54fedce Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Wed, 29 Jul 2020 03:53:21 +0530 Subject: [PATCH 02/16] Change import statement from GitRepoSizeEstimator to GitToolChooser --- .../jenkinsci/plugins/github_branch_source/GitHubSCMSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index d3798df4d..9a6ba0651 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -80,7 +80,7 @@ import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import jenkins.plugins.git.AbstractGitSCMSource; -import jenkins.plugins.git.GitRepoSizeEstimator; +import jenkins.plugins.git.GitToolChooser; import jenkins.plugins.git.GitTagSCMRevision; import jenkins.plugins.git.MergeWithGitSCMExtension; import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait; From 63e3e1da8a39a6628fc82b8a5746257456b95a55 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Wed, 29 Jul 2020 03:55:33 +0530 Subject: [PATCH 03/16] Rename extension from RepositoryAPI to RepositorySizeAPI --- .../jenkinsci/plugins/github_branch_source/GitHubSCMSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index 9a6ba0651..151e33b36 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1922,7 +1922,7 @@ public void afterSave() { * to return the size of repository. */ @Extension - public static class RepositoryAPI extends GitToolChooser.RepositorySizeAPI { + public static class RepositorySizeAPI extends GitToolChooser.RepositorySizeAPI { @Override public boolean isApplicableTo(String repoUrl) { From f41e6aad1091a8391f4569b9a6469f5203cfcb80 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Wed, 29 Jul 2020 03:56:25 +0530 Subject: [PATCH 04/16] Rename extension --- .../jenkinsci/plugins/github_branch_source/GitHubSCMSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index 151e33b36..e867f2dc4 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1922,7 +1922,7 @@ public void afterSave() { * to return the size of repository. */ @Extension - public static class RepositorySizeAPI extends GitToolChooser.RepositorySizeAPI { + public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySizeAPI { @Override public boolean isApplicableTo(String repoUrl) { From e3fe52b6b02b2dfc74939efe7ff84c7ddc53f43d Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Mon, 10 Aug 2020 19:02:23 +0530 Subject: [PATCH 05/16] Remove the extension class from GitSCMSource --- .../github_branch_source/GitHubSCMSource.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index e867f2dc4..bc549c33a 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1917,27 +1917,6 @@ public void afterSave() { } } - /** - * This extension intends to perform a GET request without any credentials on the provided repository URL - * to return the size of repository. - */ - @Extension - public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySizeAPI { - - @Override - public boolean isApplicableTo(String repoUrl) { - return repoUrl.contains("github"); - } - - @Override - public Long getSizeOfRepository(String repoUrl) throws Exception { - GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); - GitHub github = Connector.connect(info.getApiUri(), null); - GHRepository ghRepository = github.getRepository(info.getRepoOwner() + '/' + info.getRepository()); - return (long) ghRepository.getSize(); - } - } - @Symbol("github") @Extension public static class DescriptorImpl extends SCMSourceDescriptor implements CustomDescribableModel { From 4d067df5349c768dcf5bfd2efaa2447c813264bd Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Mon, 10 Aug 2020 19:02:37 +0530 Subject: [PATCH 06/16] This class contains an extension which provides the size of a remote repo on the basis of the repostitory url and the user credentials provided to it. --- .../GitHubRepoSizeEstimator.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java new file mode 100644 index 000000000..a36c4121d --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java @@ -0,0 +1,42 @@ +package org.jenkinsci.plugins.github_branch_source; + +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import hudson.Extension; +import hudson.model.Item; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import jenkins.plugins.git.GitToolChooser; + +import java.io.IOException; + +public class GitHubRepoSizeEstimator { + /** + * This extension intends to perform a GET request without any credentials on the provided repository URL + * to return the size of repository. + */ + @Extension + public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySizeAPI { + + @Override + public boolean isApplicableTo(String repoUrl, Item context, String credentialsId) { + StandardCredentials credentials = Connector.lookupScanCredentials(context, repoUrl, credentialsId); + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); + try { + GitHub gitHub = Connector.connect(info.getApiUri(), credentials); + gitHub.checkApiUrlValidity(); + } catch (IOException e) { + return false; + } + return true; + } + + @Override + public Long getSizeOfRepository(String repoUrl, Item context, String credentialsId) throws Exception { + StandardCredentials credentials = Connector.lookupScanCredentials(context, repoUrl, credentialsId); + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); + GitHub github = Connector.connect(info.getApiUri(), credentials); + GHRepository ghRepository = github.getRepository(info.getRepoOwner() + '/' + info.getRepository()); + return (long) ghRepository.getSize(); + } + } +} From b760cb8e839caa28cfdc23e9cfef02e9fd3ae497 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Mon, 10 Aug 2020 19:06:48 +0530 Subject: [PATCH 07/16] Add automated test cases for the estimator class --- .../GitHubRepoSizeEstimatorTest.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java new file mode 100644 index 000000000..1327de344 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java @@ -0,0 +1,108 @@ +package org.jenkinsci.plugins.github_branch_source; + +import com.cloudbees.plugins.credentials.*; +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.cloudbees.plugins.credentials.domains.Domain; +import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.TopLevelItem; +import hudson.plugins.git.BranchSpec; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.SubmoduleConfig; +import hudson.plugins.git.UserRemoteConfig; +import hudson.plugins.git.extensions.GitSCMExtension; +import hudson.plugins.git.extensions.impl.DisableRemotePoll; +import hudson.triggers.SCMTrigger; +import jenkins.model.Jenkins; +import org.jenkinsci.plugins.gitclient.JGitTool; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class GitHubRepoSizeEstimatorTest { + + @Rule + public final JenkinsRule j = new JenkinsRule(); + + private CredentialsStore store = null; + + @Before + public void enableSystemCredentialsProvider() { + SystemCredentialsProvider.getInstance().setDomainCredentialsMap( + Collections.singletonMap(Domain.global(), Collections.emptyList())); + for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.get())) { + if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) { + store = s; + break; + } + } + assertThat("The system credentials provider is enabled", store, notNullValue()); + } + + @Test + public void testExtensionApplicability() throws Exception { + GitHubRepoSizeEstimator.RepositorySizeGithubAPI repositorySizeGithubAPI = new GitHubRepoSizeEstimator.RepositorySizeGithubAPI(); + + String url = "https://github.com/jenkinsci/github-branch-source-plugin.git"; + + store.addCredentials(Domain.global(), createCredential(CredentialsScope.GLOBAL, "github")); + store.save(); + +// WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); +// p.setDefinition(new CpsFlowDefinition( +// "node {\n" +// + " checkout(\n" +// + " [$class: 'GitSCM', \n" +// + " userRemoteConfigs: [[credentialsId: 'github', url: $/" + url + "/$]]]\n" +// + " )" +// + "}", true)); +// WorkflowRun b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); +// j.waitForMessage("using credential github", b); + List repos = new ArrayList<>(); + repos.add(new UserRemoteConfig(url, "origin", null, "github")); + FreeStyleProject projectWithMaster = setupProject(repos, Collections.singletonList(new BranchSpec("master")), null, false); + projectWithMaster.scheduleBuild2(0); + + List items = j.jenkins.getItems(); + + assertThat(repositorySizeGithubAPI.isApplicableTo(url, items.get(0), "github"), is(true)); + } + + private StandardCredentials createCredential(CredentialsScope scope, String id) { + return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, "rishabhBudhouliya", "rishabh2020"); + } + + protected FreeStyleProject setupProject(List repos, List branchSpecs, + String scmTriggerSpec, boolean disableRemotePoll) throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + GitSCM scm = new GitSCM( + repos, + branchSpecs, + false, Collections.emptyList(), + null, JGitTool.MAGIC_EXENAME, + Collections.emptyList()); + if(disableRemotePoll) scm.getExtensions().add(new DisableRemotePoll()); + project.setScm(scm); + if(scmTriggerSpec != null) { + SCMTrigger trigger = new SCMTrigger(scmTriggerSpec); + project.addTrigger(trigger); + trigger.start(project, true); + } + //project.getBuildersList().add(new CaptureEnvironmentBuilder()); + project.save(); + return project; + } +} From 98deda585ad56c38ff283f2332b53214254e5e7d Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Tue, 11 Aug 2020 19:20:30 +0530 Subject: [PATCH 08/16] Remove unused GitToolChooser import --- .../jenkinsci/plugins/github_branch_source/GitHubSCMSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index bc549c33a..ecdb2feb0 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -80,7 +80,6 @@ import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import jenkins.plugins.git.AbstractGitSCMSource; -import jenkins.plugins.git.GitToolChooser; import jenkins.plugins.git.GitTagSCMRevision; import jenkins.plugins.git.MergeWithGitSCMExtension; import jenkins.plugins.git.traits.GitBrowserSCMSourceTrait; From 36954d133178e9030e1014a17e6dcc7edd05868d Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Tue, 11 Aug 2020 19:21:07 +0530 Subject: [PATCH 09/16] Add an automated test case which expects isApplicableTo to fail due to bad credentials --- .../GitHubRepoSizeEstimatorTest.java | 80 ++++++++----------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java index 1327de344..50f362596 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java @@ -6,6 +6,7 @@ import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; +import hudson.model.Item; import hudson.model.TopLevelItem; import hudson.plugins.git.BranchSpec; import hudson.plugins.git.GitSCM; @@ -15,6 +16,7 @@ import hudson.plugins.git.extensions.impl.DisableRemotePoll; import hudson.triggers.SCMTrigger; import jenkins.model.Jenkins; +import jenkins.plugins.git.GitToolChooser; import org.jenkinsci.plugins.gitclient.JGitTool; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.job.WorkflowJob; @@ -22,17 +24,38 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.jvnet.hudson.test.JenkinsRule; +import org.kohsuke.github.GitHub; +import org.mockito.Mock; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -public class GitHubRepoSizeEstimatorTest { +@RunWith(Parameterized.class) +public class GitHubRepoSizeEstimatorTest extends GitSCMSourceBase { + + public GitHubRepoSizeEstimatorTest(GitHubSCMSource source) { + this.source = source; + } + + @Parameterized.Parameters(name = "{index}: revision={0}") + public static GitHubSCMSource[] revisions() { + return new GitHubSCMSource[]{ + new GitHubSCMSource("cloudbeers", "yolo", null, false), + new GitHubSCMSource("", "", "https://github.com/cloudbeers/yolo", true) + }; + } @Rule public final JenkinsRule j = new JenkinsRule(); @@ -53,56 +76,23 @@ public void enableSystemCredentialsProvider() { } @Test - public void testExtensionApplicability() throws Exception { - GitHubRepoSizeEstimator.RepositorySizeGithubAPI repositorySizeGithubAPI = new GitHubRepoSizeEstimator.RepositorySizeGithubAPI(); - - String url = "https://github.com/jenkinsci/github-branch-source-plugin.git"; - + public void isApplicableToTest() throws Exception { + GitHubRepoSizeEstimator.RepositorySizeGithubAPI api = j.jenkins.getExtensionList(GitToolChooser.RepositorySizeAPI.class).get(GitHubRepoSizeEstimator.RepositorySizeGithubAPI.class); + Item context = Mockito.mock(Item.class); store.addCredentials(Domain.global(), createCredential(CredentialsScope.GLOBAL, "github")); store.save(); -// WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); -// p.setDefinition(new CpsFlowDefinition( -// "node {\n" -// + " checkout(\n" -// + " [$class: 'GitSCM', \n" -// + " userRemoteConfigs: [[credentialsId: 'github', url: $/" + url + "/$]]]\n" -// + " )" -// + "}", true)); -// WorkflowRun b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); -// j.waitForMessage("using credential github", b); - List repos = new ArrayList<>(); - repos.add(new UserRemoteConfig(url, "origin", null, "github")); - FreeStyleProject projectWithMaster = setupProject(repos, Collections.singletonList(new BranchSpec("master")), null, false); - projectWithMaster.scheduleBuild2(0); - - List items = j.jenkins.getItems(); + githubApi.stubFor( + get(urlEqualTo("/")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../__files/body-(root)-XwEI7.json"))); - assertThat(repositorySizeGithubAPI.isApplicableTo(url, items.get(0), "github"), is(true)); + assertThat(api.isApplicableTo("https://github.com/rishabhBudhouliya/zoom-suspender.git", context, "github"), is(false)); } private StandardCredentials createCredential(CredentialsScope scope, String id) { - return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, "rishabhBudhouliya", "rishabh2020"); - } - - protected FreeStyleProject setupProject(List repos, List branchSpecs, - String scmTriggerSpec, boolean disableRemotePoll) throws Exception { - FreeStyleProject project = j.createFreeStyleProject(); - GitSCM scm = new GitSCM( - repos, - branchSpecs, - false, Collections.emptyList(), - null, JGitTool.MAGIC_EXENAME, - Collections.emptyList()); - if(disableRemotePoll) scm.getExtensions().add(new DisableRemotePoll()); - project.setScm(scm); - if(scmTriggerSpec != null) { - SCMTrigger trigger = new SCMTrigger(scmTriggerSpec); - project.addTrigger(trigger); - trigger.start(project, true); - } - //project.getBuildersList().add(new CaptureEnvironmentBuilder()); - project.save(); - return project; + return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, "username", "password"); } } From 37e2afda80cc819426c9cbafa7cb93895a4812c7 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Tue, 11 Aug 2020 19:22:34 +0530 Subject: [PATCH 10/16] remove unused imports from the test class --- .../GitHubRepoSizeEstimatorTest.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java index 50f362596..72609514c 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java @@ -4,41 +4,22 @@ import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; -import hudson.model.FreeStyleBuild; -import hudson.model.FreeStyleProject; import hudson.model.Item; -import hudson.model.TopLevelItem; -import hudson.plugins.git.BranchSpec; -import hudson.plugins.git.GitSCM; -import hudson.plugins.git.SubmoduleConfig; -import hudson.plugins.git.UserRemoteConfig; -import hudson.plugins.git.extensions.GitSCMExtension; -import hudson.plugins.git.extensions.impl.DisableRemotePoll; -import hudson.triggers.SCMTrigger; import jenkins.model.Jenkins; import jenkins.plugins.git.GitToolChooser; -import org.jenkinsci.plugins.gitclient.JGitTool; -import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; -import org.jenkinsci.plugins.workflow.job.WorkflowJob; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.jvnet.hudson.test.JenkinsRule; -import org.kohsuke.github.GitHub; -import org.mockito.Mock; import org.mockito.Mockito; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; From 0eaf780fe7d9d9f6da363ed696ac22ff2f2edf58 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Tue, 1 Sep 2020 15:57:48 -0700 Subject: [PATCH 11/16] Point to local wiremock This is the first step --- pom.xml | 5 +++++ .../github_branch_source/GitHubRepoSizeEstimatorTest.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8dd88c2f8..8a004d296 100644 --- a/pom.xml +++ b/pom.xml @@ -122,6 +122,11 @@ import pom + + org.jenkins-ci.plugins + git + 4.4.0 + diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java index 72609514c..2d7d2f6b6 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java @@ -70,7 +70,7 @@ public void isApplicableToTest() throws Exception { .withHeader("Content-Type", "application/json; charset=utf-8") .withBodyFile("../__files/body-(root)-XwEI7.json"))); - assertThat(api.isApplicableTo("https://github.com/rishabhBudhouliya/zoom-suspender.git", context, "github"), is(false)); + assertThat(api.isApplicableTo(githubApi.baseUrl(), context, "github"), is(false)); } private StandardCredentials createCredential(CredentialsScope scope, String id) { From a8ba6ccb6dd2db2c15b496be947332838ca12d4f Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Sat, 5 Sep 2020 16:19:18 +0530 Subject: [PATCH 12/16] Catch exceptions raised by GitHubRepositoryInfo incase of malformed URLs --- .../plugins/github_branch_source/GitHubRepoSizeEstimator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java index a36c4121d..f74c86c5f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java @@ -20,11 +20,11 @@ public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySiz @Override public boolean isApplicableTo(String repoUrl, Item context, String credentialsId) { StandardCredentials credentials = Connector.lookupScanCredentials(context, repoUrl, credentialsId); - GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); try { + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); GitHub gitHub = Connector.connect(info.getApiUri(), credentials); gitHub.checkApiUrlValidity(); - } catch (IOException e) { + } catch (Exception e) { return false; } return true; From 5ce9fcb83bb34f2f2ac2b3c618c2e88c8df46700 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Sat, 5 Sep 2020 17:40:44 +0530 Subject: [PATCH 13/16] Fix upper bound dependencies requirement --- pom.xml | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 8a004d296..39ae41777 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,35 @@ com.coravy.hudson.plugins.github github 1.31.0 + + + org.jenkins-ci.plugins + git + + + + + org.jenkins-ci.plugins + git + 4.4.0 + + + org.jenkins-ci.plugins + ssh-credentials + + + org.jenkins-ci.plugins + git-client + + + + + org.jenkins-ci.plugins + ssh-credentials + + + org.jenkins-ci.plugins + git-client io.jsonwebtoken @@ -122,11 +151,6 @@ import pom - - org.jenkins-ci.plugins - git - 4.4.0 - From a8f522c410a96bdddfdc78a282ce6fa24df25d35 Mon Sep 17 00:00:00 2001 From: Rishabh Budhouliya Date: Sat, 5 Sep 2020 18:21:51 +0530 Subject: [PATCH 14/16] Depend on git plugin 4.4.0 and Jenkins 2.204.1 Git plugin 4.4.0 requires Jenkins 2.204.1 --- pom.xml | 62 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index 6692ee9f4..76e7d25ec 100644 --- a/pom.xml +++ b/pom.xml @@ -21,11 +21,11 @@ - 2.9.1 + 2.10.0 -SNAPSHOT 2.2.0 8 - 2.176.4 + 2.204.1 true 0.11.2 @@ -51,35 +51,6 @@ com.coravy.hudson.plugins.github github 1.31.0 - - - org.jenkins-ci.plugins - git - - - - - org.jenkins-ci.plugins - git - 4.4.0 - - - org.jenkins-ci.plugins - ssh-credentials - - - org.jenkins-ci.plugins - git-client - - - - - org.jenkins-ci.plugins - ssh-credentials - - - org.jenkins-ci.plugins - git-client org.jenkins-ci.plugins.workflow @@ -130,9 +101,31 @@ workflow-basic-steps test + + org.jenkins-ci.plugins + trilead-api + 1.0.8 + + + org.jenkins-ci.plugins + git-client + 3.4.2 + org.jenkins-ci.plugins git + 4.4.0 + + + org.apache.httpcomponents + httpclient + + + + + org.jenkins-ci.plugins + git + 4.4.0 tests test @@ -165,11 +158,16 @@ io.jenkins.tools.bom - bom-2.176.x + bom-2.204.x 11 import pom + + org.jenkins-ci.plugins + git + 4.4.0 + From 801806c0278378b588b8588ff504ef9903c000a4 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 4 Mar 2021 08:18:00 -0800 Subject: [PATCH 15/16] Base commit for auto formatting for open PRs --- pom.xml | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pom.xml b/pom.xml index 85f8e6bec..f1b8ba8d6 100644 --- a/pom.xml +++ b/pom.xml @@ -135,6 +135,63 @@ + + + + com.diffplug.spotless + spotless-maven-plugin + 2.8.1 + + + spotless-check + + + + check + + + + + + + + + + + + + + + + ci-non-windows + + + set.changelist + + + !windows + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + spotless-check + + process-sources + + check + + + + + + + + repo.jenkins-ci.org From fe70bc16b6709e1a9d96fbfc22f18ce968c21cb4 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 4 Mar 2021 11:15:08 -0800 Subject: [PATCH 16/16] Apply autoformatting --- .../AbstractGitHubNotificationStrategy.java | 32 +- .../ApiRateLimitChecker.java | 525 +- .../BranchDiscoveryTrait.java | 380 +- .../github_branch_source/BranchSCMHead.java | 53 +- .../github_branch_source/Connector.java | 1202 ++-- .../DefaultGitHubNotificationStrategy.java | 47 +- .../github_branch_source/Endpoint.java | 233 +- .../ExcludeArchivedRepositoriesTrait.java | 61 +- .../ExcludePublicRepositoriesTrait.java | 62 +- .../FillErrorResponse.java | 37 +- .../ForkPullRequestDiscoveryTrait.java | 617 +- .../GitHubAppCredentials.java | 1070 ++-- .../GitHubBranchFilter.java | 57 +- .../GitHubBuildStatusNotification.java | 493 +- .../github_branch_source/GitHubClosable.java | 6 +- .../GitHubConfiguration.java | 354 +- .../GitHubConsoleNote.java | 59 +- .../GitHubDefaultBranch.java | 118 +- .../github_branch_source/GitHubLink.java | 153 +- .../GitHubNotificationContext.java | 400 +- .../GitHubNotificationRequest.java | 236 +- .../GitHubOrgMetadataAction.java | 169 +- .../GitHubOrgWebHook.java | 174 +- .../GitHubPermissionsSource.java | 18 +- .../GitHubPullRequestFilter.java | 57 +- .../GitHubRepoMetadataAction.java | 71 +- .../GitHubRepoSizeEstimator.java | 60 +- .../GitHubRepositoryEventSubscriber.java | 202 +- .../GitHubRepositoryInfo.java | 154 +- .../GitHubSCMBuilder.java | 525 +- .../github_branch_source/GitHubSCMFile.java | 291 +- .../GitHubSCMFileSystem.java | 469 +- .../GitHubSCMNavigator.java | 3172 +++++----- .../GitHubSCMNavigatorContext.java | 161 +- .../GitHubSCMNavigatorRequest.java | 25 +- .../github_branch_source/GitHubSCMProbe.java | 272 +- .../github_branch_source/GitHubSCMSource.java | 5287 +++++++++-------- .../GitHubSCMSourceBuilder.java | 164 +- .../GitHubSCMSourceContext.java | 495 +- ...HubSCMSourceRepositoryNameContributor.java | 31 +- .../GitHubSCMSourceRequest.java | 852 ++- .../GitHubTagSCMHead.java | 31 +- .../HttpsRepositoryUriResolver.java | 22 +- .../InvalidPrivateKeyException.java | 6 +- .../github_branch_source/LazyIterable.java | 37 +- .../plugins/github_branch_source/LazySet.java | 243 +- .../MergeWithGitSCMExtension.java | 12 +- .../OriginPullRequestDiscoveryTrait.java | 292 +- .../PullRequestAction.java | 79 +- .../PullRequestGHEventSubscriber.java | 570 +- .../PullRequestSCMHead.java | 567 +- .../PullRequestSCMRevision.java | 200 +- .../PullRequestSource.java | 35 +- .../PushGHEventSubscriber.java | 552 +- .../RateLimitExceededException.java | 59 +- .../RepositoryUriResolver.java | 61 +- .../SSHCheckoutTrait.java | 293 +- .../SinglePassIterable.java | 198 +- .../SshRepositoryUriResolver.java | 17 +- .../TagDiscoveryTrait.java | 137 +- .../github_branch_source/TeamSlugTrait.java | 93 +- .../github_branch_source/TopicsTrait.java | 110 +- .../UntrustedPullRequestSCMRevision.java | 45 +- .../AbstractGitHubWireMockTest.java | 143 +- .../ApiRateLimitCheckerTest.java | 1641 ++--- .../BranchDiscoveryTraitTest.java | 161 +- ...DefaultGitHubNotificationStrategyTest.java | 163 +- .../github_branch_source/EndpointTest.java | 229 +- .../github_branch_source/EventsTest.java | 319 +- .../ForkPullRequestDiscoveryTrait2Test.java | 78 +- .../ForkPullRequestDiscoveryTraitTest.java | 224 +- ...bAppCredentialsJCasCCompatibilityTest.java | 136 +- ...ubBranchSourcesJCasCCompatibilityTest.java | 59 +- .../GitHubNotificationTest.java | 147 +- .../GitHubOrgWebHookTest.java | 64 +- .../GitHubRepoSizeEstimatorTest.java | 100 +- .../GitHubSCMBuilderTest.java | 5228 ++++++++-------- .../GitHubSCMFileSystemTest.java | 439 +- .../GitHubSCMNavigatorTest.java | 796 +-- .../GitHubSCMNavigatorTraitsTest.java | 2379 ++++---- .../GitHubSCMProbeTest.java | 333 +- .../GitHubSCMSourceHelperTest.java | 35 +- .../GitHubSCMSourceTest.java | 1238 ++-- .../GitHubSCMSourceTraitsTest.java | 3723 ++++++------ .../GitSCMSourceBase.java | 35 +- ...ppCredentialsAppInstallationTokenTest.java | 116 +- .../GithubAppCredentialsTest.java | 927 +-- .../GithubSCMSourceBranchesTest.java | 347 +- .../GithubSCMSourcePRsTest.java | 477 +- .../GithubSCMSourceTagsTest.java | 669 ++- .../OriginPullRequestDiscoveryTraitTest.java | 219 +- .../PullRequestSCMRevisionTest.java | 518 +- .../SSHCheckoutTraitTest.java | 225 +- .../TagDiscoveryTraitTest.java | 122 +- .../WireMockRuleFactory.java | 48 +- 95 files changed, 22672 insertions(+), 21169 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubNotificationStrategy.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubNotificationStrategy.java index d02e738a8..8557fc4cd 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubNotificationStrategy.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubNotificationStrategy.java @@ -25,31 +25,29 @@ package org.jenkinsci.plugins.github_branch_source; import hudson.model.TaskListener; - import java.util.List; /** * Represents a strategy for constructing GitHub status notifications + * * @since 2.3.2 */ public abstract class AbstractGitHubNotificationStrategy { - /** - * Creates the list of {@link GitHubNotificationRequest} for the given context. - * @param notificationContext {@link GitHubNotificationContext} the context details - * @param listener the listener - * @return a list of notification requests - * @since 2.3.2 - */ - public abstract List notifications(GitHubNotificationContext notificationContext, TaskListener listener); + /** + * Creates the list of {@link GitHubNotificationRequest} for the given context. + * + * @param notificationContext {@link GitHubNotificationContext} the context details + * @param listener the listener + * @return a list of notification requests + * @since 2.3.2 + */ + public abstract List notifications( + GitHubNotificationContext notificationContext, TaskListener listener); - /** - * {@inheritDoc} - */ - public abstract boolean equals(Object o); + /** {@inheritDoc} */ + public abstract boolean equals(Object o); - /** - * {@inheritDoc} - */ - public abstract int hashCode(); + /** {@inheritDoc} */ + public abstract int hashCode(); } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java index 2200ec2a6..d1405cbd5 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitChecker.java @@ -4,306 +4,315 @@ import hudson.Util; import hudson.model.TaskListener; import hudson.util.LogTaskListener; -import org.jenkinsci.plugins.github.config.GitHubServerConfig; -import org.kohsuke.github.GHRateLimit; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.RateLimitChecker; - import java.io.IOException; import java.util.Objects; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import org.jenkinsci.plugins.github.config.GitHubServerConfig; +import org.kohsuke.github.GHRateLimit; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.RateLimitChecker; public enum ApiRateLimitChecker { - /** - * Attempt to evenly distribute GitHub API requests. - */ - ThrottleForNormalize(Messages.ApiRateLimitChecker_ThrottleForNormalize()) { + /** Attempt to evenly distribute GitHub API requests. */ + ThrottleForNormalize(Messages.ApiRateLimitChecker_ThrottleForNormalize()) { + @Override + public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { + return new LocalChecker(listener) { @Override - public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { - return new LocalChecker(listener) { - @Override - long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) throws InterruptedException { - long expiration = now; - // the buffer is how much we want to avoid using to cover unplanned over-use - int buffer = calculateBuffer(rateLimit.getLimit()); - if (rateLimit.getRemaining() < buffer) { - // nothing we can do, we have burned into our minimum buffer, wait for reset - expiration = calculateExpirationWhenBufferExceeded(rateLimit, now, buffer); - } else { - // the burst is how much we want to allow for speedier response outside of the throttle - int burst = calculateNormalizedBurst(rateLimit.getLimit()); - // the ideal is how much remaining we should have (after a burst) - long rateLimitResetMillis = rateLimit.getResetDate().getTime() - now; - double resetProgress = Math.max(0, rateLimitResetMillis / MILLIS_PER_HOUR); - int ideal = (int) ((rateLimit.getLimit() - buffer - burst) * resetProgress) + buffer; - if (rateLimit.getRemaining() < ideal) { - // work out how long until remaining == ideal + 0.1 * buffer (to give some spend) - double targetFraction = (rateLimit.getRemaining() - buffer * 1.1) / (rateLimit - .getLimit() - buffer - burst); - expiration = rateLimit.getResetDate().getTime() - - Math.max(0, (long) (targetFraction * MILLIS_PER_HOUR)) - + ENTROPY.nextInt(1000); - writeLog(String.format( - "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d in %s. Sleeping for %s.", - rateLimit.getRemaining(), - ideal - rateLimit.getRemaining(), - rateLimit.getLimit(), - Util.getTimeSpanString(rateLimitResetMillis), - // The GitHubRateLimitChecker adds a one second sleep to each notification loop - Util.getTimeSpanString(1000 + expiration - now))); - } - } - if (expiration != now) { - writeLog("Jenkins is attempting to evenly distribute GitHub API requests. " - + "To configure a different rate limiting strategy, such as having Jenkins restrict GitHub API requests only when near or above the GitHub rate limit, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); - } - return expiration; - } - }; + long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) + throws InterruptedException { + long expiration = now; + // the buffer is how much we want to avoid using to cover unplanned over-use + int buffer = calculateBuffer(rateLimit.getLimit()); + if (rateLimit.getRemaining() < buffer) { + // nothing we can do, we have burned into our minimum buffer, wait for reset + expiration = calculateExpirationWhenBufferExceeded(rateLimit, now, buffer); + } else { + // the burst is how much we want to allow for speedier response outside of the throttle + int burst = calculateNormalizedBurst(rateLimit.getLimit()); + // the ideal is how much remaining we should have (after a burst) + long rateLimitResetMillis = rateLimit.getResetDate().getTime() - now; + double resetProgress = Math.max(0, rateLimitResetMillis / MILLIS_PER_HOUR); + int ideal = (int) ((rateLimit.getLimit() - buffer - burst) * resetProgress) + buffer; + if (rateLimit.getRemaining() < ideal) { + // work out how long until remaining == ideal + 0.1 * buffer (to give some spend) + double targetFraction = + (rateLimit.getRemaining() - buffer * 1.1) + / (rateLimit.getLimit() - buffer - burst); + expiration = + rateLimit.getResetDate().getTime() + - Math.max(0, (long) (targetFraction * MILLIS_PER_HOUR)) + + ENTROPY.nextInt(1000); + writeLog( + String.format( + "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d in %s. Sleeping for %s.", + rateLimit.getRemaining(), + ideal - rateLimit.getRemaining(), + rateLimit.getLimit(), + Util.getTimeSpanString(rateLimitResetMillis), + // The GitHubRateLimitChecker adds a one second sleep to each notification + // loop + Util.getTimeSpanString(1000 + expiration - now))); + } + } + if (expiration != now) { + writeLog( + "Jenkins is attempting to evenly distribute GitHub API requests. " + + "To configure a different rate limiting strategy, such as having Jenkins restrict GitHub API requests only when near or above the GitHub rate limit, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); + } + return expiration; } - }, + }; + } + }, - /** - * Restrict GitHub API requests only when near or above rate limit. - */ - ThrottleOnOver(Messages.ApiRateLimitChecker_ThrottleOnOver()) { + /** Restrict GitHub API requests only when near or above rate limit. */ + ThrottleOnOver(Messages.ApiRateLimitChecker_ThrottleOnOver()) { + @Override + public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { + return new LocalChecker(listener) { @Override - public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { - return new LocalChecker(listener) { - @Override - long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) throws InterruptedException { - // the buffer is how much we want to avoid using to cover unplanned over-use - int buffer = calculateBuffer(rateLimit.getLimit()); - // check that we have at least our minimum buffer of remaining calls - if (rateLimit.getRemaining() >= buffer) { - return now; - } - writeLog("Jenkins is restricting GitHub API requests only when near or above the rate limit. " - + "To configure a different rate limiting strategy, such as having Jenkins attempt to evenly distribute GitHub API requests, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); - return calculateExpirationWhenBufferExceeded(rateLimit, now, buffer); - } - }; + long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) + throws InterruptedException { + // the buffer is how much we want to avoid using to cover unplanned over-use + int buffer = calculateBuffer(rateLimit.getLimit()); + // check that we have at least our minimum buffer of remaining calls + if (rateLimit.getRemaining() >= buffer) { + return now; + } + writeLog( + "Jenkins is restricting GitHub API requests only when near or above the rate limit. " + + "To configure a different rate limiting strategy, such as having Jenkins attempt to evenly distribute GitHub API requests, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); + return calculateExpirationWhenBufferExceeded(rateLimit, now, buffer); } - }, - /** - * Ignore GitHub API Rate limit. Useful for GitHub Enterprise instances that might not have a limit set up. - */ - NoThrottle(Messages.ApiRateLimitChecker_NoThrottle()) { - @Override - public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { - if (GitHubServerConfig.GITHUB_URL.equals(apiUrl)) { - // If the GitHub public API is being used, this will fallback to ThrottleOnOver - LocalChecker checker = ThrottleOnOver.getChecker(listener, apiUrl); - checker.writeLog("GitHub throttling is disabled, which is not allowed for public GitHub usage, " - + "so ThrottleOnOver will be used instead. To configure a different rate limiting strategy, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); - return checker; - } else { - return new LocalChecker(listener) { - @Override - long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) throws InterruptedException { - return now; - } - }; - } - } - }; + }; + } + }, + /** + * Ignore GitHub API Rate limit. Useful for GitHub Enterprise instances that might not have a + * limit set up. + */ + NoThrottle(Messages.ApiRateLimitChecker_NoThrottle()) { + @Override + public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { + if (GitHubServerConfig.GITHUB_URL.equals(apiUrl)) { + // If the GitHub public API is being used, this will fallback to ThrottleOnOver + LocalChecker checker = ThrottleOnOver.getChecker(listener, apiUrl); + checker.writeLog( + "GitHub throttling is disabled, which is not allowed for public GitHub usage, " + + "so ThrottleOnOver will be used instead. To configure a different rate limiting strategy, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings."); + return checker; + } else { + return new LocalChecker(listener) { + @Override + long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) + throws InterruptedException { + return now; + } + }; + } + } + }; - /** - * Logger for printing output even when task listener is not set - */ - private static final Logger LOGGER = Logger.getLogger(ApiRateLimitChecker.class.getName()); + /** Logger for printing output even when task listener is not set */ + private static final Logger LOGGER = Logger.getLogger(ApiRateLimitChecker.class.getName()); - /** - * Thread-local rate limit checkers. - * - * In Jenkins multiple threads can call into one {@link GitHub} instance with each wanting to - * receive logging output on a different listener. {@link RateLimitChecker} does not support - * anything like this. We use thread-local checker instances to track rate limit checking - * state for each thread. - */ - private static final ThreadLocal localRateLimitChecker = new ThreadLocal<>(); + /** + * Thread-local rate limit checkers. + * + *

In Jenkins multiple threads can call into one {@link GitHub} instance with each wanting to + * receive logging output on a different listener. {@link RateLimitChecker} does not support + * anything like this. We use thread-local checker instances to track rate limit checking state + * for each thread. + */ + private static final ThreadLocal localRateLimitChecker = new ThreadLocal<>(); - private static final double MILLIS_PER_HOUR = TimeUnit.HOURS.toMillis(1); - private static Random ENTROPY = new Random(); - private static int EXPIRATION_WAIT_MILLIS = 65536; // approx 1 min - // A random straw poll of users concluded that 3 minutes without any visible progress in the logs - // is the point after which people believe that the process is dead. - private static long NOTIFICATION_WAIT_MILLIS = TimeUnit.MINUTES.toMillis(3); + private static final double MILLIS_PER_HOUR = TimeUnit.HOURS.toMillis(1); + private static Random ENTROPY = new Random(); + private static int EXPIRATION_WAIT_MILLIS = 65536; // approx 1 min + // A random straw poll of users concluded that 3 minutes without any visible progress in the logs + // is the point after which people believe that the process is dead. + private static long NOTIFICATION_WAIT_MILLIS = TimeUnit.MINUTES.toMillis(3); - static void setEntropy(Random random) { - ENTROPY = random; - } + static void setEntropy(Random random) { + ENTROPY = random; + } - static void setExpirationWaitMillis(int expirationWaitMillis) { - EXPIRATION_WAIT_MILLIS = expirationWaitMillis; - } + static void setExpirationWaitMillis(int expirationWaitMillis) { + EXPIRATION_WAIT_MILLIS = expirationWaitMillis; + } - static void setNotificationWaitMillis(int notificationWaitMillis) { - NOTIFICATION_WAIT_MILLIS = notificationWaitMillis; - } + static void setNotificationWaitMillis(int notificationWaitMillis) { + NOTIFICATION_WAIT_MILLIS = notificationWaitMillis; + } - private String displayName; + private String displayName; - ApiRateLimitChecker(String displayName) { - this.displayName = displayName; - } + ApiRateLimitChecker(String displayName) { + this.displayName = displayName; + } - public String getDisplayName() { - return displayName; - } + public String getDisplayName() { + return displayName; + } - public static void configureThreadLocalChecker(@NonNull TaskListener listener, @NonNull GitHub gitHub) { - configureThreadLocalChecker(listener, gitHub.getApiUrl()); - } + public static void configureThreadLocalChecker( + @NonNull TaskListener listener, @NonNull GitHub gitHub) { + configureThreadLocalChecker(listener, gitHub.getApiUrl()); + } - private static void configureThreadLocalChecker(TaskListener listener, String apiUrl) { - LocalChecker checker = GitHubConfiguration.get().getApiRateLimitChecker().getChecker(listener, apiUrl); - localRateLimitChecker.set(checker); - } + private static void configureThreadLocalChecker(TaskListener listener, String apiUrl) { + LocalChecker checker = + GitHubConfiguration.get().getApiRateLimitChecker().getChecker(listener, apiUrl); + localRateLimitChecker.set(checker); + } - /** - * For test purposes only. - */ - static LocalChecker getLocalChecker() { - return localRateLimitChecker.get(); - } + /** For test purposes only. */ + static LocalChecker getLocalChecker() { + return localRateLimitChecker.get(); + } - /** - * For test purposes only. - */ - static void resetLocalChecker() { - localRateLimitChecker.set(null); - } + /** For test purposes only. */ + static void resetLocalChecker() { + localRateLimitChecker.set(null); + } + + /** + * This method is the old code path for rate limit checks + * + *

It has been slowly refactored until it almost matches the behavior of the + * GitHubRateLimitChecker. + * + * @deprecated rate limit checking is done automatically. Use {@link + * #configureThreadLocalChecker(TaskListener, GitHub)} instead. + */ + @Deprecated + public void checkApiRateLimit(TaskListener listener, GitHub gitHub) + throws IOException, InterruptedException { + configureThreadLocalChecker(listener, gitHub); + } - /** - * This method is the old code path for rate limit checks - * - * It has been slowly refactored until it almost matches the behavior of the GitHubRateLimitChecker. - * @deprecated rate limit checking is done automatically. Use {@link #configureThreadLocalChecker(TaskListener, GitHub)} instead. - */ - @Deprecated - public void checkApiRateLimit(TaskListener listener, GitHub gitHub) throws IOException, InterruptedException { - configureThreadLocalChecker(listener, gitHub); + static final class RateLimitCheckerAdapter extends RateLimitChecker { + @Override + protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, long count) + throws InterruptedException { + LocalChecker checker = getLocalChecker(); + if (checker == null) { + // If a checker was not configured for this thread, try our best and continue. + configureThreadLocalChecker( + new LogTaskListener(LOGGER, Level.INFO), GitHubServerConfig.GITHUB_URL); + checker = getLocalChecker(); + checker.writeLog( + "LocalChecker for rate limit was not set for this thread. " + + "Configured using system settings."); + } + return checker.checkRateLimit(rateLimitRecord, count); } + } - static final class RateLimitCheckerAdapter extends RateLimitChecker { - @Override - protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, - long count) throws InterruptedException { - LocalChecker checker = getLocalChecker(); - if (checker == null) { - // If a checker was not configured for this thread, try our best and continue. - configureThreadLocalChecker(new LogTaskListener(LOGGER, Level.INFO), GitHubServerConfig.GITHUB_URL); - checker = getLocalChecker(); - checker.writeLog("LocalChecker for rate limit was not set for this thread. " - + "Configured using system settings."); - } - return checker.checkRateLimit(rateLimitRecord, count); - } + abstract static class LocalChecker { + @NonNull private final TaskListener listener; + private long expiration; + + LocalChecker(@NonNull TaskListener listener) { + this.listener = Objects.requireNonNull(listener); + resetExpiration(); } - static abstract class LocalChecker { - @NonNull - private final TaskListener listener; - private long expiration; + protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, long count) + throws InterruptedException { + if (count == 0) { + resetExpiration(); + } + long now = System.currentTimeMillis(); + if (waitUntilRateLimit(now, expiration, count)) { + return true; + } + long newExpiration = this.checkRateLimitImpl(rateLimitRecord, count, now); + if (newExpiration > expiration) { + count = 0; + } + return waitUntilRateLimit(now, newExpiration, count); + } - LocalChecker(@NonNull TaskListener listener) { - this.listener = Objects.requireNonNull(listener); - resetExpiration(); - } + // internal for testing + abstract long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) + throws InterruptedException; - protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, - long count) throws InterruptedException { - if (count == 0) { - resetExpiration(); - } - long now = System.currentTimeMillis(); - if (waitUntilRateLimit(now, expiration, count)) { - return true; - } - long newExpiration = this.checkRateLimitImpl(rateLimitRecord, count, now); - if (newExpiration > expiration) { - count = 0; - } - return waitUntilRateLimit(now, newExpiration, count); - } + void resetExpiration() { + expiration = Long.MIN_VALUE; + } - // internal for testing - abstract long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) throws InterruptedException; + long calculateExpirationWhenBufferExceeded(GHRateLimit.Record rateLimit, long now, int buffer) { + long expiration; + long rateLimitResetMillis = rateLimit.getResetDate().getTime() - now; + // we add a little bit of random to prevent CPU overload when the limit is due to reset but + // GitHub + // hasn't actually reset yet (clock synchronization is a hard problem) + if (rateLimitResetMillis < 0) { + expiration = now + ENTROPY.nextInt(EXPIRATION_WAIT_MILLIS); + writeLog( + String.format( + "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d due now. Sleeping for %s.", + rateLimit.getRemaining(), + buffer - rateLimit.getRemaining(), + rateLimit.getLimit(), + // The GitHubRateLimitChecker adds a one second sleep to each notification loop + Util.getTimeSpanString(1000 + expiration - now))); + } else { + expiration = rateLimit.getResetDate().getTime() + ENTROPY.nextInt(EXPIRATION_WAIT_MILLIS); + writeLog( + String.format( + "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d in %s. Sleeping until reset.", + rateLimit.getRemaining(), + buffer - rateLimit.getRemaining(), + rateLimit.getLimit(), + Util.getTimeSpanString(rateLimitResetMillis))); + } + return expiration; + } - void resetExpiration() { - expiration = Long.MIN_VALUE; + // Internal for testing + boolean waitUntilRateLimit(long now, long expiration, long count) throws InterruptedException { + boolean waiting = expiration > now; + if (waiting) { + long nextNotify = now + NOTIFICATION_WAIT_MILLIS; + this.expiration = expiration; + if (count > 0) { + writeLog( + String.format( + "Jenkins-Imposed API Limiter: Still sleeping, now only %s remaining.", + Util.getTimeSpanString(expiration - now))); } - - long calculateExpirationWhenBufferExceeded(GHRateLimit.Record rateLimit, long now, int buffer) { - long expiration; - long rateLimitResetMillis = rateLimit.getResetDate().getTime() - now; - // we add a little bit of random to prevent CPU overload when the limit is due to reset but GitHub - // hasn't actually reset yet (clock synchronization is a hard problem) - if (rateLimitResetMillis < 0) { - expiration = now + ENTROPY.nextInt( - EXPIRATION_WAIT_MILLIS); - writeLog(String.format( - "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d due now. Sleeping for %s.", - rateLimit.getRemaining(), - buffer - rateLimit.getRemaining(), - rateLimit.getLimit(), - // The GitHubRateLimitChecker adds a one second sleep to each notification loop - Util.getTimeSpanString(1000 + expiration - now))); - } else { - expiration = rateLimit.getResetDate().getTime() + ENTROPY.nextInt( - EXPIRATION_WAIT_MILLIS); - writeLog(String.format( - "Jenkins-Imposed API Limiter: Current quota for Github API usage has %d remaining (%d over budget). Next quota of %d in %s. Sleeping until reset.", - rateLimit.getRemaining(), - buffer - rateLimit.getRemaining(), - rateLimit.getLimit(), - Util.getTimeSpanString(rateLimitResetMillis))); - } - return expiration; + if (Thread.interrupted()) { + throw new InterruptedException(); } - - // Internal for testing - boolean waitUntilRateLimit(long now, long expiration, long count) throws InterruptedException { - boolean waiting = expiration > now; - if (waiting) { - long nextNotify = now + NOTIFICATION_WAIT_MILLIS; - this.expiration = expiration; - if (count > 0) { - writeLog(String.format( - "Jenkins-Imposed API Limiter: Still sleeping, now only %s remaining.", - Util.getTimeSpanString(expiration - now) - )); - } - if (Thread.interrupted()) { - throw new InterruptedException(); - } - long sleep = Math.min(expiration, nextNotify) - now; - if (sleep > 0) { - Thread.sleep(sleep); - } - } else { - resetExpiration(); - } - return waiting; + long sleep = Math.min(expiration, nextNotify) - now; + if (sleep > 0) { + Thread.sleep(sleep); } + } else { + resetExpiration(); + } + return waiting; + } - void writeLog(String output) { - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), output)); - } + void writeLog(String output) { + listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), output)); } + } - public abstract LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl); + public abstract LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl); - static int calculateBuffer(int limit) { - return Math.max(15, limit / 20); - } + static int calculateBuffer(int limit) { + return Math.max(15, limit / 20); + } - static int calculateNormalizedBurst(int rateLimit) { - return rateLimit < 1000 ? Math.max(5, rateLimit / 10) : Math.max(200, rateLimit / 5); - } + static int calculateNormalizedBurst(int rateLimit) { + return rateLimit < 1000 ? Math.max(5, rateLimit / 10) : Math.max(200, rateLimit / 5); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTrait.java index 376f68116..d35b0decb 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTrait.java @@ -52,239 +52,203 @@ * @since 2.2.0 */ public class BranchDiscoveryTrait extends SCMSourceTrait { - /** - * None strategy. - */ - public static final int NONE = 0; - /** - * Exclude branches that are also filed as PRs. - */ - public static final int EXCLUDE_PRS = 1; - /** - * Only branches that are also filed as PRs. - */ - public static final int ONLY_PRS = 2; - /** - * All branches. - */ - public static final int ALL_BRANCHES = 3; - - /** - * The strategy encoded as a bit-field. - */ - private final int strategyId; - - /** - * Constructor for stapler. - * - * @param strategyId the strategy id. - */ - @DataBoundConstructor - public BranchDiscoveryTrait(int strategyId) { - this.strategyId = strategyId; + /** None strategy. */ + public static final int NONE = 0; + /** Exclude branches that are also filed as PRs. */ + public static final int EXCLUDE_PRS = 1; + /** Only branches that are also filed as PRs. */ + public static final int ONLY_PRS = 2; + /** All branches. */ + public static final int ALL_BRANCHES = 3; + + /** The strategy encoded as a bit-field. */ + private final int strategyId; + + /** + * Constructor for stapler. + * + * @param strategyId the strategy id. + */ + @DataBoundConstructor + public BranchDiscoveryTrait(int strategyId) { + this.strategyId = strategyId; + } + + /** + * Constructor for legacy code. + * + * @param buildBranch build branches that are not filed as a PR. + * @param buildBranchWithPr build branches that are also PRs. + */ + public BranchDiscoveryTrait(boolean buildBranch, boolean buildBranchWithPr) { + this.strategyId = (buildBranch ? EXCLUDE_PRS : NONE) + (buildBranchWithPr ? ONLY_PRS : NONE); + } + + /** + * Returns the strategy id. + * + * @return the strategy id. + */ + public int getStrategyId() { + return strategyId; + } + + /** + * Returns {@code true} if building branches that are not filed as a PR. + * + * @return {@code true} if building branches that are not filed as a PR. + */ + @Restricted(NoExternalUse.class) + public boolean isBuildBranch() { + return (strategyId & EXCLUDE_PRS) != NONE; + } + + /** + * Returns {@code true} if building branches that are filed as a PR. + * + * @return {@code true} if building branches that are filed as a PR. + */ + @Restricted(NoExternalUse.class) + public boolean isBuildBranchesWithPR() { + return (strategyId & ONLY_PRS) != NONE; + } + + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantBranches(true); + ctx.withAuthority(new BranchSCMHeadAuthority()); + switch (strategyId) { + case BranchDiscoveryTrait.EXCLUDE_PRS: + ctx.wantOriginPRs(true); + ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter()); + break; + case BranchDiscoveryTrait.ONLY_PRS: + ctx.wantOriginPRs(true); + ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter()); + break; + case BranchDiscoveryTrait.ALL_BRANCHES: + default: + // we don't care if it is a PR or not, we're taking them all, no need to ask for PRs and no + // need + // to filter + break; } + } - /** - * Constructor for legacy code. - * - * @param buildBranch build branches that are not filed as a PR. - * @param buildBranchWithPr build branches that are also PRs. - */ - public BranchDiscoveryTrait(boolean buildBranch, boolean buildBranchWithPr) { - this.strategyId = (buildBranch ? EXCLUDE_PRS : NONE) + (buildBranchWithPr ? ONLY_PRS : NONE); + /** {@inheritDoc} */ + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category.isUncategorized(); + } + + /** Our descriptor. */ + @Symbol("gitHubBranchDiscovery") + @Extension + @Discovery + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.BranchDiscoveryTrait_displayName(); } - /** - * Returns the strategy id. - * - * @return the strategy id. - */ - public int getStrategyId() { - return strategyId; + /** {@inheritDoc} */ + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; } - /** - * Returns {@code true} if building branches that are not filed as a PR. - * - * @return {@code true} if building branches that are not filed as a PR. - */ - @Restricted(NoExternalUse.class) - public boolean isBuildBranch() { - return (strategyId & EXCLUDE_PRS) != NONE; + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; } /** - * Returns {@code true} if building branches that are filed as a PR. + * Populates the strategy options. * - * @return {@code true} if building branches that are filed as a PR. + * @return the strategy options. */ + @NonNull @Restricted(NoExternalUse.class) - public boolean isBuildBranchesWithPR() { - return (strategyId & ONLY_PRS) != NONE; - } - - /** - * {@inheritDoc} - */ - @Override - protected void decorateContext(SCMSourceContext context) { - GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; - ctx.wantBranches(true); - ctx.withAuthority(new BranchSCMHeadAuthority()); - switch (strategyId) { - case BranchDiscoveryTrait.EXCLUDE_PRS: - ctx.wantOriginPRs(true); - ctx.withFilter(new ExcludeOriginPRBranchesSCMHeadFilter()); - break; - case BranchDiscoveryTrait.ONLY_PRS: - ctx.wantOriginPRs(true); - ctx.withFilter(new OnlyOriginPRBranchesSCMHeadFilter()); - break; - case BranchDiscoveryTrait.ALL_BRANCHES: - default: - // we don't care if it is a PR or not, we're taking them all, no need to ask for PRs and no need - // to filter - break; - } + @SuppressWarnings("unused") // stapler + public ListBoxModel doFillStrategyIdItems() { + ListBoxModel result = new ListBoxModel(); + result.add(Messages.BranchDiscoveryTrait_excludePRs(), String.valueOf(EXCLUDE_PRS)); + result.add(Messages.BranchDiscoveryTrait_onlyPRs(), String.valueOf(ONLY_PRS)); + result.add(Messages.BranchDiscoveryTrait_allBranches(), String.valueOf(ALL_BRANCHES)); + return result; } + } - /** - * {@inheritDoc} - */ + /** Trusts branches from the origin repository. */ + public static class BranchSCMHeadAuthority + extends SCMHeadAuthority { + /** {@inheritDoc} */ @Override - public boolean includeCategory(@NonNull SCMHeadCategory category) { - return category.isUncategorized(); + protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull BranchSCMHead head) { + return true; } - /** - * Our descriptor. - */ - @Symbol("gitHubBranchDiscovery") + /** Out descriptor. */ + @Symbol("gitHubBranchHeadAuthority") @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.BranchDiscoveryTrait_displayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } - - /** - * Populates the strategy options. - * - * @return the strategy options. - */ - @NonNull - @Restricted(NoExternalUse.class) - @SuppressWarnings("unused") // stapler - public ListBoxModel doFillStrategyIdItems() { - ListBoxModel result = new ListBoxModel(); - result.add(Messages.BranchDiscoveryTrait_excludePRs(), String.valueOf(EXCLUDE_PRS)); - result.add(Messages.BranchDiscoveryTrait_onlyPRs(), String.valueOf(ONLY_PRS)); - result.add(Messages.BranchDiscoveryTrait_allBranches(), String.valueOf(ALL_BRANCHES)); - return result; - } + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.BranchDiscoveryTrait_authorityDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); + } } + } - /** - * Trusts branches from the origin repository. - */ - public static class BranchSCMHeadAuthority extends SCMHeadAuthority { - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull BranchSCMHead head) { + /** Filter that excludes branches that are also filed as a pull request. */ + public static class ExcludeOriginPRBranchesSCMHeadFilter extends SCMHeadFilter { + /** {@inheritDoc} */ + @Override + public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) { + if (head instanceof BranchSCMHead && request instanceof GitHubSCMSourceRequest) { + for (GHPullRequest p : ((GitHubSCMSourceRequest) request).getPullRequests()) { + GHRepository headRepo = p.getHead().getRepository(); + if (headRepo + != null // head repo can be null if the PR is from a repo that has been deleted + && p.getBase().getRepository().getFullName().equalsIgnoreCase(headRepo.getFullName()) + && p.getHead().getRef().equals(head.getName())) { return true; + } } - - /** - * Out descriptor. - */ - @Symbol("gitHubBranchHeadAuthority") - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.BranchDiscoveryTrait_authorityDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); - } - } - } - - /** - * Filter that excludes branches that are also filed as a pull request. - */ - public static class ExcludeOriginPRBranchesSCMHeadFilter extends SCMHeadFilter { - /** - * {@inheritDoc} - */ - @Override - public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) { - if (head instanceof BranchSCMHead && request instanceof GitHubSCMSourceRequest) { - for (GHPullRequest p : ((GitHubSCMSourceRequest) request).getPullRequests()) { - GHRepository headRepo = p.getHead().getRepository(); - if (headRepo != null // head repo can be null if the PR is from a repo that has been deleted - && p.getBase().getRepository().getFullName().equalsIgnoreCase(headRepo.getFullName()) - && p.getHead().getRef().equals(head.getName())) { - return true; - } - } - } - return false; - } + } + return false; } + } - /** - * Filter that excludes branches that are not also filed as a pull request. - */ - public static class OnlyOriginPRBranchesSCMHeadFilter extends SCMHeadFilter { - /** - * {@inheritDoc} - */ - @Override - public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) { - if (head instanceof BranchSCMHead && request instanceof GitHubSCMSourceRequest) { - for (GHPullRequest p : ((GitHubSCMSourceRequest) request).getPullRequests()) { - GHRepository headRepo = p.getHead().getRepository(); - if (headRepo != null // head repo can be null if the PR is from a repo that has been deleted - && p.getBase().getRepository().getFullName().equalsIgnoreCase(headRepo.getFullName()) - && p.getHead().getRef().equals(head.getName())) { - return false; - } - } - return true; - } + /** Filter that excludes branches that are not also filed as a pull request. */ + public static class OnlyOriginPRBranchesSCMHeadFilter extends SCMHeadFilter { + /** {@inheritDoc} */ + @Override + public boolean isExcluded(@NonNull SCMSourceRequest request, @NonNull SCMHead head) { + if (head instanceof BranchSCMHead && request instanceof GitHubSCMSourceRequest) { + for (GHPullRequest p : ((GitHubSCMSourceRequest) request).getPullRequests()) { + GHRepository headRepo = p.getHead().getRepository(); + if (headRepo + != null // head repo can be null if the PR is from a repo that has been deleted + && p.getBase().getRepository().getFullName().equalsIgnoreCase(headRepo.getFullName()) + && p.getHead().getRef().equals(head.getName())) { return false; + } } + return true; + } + return false; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchSCMHead.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchSCMHead.java index 139c7abd3..e2ec58b9a 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchSCMHead.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/BranchSCMHead.java @@ -35,40 +35,39 @@ /** * Head corresponding to a branch. + * * @since 2.0.0 */ public class BranchSCMHead extends SCMHead { - /** - * {@inheritDoc} - */ - public BranchSCMHead(@NonNull String name) { - super(name); + /** {@inheritDoc} */ + public BranchSCMHead(@NonNull String name) { + super(name); + } + + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.BranchSCMHead_Pronoun(); + } + + @Restricted(NoExternalUse.class) + @Extension + public static class MigrationImpl + extends SCMHeadMigration { + public MigrationImpl() { + super(GitHubSCMSource.class, SCMHead.class, AbstractGitSCMSource.SCMRevisionImpl.class); } - /** - * {@inheritDoc} - */ @Override - public String getPronoun() { - return Messages.BranchSCMHead_Pronoun(); + public SCMHead migrate(@NonNull GitHubSCMSource source, @NonNull SCMHead head) { + return new BranchSCMHead(head.getName()); } - @Restricted(NoExternalUse.class) - @Extension - public static class MigrationImpl extends SCMHeadMigration { - public MigrationImpl() { - super(GitHubSCMSource.class, SCMHead.class, AbstractGitSCMSource.SCMRevisionImpl.class); - } - - @Override - public SCMHead migrate(@NonNull GitHubSCMSource source, @NonNull SCMHead head) { - return new BranchSCMHead(head.getName()); - } - - @Override - public SCMRevision migrate(@NonNull GitHubSCMSource source, - @NonNull AbstractGitSCMSource.SCMRevisionImpl revision) { - return new AbstractGitSCMSource.SCMRevisionImpl(migrate(source, revision.getHead()), revision.getHash()); - } + @Override + public SCMRevision migrate( + @NonNull GitHubSCMSource source, @NonNull AbstractGitSCMSource.SCMRevisionImpl revision) { + return new AbstractGitSCMSource.SCMRevisionImpl( + migrate(source, revision.getHead()), revision.getHash()); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java index d0e711b05..33660bfce 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.WARNING; + import com.cloudbees.plugins.credentials.CredentialsMatcher; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsNameProvider; @@ -34,8 +37,6 @@ import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; -import okhttp3.Cache; -import okhttp3.OkHttpClient; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.Extension; @@ -58,7 +59,6 @@ import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -72,6 +72,8 @@ import jenkins.model.Jenkins; import jenkins.scm.api.SCMSourceOwner; import jenkins.util.JenkinsJVM; +import okhttp3.Cache; +import okhttp3.OkHttpClient; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.gitclient.GitClient; @@ -84,645 +86,685 @@ import org.kohsuke.github.authorization.ImmutableAuthorizationProvider; import org.kohsuke.github.extras.okhttp3.OkHttpConnector; -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.WARNING; - -/** - * Utilities that could perhaps be moved into {@code github-api}. - */ +/** Utilities that could perhaps be moved into {@code github-api}. */ public class Connector { - private static final Logger LOGGER = Logger.getLogger(Connector.class.getName()); - - private static final Map connections = new HashMap<>(); - private static final Map reverseLookup = new HashMap<>(); - - private static final Map> checked = new WeakHashMap<>(); - private static final long API_URL_REVALIDATE_MILLIS = TimeUnit.MINUTES.toMillis(5); - - private static final Random ENTROPY = new Random(); - private static final String SALT = Long.toHexString(ENTROPY.nextLong()); - private static final OkHttpClient baseClient = new OkHttpClient(); - - - private Connector() { - throw new IllegalAccessError("Utility class"); + private static final Logger LOGGER = Logger.getLogger(Connector.class.getName()); + + private static final Map connections = new HashMap<>(); + private static final Map reverseLookup = new HashMap<>(); + + private static final Map> checked = new WeakHashMap<>(); + private static final long API_URL_REVALIDATE_MILLIS = TimeUnit.MINUTES.toMillis(5); + + private static final Random ENTROPY = new Random(); + private static final String SALT = Long.toHexString(ENTROPY.nextLong()); + private static final OkHttpClient baseClient = new OkHttpClient(); + + private Connector() { + throw new IllegalAccessError("Utility class"); + } + + /** + * Retained for binary compatibility only. + * + * @param context the context. + * @param apiUri the api endpoint. + * @return a {@link ListBoxModel}. + * @deprecated use {@link #listCheckoutCredentials(Item, String)}. + */ + @NonNull + @Deprecated + public static ListBoxModel listScanCredentials( + @CheckForNull SCMSourceOwner context, String apiUri) { + return listScanCredentials((Item) context, apiUri); + } + + /** + * Populates a {@link ListBoxModel} with the scan credentials appropriate for the supplied context + * against the supplied API endpoint. + * + * @param context the context. + * @param apiUri the api endpoint. + * @return a {@link ListBoxModel}. + */ + @NonNull + public static ListBoxModel listScanCredentials(@CheckForNull Item context, String apiUri) { + return new StandardListBoxModel() + .includeEmptyValue() + .includeMatchingAs( + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + context, + StandardUsernameCredentials.class, + githubDomainRequirements(apiUri), + githubScanCredentialsMatcher()); + } + + /** + * Retained for binary compatibility only. + * + * @param context the context. + * @param apiUri the api endpoint. + * @param scanCredentialsId the credentials ID. + * @return the {@link FormValidation} results. + * @deprecated use {@link #checkScanCredentials(Item, String, String)} + */ + @Deprecated + public static FormValidation checkScanCredentials( + @CheckForNull SCMSourceOwner context, String apiUri, String scanCredentialsId) { + return checkScanCredentials((Item) context, apiUri, scanCredentialsId); + } + + /** + * Checks the credential ID for use as scan credentials in the supplied context against the + * supplied API endpoint. + * + * @param context the context. + * @param apiUri the api endpoint. + * @param scanCredentialsId the credentials ID. + * @return the {@link FormValidation} results. + */ + public static FormValidation checkScanCredentials( + @CheckForNull Item context, String apiUri, String scanCredentialsId) { + if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + || context != null && !context.hasPermission(Item.EXTENDED_READ)) { + return FormValidation.ok(); } - - /** - * Retained for binary compatibility only. - * - * @param context the context. - * @param apiUri the api endpoint. - * @return a {@link ListBoxModel}. - * @deprecated use {@link #listCheckoutCredentials(Item, String)}. - */ - @NonNull - @Deprecated - public static ListBoxModel listScanCredentials(@CheckForNull SCMSourceOwner context, String apiUri) { - return listScanCredentials((Item) context, apiUri); - } - - /** - * Populates a {@link ListBoxModel} with the scan credentials appropriate for the supplied context against the - * supplied API endpoint. - * - * @param context the context. - * @param apiUri the api endpoint. - * @return a {@link ListBoxModel}. - */ - @NonNull - public static ListBoxModel listScanCredentials(@CheckForNull Item context, String apiUri) { - return new StandardListBoxModel() - .includeEmptyValue() - .includeMatchingAs( - context instanceof Queue.Task - ? ((Queue.Task) context).getDefaultAuthentication() - : ACL.SYSTEM, - context, - StandardUsernameCredentials.class, - githubDomainRequirements(apiUri), - githubScanCredentialsMatcher() - ); - } - - /** - * Retained for binary compatibility only. - * - * @param context the context. - * @param apiUri the api endpoint. - * @param scanCredentialsId the credentials ID. - * @return the {@link FormValidation} results. - * @deprecated use {@link #checkScanCredentials(Item, String, String)} - */ - @Deprecated - public static FormValidation checkScanCredentials(@CheckForNull SCMSourceOwner context, String apiUri, String scanCredentialsId) { - return checkScanCredentials((Item) context, apiUri, scanCredentialsId); - } - - /** - * Checks the credential ID for use as scan credentials in the supplied context against the supplied API endpoint. - * - * @param context the context. - * @param apiUri the api endpoint. - * @param scanCredentialsId the credentials ID. - * @return the {@link FormValidation} results. - */ - public static FormValidation checkScanCredentials(@CheckForNull Item context, String apiUri, String scanCredentialsId) { - if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) || - context != null && !context.hasPermission(Item.EXTENDED_READ)) { - return FormValidation.ok(); + if (!scanCredentialsId.isEmpty()) { + ListBoxModel options = listScanCredentials(context, apiUri); + boolean found = false; + for (ListBoxModel.Option b : options) { + if (scanCredentialsId.equals(b.value)) { + found = true; + break; } - if (!scanCredentialsId.isEmpty()) { - ListBoxModel options = listScanCredentials(context, apiUri); - boolean found = false; - for (ListBoxModel.Option b: options) { - if (scanCredentialsId.equals(b.value)) { - found = true; - break; - } - } - if (!found) { - return FormValidation.error("Credentials not found"); - } - if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { - return FormValidation.ok("Credentials found"); - } - StandardCredentials credentials = Connector.lookupScanCredentials(context, StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL), scanCredentialsId); - if (credentials == null) { - return FormValidation.error("Credentials not found"); - } else { - try { - GitHub connector = Connector.connect(apiUri, credentials); - try { - try { - boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; - if (githubAppAuthentication) { - int remaining = connector.getRateLimit().getRemaining(); - return FormValidation.ok("GHApp verified, remaining rate limit: %d", remaining); - } - - return FormValidation.ok("User %s", connector.getMyself().getLogin()); - } catch (Exception e) { - return FormValidation.error("Invalid credentials: %s", e.getMessage()); - } - } finally { - Connector.release(connector); - } - } catch (IllegalArgumentException | InvalidPrivateKeyException e) { - String msg = "Exception validating credentials " + CredentialsNameProvider.name(credentials); - LOGGER.log(Level.WARNING, msg, e); - return FormValidation.error(e, msg); - } catch (IOException e) { - // ignore, never thrown - LOGGER.log(Level.WARNING, "Exception validating credentials {0} on {1}", new Object[]{ - CredentialsNameProvider.name(credentials), apiUri - }); - return FormValidation.error("Exception validating credentials"); - } + } + if (!found) { + return FormValidation.error("Credentials not found"); + } + if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { + return FormValidation.ok("Credentials found"); + } + StandardCredentials credentials = + Connector.lookupScanCredentials( + context, + StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL), + scanCredentialsId); + if (credentials == null) { + return FormValidation.error("Credentials not found"); + } else { + try { + GitHub connector = Connector.connect(apiUri, credentials); + try { + try { + boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; + if (githubAppAuthentication) { + int remaining = connector.getRateLimit().getRemaining(); + return FormValidation.ok("GHApp verified, remaining rate limit: %d", remaining); + } + + return FormValidation.ok("User %s", connector.getMyself().getLogin()); + } catch (Exception e) { + return FormValidation.error("Invalid credentials: %s", e.getMessage()); } - } else { - return FormValidation.warning("Credentials are recommended"); - } - } - - /** - * Retained for binary compatibility only. - * - * @param context the context. - * @param apiUri the API endpoint. - * @param scanCredentialsId the credentials to resolve. - * @return the {@link StandardCredentials} or {@code null} - * @deprecated use {@link #lookupScanCredentials(Item, String, String)} - */ - @Deprecated - @CheckForNull - public static StandardCredentials lookupScanCredentials(@CheckForNull SCMSourceOwner context, - @CheckForNull String apiUri, - @CheckForNull String scanCredentialsId) { - return lookupScanCredentials((Item) context, apiUri, scanCredentialsId); - } - - /** - * Resolves the specified scan credentials in the specified context for use against the specified API endpoint. - * - * @param context the context. - * @param apiUri the API endpoint. - * @param scanCredentialsId the credentials to resolve. - * @return the {@link StandardCredentials} or {@code null} - */ - @CheckForNull - public static StandardCredentials lookupScanCredentials(@CheckForNull Item context, - @CheckForNull String apiUri, - @CheckForNull String scanCredentialsId) { - if (Util.fixEmpty(scanCredentialsId) == null) { - return null; - } else { - return CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials( - StandardUsernameCredentials.class, - context, - context instanceof Queue.Task - ? ((Queue.Task) context).getDefaultAuthentication() - : ACL.SYSTEM, - githubDomainRequirements(apiUri) - ), - CredentialsMatchers.allOf(CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()) - ); + } finally { + Connector.release(connector); + } + } catch (IllegalArgumentException | InvalidPrivateKeyException e) { + String msg = + "Exception validating credentials " + CredentialsNameProvider.name(credentials); + LOGGER.log(Level.WARNING, msg, e); + return FormValidation.error(e, msg); + } catch (IOException e) { + // ignore, never thrown + LOGGER.log( + Level.WARNING, + "Exception validating credentials {0} on {1}", + new Object[] {CredentialsNameProvider.name(credentials), apiUri}); + return FormValidation.error("Exception validating credentials"); } + } + } else { + return FormValidation.warning("Credentials are recommended"); } - - /** - * Retained for binary compatibility only. - * - * @param context the context. - * @param apiUri the API endpoint. - * @return the {@link StandardCredentials} or {@code null} - * @deprecated use {@link #listCheckoutCredentials(Item, String)} - */ - @NonNull - public static ListBoxModel listCheckoutCredentials(@CheckForNull SCMSourceOwner context, String apiUri) { - return listCheckoutCredentials((Item) context, apiUri); + } + + /** + * Retained for binary compatibility only. + * + * @param context the context. + * @param apiUri the API endpoint. + * @param scanCredentialsId the credentials to resolve. + * @return the {@link StandardCredentials} or {@code null} + * @deprecated use {@link #lookupScanCredentials(Item, String, String)} + */ + @Deprecated + @CheckForNull + public static StandardCredentials lookupScanCredentials( + @CheckForNull SCMSourceOwner context, + @CheckForNull String apiUri, + @CheckForNull String scanCredentialsId) { + return lookupScanCredentials((Item) context, apiUri, scanCredentialsId); + } + + /** + * Resolves the specified scan credentials in the specified context for use against the specified + * API endpoint. + * + * @param context the context. + * @param apiUri the API endpoint. + * @param scanCredentialsId the credentials to resolve. + * @return the {@link StandardCredentials} or {@code null} + */ + @CheckForNull + public static StandardCredentials lookupScanCredentials( + @CheckForNull Item context, + @CheckForNull String apiUri, + @CheckForNull String scanCredentialsId) { + if (Util.fixEmpty(scanCredentialsId) == null) { + return null; + } else { + return CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + StandardUsernameCredentials.class, + context, + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + githubDomainRequirements(apiUri)), + CredentialsMatchers.allOf( + CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher())); } - - /** - * Populates a {@link ListBoxModel} with the checkout credentials appropriate for the supplied context against the - * supplied API endpoint. - * - * @param context the context. - * @param apiUri the api endpoint. - * @return a {@link ListBoxModel}. - */ - @NonNull - public static ListBoxModel listCheckoutCredentials(@CheckForNull Item context, String apiUri) { - StandardListBoxModel result = new StandardListBoxModel(); - result.includeEmptyValue(); - result.add("- same as scan credentials -", GitHubSCMSource.DescriptorImpl.SAME); - result.add("- anonymous -", GitHubSCMSource.DescriptorImpl.ANONYMOUS); - return result.includeMatchingAs( - context instanceof Queue.Task - ? ((Queue.Task) context).getDefaultAuthentication() - : ACL.SYSTEM, - context, - StandardUsernameCredentials.class, - githubDomainRequirements(apiUri), - GitClient.CREDENTIALS_MATCHER - ); + } + + /** + * Retained for binary compatibility only. + * + * @param context the context. + * @param apiUri the API endpoint. + * @return the {@link StandardCredentials} or {@code null} + * @deprecated use {@link #listCheckoutCredentials(Item, String)} + */ + @NonNull + public static ListBoxModel listCheckoutCredentials( + @CheckForNull SCMSourceOwner context, String apiUri) { + return listCheckoutCredentials((Item) context, apiUri); + } + + /** + * Populates a {@link ListBoxModel} with the checkout credentials appropriate for the supplied + * context against the supplied API endpoint. + * + * @param context the context. + * @param apiUri the api endpoint. + * @return a {@link ListBoxModel}. + */ + @NonNull + public static ListBoxModel listCheckoutCredentials(@CheckForNull Item context, String apiUri) { + StandardListBoxModel result = new StandardListBoxModel(); + result.includeEmptyValue(); + result.add("- same as scan credentials -", GitHubSCMSource.DescriptorImpl.SAME); + result.add("- anonymous -", GitHubSCMSource.DescriptorImpl.ANONYMOUS); + return result.includeMatchingAs( + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + context, + StandardUsernameCredentials.class, + githubDomainRequirements(apiUri), + GitClient.CREDENTIALS_MATCHER); + } + + public static @Nonnull GitHub connect( + @CheckForNull String apiUri, @CheckForNull StandardCredentials credentials) + throws IOException { + String apiUrl = Util.fixEmptyAndTrim(apiUri); + apiUrl = apiUrl != null ? apiUrl : GitHubServerConfig.GITHUB_URL; + String username; + String password = null; + String hash; + String authHash; + GitHubAppCredentials gitHubAppCredentials = null; + Jenkins jenkins = Jenkins.get(); + if (credentials == null) { + username = null; + password = null; + hash = "anonymous"; + authHash = "anonymous"; + } else if (credentials instanceof GitHubAppCredentials) { + gitHubAppCredentials = (GitHubAppCredentials) credentials; + hash = + Util.getDigestOf( + gitHubAppCredentials.getAppID() + + gitHubAppCredentials.getOwner() + + gitHubAppCredentials.getPrivateKey().getPlainText() + + SALT); // want to ensure pooling by credential + authHash = + Util.getDigestOf( + gitHubAppCredentials.getAppID() + + "::" + + gitHubAppCredentials.getOwner() + + "::" + + gitHubAppCredentials.getPrivateKey().getPlainText() + + "::" + + jenkins.getLegacyInstanceId()); + username = gitHubAppCredentials.getUsername(); + } else if (credentials instanceof StandardUsernamePasswordCredentials) { + StandardUsernamePasswordCredentials c = (StandardUsernamePasswordCredentials) credentials; + username = c.getUsername(); + password = c.getPassword().getPlainText(); + hash = Util.getDigestOf(password + SALT); // want to ensure pooling by credential + authHash = Util.getDigestOf(password + "::" + jenkins.getLegacyInstanceId()); + } else { + // TODO OAuth support + throw new IOException("Unsupported credential type: " + credentials.getClass().getName()); } - public static @Nonnull GitHub connect(@CheckForNull String apiUri, @CheckForNull StandardCredentials credentials) - throws IOException { - String apiUrl = Util.fixEmptyAndTrim(apiUri); - apiUrl = apiUrl != null ? apiUrl : GitHubServerConfig.GITHUB_URL; - String username; - String password = null; - String hash; - String authHash; - GitHubAppCredentials gitHubAppCredentials = null; - Jenkins jenkins = Jenkins.get(); - if (credentials == null) { - username = null; - password = null; - hash = "anonymous"; - authHash = "anonymous"; - } else if (credentials instanceof GitHubAppCredentials) { - gitHubAppCredentials = (GitHubAppCredentials) credentials; - hash = Util.getDigestOf(gitHubAppCredentials.getAppID() + gitHubAppCredentials.getOwner() + gitHubAppCredentials.getPrivateKey().getPlainText() + SALT); // want to ensure pooling by credential - authHash = Util.getDigestOf(gitHubAppCredentials.getAppID() + "::" + gitHubAppCredentials.getOwner() + "::" + gitHubAppCredentials.getPrivateKey().getPlainText() + "::" + jenkins.getLegacyInstanceId()); - username = gitHubAppCredentials.getUsername(); - } else if (credentials instanceof StandardUsernamePasswordCredentials) { - StandardUsernamePasswordCredentials c = (StandardUsernamePasswordCredentials) credentials; - username = c.getUsername(); - password = c.getPassword().getPlainText(); - hash = Util.getDigestOf(password + SALT); // want to ensure pooling by credential - authHash = Util.getDigestOf(password + "::" + jenkins.getLegacyInstanceId()); - } else { - // TODO OAuth support - throw new IOException("Unsupported credential type: " + credentials.getClass().getName()); - } + ConnectionId connectionId = new ConnectionId(apiUrl, hash); - ConnectionId connectionId = new ConnectionId(apiUrl, hash); + synchronized (connections) { + GitHubConnection record = GitHubConnection.lookup(connectionId); + if (record == null) { + Cache cache = getCache(jenkins, apiUrl, authHash, username); - synchronized (connections) { - GitHubConnection record = GitHubConnection.lookup(connectionId); - if (record == null) { - Cache cache = getCache(jenkins, apiUrl, authHash, username); + GitHubBuilder gb = createGitHubBuilder(apiUrl, cache); - GitHubBuilder gb = createGitHubBuilder(apiUrl, cache); + if (gitHubAppCredentials != null) { + gb.withAuthorizationProvider(gitHubAppCredentials.getAuthorizationProvider()); + } else if (username != null && password != null) { + // At the time of this change this works for OAuth tokens as well. + // This may not continue to work in the future, as GitHub has deprecated Login/Password + // credentials. + gb.withAuthorizationProvider( + ImmutableAuthorizationProvider.fromLoginAndPassword(username, password)); + } - if (gitHubAppCredentials != null) { - gb.withAuthorizationProvider(gitHubAppCredentials.getAuthorizationProvider()); - } else if (username != null && password != null) { - // At the time of this change this works for OAuth tokens as well. - // This may not continue to work in the future, as GitHub has deprecated Login/Password credentials. - gb.withAuthorizationProvider(ImmutableAuthorizationProvider.fromLoginAndPassword(username, password)); - } + record = + GitHubConnection.connect( + connectionId, gb.build(), cache, credentials instanceof GitHubAppCredentials); + } - record = GitHubConnection - .connect(connectionId, gb.build(), cache, credentials instanceof GitHubAppCredentials); + return record.getGitHub(); + } + } + + /** + * Creates a {@link GitHubBuilder} that can be used to build a {@link GitHub} instance. + * + *

This method creates and configures a new {@link GitHubBuilder}. This should be used only + * when {@link #connect(String, StandardCredentials)} cannot be used, such as when using {@link + * GitHubBuilder#withJwtToken(String)} to getting the {@link GHAppInstallationToken}. + * + *

This method intentionally does not support caching requests or {@link GitHub} instances. + * + * @param apiUrl the GitHub API URL to be used for the connection + * @return a configured GitHubBuilder instance + * @throws IOException if I/O error occurs + */ + static GitHubBuilder createGitHubBuilder(@Nonnull String apiUrl) throws IOException { + return createGitHubBuilder(apiUrl, null); + } + + @Nonnull + private static GitHubBuilder createGitHubBuilder( + @Nonnull String apiUrl, @CheckForNull Cache cache) throws IOException { + String host; + try { + host = new URL(apiUrl).getHost(); + } catch (MalformedURLException e) { + throw new IOException("Invalid GitHub API URL: " + apiUrl, e); + } - } + GitHubBuilder gb = new GitHubBuilder(); + gb.withEndpoint(apiUrl); + gb.withRateLimitChecker(new ApiRateLimitChecker.RateLimitCheckerAdapter()); + gb.withRateLimitHandler(CUSTOMIZED); - return record.getGitHub(); + OkHttpClient.Builder clientBuilder = baseClient.newBuilder(); + if (JenkinsJVM.isJenkinsJVM()) { + clientBuilder.proxy(getProxy(host)); + } + if (cache != null) { + clientBuilder.cache(cache); + } + gb.withConnector(new OkHttpConnector(clientBuilder.build())); + return gb; + } + + @CheckForNull + private static Cache getCache( + @Nonnull Jenkins jenkins, + @Nonnull String apiUrl, + @Nonnull String authHash, + @CheckForNull String username) { + Cache cache = null; + int cacheSize = GitHubSCMSource.getCacheSize(); + if (cacheSize > 0) { + File cacheBase = new File(jenkins.getRootDir(), GitHubSCMProbe.class.getName() + ".cache"); + File cacheDir = null; + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(apiUrl.getBytes(StandardCharsets.UTF_8)); + sha256.update("::".getBytes(StandardCharsets.UTF_8)); + if (username != null) { + sha256.update(username.getBytes(StandardCharsets.UTF_8)); } + sha256.update("::".getBytes(StandardCharsets.UTF_8)); + sha256.update(authHash.getBytes(StandardCharsets.UTF_8)); + cacheDir = new File(cacheBase, Base64.encodeBase64URLSafeString(sha256.digest())); + } catch (NoSuchAlgorithmException e) { + // no cache for you mr non-spec compliant JVM + } + if (cacheDir != null) { + cache = new Cache(cacheDir, cacheSize * 1024L * 1024L); + } } + return cache; + } - /** - * Creates a {@link GitHubBuilder} that can be used to build a {@link GitHub} instance. - * - * This method creates and configures a new {@link GitHubBuilder}. - * This should be used only when {@link #connect(String, StandardCredentials)} cannot be used, - * such as when using {@link GitHubBuilder#withJwtToken(String)} to getting the {@link GHAppInstallationToken}. - * - * This method intentionally does not support caching requests or {@link GitHub} instances. - * - * @param apiUrl the GitHub API URL to be used for the connection - * @return a configured GitHubBuilder instance - * @throws IOException if I/O error occurs - */ - static GitHubBuilder createGitHubBuilder(@Nonnull String apiUrl) throws IOException { - return createGitHubBuilder(apiUrl, null); + public static void release(@CheckForNull GitHub hub) { + if (hub == null) { + return; } - @Nonnull - private static GitHubBuilder createGitHubBuilder(@Nonnull String apiUrl, @CheckForNull Cache cache) throws IOException { - String host; + synchronized (connections) { + GitHubConnection record = reverseLookup.get(hub); + if (record != null) { try { - host = new URL(apiUrl).getHost(); - } catch (MalformedURLException e) { - throw new IOException("Invalid GitHub API URL: " + apiUrl, e); + record.release(); + } catch (IOException e) { + LOGGER.log(WARNING, "There is a mismatch in connect and release calls.", e); } + } + } + } + + private static CredentialsMatcher githubScanCredentialsMatcher() { + // TODO OAuth credentials + return CredentialsMatchers.anyOf( + CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class)); + } + + static List githubDomainRequirements(String apiUri) { + return URIRequirementBuilder.fromUri( + StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL)) + .build(); + } + + /** + * Uses proxy if configured on pluginManager/advanced page + * + * @param host GitHub's hostname to build proxy to + * @return proxy to use it in connector. Should not be null as it can lead to unexpected behaviour + */ + @Nonnull + private static Proxy getProxy(@Nonnull String host) { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins == null || jenkins.proxy == null) { + return Proxy.NO_PROXY; + } else { + return jenkins.proxy.createProxy(host); + } + } - GitHubBuilder gb = new GitHubBuilder(); - gb.withEndpoint(apiUrl); - gb.withRateLimitChecker(new ApiRateLimitChecker.RateLimitCheckerAdapter()); - gb.withRateLimitHandler(CUSTOMIZED); + /** Fail immediately and throw a customized exception. */ + public static final RateLimitHandler CUSTOMIZED = + new RateLimitHandler() { - OkHttpClient.Builder clientBuilder = baseClient.newBuilder(); - if (JenkinsJVM.isJenkinsJVM()) { - clientBuilder.proxy(getProxy(host)); + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + try { + long limit = Long.parseLong(uc.getHeaderField("X-RateLimit-Limit")); + long remaining = Long.parseLong(uc.getHeaderField("X-RateLimit-Remaining")); + long reset = Long.parseLong(uc.getHeaderField("X-RateLimit-Reset")); + + throw new RateLimitExceededException( + "GitHub API rate limit exceeded", limit, remaining, reset); + } catch (NumberFormatException nfe) { + // Something wrong happened + throw new IOException(nfe); + } } - if (cache != null) { - clientBuilder.cache(cache); + }; + + /** + * Alternative to {@link GitHub#isCredentialValid()} that relies on the cached user object in the + * {@link GitHub} instance and hence reduced rate limit consumption. + * + * @param gitHub the instance to check. + * @return {@code true} if the credentials are valid. + */ + static boolean isCredentialValid(GitHub gitHub) { + if (gitHub.isAnonymous()) { + return true; + } else { + try { + gitHub.getRateLimit(); + return true; + } catch (IOException e) { + if (LOGGER.isLoggable(FINE)) { + LOGGER.log(FINE, "Exception validating credentials on " + gitHub.getApiUrl(), e); } - gb.withConnector(new OkHttpConnector(clientBuilder.build())); - return gb; + return false; + } } - - @CheckForNull - private static Cache getCache(@Nonnull Jenkins jenkins, @Nonnull String apiUrl, @Nonnull String authHash, @CheckForNull String username) { - Cache cache = null; - int cacheSize = GitHubSCMSource.getCacheSize(); - if (cacheSize > 0) { - File cacheBase = new File(jenkins.getRootDir(), - GitHubSCMProbe.class.getName() + ".cache"); - File cacheDir = null; - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - sha256.update(apiUrl.getBytes(StandardCharsets.UTF_8)); - sha256.update("::".getBytes(StandardCharsets.UTF_8)); - if (username != null) { - sha256.update(username.getBytes(StandardCharsets.UTF_8)); - } - sha256.update("::".getBytes(StandardCharsets.UTF_8)); - sha256.update(authHash.getBytes(StandardCharsets.UTF_8)); - cacheDir = new File(cacheBase, Base64.encodeBase64URLSafeString(sha256.digest())); - } catch (NoSuchAlgorithmException e) { - // no cache for you mr non-spec compliant JVM - } - if (cacheDir != null) { - cache = new Cache(cacheDir, cacheSize * 1024L * 1024L); - } - } - return cache; + } + + /*package*/ static void checkConnectionValidity( + String apiUri, @NonNull TaskListener listener, StandardCredentials credentials, GitHub github) + throws IOException { + synchronized (checked) { + Map hubs = checked.get(listener); + if (hubs != null && hubs.containsKey(github)) { + // only check if not already in use + return; + } + if (hubs == null) { + hubs = new WeakHashMap<>(); + checked.put(listener, hubs); + } + hubs.put(github, null); + } + if (credentials != null && !isCredentialValid(github)) { + String message = + String.format( + "Invalid scan credentials %s to connect to %s, skipping", + CredentialsNameProvider.name(credentials), + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); + throw new AbortException(message); + } + if (!github.isAnonymous()) { + assert credentials != null; + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Connecting to %s using %s", + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri, + CredentialsNameProvider.name(credentials)))); + } else { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Connecting to %s with no credentials, anonymous access", + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri))); } + } - public static void release(@CheckForNull GitHub hub) { - if (hub == null) { - return; - } + /*package*/ + static void configureLocalRateLimitChecker(@NonNull TaskListener listener, GitHub github) + throws IOException, InterruptedException { + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + } - synchronized (connections) { - GitHubConnection record = reverseLookup.get(hub); - if (record != null) { - try { - record.release(); - } catch (IOException e) { - LOGGER.log(WARNING, "There is a mismatch in connect and release calls.", e); - } - } - } - } + @Extension + public static class UnusedConnectionDestroyer extends PeriodicWork { - private static CredentialsMatcher githubScanCredentialsMatcher() { - // TODO OAuth credentials - return CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class)); + @Override + public long getRecurrencePeriod() { + return TimeUnit.MINUTES.toMillis(5); } - static List githubDomainRequirements(String apiUri) { - return URIRequirementBuilder.fromUri(StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL)).build(); - } + @Override + protected void doRun() throws Exception { + // Free any connection that is unused (zero refs) + // and has not been looked up or released for the last 30 minutes + long unusedThreshold = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30); - /** - * Uses proxy if configured on pluginManager/advanced page - * - * @param host GitHub's hostname to build proxy to - * - * @return proxy to use it in connector. Should not be null as it can lead to unexpected behaviour - */ - @Nonnull - private static Proxy getProxy(@Nonnull String host) { - Jenkins jenkins = Jenkins.getInstanceOrNull(); - if (jenkins == null || jenkins.proxy == null) { - return Proxy.NO_PROXY; - } else { - return jenkins.proxy.createProxy(host); - } + synchronized (connections) { + GitHubConnection.removeAllUnused(unusedThreshold); + } } + } - /** - * Fail immediately and throw a customized exception. - */ - public static final RateLimitHandler CUSTOMIZED = new RateLimitHandler() { + private static class GitHubConnection { + @NonNull private final GitHub gitHub; - @Override - public void onError(IOException e, HttpURLConnection uc) throws IOException { - try { - long limit = Long.parseLong(uc.getHeaderField("X-RateLimit-Limit")); - long remaining = Long.parseLong(uc.getHeaderField("X-RateLimit-Remaining")); - long reset = Long.parseLong(uc.getHeaderField("X-RateLimit-Reset")); - - throw new RateLimitExceededException("GitHub API rate limit exceeded", limit, remaining, reset); - } catch (NumberFormatException nfe) { - // Something wrong happened - throw new IOException(nfe); - } - } + @CheckForNull private final Cache cache; - }; + private final boolean cleanupCacheFolder; + private int usageCount = 1; + private long lastUsed = System.currentTimeMillis(); + private long lastVerified = Long.MIN_VALUE; + + private GitHubConnection(GitHub gitHub, Cache cache, boolean cleanupCacheFolder) { + this.gitHub = gitHub; + this.cache = cache; + this.cleanupCacheFolder = cleanupCacheFolder; + } /** - * Alternative to {@link GitHub#isCredentialValid()} that relies on the cached user object in the {@link GitHub} - * instance and hence reduced rate limit consumption. + * Gets the {@link GitHub} instance for this connection * - * @param gitHub the instance to check. - * @return {@code true} if the credentials are valid. + * @return the {@link GitHub} instance */ - static boolean isCredentialValid(GitHub gitHub) { - if (gitHub.isAnonymous()) { - return true; - } else { - try { - gitHub.getRateLimit(); - return true; - } catch (IOException e) { - if (LOGGER.isLoggable(FINE)) { - LOGGER.log(FINE, "Exception validating credentials on " + gitHub.getApiUrl(), e); - } - return false; - } - } + public GitHub getGitHub() { + return gitHub; } - /*package*/ static void checkConnectionValidity(String apiUri, @NonNull TaskListener listener, - StandardCredentials credentials, - GitHub github) - throws IOException { - synchronized (checked) { - Map hubs = checked.get(listener); - if (hubs != null && hubs.containsKey(github)) { - // only check if not already in use - return; - } - if (hubs == null) { - hubs = new WeakHashMap<>(); - checked.put(listener, hubs); - } - hubs.put(github, null); - } - if (credentials != null && !isCredentialValid(github)) { - String message = String.format("Invalid scan credentials %s to connect to %s, skipping", - CredentialsNameProvider.name(credentials), apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); - throw new AbortException(message); - } - if (!github.isAnonymous()) { - assert credentials != null; - listener.getLogger().println(GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format("Connecting to %s using %s", - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri, - CredentialsNameProvider.name(credentials)) - )); - } else { - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Connecting to %s with no credentials, anonymous access", - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri - ))); - } + @CheckForNull + private static GitHubConnection lookup(@NonNull ConnectionId connectionId) throws IOException { + GitHubConnection record; + record = connections.get(connectionId); + if (record != null) { + record.verifyConnection(); + record.usageCount += 1; + record.lastUsed = System.currentTimeMillis(); + } + return record; } - /*package*/ - static void configureLocalRateLimitChecker(@NonNull TaskListener listener, GitHub github) - throws IOException, InterruptedException { - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + @NonNull + private static GitHubConnection connect( + @NonNull ConnectionId connectionId, + @NonNull GitHub gitHub, + @CheckForNull Cache cache, + boolean cleanupCacheFolder) + throws IOException { + GitHubConnection record = new GitHubConnection(gitHub, cache, cleanupCacheFolder); + record.verifyConnection(); + connections.put(connectionId, record); + reverseLookup.put(record.gitHub, record); + return record; } - @Extension - public static class UnusedConnectionDestroyer extends PeriodicWork { - - @Override - public long getRecurrencePeriod() { - return TimeUnit.MINUTES.toMillis(5); - } - - @Override - protected void doRun() throws Exception { - // Free any connection that is unused (zero refs) - // and has not been looked up or released for the last 30 minutes - long unusedThreshold = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30); + private void release() throws IOException { + if (this.usageCount <= 0) { + throw new IOException( + "Tried to release a GitHubConnection that should have no references."); + } - synchronized (connections) { - GitHubConnection.removeAllUnused(unusedThreshold); - } - } + this.usageCount -= 1; + this.lastUsed = System.currentTimeMillis(); } - private static class GitHubConnection { - @NonNull - private final GitHub gitHub; - - @CheckForNull - private final Cache cache; - - private final boolean cleanupCacheFolder; - private int usageCount = 1; - private long lastUsed = System.currentTimeMillis(); - private long lastVerified = Long.MIN_VALUE; - - private GitHubConnection(GitHub gitHub, Cache cache, boolean cleanupCacheFolder) { - this.gitHub = gitHub; - this.cache = cache; - this.cleanupCacheFolder = cleanupCacheFolder; - } - - /** - * Gets the {@link GitHub} instance for this connection - * - * @return the {@link GitHub} instance - */ - public GitHub getGitHub() { - return gitHub; - } - - @CheckForNull - private static GitHubConnection lookup(@NonNull ConnectionId connectionId) throws IOException { - GitHubConnection record; - record = connections.get(connectionId); - if (record != null) { - record.verifyConnection(); - record.usageCount += 1; - record.lastUsed = System.currentTimeMillis(); - } - return record; - } - - @NonNull - private static GitHubConnection connect( - @NonNull ConnectionId connectionId, - @NonNull GitHub gitHub, - @CheckForNull Cache cache, - boolean cleanupCacheFolder) throws IOException { - GitHubConnection record = new GitHubConnection(gitHub, cache, cleanupCacheFolder); - record.verifyConnection(); - connections.put(connectionId, record); - reverseLookup.put(record.gitHub, record); - return record; - } - - - private void release() throws IOException { - if (this.usageCount <= 0) { - throw new IOException("Tried to release a GitHubConnection that should have no references."); + private static void removeAllUnused(long threshold) throws IOException { + for (Iterator> iterator = + connections.entrySet().iterator(); + iterator.hasNext(); ) { + Map.Entry entry = iterator.next(); + try { + GitHubConnection record = Objects.requireNonNull(entry.getValue()); + long lastUse = record.lastUsed; + if (record.usageCount == 0 && lastUse < threshold) { + iterator.remove(); + reverseLookup.remove(record.gitHub); + if (record.cache != null && record.cleanupCacheFolder) { + record.cache.delete(); + record.cache.close(); } - - this.usageCount -= 1; - this.lastUsed = System.currentTimeMillis(); + } + } catch (IOException | NullPointerException e) { + LOGGER.log( + WARNING, + "Exception removing cache directory for unused connection: " + entry.getKey(), + e); } + } + } - private static void removeAllUnused(long threshold) throws IOException { - for (Iterator> iterator = connections.entrySet().iterator(); - iterator.hasNext(); ) { - Map.Entry entry = iterator.next(); - try { - GitHubConnection record = Objects.requireNonNull(entry.getValue()); - long lastUse = record.lastUsed; - if (record.usageCount == 0 && lastUse < threshold) { - iterator.remove(); - reverseLookup.remove(record.gitHub); - if (record.cache != null && record.cleanupCacheFolder) { - record.cache.delete(); - record.cache.close(); - } - } - } catch (IOException | NullPointerException e) { - LOGGER.log(WARNING, "Exception removing cache directory for unused connection: " + entry.getKey(), e); - } - } + public void verifyConnection() throws IOException { + synchronized (this) { + if (lastVerified > System.currentTimeMillis() - API_URL_REVALIDATE_MILLIS) { + return; } - - public void verifyConnection() throws IOException { - synchronized (this) { - if (lastVerified > System.currentTimeMillis() - API_URL_REVALIDATE_MILLIS) { - return; - } - try { - gitHub.checkApiUrlValidity(); - } catch (HttpException e) { - String message = String.format("It seems %s is unreachable", gitHub.getApiUrl()); - throw new IOException(message, e); - } - lastVerified = System.currentTimeMillis(); - } + try { + gitHub.checkApiUrlValidity(); + } catch (HttpException e) { + String message = String.format("It seems %s is unreachable", gitHub.getApiUrl()); + throw new IOException(message, e); } + lastVerified = System.currentTimeMillis(); + } } + } - private static class ConnectionId { - private final String apiUrl; - private final String credentialsHash; - - private ConnectionId(String apiUrl, String credentialsHash) { - this.apiUrl = apiUrl; - this.credentialsHash = credentialsHash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ConnectionId that = (ConnectionId) o; + private static class ConnectionId { + private final String apiUrl; + private final String credentialsHash; - if (!Objects.equals(apiUrl, that.apiUrl)) { - return false; - } - return StringUtils.equals(credentialsHash, that.credentialsHash); - } + private ConnectionId(String apiUrl, String credentialsHash) { + this.apiUrl = apiUrl; + this.credentialsHash = credentialsHash; + } - @Override - public int hashCode() { - return apiUrl != null ? apiUrl.hashCode() : 0; - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConnectionId that = (ConnectionId) o; + + if (!Objects.equals(apiUrl, that.apiUrl)) { + return false; + } + return StringUtils.equals(credentialsHash, that.credentialsHash); + } - @Override - public String toString() { - return "ConnectionId{" + - "apiUrl='" + apiUrl + '\'' + - ", credentialsHash=" + credentialsHash + - '}'; - } + @Override + public int hashCode() { + return apiUrl != null ? apiUrl.hashCode() : 0; + } + @Override + public String toString() { + return "ConnectionId{" + + "apiUrl='" + + apiUrl + + '\'' + + ", credentialsHash=" + + credentialsHash + + '}'; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategy.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategy.java index 16328eb8f..12d5baaad 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategy.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategy.java @@ -25,40 +25,37 @@ package org.jenkinsci.plugins.github_branch_source; import hudson.model.TaskListener; - import java.util.Collections; import java.util.List; /** * Default implementation of {@link AbstractGitHubNotificationStrategy} + * * @since 2.3.2 */ public final class DefaultGitHubNotificationStrategy extends AbstractGitHubNotificationStrategy { - /** - * {@inheritDoc} - */ - public List notifications(GitHubNotificationContext notificationContext, TaskListener listener) { - return Collections.singletonList(GitHubNotificationRequest.build(notificationContext.getDefaultContext(listener), - notificationContext.getDefaultUrl(listener), - notificationContext.getDefaultMessage(listener), - notificationContext.getDefaultState(listener), - notificationContext.getDefaultIgnoreError(listener))); - } + /** {@inheritDoc} */ + public List notifications( + GitHubNotificationContext notificationContext, TaskListener listener) { + return Collections.singletonList( + GitHubNotificationRequest.build( + notificationContext.getDefaultContext(listener), + notificationContext.getDefaultUrl(listener), + notificationContext.getDefaultMessage(listener), + notificationContext.getDefaultState(listener), + notificationContext.getDefaultIgnoreError(listener))); + } - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - return this == o || (o != null && getClass() == o.getClass()); - } + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + return this == o || (o != null && getClass() == o.getClass()); + } - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return 42; - } + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 42; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java index 9bd208dee..d2ceee091 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/Endpoint.java @@ -32,16 +32,14 @@ import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import hudson.util.FormValidation; - import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectStreamException; import java.net.MalformedURLException; import java.net.URL; import java.util.Objects; -import java.util.logging.Logger; import java.util.logging.Level; - +import java.util.logging.Logger; import jenkins.model.Jenkins; import jenkins.scm.api.SCMName; import org.apache.commons.lang.StringUtils; @@ -52,135 +50,140 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -/** - * @author Stephen Connolly - */ +/** @author Stephen Connolly */ public class Endpoint extends AbstractDescribableImpl { - /** - * Common prefixes that we should remove when inferring a display name. - */ - private static final String[] COMMON_PREFIX_HOSTNAMES = { - "git.", - "github.", - "vcs.", - "scm.", - "source." - }; - - private final String name; - private final String apiUri; - - @DataBoundConstructor - public Endpoint(String apiUri, String name) { - this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); - if (StringUtils.isBlank(name)) { - this.name = SCMName.fromUrl(this.apiUri, COMMON_PREFIX_HOSTNAMES); - } else { - this.name = name.trim(); - } + /** Common prefixes that we should remove when inferring a display name. */ + private static final String[] COMMON_PREFIX_HOSTNAMES = { + "git.", "github.", "vcs.", "scm.", "source." + }; + + private final String name; + private final String apiUri; + + @DataBoundConstructor + public Endpoint(String apiUri, String name) { + this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); + if (StringUtils.isBlank(name)) { + this.name = SCMName.fromUrl(this.apiUri, COMMON_PREFIX_HOSTNAMES); + } else { + this.name = name.trim(); } + } - private Object readResolve() throws ObjectStreamException { - if (!apiUri.equals(GitHubConfiguration.normalizeApiUri(apiUri))) { - return new Endpoint(apiUri, name); - } - return this; + private Object readResolve() throws ObjectStreamException { + if (!apiUri.equals(GitHubConfiguration.normalizeApiUri(apiUri))) { + return new Endpoint(apiUri, name); } - - @NonNull - public String getApiUri() { - return apiUri; + return this; + } + + @NonNull + public String getApiUri() { + return apiUri; + } + + @CheckForNull + public String getName() { + return name; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Endpoint{"); + sb.append("apiUrl='").append(apiUri).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @CheckForNull - public String getName() { - return name; + if (!(o instanceof Endpoint)) { + return false; } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("Endpoint{"); - sb.append("apiUrl='").append(apiUri).append('\''); - sb.append(", name='").append(name).append('\''); - sb.append('}'); - return sb.toString(); + Endpoint endpoint = (Endpoint) o; + + if (!Objects.equals(apiUri, endpoint.apiUri)) { + return false; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Endpoint)) { - return false; - } + return true; + } - Endpoint endpoint = (Endpoint) o; + @Override + public int hashCode() { + return apiUri != null ? apiUri.hashCode() : 0; + } - if (!Objects.equals(apiUri, endpoint.apiUri)) { - return false; - } + @Extension + public static class DescriptorImpl extends Descriptor { - return true; - } + private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); @Override - public int hashCode() { - return apiUri != null ? apiUri.hashCode() : 0; + public String getDisplayName() { + return ""; } - @Extension - public static class DescriptorImpl extends Descriptor { - - private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); - - @Override - public String getDisplayName() { - return ""; - } - - @RequirePOST - @Restricted(NoExternalUse.class) - public FormValidation doCheckApiUri(@QueryParameter String apiUri) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); - if (Util.fixEmptyAndTrim(apiUri) == null) { - return FormValidation.warning("You must specify the API URL"); - } - try { - URL api = new URL(apiUri); - GitHub github = GitHub.connectToEnterpriseAnonymously(api.toString()); - github.checkApiUrlValidity(); - LOGGER.log(Level.FINE, "Trying to configure a GitHub Enterprise server"); - // For example: https://api.github.com/ or https://github.mycompany.com/api/v3/ (with private mode disabled). - return FormValidation.ok("GitHub Enterprise server verified"); - } catch (MalformedURLException mue) { - // For example: https:/api.github.com - LOGGER.log(Level.WARNING, "Trying to configure a GitHub Enterprise server: " + apiUri, mue.getCause()); - return FormValidation.error("The endpoint does not look like a GitHub Enterprise (malformed URL)"); - } catch (JsonParseException jpe) { - LOGGER.log(Level.WARNING, "Trying to configure a GitHub Enterprise server: " + apiUri, jpe.getCause()); - return FormValidation.error("The endpoint does not look like a GitHub Enterprise (invalid JSON response)"); - } catch (FileNotFoundException fnt) { - // For example: https://github.mycompany.com/server/api/v3/ gets a FileNotFoundException - LOGGER.log(Level.WARNING, "Getting HTTP Error 404 for " + apiUri); - return FormValidation.error("The endpoint does not look like a GitHub Enterprise (page not found"); - } catch (IOException e) { - // For example: https://github.mycompany.com/api/v3/ or https://github.mycompany.com/api/v3/mypath - if (e.getMessage().contains("private mode enabled")) { - LOGGER.log(Level.FINE, e.getMessage()); - return FormValidation.warning("Private mode enabled, validation disabled"); - } - LOGGER.log(Level.WARNING, e.getMessage()); - return FormValidation.error("The endpoint does not look like a GitHub Enterprise (verify network and/or try again later)"); - } + @RequirePOST + @Restricted(NoExternalUse.class) + public FormValidation doCheckApiUri(@QueryParameter String apiUri) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + if (Util.fixEmptyAndTrim(apiUri) == null) { + return FormValidation.warning("You must specify the API URL"); + } + try { + URL api = new URL(apiUri); + GitHub github = GitHub.connectToEnterpriseAnonymously(api.toString()); + github.checkApiUrlValidity(); + LOGGER.log(Level.FINE, "Trying to configure a GitHub Enterprise server"); + // For example: https://api.github.com/ or https://github.mycompany.com/api/v3/ (with + // private mode disabled). + return FormValidation.ok("GitHub Enterprise server verified"); + } catch (MalformedURLException mue) { + // For example: https:/api.github.com + LOGGER.log( + Level.WARNING, + "Trying to configure a GitHub Enterprise server: " + apiUri, + mue.getCause()); + return FormValidation.error( + "The endpoint does not look like a GitHub Enterprise (malformed URL)"); + } catch (JsonParseException jpe) { + LOGGER.log( + Level.WARNING, + "Trying to configure a GitHub Enterprise server: " + apiUri, + jpe.getCause()); + return FormValidation.error( + "The endpoint does not look like a GitHub Enterprise (invalid JSON response)"); + } catch (FileNotFoundException fnt) { + // For example: https://github.mycompany.com/server/api/v3/ gets a FileNotFoundException + LOGGER.log(Level.WARNING, "Getting HTTP Error 404 for " + apiUri); + return FormValidation.error( + "The endpoint does not look like a GitHub Enterprise (page not found"); + } catch (IOException e) { + // For example: https://github.mycompany.com/api/v3/ or + // https://github.mycompany.com/api/v3/mypath + if (e.getMessage().contains("private mode enabled")) { + LOGGER.log(Level.FINE, e.getMessage()); + return FormValidation.warning("Private mode enabled, validation disabled"); } + LOGGER.log(Level.WARNING, e.getMessage()); + return FormValidation.error( + "The endpoint does not look like a GitHub Enterprise (verify network and/or try again later)"); + } + } - @Restricted(NoExternalUse.class) - public FormValidation doCheckName(@QueryParameter String name) { - if (Util.fixEmptyAndTrim(name) == null) { - return FormValidation.warning("A name is recommended to help differentiate similar endpoints"); - } - return FormValidation.ok(); - } + @Restricted(NoExternalUse.class) + public FormValidation doCheckName(@QueryParameter String name) { + if (Util.fixEmptyAndTrim(name) == null) { + return FormValidation.warning( + "A name is recommended to help differentiate similar endpoints"); + } + return FormValidation.ok(); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeArchivedRepositoriesTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeArchivedRepositoriesTrait.java index 9b446aaf9..5be67c2be 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeArchivedRepositoriesTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeArchivedRepositoriesTrait.java @@ -1,6 +1,7 @@ package org.jenkinsci.plugins.github_branch_source; import hudson.Extension; +import javax.annotation.Nonnull; import jenkins.scm.api.trait.SCMNavigatorContext; import jenkins.scm.api.trait.SCMNavigatorTrait; import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; @@ -8,47 +9,39 @@ import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; -import javax.annotation.Nonnull; - /** - * A {@link Selection} trait that will restrict the discovery of repositories that have been archived. + * A {@link Selection} trait that will restrict the discovery of repositories that have been + * archived. */ public class ExcludeArchivedRepositoriesTrait extends SCMNavigatorTrait { - /** - * Constructor for stapler. - */ - @DataBoundConstructor - public ExcludeArchivedRepositoriesTrait() { - } + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludeArchivedRepositoriesTrait() {} - /** - * {@inheritDoc} - */ - @Override - protected void decorateContext(SCMNavigatorContext context) { - super.decorateContext(context); - GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; - ctx.setExcludeArchivedRepositories(true); - } + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludeArchivedRepositories(true); + } - /** - * Exclude archived repositories filter - */ - @Symbol("gitHubExcludeArchivedRepositories") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + /** Exclude archived repositories filter */ + @Symbol("gitHubExcludeArchivedRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { - @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; - } + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } - @Nonnull - @Override - public String getDisplayName() { - return Messages.ExcludeArchivedRepositoriesTrait_displayName(); - } + @Nonnull + @Override + public String getDisplayName() { + return Messages.ExcludeArchivedRepositoriesTrait_displayName(); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePublicRepositoriesTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePublicRepositoriesTrait.java index e1ff5c31b..bed571c72 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePublicRepositoriesTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePublicRepositoriesTrait.java @@ -1,6 +1,7 @@ package org.jenkinsci.plugins.github_branch_source; import hudson.Extension; +import javax.annotation.Nonnull; import jenkins.scm.api.trait.SCMNavigatorContext; import jenkins.scm.api.trait.SCMNavigatorTrait; import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; @@ -8,47 +9,36 @@ import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; -import javax.annotation.Nonnull; - -/** - * A {@link Selection} trait that will restrict the discovery of repositories that are public. - */ +/** A {@link Selection} trait that will restrict the discovery of repositories that are public. */ public class ExcludePublicRepositoriesTrait extends SCMNavigatorTrait { - /** - * Constructor for stapler. - */ - @DataBoundConstructor - public ExcludePublicRepositoriesTrait() { - } + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludePublicRepositoriesTrait() {} - /** - * {@inheritDoc} - */ - @Override - protected void decorateContext(SCMNavigatorContext context) { - super.decorateContext(context); - GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; - ctx.setExcludePublicRepositories(true); - } + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludePublicRepositories(true); + } - /** - * Exclude archived repositories filter - */ - @Symbol("gitHubExcludePublicRepositories") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + /** Exclude archived repositories filter */ + @Symbol("gitHubExcludePublicRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { - @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; - } + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } - @Nonnull - @Override - public String getDisplayName() { - return Messages.ExcludePublicRepositoriesTrait_displayName(); - } + @Nonnull + @Override + public String getDisplayName() { + return Messages.ExcludePublicRepositoriesTrait_displayName(); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java index b0a8c059a..a52b75914 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/FillErrorResponse.java @@ -12,23 +12,26 @@ // TODO replace with corresponding core functionality once Jenkins core has JENKINS-42443 class FillErrorResponse extends IOException implements HttpResponse { - private final boolean clearList; + private final boolean clearList; - public FillErrorResponse(String message, boolean clearList) { - super(message); - this.clearList = clearList; - } + public FillErrorResponse(String message, boolean clearList) { + super(message); + this.clearList = clearList; + } - @Override - public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) - throws IOException, ServletException { - rsp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - rsp.setContentType("text/html;charset=UTF-8"); - rsp.setHeader("X-Jenkins-Select-Error", clearList ? "clear" : "retain"); - rsp.getWriter().print( - "

" + Util.escape(getMessage()) + - "
"); - - } + @Override + public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) + throws IOException, ServletException { + rsp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + rsp.setContentType("text/html;charset=UTF-8"); + rsp.setHeader("X-Jenkins-Select-Error", clearList ? "clear" : "retain"); + rsp.getWriter() + .print( + "
" + + Util.escape(getMessage()) + + "
"); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait.java index 0e07170ce..5037b77d8 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait.java @@ -51,385 +51,336 @@ import org.kohsuke.stapler.DataBoundConstructor; /** - * A {@link Discovery} trait for GitHub that will discover pull requests from forks of the repository. + * A {@link Discovery} trait for GitHub that will discover pull requests from forks of the + * repository. * * @since 2.2.0 */ public class ForkPullRequestDiscoveryTrait extends SCMSourceTrait { - /** - * None strategy. - */ - public static final int NONE = 0; - /** - * Merging the pull request with the current target branch revision. - */ - public static final int MERGE = 1; - /** - * The current pull request revision. - */ - public static final int HEAD = 2; - /** - * Both the current pull request revision and the pull request merged with the current target branch revision. - */ - public static final int HEAD_AND_MERGE = 3; - /** - * The strategy encoded as a bit-field. - */ - private final int strategyId; - /** - * The authority. - */ - @NonNull - private final SCMHeadAuthority trust; + /** None strategy. */ + public static final int NONE = 0; + /** Merging the pull request with the current target branch revision. */ + public static final int MERGE = 1; + /** The current pull request revision. */ + public static final int HEAD = 2; + /** + * Both the current pull request revision and the pull request merged with the current target + * branch revision. + */ + public static final int HEAD_AND_MERGE = 3; + /** The strategy encoded as a bit-field. */ + private final int strategyId; + /** The authority. */ + @NonNull + private final SCMHeadAuthority< + ? super GitHubSCMSourceRequest, ? extends ChangeRequestSCMHead2, ? extends SCMRevision> + trust; + + /** + * Constructor for stapler. + * + * @param strategyId the strategy id. + * @param trust the authority to use. + */ + @DataBoundConstructor + public ForkPullRequestDiscoveryTrait( + int strategyId, + @NonNull + SCMHeadAuthority< + ? super GitHubSCMSourceRequest, + ? extends ChangeRequestSCMHead2, + ? extends SCMRevision> + trust) { + this.strategyId = strategyId; + this.trust = trust; + } + + /** + * Constructor for programmatic instantiation. + * + * @param strategies the {@link ChangeRequestCheckoutStrategy} instances. + * @param trust the authority. + */ + public ForkPullRequestDiscoveryTrait( + @NonNull Set strategies, + @NonNull + SCMHeadAuthority< + ? super GitHubSCMSourceRequest, + ? extends ChangeRequestSCMHead2, + ? extends SCMRevision> + trust) { + this( + (strategies.contains(ChangeRequestCheckoutStrategy.MERGE) ? MERGE : NONE) + + (strategies.contains(ChangeRequestCheckoutStrategy.HEAD) ? HEAD : NONE), + trust); + } + + /** + * Gets the strategy id. + * + * @return the strategy id. + */ + public int getStrategyId() { + return strategyId; + } + + /** + * Returns the strategies. + * + * @return the strategies. + */ + @NonNull + public Set getStrategies() { + switch (strategyId) { + case ForkPullRequestDiscoveryTrait.MERGE: + return EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); + case ForkPullRequestDiscoveryTrait.HEAD: + return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD); + case ForkPullRequestDiscoveryTrait.HEAD_AND_MERGE: + return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD, ChangeRequestCheckoutStrategy.MERGE); + default: + return EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + } + } + + /** + * Gets the authority. + * + * @return the authority. + */ + @NonNull + public SCMHeadAuthority< + ? super GitHubSCMSourceRequest, ? extends ChangeRequestSCMHead2, ? extends SCMRevision> + getTrust() { + return trust; + } + + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantForkPRs(true); + ctx.withAuthority(trust); + ctx.withForkPRStrategies(getStrategies()); + } + + /** {@inheritDoc} */ + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category instanceof ChangeRequestSCMHeadCategory; + } + + /** Our descriptor. */ + @Symbol("gitHubForkDiscovery") + @Extension + @Discovery + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.ForkPullRequestDiscoveryTrait_displayName(); + } - /** - * Constructor for stapler. - * - * @param strategyId the strategy id. - * @param trust the authority to use. - */ - @DataBoundConstructor - public ForkPullRequestDiscoveryTrait(int strategyId, - @NonNull SCMHeadAuthority trust) { - this.strategyId = strategyId; - this.trust = trust; + /** {@inheritDoc} */ + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; } - /** - * Constructor for programmatic instantiation. - * - * @param strategies the {@link ChangeRequestCheckoutStrategy} instances. - * @param trust the authority. - */ - public ForkPullRequestDiscoveryTrait(@NonNull Set strategies, - @NonNull SCMHeadAuthority trust) { - this((strategies.contains(ChangeRequestCheckoutStrategy.MERGE) ? MERGE : NONE) - + (strategies.contains(ChangeRequestCheckoutStrategy.HEAD) ? HEAD : NONE), trust); + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; } /** - * Gets the strategy id. + * Populates the strategy options. * - * @return the strategy id. + * @return the strategy options. */ - public int getStrategyId() { - return strategyId; + @NonNull + @Restricted(NoExternalUse.class) + @SuppressWarnings("unused") // stapler + public ListBoxModel doFillStrategyIdItems() { + ListBoxModel result = new ListBoxModel(); + result.add(Messages.ForkPullRequestDiscoveryTrait_mergeOnly(), String.valueOf(MERGE)); + result.add(Messages.ForkPullRequestDiscoveryTrait_headOnly(), String.valueOf(HEAD)); + result.add( + Messages.ForkPullRequestDiscoveryTrait_headAndMerge(), String.valueOf(HEAD_AND_MERGE)); + return result; } /** - * Returns the strategies. + * Returns the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. * - * @return the strategies. + * @return the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. */ @NonNull - public Set getStrategies() { - switch (strategyId) { - case ForkPullRequestDiscoveryTrait.MERGE: - return EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); - case ForkPullRequestDiscoveryTrait.HEAD: - return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD); - case ForkPullRequestDiscoveryTrait.HEAD_AND_MERGE: - return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD, ChangeRequestCheckoutStrategy.MERGE); - default: - return EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - } + @SuppressWarnings("unused") // stapler + public List getTrustDescriptors() { + return SCMHeadAuthority._for( + GitHubSCMSourceRequest.class, + PullRequestSCMHead.class, + PullRequestSCMRevision.class, + SCMHeadOrigin.Fork.class); } /** - * Gets the authority. + * Returns the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. * - * @return the authority. + * @return the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. */ @NonNull - public SCMHeadAuthority getTrust() { - return trust; + @SuppressWarnings("unused") // stapler + public SCMHeadAuthority getDefaultTrust() { + return new TrustPermission(); } + } - /** - * {@inheritDoc} - */ - @Override - protected void decorateContext(SCMSourceContext context) { - GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; - ctx.wantForkPRs(true); - ctx.withAuthority(trust); - ctx.withForkPRStrategies(getStrategies()); - } + /** An {@link SCMHeadAuthority} that trusts nothing. */ + public static class TrustNobody + extends SCMHeadAuthority { - /** - * {@inheritDoc} - */ + /** Constructor. */ + @DataBoundConstructor + public TrustNobody() {} + + /** {@inheritDoc} */ @Override - public boolean includeCategory(@NonNull SCMHeadCategory category) { - return category instanceof ChangeRequestSCMHeadCategory; + public boolean checkTrusted( + @NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { + return false; } - /** - * Our descriptor. - */ - @Symbol("gitHubForkDiscovery") + /** Our descriptor. */ + @Symbol("gitHubTrustNobody") @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.ForkPullRequestDiscoveryTrait_displayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } - - /** - * Populates the strategy options. - * - * @return the strategy options. - */ - @NonNull - @Restricted(NoExternalUse.class) - @SuppressWarnings("unused") // stapler - public ListBoxModel doFillStrategyIdItems() { - ListBoxModel result = new ListBoxModel(); - result.add(Messages.ForkPullRequestDiscoveryTrait_mergeOnly(), String.valueOf(MERGE)); - result.add(Messages.ForkPullRequestDiscoveryTrait_headOnly(), String.valueOf(HEAD)); - result.add(Messages.ForkPullRequestDiscoveryTrait_headAndMerge(), String.valueOf(HEAD_AND_MERGE)); - return result; - } + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.ForkPullRequestDiscoveryTrait_nobodyDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); + } + } + } - /** - * Returns the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. - * - * @return the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. - */ - @NonNull - @SuppressWarnings("unused") // stapler - public List getTrustDescriptors() { - return SCMHeadAuthority._for( - GitHubSCMSourceRequest.class, - PullRequestSCMHead.class, - PullRequestSCMRevision.class, - SCMHeadOrigin.Fork.class - ); - } + /** An {@link SCMHeadAuthority} that trusts contributors to the repository. */ + public static class TrustContributors + extends SCMHeadAuthority { + /** Constructor. */ + @DataBoundConstructor + public TrustContributors() {} - /** - * Returns the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. - * - * @return the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. - */ - @NonNull - @SuppressWarnings("unused") // stapler - public SCMHeadAuthority getDefaultTrust() { - return new TrustPermission(); - } + /** {@inheritDoc} */ + @Override + protected boolean checkTrusted( + @NonNull GitHubSCMSourceRequest request, @NonNull PullRequestSCMHead head) { + return !head.getOrigin().equals(SCMHeadOrigin.DEFAULT) + && request.getCollaboratorNames().contains(head.getSourceOwner()); } + /** Our descriptor. */ + @Symbol("gitHubTrustContributors") + @Extension + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.ForkPullRequestDiscoveryTrait_contributorsDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); + } + } + } - /** - * An {@link SCMHeadAuthority} that trusts nothing. - */ - public static class TrustNobody extends SCMHeadAuthority { + /** An {@link SCMHeadAuthority} that trusts those with write permission to the repository. */ + public static class TrustPermission + extends SCMHeadAuthority { - /** - * Constructor. - */ - @DataBoundConstructor - public TrustNobody() { - } + /** Constructor. */ + @DataBoundConstructor + public TrustPermission() {} - /** - * {@inheritDoc} - */ - @Override - public boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { + /** {@inheritDoc} */ + @Override + protected boolean checkTrusted( + @NonNull GitHubSCMSourceRequest request, @NonNull PullRequestSCMHead head) + throws IOException, InterruptedException { + if (!head.getOrigin().equals(SCMHeadOrigin.DEFAULT)) { + GHPermissionType permission = request.getPermissions(head.getSourceOwner()); + switch (permission) { + case ADMIN: + case WRITE: + return true; + default: return false; } - - /** - * Our descriptor. - */ - @Symbol("gitHubTrustNobody") - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.ForkPullRequestDiscoveryTrait_nobodyDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); - } - } + } + return false; } - /** - * An {@link SCMHeadAuthority} that trusts contributors to the repository. - */ - public static class TrustContributors - extends SCMHeadAuthority { - /** - * Constructor. - */ - @DataBoundConstructor - public TrustContributors() { - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull GitHubSCMSourceRequest request, @NonNull PullRequestSCMHead head) { - return !head.getOrigin().equals(SCMHeadOrigin.DEFAULT) - && request.getCollaboratorNames().contains(head.getSourceOwner()); - } - - /** - * Our descriptor. - */ - @Symbol("gitHubTrustContributors") - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.ForkPullRequestDiscoveryTrait_contributorsDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); - } - - } + /** Our descriptor. */ + @Symbol("gitHubTrustPermissions") + @Extension + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.ForkPullRequestDiscoveryTrait_permissionsDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); + } } + } - /** - * An {@link SCMHeadAuthority} that trusts those with write permission to the repository. - */ - public static class TrustPermission - extends SCMHeadAuthority { - - /** - * Constructor. - */ - @DataBoundConstructor - public TrustPermission() { - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull GitHubSCMSourceRequest request, @NonNull PullRequestSCMHead head) - throws IOException, InterruptedException { - if (!head.getOrigin().equals(SCMHeadOrigin.DEFAULT)) { - GHPermissionType permission = request.getPermissions(head.getSourceOwner()); - switch (permission) { - case ADMIN: - case WRITE: - return true; - default:return false; - } - } - return false; - } + /** An {@link SCMHeadAuthority} that trusts everyone. */ + public static class TrustEveryone + extends SCMHeadAuthority { + /** Constructor. */ + @DataBoundConstructor + public TrustEveryone() {} - /** - * Our descriptor. - */ - @Symbol("gitHubTrustPermissions") - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.ForkPullRequestDiscoveryTrait_permissionsDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); - } - } + /** {@inheritDoc} */ + @Override + protected boolean checkTrusted( + @NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { + return true; } - /** - * An {@link SCMHeadAuthority} that trusts everyone. - */ - public static class TrustEveryone extends SCMHeadAuthority { - /** - * Constructor. - */ - @DataBoundConstructor - public TrustEveryone() { - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { - return true; - } - - /** - * Our descriptor. - */ - @Symbol("gitHubTrustEveryone") - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.ForkPullRequestDiscoveryTrait_everyoneDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); - } - } + /** Our descriptor. */ + @Symbol("gitHubTrustEveryone") + @Extension + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.ForkPullRequestDiscoveryTrait_everyoneDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Fork.class.isAssignableFrom(originClass); + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java index 53bcf8b18..b6b43d88d 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java @@ -1,5 +1,7 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator.DescriptorImpl.getPossibleApiUriItems; + import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; @@ -19,11 +21,9 @@ import java.time.Duration; import java.time.Instant; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; - import jenkins.security.SlaveToMasterCallable; import jenkins.util.JenkinsJVM; import net.sf.json.JSONObject; @@ -41,595 +41,627 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; -import static org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator.DescriptorImpl.getPossibleApiUriItems; - @SuppressFBWarnings(value = "SE_NO_SERIALVERSIONID", justification = "XStream") -public class GitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { - - private static final Logger LOGGER = Logger.getLogger(GitHubAppCredentials.class.getName()); +public class GitHubAppCredentials extends BaseStandardCredentials + implements StandardUsernamePasswordCredentials { + + private static final Logger LOGGER = Logger.getLogger(GitHubAppCredentials.class.getName()); + + private static final String ERROR_AUTHENTICATING_GITHUB_APP = + "Couldn't authenticate with GitHub app ID %s"; + private static final String NOT_INSTALLED = + ", has it been installed to your GitHub organisation / user?"; + + private static final String ERROR_NOT_INSTALLED = ERROR_AUTHENTICATING_GITHUB_APP + NOT_INSTALLED; + + /** + * When a new {@link AppInstallationToken} is generated, wait this many seconds before continuing. + * Has no effect when a cached token is used, only when a new token is generated. + * + *

Provided as one more possible avenue for debugging/stabilizing JENKINS-62249. + */ + private static long AFTER_TOKEN_GENERATION_DELAY_SECONDS = + Long.getLong( + GitHubAppCredentials.class.getName() + ".AFTER_TOKEN_GENERATION_DELAY_SECONDS", 0); + + @NonNull private final String appID; + + @NonNull private final Secret privateKey; + + private String apiUri; + + private String owner; + + private transient AppInstallationToken cachedToken; + + @DataBoundConstructor + @SuppressWarnings("unused") // by stapler + public GitHubAppCredentials( + CredentialsScope scope, + String id, + @CheckForNull String description, + @NonNull String appID, + @NonNull Secret privateKey) { + super(scope, id, description); + this.appID = appID; + this.privateKey = privateKey; + } + + public String getApiUri() { + return apiUri; + } + + @DataBoundSetter + public void setApiUri(String apiUri) { + this.apiUri = apiUri; + } + + @NonNull + public String getAppID() { + return appID; + } + + @NonNull + public Secret getPrivateKey() { + return privateKey; + } + + /** + * Owner of this installation, i.e. a user or organisation, used to differeniate app installations + * when the app is installed to multiple organisations / users. + * + *

If this is null then call listInstallations and if there's only one in the list then use + * that installation. + * + * @return the owner of the organisation or null. + */ + @CheckForNull + public String getOwner() { + return owner; + } + + @DataBoundSetter + public void setOwner(String owner) { + this.owner = Util.fixEmpty(owner); + } + + @SuppressWarnings("deprecation") + AuthorizationProvider getAuthorizationProvider() { + return new CredentialsTokenProvider(this); + } + + private static AuthorizationProvider createJwtProvider(String appId, String appPrivateKey) { + try { + return new JWTTokenProvider(appId, appPrivateKey); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException( + "Couldn't parse private key for GitHub app, make sure it's PKCS#8 format", e); + } + } - private static final String ERROR_AUTHENTICATING_GITHUB_APP = "Couldn't authenticate with GitHub app ID %s"; - private static final String NOT_INSTALLED = ", has it been installed to your GitHub organisation / user?"; + private abstract static class TokenProvider extends GitHub.DependentAuthorizationProvider { - private static final String ERROR_NOT_INSTALLED = ERROR_AUTHENTICATING_GITHUB_APP + NOT_INSTALLED; + protected TokenProvider(String appID, String privateKey) { + super(createJwtProvider(appID, privateKey)); + } /** - * When a new {@link AppInstallationToken} is generated, wait this many seconds before continuing. - * Has no effect when a cached token is used, only when a new token is generated. + * Create and return the specialized GitHub instance to be used for refreshing + * AppInstallationToken * - * Provided as one more possible avenue for debugging/stabilizing JENKINS-62249. + *

The {@link GitHub.DependentAuthorizationProvider} provides a specialized GitHub instance + * that uses JWT for authorization and does not check rate limit since it doesn't apply for the + * App endpoints when using JWT. */ - private static long AFTER_TOKEN_GENERATION_DELAY_SECONDS = - Long.getLong(GitHubAppCredentials.class.getName() + ".AFTER_TOKEN_GENERATION_DELAY_SECONDS", 0); - - @NonNull - private final String appID; + static GitHub createTokenRefreshGitHub(String appId, String appPrivateKey, String apiUrl) + throws IOException { + TokenProvider provider = + new TokenProvider(appId, appPrivateKey) { + @Override + public String getEncodedAuthorization() throws IOException { + // Will never be called + return null; + } + }; + Connector.createGitHubBuilder(apiUrl).withAuthorizationProvider(provider).build(); - @NonNull - private final Secret privateKey; - - private String apiUri; - - private String owner; - - private transient AppInstallationToken cachedToken; - - @DataBoundConstructor - @SuppressWarnings("unused") // by stapler - public GitHubAppCredentials( - CredentialsScope scope, - String id, - @CheckForNull String description, - @NonNull String appID, - @NonNull Secret privateKey - ) { - super(scope, id, description); - this.appID = appID; - this.privateKey = privateKey; + return provider.gitHub(); } + } - public String getApiUri() { - return apiUri; - } + private static class CredentialsTokenProvider extends TokenProvider { + private final GitHubAppCredentials credentials; - @DataBoundSetter - public void setApiUri(String apiUri) { - this.apiUri = apiUri; + CredentialsTokenProvider(GitHubAppCredentials credentials) { + super(credentials.appID, credentials.privateKey.getPlainText()); + this.credentials = credentials; } - @NonNull - public String getAppID() { - return appID; + public String getEncodedAuthorization() throws IOException { + Secret token = credentials.getToken(gitHub()).getToken(); + return String.format("token %s", token.getPlainText()); } + } + + @SuppressWarnings( + "deprecation") // preview features are required for GitHub app integration, GitHub api adds + // deprecated to all preview methods + static AppInstallationToken generateAppInstallationToken( + GitHub gitHubApp, String appId, String appPrivateKey, String apiUrl, String owner) { + JenkinsJVM.checkJenkinsJVM(); + // We expect this to be fast but if anything hangs in here we do not want to block indefinitely + + try (Timeout ignored = Timeout.limit(30, TimeUnit.SECONDS)) { + if (gitHubApp == null) { + gitHubApp = TokenProvider.createTokenRefreshGitHub(appId, appPrivateKey, apiUrl); + } + + GHApp app; + try { + app = gitHubApp.getApp(); + } catch (IOException e) { + throw new IllegalArgumentException( + String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId), e); + } + + List appInstallations = app.listInstallations().asList(); + if (appInstallations.isEmpty()) { + throw new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId)); + } + GHAppInstallation appInstallation; + if (appInstallations.size() == 1) { + appInstallation = appInstallations.get(0); + } else { + appInstallation = + appInstallations.stream() + .filter(installation -> installation.getAccount().getLogin().equals(owner)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId))); + } + + GHAppInstallationToken appInstallationToken = + appInstallation.createToken(appInstallation.getPermissions()).create(); + + long expiration = getExpirationSeconds(appInstallationToken); + AppInstallationToken token = + new AppInstallationToken(Secret.fromString(appInstallationToken.getToken()), expiration); + LOGGER.log(Level.FINER, "Generated App Installation Token for app ID {0}", appId); + LOGGER.log( + Level.FINEST, + () -> "Generated App Installation Token at " + Instant.now().toEpochMilli()); + + if (AFTER_TOKEN_GENERATION_DELAY_SECONDS > 0) { + // Delay can be up to 10 seconds. + long tokenDelay = Math.min(10, AFTER_TOKEN_GENERATION_DELAY_SECONDS); + LOGGER.log(Level.FINER, "Waiting {0} seconds after token generation", tokenDelay); + Thread.sleep(Duration.ofSeconds(tokenDelay).toMillis()); + } + + return token; + } catch (IOException | InterruptedException e) { + throw new IllegalArgumentException( + "Failed to generate GitHub App installation token for app ID " + appId, e); + } + } + + private static long getExpirationSeconds(GHAppInstallationToken appInstallationToken) { + try { + return appInstallationToken.getExpiresAt().toInstant().getEpochSecond(); + } catch (Exception e) { + // if we fail to calculate the expiration, guess at a reasonable value. + LOGGER.log(Level.WARNING, "Unable to get GitHub App installation token expiration", e); + return Instant.now().getEpochSecond() + AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; + } + } + + @NonNull + String actualApiUri() { + return Util.fixEmpty(apiUri) == null ? "https://api.github.com" : apiUri; + } + + private AppInstallationToken getToken(GitHub gitHub) { + synchronized (this) { + try { + if (cachedToken == null || cachedToken.isStale()) { + LOGGER.log(Level.FINE, "Generating App Installation Token for app ID {0}", appID); + cachedToken = + generateAppInstallationToken( + gitHub, appID, privateKey.getPlainText(), actualApiUri(), owner); + LOGGER.log(Level.FINER, "Retrieved GitHub App Installation Token for app ID {0}", appID); + } + } catch (Exception e) { + if (cachedToken != null && !cachedToken.isExpired()) { + // Requesting a new token failed. If the cached token is not expired, continue to use it. + // This minimizes failures due to occasional network instability, + // while only slightly increasing the chance that tokens will expire while in use. + LOGGER.log( + Level.WARNING, + "Failed to generate new GitHub App Installation Token for app ID " + + appID + + ": cached token is stale but has not expired", + e); + } else { + throw e; + } + } + LOGGER.log(Level.FINEST, "Returned GitHub App Installation Token for app ID {0}", appID); - @NonNull - public Secret getPrivateKey() { - return privateKey; + return cachedToken; + } + } + + /** {@inheritDoc} */ + @NonNull + @Override + public Secret getPassword() { + return this.getToken(null).getToken(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getUsername() { + return appID; + } + + private AppInstallationToken getCachedToken() { + synchronized (this) { + return cachedToken; } + } + static class AppInstallationToken implements Serializable { /** - * Owner of this installation, i.e. a user or organisation, - * used to differeniate app installations when the app is installed to multiple organisations / users. + * {@link #getPassword()} checks that the token is still valid before returning it. The token + * generally will not expire for at least this amount of time after it is returned. * - * If this is null then call listInstallations and if there's only one in the list then use that installation. + *

Using a larger value will result in longer time-to-live for the token, but also more + * network calls related to getting new tokens. Setting a smaller value will result in less + * token generation but runs the the risk of the token expiring while it is still being used. * - * @return the owner of the organisation or null. + *

The time-to-live for the token may be less than this if the initial expiration for the + * token when it is returned from GitHub is less than this or if the token is kept and due to + * failures while retrieving a new token. Non-final for testing/debugging purposes. */ - @CheckForNull - public String getOwner() { - return owner; - } + static long STALE_BEFORE_EXPIRATION_SECONDS = + Long.getLong( + GitHubAppCredentials.class.getName() + ".STALE_BEFORE_EXPIRATION_SECONDS", + Duration.ofMinutes(45).getSeconds()); - @DataBoundSetter - public void setOwner(String owner) { - this.owner = Util.fixEmpty(owner); - } + /** + * Any token older than this is considered stale. + * + *

This is a back stop to ensure that, in case of unforeseen error, expired tokens are not + * accidentally retained past their expiration. + */ + static final long STALE_AFTER_SECONDS = Duration.ofMinutes(30).getSeconds(); - @SuppressWarnings("deprecation") - AuthorizationProvider getAuthorizationProvider() { - return new CredentialsTokenProvider(this); - } + /** + * When a token is retrieved it cannot got stale for at least this many seconds. + * + *

Prevents continuous refreshing of credentials. Non-final for testing purposes. This value + * takes precedence over {@link #STALE_BEFORE_EXPIRATION_SECONDS}. If {@link + * #STALE_AFTER_SECONDS} is less than this value, {@link #STALE_AFTER_SECONDS} takes precedence + * over this value. Minimum value of 1. + */ + static long NOT_STALE_MINIMUM_SECONDS = + Long.getLong( + GitHubAppCredentials.class.getName() + ".NOT_STALE_MINIMUM_SECONDS", + Duration.ofMinutes(1).getSeconds()); - private static AuthorizationProvider createJwtProvider(String appId, String appPrivateKey) { - try { - return new JWTTokenProvider(appId, appPrivateKey); - } catch (GeneralSecurityException e) { - throw new IllegalArgumentException("Couldn't parse private key for GitHub app, make sure it's PKCS#8 format", e); - } - } + private final Secret token; + private final long expirationEpochSeconds; + private final long staleEpochSeconds; + /** + * Create a AppInstallationToken instance. + * + *

Tokens will always become stale after {@link #STALE_AFTER_SECONDS} seconds. Tokens will + * not become stale for at least {@link #NOT_STALE_MINIMUM_SECONDS}, as long as that does not + * exceed {@link #STALE_AFTER_SECONDS}. Within the bounds of {@link #NOT_STALE_MINIMUM_SECONDS} + * and {@link #STALE_AFTER_SECONDS}, tokens will become stale {@link + * #STALE_BEFORE_EXPIRATION_SECONDS} seconds before they expire. + * + * @param token the token string + * @param expirationEpochSeconds the time in epoch seconds that this token will expire + */ + public AppInstallationToken(Secret token, long expirationEpochSeconds) { + long now = Instant.now().getEpochSecond(); + long secondsUntilExpiration = expirationEpochSeconds - now; - private static abstract class TokenProvider extends GitHub.DependentAuthorizationProvider { + long minimumAllowedAge = Math.max(1, NOT_STALE_MINIMUM_SECONDS); + long maximumAllowedAge = Math.max(1, 1 + STALE_AFTER_SECONDS); - protected TokenProvider(String appID, String privateKey) { - super(createJwtProvider(appID, privateKey)); - } + // Tokens go stale a while before they will expire + long secondsUntilStale = secondsUntilExpiration - STALE_BEFORE_EXPIRATION_SECONDS; - /** - * Create and return the specialized GitHub instance to be used for refreshing AppInstallationToken - * - * The {@link GitHub.DependentAuthorizationProvider} provides a specialized GitHub instance - * that uses JWT for authorization and does not check rate limit since it doesn't apply for - * the App endpoints when using JWT. - */ - static GitHub createTokenRefreshGitHub(String appId, - String appPrivateKey, - String apiUrl) throws IOException { - TokenProvider provider = new TokenProvider(appId, appPrivateKey) { - @Override - public String getEncodedAuthorization() throws IOException { - // Will never be called - return null; - } - }; - Connector - .createGitHubBuilder(apiUrl) - .withAuthorizationProvider(provider) - .build(); - - return provider.gitHub(); - } - } + // Tokens are never stale as soon as they are made + if (secondsUntilStale < minimumAllowedAge) { + secondsUntilStale = minimumAllowedAge; + } - private static class CredentialsTokenProvider extends TokenProvider { - private final GitHubAppCredentials credentials; + // Tokens have a maximum age at which they go stale + if (secondsUntilStale > maximumAllowedAge) { + secondsUntilStale = maximumAllowedAge; + } - CredentialsTokenProvider(GitHubAppCredentials credentials) { - super(credentials.appID, credentials.privateKey.getPlainText()); - this.credentials = credentials; - } + LOGGER.log(Level.FINER, "Token will become stale after " + secondsUntilStale + " seconds"); - public String getEncodedAuthorization() throws IOException { - Secret token = credentials.getToken(gitHub()).getToken(); - return String.format("token %s", token.getPlainText()); - } + this.token = token; + this.expirationEpochSeconds = expirationEpochSeconds; + this.staleEpochSeconds = now + secondsUntilStale; } - @SuppressWarnings("deprecation") // preview features are required for GitHub app integration, GitHub api adds deprecated to all preview methods - static AppInstallationToken generateAppInstallationToken(GitHub gitHubApp, String appId, String appPrivateKey, String apiUrl, String owner) { - JenkinsJVM.checkJenkinsJVM(); - // We expect this to be fast but if anything hangs in here we do not want to block indefinitely - - try (Timeout ignored = Timeout.limit(30, TimeUnit.SECONDS)) { - if (gitHubApp == null) { - gitHubApp = TokenProvider.createTokenRefreshGitHub(appId, appPrivateKey, apiUrl); - } - - GHApp app; - try { - app = gitHubApp.getApp(); - } catch (IOException e) { - throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId), e); - } - - List appInstallations = app.listInstallations().asList(); - if (appInstallations.isEmpty()) { - throw new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId)); - } - GHAppInstallation appInstallation; - if (appInstallations.size() == 1) { - appInstallation = appInstallations.get(0); - } else { - appInstallation = appInstallations.stream() - .filter(installation -> installation.getAccount().getLogin().equals(owner)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, - appId))); - } - - GHAppInstallationToken appInstallationToken = appInstallation - .createToken(appInstallation.getPermissions()) - .create(); - - long expiration = getExpirationSeconds(appInstallationToken); - AppInstallationToken token = new AppInstallationToken( - Secret.fromString(appInstallationToken.getToken()), - expiration); - LOGGER.log(Level.FINER, - "Generated App Installation Token for app ID {0}", - appId); - LOGGER.log(Level.FINEST, () -> "Generated App Installation Token at " + Instant.now().toEpochMilli()); - - if (AFTER_TOKEN_GENERATION_DELAY_SECONDS > 0) { - // Delay can be up to 10 seconds. - long tokenDelay = Math.min(10, AFTER_TOKEN_GENERATION_DELAY_SECONDS); - LOGGER.log(Level.FINER, "Waiting {0} seconds after token generation", tokenDelay); - Thread.sleep(Duration.ofSeconds(tokenDelay).toMillis()); - } - - return token; - } catch (IOException | InterruptedException e) { - throw new IllegalArgumentException("Failed to generate GitHub App installation token for app ID " + appId , e); - } + public Secret getToken() { + return token; } - private static long getExpirationSeconds(GHAppInstallationToken appInstallationToken) { - try { - return appInstallationToken.getExpiresAt() - .toInstant() - .getEpochSecond(); - } catch (Exception e) { - // if we fail to calculate the expiration, guess at a reasonable value. - LOGGER.log(Level.WARNING, - "Unable to get GitHub App installation token expiration", - e); - return Instant.now().getEpochSecond() + AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; - } + /** + * Whether a token is stale and should be replaced with a new token. + * + *

{@link #getPassword()} checks that the token is not "stale" before returning it. If a + * token is "stale" if it has expired, exceeded {@link #STALE_AFTER_SECONDS}, or will expire in + * less than {@link #STALE_BEFORE_EXPIRATION_SECONDS}. + * + * @return {@code true} if token should be refreshed, otherwise {@code false}. + */ + public boolean isStale() { + return Instant.now().getEpochSecond() >= staleEpochSeconds; } - @NonNull String actualApiUri() { - return Util.fixEmpty(apiUri) == null ? "https://api.github.com" : apiUri; + public boolean isExpired() { + return Instant.now().getEpochSecond() >= expirationEpochSeconds; } - private AppInstallationToken getToken(GitHub gitHub) { - synchronized (this) { - try { - if (cachedToken == null || cachedToken.isStale()) { - LOGGER.log(Level.FINE, "Generating App Installation Token for app ID {0}", appID); - cachedToken = generateAppInstallationToken( - gitHub, - appID, - privateKey.getPlainText(), - actualApiUri(), - owner); - LOGGER.log(Level.FINER, "Retrieved GitHub App Installation Token for app ID {0}", appID); - } - } catch (Exception e) { - if (cachedToken != null && !cachedToken.isExpired()) { - // Requesting a new token failed. If the cached token is not expired, continue to use it. - // This minimizes failures due to occasional network instability, - // while only slightly increasing the chance that tokens will expire while in use. - LOGGER.log(Level.WARNING, - "Failed to generate new GitHub App Installation Token for app ID " + appID + ": cached token is stale but has not expired", - e); - } else { - throw e; - } - } - LOGGER.log(Level.FINEST, "Returned GitHub App Installation Token for app ID {0}", appID); - - return cachedToken; - } + long getTokenStaleEpochSeconds() { + return staleEpochSeconds; } + } + + /** + * Ensures that the credentials state as serialized via Remoting to an agent calls back to the + * controller. Benefits: + * + *

    + *
  • The token is cached locally and used until it is stale. + *
  • The agent never needs to have access to the plaintext private key. + *
  • We avoid the considerable amount of class loading associated with the JWT library, + * Jackson data binding, Bouncy Castle, etc. + *
  • The agent need not be able to contact GitHub. + *
+ */ + private Object writeReplace() { + if ( + /* XStream */ Channel.current() == null) { + return this; + } + return new DelegatingGitHubAppCredentials(this); + } + + private static final class DelegatingGitHubAppCredentials extends BaseStandardCredentials + implements StandardUsernamePasswordCredentials { + private final String appID; /** - * {@inheritDoc} + * An encrypted form of all data needed to refresh the token. Used to prevent {@link GetToken} + * from being abused by compromised build agents. */ - @NonNull - @Override - public Secret getPassword() { - return this.getToken(null).getToken(); + private final String tokenRefreshData; + + private AppInstallationToken cachedToken; + + private transient Channel ch; + + DelegatingGitHubAppCredentials(GitHubAppCredentials onMaster) { + super(onMaster.getScope(), onMaster.getId(), onMaster.getDescription()); + JenkinsJVM.checkJenkinsJVM(); + appID = onMaster.appID; + JSONObject j = new JSONObject(); + j.put("appID", appID); + j.put("privateKey", onMaster.privateKey.getPlainText()); + j.put("apiUri", onMaster.actualApiUri()); + j.put("owner", onMaster.owner); + tokenRefreshData = Secret.fromString(j.toString()).getEncryptedValue(); + + // Check token is valid before sending it to the agent. + // Ensuring the cached token is not stale before sending it to agents keeps agents from having + // to + // immediately refresh the token. + // This is intentionally only a best-effort attempt. + // If this fails, the agent will fallback to making the request (which may or may not fail). + try { + LOGGER.log( + Level.FINEST, + "Checking App Installation Token for app ID {0} before sending to agent", + onMaster.appID); + onMaster.getPassword(); + } catch (Exception e) { + LOGGER.log( + Level.WARNING, + "Failed to update stale GitHub App installation token for app ID " + + onMaster.getAppID() + + " before sending to agent", + e); + } + + cachedToken = onMaster.getCachedToken(); + } + + private Object readResolve() { + JenkinsJVM.checkNotJenkinsJVM(); + synchronized (this) { + ch = Channel.currentOrFail(); + } + return this; } - /** - * {@inheritDoc} - */ @NonNull @Override public String getUsername() { - return appID; + return appID; } - private AppInstallationToken getCachedToken() { + @Override + public Secret getPassword() { + JenkinsJVM.checkNotJenkinsJVM(); + try { synchronized (this) { - return cachedToken; - } - } - - static class AppInstallationToken implements Serializable { - /** - * {@link #getPassword()} checks that the token is still valid before returning it. - * The token generally will not expire for at least this amount of time after it is returned. - * - * Using a larger value will result in longer time-to-live for the token, but also more network - * calls related to getting new tokens. Setting a smaller value will result in less token generation - * but runs the the risk of the token expiring while it is still being used. - * - * The time-to-live for the token may be less than this if the initial expiration for the token when - * it is returned from GitHub is less than this or if the token is kept and due to failures - * while retrieving a new token. - * Non-final for testing/debugging purposes. - */ - static long STALE_BEFORE_EXPIRATION_SECONDS = - Long.getLong(GitHubAppCredentials.class.getName() + ".STALE_BEFORE_EXPIRATION_SECONDS", Duration.ofMinutes(45).getSeconds()); - - /** - * Any token older than this is considered stale. - * - * This is a back stop to ensure that, in case of unforeseen error, - * expired tokens are not accidentally retained past their expiration. - */ - static final long STALE_AFTER_SECONDS = Duration.ofMinutes(30).getSeconds(); - - /** - * When a token is retrieved it cannot got stale for at least this many seconds. - * - * Prevents continuous refreshing of credentials. - * Non-final for testing purposes. - * This value takes precedence over {@link #STALE_BEFORE_EXPIRATION_SECONDS}. - * If {@link #STALE_AFTER_SECONDS} is less than this value, {@link #STALE_AFTER_SECONDS} takes precedence over this value. - * Minimum value of 1. - */ - static long NOT_STALE_MINIMUM_SECONDS = - Long.getLong(GitHubAppCredentials.class.getName() + ".NOT_STALE_MINIMUM_SECONDS", Duration.ofMinutes(1).getSeconds()); - - private final Secret token; - private final long expirationEpochSeconds; - private final long staleEpochSeconds; - - /** - * Create a AppInstallationToken instance. - * - * Tokens will always become stale after {@link #STALE_AFTER_SECONDS} seconds. - * Tokens will not become stale for at least {@link #NOT_STALE_MINIMUM_SECONDS}, - * as long as that does not exceed {@link #STALE_AFTER_SECONDS}. - * Within the bounds of {@link #NOT_STALE_MINIMUM_SECONDS} and {@link #STALE_AFTER_SECONDS}, - * tokens will become stale {@link #STALE_BEFORE_EXPIRATION_SECONDS} seconds before they expire. - * - * @param token the token string - * @param expirationEpochSeconds the time in epoch seconds that this token will expire - */ - public AppInstallationToken(Secret token, long expirationEpochSeconds) { - long now = Instant.now().getEpochSecond(); - long secondsUntilExpiration = expirationEpochSeconds - now; - - long minimumAllowedAge = Math.max(1, NOT_STALE_MINIMUM_SECONDS); - long maximumAllowedAge = Math.max(1, 1 + STALE_AFTER_SECONDS); - - // Tokens go stale a while before they will expire - long secondsUntilStale = secondsUntilExpiration - STALE_BEFORE_EXPIRATION_SECONDS; - - // Tokens are never stale as soon as they are made - if (secondsUntilStale < minimumAllowedAge) { - secondsUntilStale = minimumAllowedAge; + try { + if (cachedToken == null || cachedToken.isStale()) { + LOGGER.log( + Level.FINE, "Generating App Installation Token for app ID {0} on agent", appID); + cachedToken = ch.call(new GetToken(tokenRefreshData)); + LOGGER.log( + Level.FINER, + "Retrieved GitHub App Installation Token for app ID {0} on agent", + appID); + LOGGER.log( + Level.FINEST, + () -> + "Generated App Installation Token at " + + Instant.now().toEpochMilli() + + " on agent"); } - - // Tokens have a maximum age at which they go stale - if (secondsUntilStale > maximumAllowedAge) { - secondsUntilStale = maximumAllowedAge; + } catch (Exception e) { + if (cachedToken != null && !cachedToken.isExpired()) { + // Requesting a new token failed. If the cached token is not expired, continue to use + // it. + // This minimizes failures due to occasional network instability, + // while only slightly increasing the chance that tokens will expire while in use. + LOGGER.log( + Level.WARNING, + "Failed to generate new GitHub App Installation Token for app ID " + + appID + + " on agent: cached token is stale but has not expired"); + // Logging the exception here caused a security exception when trying to read the + // agent logs during testing + // Added the exception to a secondary log message that can be viewed if it is needed + LOGGER.log(Level.FINER, () -> Functions.printThrowable(e)); + } else { + throw e; } + } + LOGGER.log( + Level.FINEST, + "Returned GitHub App Installation Token for app ID {0} on agent", + appID); - LOGGER.log(Level.FINER, "Token will become stale after " + secondsUntilStale + " seconds"); - - this.token = token; - this.expirationEpochSeconds = expirationEpochSeconds; - this.staleEpochSeconds = now + secondsUntilStale; - } - - public Secret getToken() { - return token; - } - - /** - * Whether a token is stale and should be replaced with a new token. - * - * {@link #getPassword()} checks that the token is not "stale" before returning it. - * If a token is "stale" if it has expired, exceeded {@link #STALE_AFTER_SECONDS}, or - * will expire in less than {@link #STALE_BEFORE_EXPIRATION_SECONDS}. - * - * @return {@code true} if token should be refreshed, otherwise {@code false}. - */ - public boolean isStale() { - return Instant.now().getEpochSecond() >= staleEpochSeconds; + return cachedToken.getToken(); } - public boolean isExpired() { - return Instant.now().getEpochSecond() >= expirationEpochSeconds; - } - - long getTokenStaleEpochSeconds() { - return staleEpochSeconds; - } - } - - /** - * Ensures that the credentials state as serialized via Remoting to an agent calls back to the controller. - * Benefits: - *
    - *
  • The token is cached locally and used until it is stale. - *
  • The agent never needs to have access to the plaintext private key. - *
  • We avoid the considerable amount of class loading associated with the JWT library, Jackson data binding, Bouncy Castle, etc. - *
  • The agent need not be able to contact GitHub. - *
- */ - private Object writeReplace() { - if (/* XStream */Channel.current() == null) { - return this; - } - return new DelegatingGitHubAppCredentials(this); + } catch (IOException | InterruptedException x) { + throw new RuntimeException(x); + } } - private static final class DelegatingGitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { - - private final String appID; - /** - * An encrypted form of all data needed to refresh the token. - * Used to prevent {@link GetToken} from being abused by compromised build agents. - */ - private final String tokenRefreshData; - private AppInstallationToken cachedToken; - - private transient Channel ch; - - DelegatingGitHubAppCredentials(GitHubAppCredentials onMaster) { - super(onMaster.getScope(), onMaster.getId(), onMaster.getDescription()); - JenkinsJVM.checkJenkinsJVM(); - appID = onMaster.appID; - JSONObject j = new JSONObject(); - j.put("appID", appID); - j.put("privateKey", onMaster.privateKey.getPlainText()); - j.put("apiUri", onMaster.actualApiUri()); - j.put("owner", onMaster.owner); - tokenRefreshData = Secret.fromString(j.toString()).getEncryptedValue(); - - // Check token is valid before sending it to the agent. - // Ensuring the cached token is not stale before sending it to agents keeps agents from having to - // immediately refresh the token. - // This is intentionally only a best-effort attempt. - // If this fails, the agent will fallback to making the request (which may or may not fail). - try { - LOGGER.log(Level.FINEST, "Checking App Installation Token for app ID {0} before sending to agent", onMaster.appID); - onMaster.getPassword(); - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Failed to update stale GitHub App installation token for app ID " + onMaster.getAppID() + " before sending to agent", e); - } - - cachedToken = onMaster.getCachedToken(); - } + private static final class GetToken + extends SlaveToMasterCallable { - private Object readResolve() { - JenkinsJVM.checkNotJenkinsJVM(); - synchronized (this) { - ch = Channel.currentOrFail(); - } - return this; - } + private final String data; - @NonNull - @Override - public String getUsername() { - return appID; - } + GetToken(String data) { + this.data = data; + } - @Override - public Secret getPassword() { - JenkinsJVM.checkNotJenkinsJVM(); - try { - synchronized (this) { - try { - if (cachedToken == null || cachedToken.isStale()) { - LOGGER.log(Level.FINE, "Generating App Installation Token for app ID {0} on agent", appID); - cachedToken = ch.call(new GetToken(tokenRefreshData)); - LOGGER.log(Level.FINER, "Retrieved GitHub App Installation Token for app ID {0} on agent", appID); - LOGGER.log(Level.FINEST, () -> "Generated App Installation Token at " + Instant.now().toEpochMilli() + " on agent"); - } - } catch (Exception e) { - if (cachedToken != null && !cachedToken.isExpired()) { - // Requesting a new token failed. If the cached token is not expired, continue to use it. - // This minimizes failures due to occasional network instability, - // while only slightly increasing the chance that tokens will expire while in use. - LOGGER.log(Level.WARNING, - "Failed to generate new GitHub App Installation Token for app ID " + appID + " on agent: cached token is stale but has not expired"); - // Logging the exception here caused a security exception when trying to read the agent logs during testing - // Added the exception to a secondary log message that can be viewed if it is needed - LOGGER.log(Level.FINER, () -> Functions.printThrowable(e)); - } else { - throw e; - } - } - LOGGER.log(Level.FINEST, "Returned GitHub App Installation Token for app ID {0} on agent", appID); - - return cachedToken.getToken(); - } - - } catch (IOException | InterruptedException x) { - throw new RuntimeException(x); - } - } + @Override + public AppInstallationToken call() throws RuntimeException { + JenkinsJVM.checkJenkinsJVM(); + JSONObject fields = JSONObject.fromObject(Secret.fromString(data).getPlainText()); + LOGGER.log( + Level.FINE, + "Generating App Installation Token for app ID {0} for agent", + fields.get("appID")); + AppInstallationToken token = + generateAppInstallationToken( + null, + (String) fields.get("appID"), + (String) fields.get("privateKey"), + (String) fields.get("apiUri"), + (String) fields.get("owner")); + LOGGER.log( + Level.FINER, + "Retrieved GitHub App Installation Token for app ID {0} for agent", + fields.get("appID")); + return token; + } + } + } - private static final class GetToken extends SlaveToMasterCallable { + /** {@inheritDoc} */ + @Extension + public static class DescriptorImpl extends BaseStandardCredentialsDescriptor { - private final String data; + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.GitHubAppCredentials_displayName(); + } - GetToken(String data) { - this.data = data; - } + /** {@inheritDoc} */ + @Override + public String getIconClassName() { + return "icon-github-logo"; + } - @Override - public AppInstallationToken call() throws RuntimeException { - JenkinsJVM.checkJenkinsJVM(); - JSONObject fields = JSONObject.fromObject(Secret.fromString(data).getPlainText()); - LOGGER.log(Level.FINE, "Generating App Installation Token for app ID {0} for agent", fields.get("appID")); - AppInstallationToken token = generateAppInstallationToken( - null, - (String)fields.get("appID"), - (String)fields.get("privateKey"), - (String)fields.get("apiUri"), - (String)fields.get("owner")); - LOGGER.log(Level.FINER, - "Retrieved GitHub App Installation Token for app ID {0} for agent", - fields.get("appID")); - return token; - } - } + @SuppressWarnings("unused") // jelly + public boolean isApiUriSelectable() { + return !GitHubConfiguration.get().getEndpoints().isEmpty(); } /** - * {@inheritDoc} + * Returns the available GitHub endpoint items. + * + * @return the available GitHub endpoint items. */ - @Extension - public static class DescriptorImpl extends BaseStandardCredentialsDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.GitHubAppCredentials_displayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public String getIconClassName() { - return "icon-github-logo"; - } - - @SuppressWarnings("unused") // jelly - public boolean isApiUriSelectable() { - return !GitHubConfiguration.get().getEndpoints().isEmpty(); - } - - /** - * Returns the available GitHub endpoint items. - * - * @return the available GitHub endpoint items. - */ - @SuppressWarnings("unused") // stapler - @Restricted(NoExternalUse.class) // stapler - public ListBoxModel doFillApiUriItems() { - return getPossibleApiUriItems(); - } + @SuppressWarnings("unused") // stapler + @Restricted(NoExternalUse.class) // stapler + public ListBoxModel doFillApiUriItems() { + return getPossibleApiUriItems(); + } - public FormValidation doCheckAppID(@QueryParameter String appID) { - if (!appID.isEmpty()) { - try { - Integer.parseInt(appID); - } catch (NumberFormatException x) { - return FormValidation.warning("An app ID is likely to be a number, distinct from the app name"); - } - } - return FormValidation.ok(); + public FormValidation doCheckAppID(@QueryParameter String appID) { + if (!appID.isEmpty()) { + try { + Integer.parseInt(appID); + } catch (NumberFormatException x) { + return FormValidation.warning( + "An app ID is likely to be a number, distinct from the app name"); } + } + return FormValidation.ok(); + } - @POST - @SuppressWarnings("unused") // stapler - @Restricted(NoExternalUse.class) // stapler - public FormValidation doTestConnection( - @QueryParameter("appID") final String appID, - @QueryParameter("privateKey") final String privateKey, - @QueryParameter("apiUri") final String apiUri, - @QueryParameter("owner") final String owner - - ) { - GitHubAppCredentials gitHubAppCredential = new GitHubAppCredentials( - CredentialsScope.GLOBAL, "test-id-not-being-saved", null, - appID, Secret.fromString(privateKey) - ); - gitHubAppCredential.setApiUri(apiUri); - gitHubAppCredential.setOwner(owner); - - try { - GitHub connect = Connector.connect(apiUri, gitHubAppCredential); - try { - return FormValidation.ok("Success, Remaining rate limit: " + connect.getRateLimit().getRemaining()); - } finally { - Connector.release(connect); - } - } catch (Exception e) { - return FormValidation.error(e, String.format(ERROR_AUTHENTICATING_GITHUB_APP, appID)); - } + @POST + @SuppressWarnings("unused") // stapler + @Restricted(NoExternalUse.class) // stapler + public FormValidation doTestConnection( + @QueryParameter("appID") final String appID, + @QueryParameter("privateKey") final String privateKey, + @QueryParameter("apiUri") final String apiUri, + @QueryParameter("owner") final String owner) { + + GitHubAppCredentials gitHubAppCredential = + new GitHubAppCredentials( + CredentialsScope.GLOBAL, + "test-id-not-being-saved", + null, + appID, + Secret.fromString(privateKey)); + gitHubAppCredential.setApiUri(apiUri); + gitHubAppCredential.setOwner(owner); + + try { + GitHub connect = Connector.connect(apiUri, gitHubAppCredential); + try { + return FormValidation.ok( + "Success, Remaining rate limit: " + connect.getRateLimit().getRemaining()); + } finally { + Connector.release(connect); } + } catch (Exception e) { + return FormValidation.error(e, String.format(ERROR_AUTHENTICATING_GITHUB_APP, appID)); + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchFilter.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchFilter.java index cf32c0ec0..05709ca99 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchFilter.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchFilter.java @@ -39,42 +39,33 @@ * @since 2.0.0 */ public class GitHubBranchFilter extends ViewJobFilter { - /** - * Our constructor. - */ - @DataBoundConstructor - public GitHubBranchFilter() { - } + /** Our constructor. */ + @DataBoundConstructor + public GitHubBranchFilter() {} - /** - * {@inheritDoc} - */ - @Override - public List filter(List added, List all, View filteringView) { - for (TopLevelItem item:all) { - if (added.contains(item)) { - continue; - } - if (SCMHead.HeadByItem.findHead(item) instanceof BranchSCMHead) { - added.add(item); - } - } - return added; + /** {@inheritDoc} */ + @Override + public List filter( + List added, List all, View filteringView) { + for (TopLevelItem item : all) { + if (added.contains(item)) { + continue; + } + if (SCMHead.HeadByItem.findHead(item) instanceof BranchSCMHead) { + added.add(item); + } } + return added; + } - /** - * Our descriptor. - */ - @Extension(optional = true) - public static class DescriptorImpl extends Descriptor { + /** Our descriptor. */ + @Extension(optional = true) + public static class DescriptorImpl extends Descriptor { - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.GitHubBranchFilter_DisplayName(); - } + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.GitHubBranchFilter_DisplayName(); } - + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBuildStatusNotification.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBuildStatusNotification.java index afb401761..f38ab10e9 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBuildStatusNotification.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBuildStatusNotification.java @@ -55,269 +55,306 @@ import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; - /** * Manages GitHub Statuses. * *
    - *
  • Job (associated to a PR) scheduled: PENDING
  • - *
  • Build doing a checkout: PENDING
  • - *
  • Build done: SUCCESS, FAILURE or ERROR
  • + *
  • Job (associated to a PR) scheduled: PENDING + *
  • Build doing a checkout: PENDING + *
  • Build done: SUCCESS, FAILURE or ERROR *
*/ public class GitHubBuildStatusNotification { - private static final Logger LOGGER = Logger.getLogger(GitHubBuildStatusNotification.class.getName()); + private static final Logger LOGGER = + Logger.getLogger(GitHubBuildStatusNotification.class.getName()); - private static void createBuildCommitStatus(Run build, TaskListener listener) { - SCMSource src = SCMSource.SourceByItem.findSource(build.getParent()); - SCMRevision revision = src != null ? SCMRevisionAction.getRevision(src, build) : null; - if (revision != null) { // only notify if we have a revision to notify - try { - GitHub gitHub = lookUpGitHub(build.getParent()); + private static void createBuildCommitStatus(Run build, TaskListener listener) { + SCMSource src = SCMSource.SourceByItem.findSource(build.getParent()); + SCMRevision revision = src != null ? SCMRevisionAction.getRevision(src, build) : null; + if (revision != null) { // only notify if we have a revision to notify + try { + GitHub gitHub = lookUpGitHub(build.getParent()); + try { + GHRepository repo = lookUpRepo(gitHub, build.getParent()); + if (repo != null) { + Result result = build.getResult(); + String revisionToNotify = resolveHeadCommit(revision); + SCMHead head = revision.getHead(); + List strategies = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()) + .withTraits(((GitHubSCMSource) src).getTraits()) + .notificationStrategies(); + for (AbstractGitHubNotificationStrategy strategy : strategies) { + // TODO allow strategies to combine/cooperate on a notification + GitHubNotificationContext notificationContext = + GitHubNotificationContext.build(null, build, src, head); + List details = + strategy.notifications(notificationContext, listener); + for (GitHubNotificationRequest request : details) { + boolean ignoreError = request.isIgnoreError(); try { - GHRepository repo = lookUpRepo(gitHub, build.getParent()); - if (repo != null) { - Result result = build.getResult(); - String revisionToNotify = resolveHeadCommit(revision); - SCMHead head = revision.getHead(); - List strategies = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(((GitHubSCMSource) src).getTraits()).notificationStrategies(); - for (AbstractGitHubNotificationStrategy strategy : strategies) { - // TODO allow strategies to combine/cooperate on a notification - GitHubNotificationContext notificationContext = GitHubNotificationContext.build(null, build, - src, head); - List details = strategy.notifications(notificationContext, listener); - for (GitHubNotificationRequest request : details) { - boolean ignoreError = request.isIgnoreError(); - try { - repo.createCommitStatus(revisionToNotify, request.getState(), request.getUrl(), request.getMessage(), - request.getContext()); - } catch (FileNotFoundException fnfe) { - if (!ignoreError) { - listener.getLogger().format("%nCould not update commit status, please check if your scan " + - "credentials belong to a member of the organization or a collaborator of the " + - "repository and repo:status scope is selected%n%n"); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Could not update commit status, for run " - + build.getFullDisplayName() - + " please check if your scan " - + "credentials belong to a member of the organization or a " - + "collaborator of the repository and repo:status scope is selected", fnfe); - } - } - } - } - } - if (result != null) { - listener.getLogger().format("%n" + Messages.GitHubBuildStatusNotification_CommitStatusSet() + "%n%n"); - } + repo.createCommitStatus( + revisionToNotify, + request.getState(), + request.getUrl(), + request.getMessage(), + request.getContext()); + } catch (FileNotFoundException fnfe) { + if (!ignoreError) { + listener + .getLogger() + .format( + "%nCould not update commit status, please check if your scan " + + "credentials belong to a member of the organization or a collaborator of the " + + "repository and repo:status scope is selected%n%n"); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log( + Level.FINE, + "Could not update commit status, for run " + + build.getFullDisplayName() + + " please check if your scan " + + "credentials belong to a member of the organization or a " + + "collaborator of the repository and repo:status scope is selected", + fnfe); } - } finally { - Connector.release(gitHub); - } - } catch (IOException ioe) { - listener.getLogger().format("%n" - + "Could not update commit status. Message: %s%n" - + "%n", ioe.getMessage()); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Could not update commit status of run " + build.getFullDisplayName(), ioe); + } } + } } + if (result != null) { + listener + .getLogger() + .format("%n" + Messages.GitHubBuildStatusNotification_CommitStatusSet() + "%n%n"); + } + } + } finally { + Connector.release(gitHub); + } + } catch (IOException ioe) { + listener + .getLogger() + .format( + "%n" + "Could not update commit status. Message: %s%n" + "%n", ioe.getMessage()); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log( + Level.FINE, + "Could not update commit status of run " + build.getFullDisplayName(), + ioe); } + } } + } - /** - * Returns the GitHub Repository associated to a Job. - * - * @param job A {@link Job} - * @return A {@link GHRepository} or null, either if a scan credentials was not provided, or a GitHubSCMSource was not defined. - * @throws IOException - */ - @CheckForNull - private static GHRepository lookUpRepo(GitHub github, @NonNull Job job) throws IOException { - if (github == null) { - return null; - } - SCMSource src = SCMSource.SourceByItem.findSource(job); - if (src instanceof GitHubSCMSource) { - GitHubSCMSource source = (GitHubSCMSource) src; - if (source.getScanCredentialsId() != null) { - return github.getRepository(source.getRepoOwner() + "/" + source.getRepository()); - } - } - return null; + /** + * Returns the GitHub Repository associated to a Job. + * + * @param job A {@link Job} + * @return A {@link GHRepository} or null, either if a scan credentials was not provided, or a + * GitHubSCMSource was not defined. + * @throws IOException + */ + @CheckForNull + private static GHRepository lookUpRepo(GitHub github, @NonNull Job job) throws IOException { + if (github == null) { + return null; + } + SCMSource src = SCMSource.SourceByItem.findSource(job); + if (src instanceof GitHubSCMSource) { + GitHubSCMSource source = (GitHubSCMSource) src; + if (source.getScanCredentialsId() != null) { + return github.getRepository(source.getRepoOwner() + "/" + source.getRepository()); + } } + return null; + } - /** - * Returns the GitHub Repository associated to a Job. - * - * @param job A {@link Job} - * @return A {@link GHRepository} or {@code null}, if any of: a credentials was not provided; notifications were - * disabled, or the job is not from a {@link GitHubSCMSource}. - * @throws IOException - */ - @CheckForNull - private static GitHub lookUpGitHub(@NonNull Job job) throws IOException { - SCMSource src = SCMSource.SourceByItem.findSource(job); - if (src instanceof GitHubSCMSource) { - GitHubSCMSource source = (GitHubSCMSource) src; - if (new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(source.getTraits()) - .notificationsDisabled()) { - return null; - } - if (source.getScanCredentialsId() != null) { - return Connector.connect(source.getApiUri(), Connector.lookupScanCredentials - (job, source.getApiUri(), source.getScanCredentialsId())); - } - } + /** + * Returns the GitHub Repository associated to a Job. + * + * @param job A {@link Job} + * @return A {@link GHRepository} or {@code null}, if any of: a credentials was not provided; + * notifications were disabled, or the job is not from a {@link GitHubSCMSource}. + * @throws IOException + */ + @CheckForNull + private static GitHub lookUpGitHub(@NonNull Job job) throws IOException { + SCMSource src = SCMSource.SourceByItem.findSource(job); + if (src instanceof GitHubSCMSource) { + GitHubSCMSource source = (GitHubSCMSource) src; + if (new GitHubSCMSourceContext(null, SCMHeadObserver.none()) + .withTraits(source.getTraits()) + .notificationsDisabled()) { return null; + } + if (source.getScanCredentialsId() != null) { + return Connector.connect( + source.getApiUri(), + Connector.lookupScanCredentials( + job, source.getApiUri(), source.getScanCredentialsId())); + } } + return null; + } - /** - * With this listener one notifies to GitHub when a Job has been scheduled. - * - * Sends: GHCommitState.PENDING - */ - @Extension - public static class JobScheduledListener extends QueueListener { + /** + * With this listener one notifies to GitHub when a Job has been scheduled. + * + *

Sends: GHCommitState.PENDING + */ + @Extension + public static class JobScheduledListener extends QueueListener { - /** - * Manages the GitHub Commit Pending Status. - */ - @Override - public void onEnterWaiting(Queue.WaitingItem wi) { - if (!(wi.task instanceof Job)) { - return; - } - final long taskId = wi.getId(); - final Job job = (Job) wi.task; - final SCMSource source = SCMSource.SourceByItem.findSource(job); - if (!(source instanceof GitHubSCMSource)) { - return; - } - final SCMHead head = SCMHead.HeadByItem.findHead(job); - if (!(head instanceof PullRequestSCMHead)) { - return; - } - final GitHubSCMSourceContext sourceContext = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(((GitHubSCMSource) source).getTraits()); - if (sourceContext.notificationsDisabled()) { - return; - } - // prevent delays in the queue when updating github - Computer.threadPoolForRemoting.submit(new Runnable() { - @Override - public void run() { - try { - GitHub gitHub = lookUpGitHub(job); - try { - if (gitHub == null || gitHub.rateLimit().remaining < 8) { - // we are an optimization to signal commit status early, no point waiting for - // the rate limit to refresh as the checkout will ensure the status is set - return; - } - String hash = resolveHeadCommit(source.fetch(head, null)); - if (gitHub.rateLimit().remaining < 8) { // should only need 2 but may be concurrent threads - // we are an optimization to signal commit status early, no point waiting for - // the rate limit to refresh as the checkout will ensure the status is set - return; - } - GHRepository repo = lookUpRepo(gitHub, job); - if (repo != null) { - // The submitter might push another commit before this build even starts. - if (Jenkins.get().getQueue().getItem(taskId) instanceof Queue.LeftItem) { - // we took too long and the item has left the queue, no longer valid to apply pending + /** Manages the GitHub Commit Pending Status. */ + @Override + public void onEnterWaiting(Queue.WaitingItem wi) { + if (!(wi.task instanceof Job)) { + return; + } + final long taskId = wi.getId(); + final Job job = (Job) wi.task; + final SCMSource source = SCMSource.SourceByItem.findSource(job); + if (!(source instanceof GitHubSCMSource)) { + return; + } + final SCMHead head = SCMHead.HeadByItem.findHead(job); + if (!(head instanceof PullRequestSCMHead)) { + return; + } + final GitHubSCMSourceContext sourceContext = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()) + .withTraits(((GitHubSCMSource) source).getTraits()); + if (sourceContext.notificationsDisabled()) { + return; + } + // prevent delays in the queue when updating github + Computer.threadPoolForRemoting.submit( + new Runnable() { + @Override + public void run() { + try { + GitHub gitHub = lookUpGitHub(job); + try { + if (gitHub == null || gitHub.rateLimit().remaining < 8) { + // we are an optimization to signal commit status early, no point waiting for + // the rate limit to refresh as the checkout will ensure the status is set + return; + } + String hash = resolveHeadCommit(source.fetch(head, null)); + if (gitHub.rateLimit().remaining + < 8) { // should only need 2 but may be concurrent threads + // we are an optimization to signal commit status early, no point waiting for + // the rate limit to refresh as the checkout will ensure the status is set + return; + } + GHRepository repo = lookUpRepo(gitHub, job); + if (repo != null) { + // The submitter might push another commit before this build even starts. + if (Jenkins.get().getQueue().getItem(taskId) instanceof Queue.LeftItem) { + // we took too long and the item has left the queue, no longer valid to apply + // pending - // status. JobCheckOutListener is now responsible for setting the pending status. - return; - } - List strategies = sourceContext.notificationStrategies(); - for (AbstractGitHubNotificationStrategy strategy : strategies) { - // TODO allow strategies to combine/cooperate on a notification - GitHubNotificationContext notificationContext = GitHubNotificationContext.build(job, null, - source, head); - List details = strategy.notifications(notificationContext, null); - for (GitHubNotificationRequest request : details) { - boolean ignoreErrors = request.isIgnoreError(); - try { - repo.createCommitStatus(hash, request.getState(), request.getUrl(), request.getMessage(), - request.getContext()); - } catch (FileNotFoundException e) { - if (!ignoreErrors) { - LOGGER.log(Level.WARNING, - "Could not update commit status to PENDING. Valid scan credentials? Valid scopes?", - LOGGER.isLoggable(Level.FINE) ? e : null); - } - } - } - } - } - } finally { - Connector.release(gitHub); - } - } catch (FileNotFoundException e) { - LOGGER.log(Level.WARNING, + // status. JobCheckOutListener is now responsible for setting the pending + // status. + return; + } + List strategies = + sourceContext.notificationStrategies(); + for (AbstractGitHubNotificationStrategy strategy : strategies) { + // TODO allow strategies to combine/cooperate on a notification + GitHubNotificationContext notificationContext = + GitHubNotificationContext.build(job, null, source, head); + List details = + strategy.notifications(notificationContext, null); + for (GitHubNotificationRequest request : details) { + boolean ignoreErrors = request.isIgnoreError(); + try { + repo.createCommitStatus( + hash, + request.getState(), + request.getUrl(), + request.getMessage(), + request.getContext()); + } catch (FileNotFoundException e) { + if (!ignoreErrors) { + LOGGER.log( + Level.WARNING, "Could not update commit status to PENDING. Valid scan credentials? Valid scopes?", LOGGER.isLoggable(Level.FINE) ? e : null); - } catch (IOException e) { - LOGGER.log(Level.WARNING, - "Could not update commit status to PENDING. Message: " + e.getMessage(), - LOGGER.isLoggable(Level.FINE) ? e : null); - } catch (InterruptedException e) { - LOGGER.log(Level.WARNING, - "Could not update commit status to PENDING. Rate limit exhausted", - LOGGER.isLoggable(Level.FINE) ? e : null); - LOGGER.log(Level.FINE, null, e); + } + } + } } + } + } finally { + Connector.release(gitHub); } - }); - } - + } catch (FileNotFoundException e) { + LOGGER.log( + Level.WARNING, + "Could not update commit status to PENDING. Valid scan credentials? Valid scopes?", + LOGGER.isLoggable(Level.FINE) ? e : null); + } catch (IOException e) { + LOGGER.log( + Level.WARNING, + "Could not update commit status to PENDING. Message: " + e.getMessage(), + LOGGER.isLoggable(Level.FINE) ? e : null); + } catch (InterruptedException e) { + LOGGER.log( + Level.WARNING, + "Could not update commit status to PENDING. Rate limit exhausted", + LOGGER.isLoggable(Level.FINE) ? e : null); + LOGGER.log(Level.FINE, null, e); + } + } + }); } + } - /** - * With this listener one notifies to GitHub when the SCM checkout process has started. - * - * Possible option: GHCommitState.PENDING - */ - @Extension - public static class JobCheckOutListener extends SCMListener { - - @Override - public void onCheckout(Run build, SCM scm, FilePath workspace, TaskListener listener, File changelogFile, - SCMRevisionState pollingBaseline) throws Exception { - createBuildCommitStatus(build, listener); - } + /** + * With this listener one notifies to GitHub when the SCM checkout process has started. + * + *

Possible option: GHCommitState.PENDING + */ + @Extension + public static class JobCheckOutListener extends SCMListener { + @Override + public void onCheckout( + Run build, + SCM scm, + FilePath workspace, + TaskListener listener, + File changelogFile, + SCMRevisionState pollingBaseline) + throws Exception { + createBuildCommitStatus(build, listener); } + } - /** - * With this listener one notifies to GitHub the build result. - * - * Possible options: GHCommitState.SUCCESS, GHCommitState.ERROR or GHCommitState.FAILURE - */ - @Extension - public static class JobCompletedListener extends RunListener> { - - @Override - public void onCompleted(Run build, TaskListener listener) { - createBuildCommitStatus(build, listener); - } + /** + * With this listener one notifies to GitHub the build result. + * + *

Possible options: GHCommitState.SUCCESS, GHCommitState.ERROR or GHCommitState.FAILURE + */ + @Extension + public static class JobCompletedListener extends RunListener> { + @Override + public void onCompleted(Run build, TaskListener listener) { + createBuildCommitStatus(build, listener); } + } - private static String resolveHeadCommit(SCMRevision revision) throws IllegalArgumentException { - if (revision instanceof SCMRevisionImpl) { - return ((SCMRevisionImpl) revision).getHash(); - } else if (revision instanceof PullRequestSCMRevision) { - return ((PullRequestSCMRevision) revision).getPullHash(); - } else { - throw new IllegalArgumentException("did not recognize " + revision); - } + private static String resolveHeadCommit(SCMRevision revision) throws IllegalArgumentException { + if (revision instanceof SCMRevisionImpl) { + return ((SCMRevisionImpl) revision).getHash(); + } else if (revision instanceof PullRequestSCMRevision) { + return ((PullRequestSCMRevision) revision).getPullHash(); + } else { + throw new IllegalArgumentException("did not recognize " + revision); } + } - private GitHubBuildStatusNotification() {} - + private GitHubBuildStatusNotification() {} } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubClosable.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubClosable.java index 7b00a84d2..df4ba5218 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubClosable.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubClosable.java @@ -27,9 +27,9 @@ import java.io.Closeable; /** - * Package private interface to allow {@link GitHubSCMFile} and {@link GitHubSCMFileSystem} to be aware of the state - * of their connection. + * Package private interface to allow {@link GitHubSCMFile} and {@link GitHubSCMFileSystem} to be + * aware of the state of their connection. */ interface GitHubClosable extends Closeable { - boolean isOpen(); + boolean isOpen(); } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java index 8982040a7..cc320b748 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConfiguration.java @@ -42,198 +42,202 @@ import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.StaplerRequest; -@Extension public class GitHubConfiguration extends GlobalConfiguration { +@Extension +public class GitHubConfiguration extends GlobalConfiguration { - public static GitHubConfiguration get() { - return GlobalConfiguration.all().get(GitHubConfiguration.class); - } + public static GitHubConfiguration get() { + return GlobalConfiguration.all().get(GitHubConfiguration.class); + } - private List endpoints; + private List endpoints; - private ApiRateLimitChecker apiRateLimitChecker; + private ApiRateLimitChecker apiRateLimitChecker; - public GitHubConfiguration() { - load(); - } + public GitHubConfiguration() { + load(); + } - @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { - req.bindJSON(this, json); - return true; - } + @Override + public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + req.bindJSON(this, json); + return true; + } - @NonNull - public synchronized List getEndpoints() { - return endpoints == null ? Collections.emptyList() : Collections.unmodifiableList(endpoints); - } + @NonNull + public synchronized List getEndpoints() { + return endpoints == null ? Collections.emptyList() : Collections.unmodifiableList(endpoints); + } - @NonNull - public synchronized ApiRateLimitChecker getApiRateLimitChecker() { - if (apiRateLimitChecker == null) { - return ApiRateLimitChecker.ThrottleForNormalize; - } - return apiRateLimitChecker; + @NonNull + public synchronized ApiRateLimitChecker getApiRateLimitChecker() { + if (apiRateLimitChecker == null) { + return ApiRateLimitChecker.ThrottleForNormalize; } - - public synchronized void setApiRateLimitChecker(@CheckForNull ApiRateLimitChecker apiRateLimitChecker) { - this.apiRateLimitChecker = apiRateLimitChecker; - save(); + return apiRateLimitChecker; + } + + public synchronized void setApiRateLimitChecker( + @CheckForNull ApiRateLimitChecker apiRateLimitChecker) { + this.apiRateLimitChecker = apiRateLimitChecker; + save(); + } + + /** + * Fix an apiUri. + * + * @param apiUri the api URI. + * @return the normalized api URI. + */ + @CheckForNull + public static String normalizeApiUri(@CheckForNull String apiUri) { + if (apiUri == null) { + return null; } - - /** - * Fix an apiUri. - * - * @param apiUri the api URI. - * @return the normalized api URI. - */ - @CheckForNull - public static String normalizeApiUri(@CheckForNull String apiUri) { - if (apiUri == null) { - return null; + try { + URI uri = new URI(apiUri).normalize(); + String scheme = uri.getScheme(); + if ("http".equals(scheme) || "https".equals(scheme)) { + // we only expect http / https, but also these are the only ones where we know the authority + // is server based, i.e. [userinfo@]server[:port] + // DNS names must be US-ASCII and are case insensitive, so we force all to lowercase + + String host = uri.getHost() == null ? null : uri.getHost().toLowerCase(Locale.ENGLISH); + int port = uri.getPort(); + if ("http".equals(scheme) && port == 80) { + port = -1; + } else if ("https".equals(scheme) && port == 443) { + port = -1; } - try { - URI uri = new URI(apiUri).normalize(); - String scheme = uri.getScheme(); - if ("http".equals(scheme) || "https".equals(scheme)) { - // we only expect http / https, but also these are the only ones where we know the authority - // is server based, i.e. [userinfo@]server[:port] - // DNS names must be US-ASCII and are case insensitive, so we force all to lowercase - - String host = uri.getHost() == null ? null : uri.getHost().toLowerCase(Locale.ENGLISH); - int port = uri.getPort(); - if ("http".equals(scheme) && port == 80) { - port = -1; - } else if ("https".equals(scheme) && port == 443) { - port = -1; - } - apiUri = new URI( - scheme, - uri.getUserInfo(), - host, - port, - uri.getPath(), - uri.getQuery(), - uri.getFragment() - ).toASCIIString(); - } - } catch (URISyntaxException e) { - // ignore, this was a best effort tidy-up - } - return apiUri.replaceAll("/$", ""); + apiUri = + new URI( + scheme, + uri.getUserInfo(), + host, + port, + uri.getPath(), + uri.getQuery(), + uri.getFragment()) + .toASCIIString(); + } + } catch (URISyntaxException e) { + // ignore, this was a best effort tidy-up } - - public synchronized void setEndpoints(@CheckForNull List endpoints) { - endpoints = new ArrayList<>(endpoints == null ? Collections.emptyList() : endpoints); - // remove duplicates and empty urls - Set apiUris = new HashSet<>(); - for (Iterator iterator = endpoints.iterator(); iterator.hasNext(); ) { - Endpoint endpoint = iterator.next(); - if (StringUtils.isBlank(endpoint.getApiUri()) || apiUris.contains(endpoint.getApiUri())) { - iterator.remove(); - } - apiUris.add(endpoint.getApiUri()); - } - this.endpoints = endpoints; - save(); + return apiUri.replaceAll("/$", ""); + } + + public synchronized void setEndpoints(@CheckForNull List endpoints) { + endpoints = new ArrayList<>(endpoints == null ? Collections.emptyList() : endpoints); + // remove duplicates and empty urls + Set apiUris = new HashSet<>(); + for (Iterator iterator = endpoints.iterator(); iterator.hasNext(); ) { + Endpoint endpoint = iterator.next(); + if (StringUtils.isBlank(endpoint.getApiUri()) || apiUris.contains(endpoint.getApiUri())) { + iterator.remove(); + } + apiUris.add(endpoint.getApiUri()); } - - /** - * Adds an endpoint. - * - * @param endpoint the endpoint to add. - * @return {@code true} if the list of endpoints was modified - */ - public synchronized boolean addEndpoint(@NonNull Endpoint endpoint) { - if (StringUtils.isBlank(endpoint.getApiUri())) { - return false; - } - List endpoints = new ArrayList<>(getEndpoints()); - for (Endpoint ep : endpoints) { - if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) { - return false; - } - } - endpoints.add(endpoint); - setEndpoints(endpoints); - return true; + this.endpoints = endpoints; + save(); + } + + /** + * Adds an endpoint. + * + * @param endpoint the endpoint to add. + * @return {@code true} if the list of endpoints was modified + */ + public synchronized boolean addEndpoint(@NonNull Endpoint endpoint) { + if (StringUtils.isBlank(endpoint.getApiUri())) { + return false; } - - /** - * Updates an existing endpoint (or adds if missing). - * - * @param endpoint the endpoint to update. - */ - public synchronized void updateEndpoint(@NonNull Endpoint endpoint) { - if (StringUtils.isBlank(endpoint.getApiUri())) { - return; - } - List endpoints = new ArrayList<>(getEndpoints()); - boolean found = false; - for (int i = 0; i < endpoints.size(); i++) { - Endpoint ep = endpoints.get(i); - if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) { - endpoints.set(i, endpoint); - found = true; - break; - } - } - if (!found) { - endpoints.add(endpoint); - } - setEndpoints(endpoints); + List endpoints = new ArrayList<>(getEndpoints()); + for (Endpoint ep : endpoints) { + if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) { + return false; + } } - - /** - * Removes an endpoint. - * - * @param endpoint the endpoint to remove. - * @return {@code true} if the list of endpoints was modified - */ - public boolean removeEndpoint(@NonNull Endpoint endpoint) { - return removeEndpoint(endpoint.getApiUri()); + endpoints.add(endpoint); + setEndpoints(endpoints); + return true; + } + + /** + * Updates an existing endpoint (or adds if missing). + * + * @param endpoint the endpoint to update. + */ + public synchronized void updateEndpoint(@NonNull Endpoint endpoint) { + if (StringUtils.isBlank(endpoint.getApiUri())) { + return; } - - /** - * Removes an endpoint. - * - * @param apiUri the API URI to remove. - * @return {@code true} if the list of endpoints was modified - */ - public synchronized boolean removeEndpoint(@CheckForNull String apiUri) { - apiUri = normalizeApiUri(apiUri); - boolean modified = false; - List endpoints = new ArrayList<>(getEndpoints()); - for (Iterator iterator = endpoints.iterator(); iterator.hasNext(); ) { - if (StringUtils.equals(apiUri, iterator.next().getApiUri())) { - iterator.remove(); - modified = true; - } - } - setEndpoints(endpoints); - return modified; + List endpoints = new ArrayList<>(getEndpoints()); + boolean found = false; + for (int i = 0; i < endpoints.size(); i++) { + Endpoint ep = endpoints.get(i); + if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) { + endpoints.set(i, endpoint); + found = true; + break; + } } - - /** - * Checks to see if the supplied server URL is defined in the global configuration. - * - * @param apiUri the server url to check. - * @return the global configuration for the specified server url or {@code null} if not defined. - */ - @CheckForNull - public synchronized Endpoint findEndpoint(@CheckForNull String apiUri) { - apiUri = normalizeApiUri(apiUri); - for (Endpoint endpoint : getEndpoints()) { - if (StringUtils.equals(apiUri, endpoint.getApiUri())) { - return endpoint; - } - } - return null; + if (!found) { + endpoints.add(endpoint); } + setEndpoints(endpoints); + } + + /** + * Removes an endpoint. + * + * @param endpoint the endpoint to remove. + * @return {@code true} if the list of endpoints was modified + */ + public boolean removeEndpoint(@NonNull Endpoint endpoint) { + return removeEndpoint(endpoint.getApiUri()); + } + + /** + * Removes an endpoint. + * + * @param apiUri the API URI to remove. + * @return {@code true} if the list of endpoints was modified + */ + public synchronized boolean removeEndpoint(@CheckForNull String apiUri) { + apiUri = normalizeApiUri(apiUri); + boolean modified = false; + List endpoints = new ArrayList<>(getEndpoints()); + for (Iterator iterator = endpoints.iterator(); iterator.hasNext(); ) { + if (StringUtils.equals(apiUri, iterator.next().getApiUri())) { + iterator.remove(); + modified = true; + } + } + setEndpoints(endpoints); + return modified; + } + + /** + * Checks to see if the supplied server URL is defined in the global configuration. + * + * @param apiUri the server url to check. + * @return the global configuration for the specified server url or {@code null} if not defined. + */ + @CheckForNull + public synchronized Endpoint findEndpoint(@CheckForNull String apiUri) { + apiUri = normalizeApiUri(apiUri); + for (Endpoint endpoint : getEndpoints()) { + if (StringUtils.equals(apiUri, endpoint.getApiUri())) { + return endpoint; + } + } + return null; + } - public ListBoxModel doFillApiRateLimitCheckerItems() { - ListBoxModel items = new ListBoxModel(); - for (ApiRateLimitChecker mode : ApiRateLimitChecker.values()) { - items.add(mode.getDisplayName(), mode.name()); - } - return items; + public ListBoxModel doFillApiRateLimitCheckerItems() { + ListBoxModel items = new ListBoxModel(); + for (ApiRateLimitChecker mode : ApiRateLimitChecker.values()) { + items.add(mode.getDisplayName(), mode.name()); } + return items; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConsoleNote.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConsoleNote.java index b2de1e623..fbaa43882 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConsoleNote.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubConsoleNote.java @@ -35,43 +35,44 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -/** - * A timestamped console note from the GitHub branch source. - */ +/** A timestamped console note from the GitHub branch source. */ @Restricted(NoExternalUse.class) public class GitHubConsoleNote extends ConsoleNote { - private static final Logger LOGGER = Logger.getLogger(GitHubConsoleNote.class.getName()); - - private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(GitHubConsoleNote.class.getName()); - private final long timestamp; + private static final long serialVersionUID = 1L; - public GitHubConsoleNote(long timestamp) { - this.timestamp = timestamp; - } + private final long timestamp; - @Override - public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { - text.addMarkup(0, text.length(), String.format("%tT ", timestamp), ""); - return null; - } + public GitHubConsoleNote(long timestamp) { + this.timestamp = timestamp; + } - public static String create(long timestamp, String text) { - try { - return new GitHubConsoleNote(timestamp).encode() + text; - } catch (IOException e) { - // impossible, but don't make this a fatal problem - LOGGER.log(Level.WARNING, "Failed to serialize " + GitHubConsoleNote.class, e); - return text; - } - } + @Override + public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { + text.addMarkup( + 0, + text.length(), + String.format("%tT ", timestamp), + ""); + return null; + } - @Extension - public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { - public String getDisplayName() { - return "GitHub API Usage"; - } + public static String create(long timestamp, String text) { + try { + return new GitHubConsoleNote(timestamp).encode() + text; + } catch (IOException e) { + // impossible, but don't make this a fatal problem + LOGGER.log(Level.WARNING, "Failed to serialize " + GitHubConsoleNote.class, e); + return text; } + } + @Extension + public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { + public String getDisplayName() { + return "GitHub API Usage"; + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubDefaultBranch.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubDefaultBranch.java index 9e9f1fc91..e9e3deb55 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubDefaultBranch.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubDefaultBranch.java @@ -28,75 +28,75 @@ import hudson.model.InvisibleAction; import java.io.Serializable; -/** - * @author Stephen Connolly - */ +/** @author Stephen Connolly */ public class GitHubDefaultBranch extends InvisibleAction implements Serializable { - private static final long serialVersionUID = 1L; - @NonNull - private final String repoOwner; - @NonNull - private final String repository; - @NonNull - private final String defaultBranch; - - public GitHubDefaultBranch(@NonNull String repoOwner, @NonNull String repository, @NonNull String defaultBranch) { - this.repoOwner = repoOwner; - this.repository = repository; - this.defaultBranch = defaultBranch; - } - - @NonNull - public String getRepoOwner() { - return repoOwner; - } + private static final long serialVersionUID = 1L; + @NonNull private final String repoOwner; + @NonNull private final String repository; + @NonNull private final String defaultBranch; - @NonNull - public String getRepository() { - return repository; - } + public GitHubDefaultBranch( + @NonNull String repoOwner, @NonNull String repository, @NonNull String defaultBranch) { + this.repoOwner = repoOwner; + this.repository = repository; + this.defaultBranch = defaultBranch; + } - @NonNull - public String getDefaultBranch() { - return defaultBranch; - } + @NonNull + public String getRepoOwner() { + return repoOwner; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + @NonNull + public String getRepository() { + return repository; + } - GitHubDefaultBranch that = (GitHubDefaultBranch) o; + @NonNull + public String getDefaultBranch() { + return defaultBranch; + } - if (!repoOwner.equals(that.repoOwner)) { - return false; - } - if (!repository.equals(that.repository)) { - return false; - } - return defaultBranch.equals(that.defaultBranch); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public int hashCode() { - int result = repoOwner.hashCode(); - result = 31 * result + repository.hashCode(); - result = 31 * result + defaultBranch.hashCode(); - return result; + if (o == null || getClass() != o.getClass()) { + return false; } - @Override - public String toString() { - return "GitHubDefaultBranch{" + - "repoOwner='" + repoOwner + '\'' + - ", repository='" + repository + '\'' + - ", defaultBranch='" + defaultBranch + '\'' + - '}'; + GitHubDefaultBranch that = (GitHubDefaultBranch) o; + + if (!repoOwner.equals(that.repoOwner)) { + return false; + } + if (!repository.equals(that.repository)) { + return false; } + return defaultBranch.equals(that.defaultBranch); + } + @Override + public int hashCode() { + int result = repoOwner.hashCode(); + result = 31 * result + repository.hashCode(); + result = 31 * result + defaultBranch.hashCode(); + return result; + } + @Override + public String toString() { + return "GitHubDefaultBranch{" + + "repoOwner='" + + repoOwner + + '\'' + + ", repository='" + + repository + + '\'' + + ", defaultBranch='" + + defaultBranch + + '\'' + + '}'; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubLink.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubLink.java index 116a4ffb4..82324fd3e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubLink.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubLink.java @@ -40,91 +40,82 @@ * @author Kohsuke Kawaguchi */ public class GitHubLink implements Action, IconSpec { - /** - * The icon class name to use. - */ - @NonNull - private final String iconClassName; - - /** - * Target of the hyperlink to take the user to. - */ - @NonNull - private final String url; - - public GitHubLink(@NonNull String iconClassName, @NonNull String url) { - this.iconClassName = iconClassName; - this.url = url; + /** The icon class name to use. */ + @NonNull private final String iconClassName; + + /** Target of the hyperlink to take the user to. */ + @NonNull private final String url; + + public GitHubLink(@NonNull String iconClassName, @NonNull String url) { + this.iconClassName = iconClassName; + this.url = url; + } + + public GitHubLink(String iconClassName, URL url) { + this(iconClassName, url.toExternalForm()); + } + + @NonNull + public String getUrl() { + return url; + } + + @Override + public String getIconClassName() { + return iconClassName; + } + + @Override + public String getIconFileName() { + String iconClassName = getIconClassName(); + if (iconClassName != null) { + Icon icon = IconSet.icons.getIconByClassSpec(iconClassName + " icon-md"); + if (icon != null) { + JellyContext ctx = new JellyContext(); + ctx.setVariable( + "resURL", Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH); + return icon.getQualifiedUrl(ctx); + } } - - public GitHubLink(String iconClassName, URL url) { - this(iconClassName, url.toExternalForm()); - } - - @NonNull - public String getUrl() { - return url; - } - - @Override - public String getIconClassName() { - return iconClassName; - } - - @Override - public String getIconFileName() { - String iconClassName = getIconClassName(); - if (iconClassName != null) { - Icon icon = IconSet.icons.getIconByClassSpec(iconClassName + " icon-md"); - if (icon != null) { - JellyContext ctx = new JellyContext(); - ctx.setVariable("resURL", Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH); - return icon.getQualifiedUrl(ctx); - } - } - return null; + return null; + } + + @Override + public String getDisplayName() { + return Messages.GitHubLink_DisplayName(); + } + + @Override + public String getUrlName() { + return url; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public String getDisplayName() { - return Messages.GitHubLink_DisplayName(); - } - - @Override - public String getUrlName() { - return url; + if (o == null || getClass() != o.getClass()) { + return false; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - GitHubLink that = (GitHubLink) o; - - if (!iconClassName.equals(that.iconClassName)) { - return false; - } - return url.equals(that.url); - } + GitHubLink that = (GitHubLink) o; - @Override - public int hashCode() { - int result = iconClassName.hashCode(); - result = 31 * result + url.hashCode(); - return result; + if (!iconClassName.equals(that.iconClassName)) { + return false; } - - @Override - public String toString() { - return "GitHubLink{" + - "iconClassName='" + iconClassName + '\'' + - ", url='" + url + '\'' + - '}'; - } - + return url.equals(that.url); + } + + @Override + public int hashCode() { + int result = iconClassName.hashCode(); + result = 31 * result + url.hashCode(); + return result; + } + + @Override + public String toString() { + return "GitHubLink{" + "iconClassName='" + iconClassName + '\'' + ", url='" + url + '\'' + '}'; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationContext.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationContext.java index cd15bf827..284b32b2b 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationContext.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationContext.java @@ -38,208 +38,216 @@ /** * Parameter object used in notification strategies {@link AbstractGitHubNotificationStrategy}. * - * When creating a new point of notification (e.g. on build completion), populate this object with the relevant details - * accessible at that point. When implementing a notification strategy, be aware that some details may be absent - * depending on the point of notification. + *

When creating a new point of notification (e.g. on build completion), populate this object + * with the relevant details accessible at that point. When implementing a notification strategy, be + * aware that some details may be absent depending on the point of notification. * * @since 2.3.2 */ public final class GitHubNotificationContext { - private final Job job; - private final Run build; - private final SCMSource source; - private final SCMHead head; - - /** - * @since 2.3.2 - */ - private GitHubNotificationContext(Job job, Run build, SCMSource source, SCMHead head) { - this.job = job; - this.build = build; - this.source = source; - this.head = head; + private final Job job; + private final Run build; + private final SCMSource source; + private final SCMHead head; + + /** @since 2.3.2 */ + private GitHubNotificationContext( + Job job, Run build, SCMSource source, SCMHead head) { + this.job = job; + this.build = build; + this.source = source; + this.head = head; + } + + public static GitHubNotificationContext build( + @Nullable Job job, @Nullable Run build, SCMSource source, SCMHead head) { + return new GitHubNotificationContext(job, build, source, head); + } + + /** + * Returns the job, if any, associated with the planned notification event + * + * @return Job + * @since 2.3.2 + */ + public Job getJob() { + return job; + } + + /** + * Returns the run, if any, associated with the planned notification event + * + * @return Run + * @since 2.3.2 + */ + public Run getBuild() { + return build; + } + + /** + * Returns the SCMSource associated with the planned notification event + * + * @return SCMSource + * @since 2.3.2 + */ + public SCMSource getSource() { + return source; + } + + /** + * Returns the SCMHead associated with the planned notification event + * + * @return SCMHead + * @since 2.3.2 + */ + public SCMHead getHead() { + return head; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "GitHubNotificationContext{" + + "job=" + + job + + ", build=" + + build + + ", source=" + + source + + ", head=" + + head + + '}'; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GitHubNotificationContext that = (GitHubNotificationContext) o; + + if (!Objects.equals(job, that.job)) return false; + if (!Objects.equals(build, that.build)) return false; + if (!Objects.equals(source, that.source)) return false; + return Objects.equals(head, that.head); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = job != null ? job.hashCode() : 0; + result = 31 * result + (build != null ? build.hashCode() : 0); + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (head != null ? head.hashCode() : 0); + return result; + } + + /** + * Retrieves default context + * + * @param listener Listener for the build, if any + * @return Default notification context + * @since 2.3.2 + */ + public String getDefaultContext(TaskListener listener) { + if (head instanceof PullRequestSCMHead) { + if (((PullRequestSCMHead) head).isMerge()) { + return "continuous-integration/jenkins/pr-merge"; + } else { + return "continuous-integration/jenkins/pr-head"; + } + } else { + return "continuous-integration/jenkins/branch"; } - - public static GitHubNotificationContext build(@Nullable Job job, @Nullable Run build, SCMSource source, SCMHead head) { - return new GitHubNotificationContext(job, build, source, head); - } - - /** - * Returns the job, if any, associated with the planned notification event - * @return Job - * @since 2.3.2 - */ - public Job getJob() { - return job; + } + + /** + * Retrieves default URL + * + * @param listener Listener for the build, if any + * @return Default notification URL backref + * @since 2.3.2 + */ + public String getDefaultUrl(TaskListener listener) { + String url = null; + try { + if (null != build) { + url = DisplayURLProvider.get().getRunURL(build); + } else if (null != job) { + url = DisplayURLProvider.get().getJobURL(job); + } + } catch (IllegalStateException e) { + listener + .getLogger() + .println( + "Can not determine Jenkins root URL. Commit status notifications are sent without URL " + + "until a root URL is" + + " configured in Jenkins global configuration."); } - - /** - * Returns the run, if any, associated with the planned notification event - * @return Run - * @since 2.3.2 - */ - public Run getBuild() { - return build; + return url; + } + + /** + * Retrieves default notification message + * + * @param listener Listener for the build, if any + * @return Default notification message + * @since 2.3.2 + */ + public String getDefaultMessage(TaskListener listener) { + if (null != build) { + Result result = build.getResult(); + if (Result.SUCCESS.equals(result)) { + return Messages.GitHubBuildStatusNotification_CommitStatus_Good(); + } else if (Result.UNSTABLE.equals(result)) { + return Messages.GitHubBuildStatusNotification_CommitStatus_Unstable(); + } else if (Result.FAILURE.equals(result)) { + return Messages.GitHubBuildStatusNotification_CommitStatus_Failure(); + } else if (Result.ABORTED.equals(result)) { + return Messages.GitHubBuildStatusNotification_CommitStatus_Aborted(); + } else if (result != null) { // NOT_BUILT etc. + return Messages.GitHubBuildStatusNotification_CommitStatus_Other(); + } else { + return Messages.GitHubBuildStatusNotification_CommitStatus_Pending(); + } } - - /** - * Returns the SCMSource associated with the planned notification event - * @return SCMSource - * @since 2.3.2 - */ - public SCMSource getSource() { - return source; - } - - /** - * Returns the SCMHead associated with the planned notification event - * @return SCMHead - * @since 2.3.2 - */ - public SCMHead getHead() { - return head; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "GitHubNotificationContext{" + - "job=" + job + - ", build=" + build + - ", source=" + source + - ", head=" + head + - '}'; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GitHubNotificationContext that = (GitHubNotificationContext) o; - - if (!Objects.equals(job, that.job)) return false; - if (!Objects.equals(build, that.build)) return false; - if (!Objects.equals(source, that.source)) return false; - return Objects.equals(head, that.head); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - int result = job != null ? job.hashCode() : 0; - result = 31 * result + (build != null ? build.hashCode() : 0); - result = 31 * result + (source != null ? source.hashCode() : 0); - result = 31 * result + (head != null ? head.hashCode() : 0); - return result; - } - - /** - * Retrieves default context - * @param listener Listener for the build, if any - * @return Default notification context - * @since 2.3.2 - */ - public String getDefaultContext(TaskListener listener) { - if (head instanceof PullRequestSCMHead) { - if (((PullRequestSCMHead) head).isMerge()) { - return "continuous-integration/jenkins/pr-merge"; - } else { - return "continuous-integration/jenkins/pr-head"; - } - } else { - return "continuous-integration/jenkins/branch"; - } - } - - /** - * Retrieves default URL - * @param listener Listener for the build, if any - * @return Default notification URL backref - * @since 2.3.2 - */ - public String getDefaultUrl(TaskListener listener) { - String url = null; - try { - if (null != build) { - url = DisplayURLProvider.get().getRunURL(build); - } - else if (null != job) { - url = DisplayURLProvider.get().getJobURL(job); - } - } catch (IllegalStateException e) { - listener.getLogger().println( - "Can not determine Jenkins root URL. Commit status notifications are sent without URL " - + "until a root URL is" - + " configured in Jenkins global configuration."); - } - return url; - } - - /** - * Retrieves default notification message - * @param listener Listener for the build, if any - * @return Default notification message - * @since 2.3.2 - */ - public String getDefaultMessage(TaskListener listener) { - if (null != build) { - Result result = build.getResult(); - if (Result.SUCCESS.equals(result)) { - return Messages.GitHubBuildStatusNotification_CommitStatus_Good(); - } else if (Result.UNSTABLE.equals(result)) { - return Messages.GitHubBuildStatusNotification_CommitStatus_Unstable(); - } else if (Result.FAILURE.equals(result)) { - return Messages.GitHubBuildStatusNotification_CommitStatus_Failure(); - } else if (Result.ABORTED.equals(result)) { - return Messages.GitHubBuildStatusNotification_CommitStatus_Aborted(); - } else if (result != null) { // NOT_BUILT etc. - return Messages.GitHubBuildStatusNotification_CommitStatus_Other(); - } else { - return Messages.GitHubBuildStatusNotification_CommitStatus_Pending(); - } - } - return Messages.GitHubBuildStatusNotification_CommitStatus_Queued(); - } - - /** - * Retrieves default notification state - * @param listener Listener for the build, if any - * @return Default notification state - * @since 2.3.2 - */ - public GHCommitState getDefaultState(TaskListener listener) { - if (null != build && !build.isBuilding()) { - Result result = build.getResult(); - if (Result.SUCCESS.equals(result)) { - return GHCommitState.SUCCESS; - } else if (Result.UNSTABLE.equals(result)) { - return GHCommitState.FAILURE; - } else if (Result.FAILURE.equals(result)) { - return GHCommitState.ERROR; - } else if (Result.ABORTED.equals(result)) { - return GHCommitState.ERROR; - } else if (result != null) { // NOT_BUILT etc. - return GHCommitState.ERROR; - } - } - return GHCommitState.PENDING; - } - - /** - * Retrieves whether plugin should ignore errors when updating the GitHub status - * @param listener Listener for the build, if any - * @return Default ignore errors policy - * @since 2.3.2 - */ - public boolean getDefaultIgnoreError(TaskListener listener) { - return null == build || null == build.getResult(); + return Messages.GitHubBuildStatusNotification_CommitStatus_Queued(); + } + + /** + * Retrieves default notification state + * + * @param listener Listener for the build, if any + * @return Default notification state + * @since 2.3.2 + */ + public GHCommitState getDefaultState(TaskListener listener) { + if (null != build && !build.isBuilding()) { + Result result = build.getResult(); + if (Result.SUCCESS.equals(result)) { + return GHCommitState.SUCCESS; + } else if (Result.UNSTABLE.equals(result)) { + return GHCommitState.FAILURE; + } else if (Result.FAILURE.equals(result)) { + return GHCommitState.ERROR; + } else if (Result.ABORTED.equals(result)) { + return GHCommitState.ERROR; + } else if (result != null) { // NOT_BUILT etc. + return GHCommitState.ERROR; + } } + return GHCommitState.PENDING; + } + + /** + * Retrieves whether plugin should ignore errors when updating the GitHub status + * + * @param listener Listener for the build, if any + * @return Default ignore errors policy + * @since 2.3.2 + */ + public boolean getDefaultIgnoreError(TaskListener listener) { + return null == build || null == build.getResult(); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationRequest.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationRequest.java index decfacc09..439f33912 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationRequest.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationRequest.java @@ -30,124 +30,130 @@ /** * Details of a GitHub status notification to be sent. * - * One GitHubNotificationRequest represents one notification. A strategy supplies a list of these to request one or more - * notifications. Notifications are differentiated by their Context label. If two notification requests with the same - * Context label are provided, one will override the other. - * - * @see GitHub API for details of the purpose of each - * notification field. + *

One GitHubNotificationRequest represents one notification. A strategy supplies a list of these + * to request one or more notifications. Notifications are differentiated by their Context label. If + * two notification requests with the same Context label are provided, one will override the other. * + * @see GitHub API for details of the + * purpose of each notification field. * @since 2.3.2 */ public class GitHubNotificationRequest { - private final String context; - private final String url; - private final String message; - private final GHCommitState state; - private final boolean ignoreError; - - /** - * @since 2.3.2 - */ - private GitHubNotificationRequest(String context, String url, String message, GHCommitState state, boolean ignoreError) { - this.context = context; - this.url = url; - this.message = message; - this.state = state; - this.ignoreError = ignoreError; - } - - public static GitHubNotificationRequest build(String context, String url, String message, GHCommitState state, boolean ignoreError) { - return new GitHubNotificationRequest(context, url, message, state, ignoreError); - } - - /** - * Returns the context label to be used for a notification - * @return context - * @since 2.3.2 - */ - public String getContext() { - return context; - } - - /** - * Returns the URL to be supplied with a notification - * @return url - * @since 2.3.2 - */ - public String getUrl() { - return url; - } - - /** - * Returns the message for a notification - * @return message - * @since 2.3.2 - */ - public String getMessage() { - return message; - } - - /** - * Returns the commit state of a notification - * @return state - * @since 2.3.2 - */ - public GHCommitState getState() { - return state; - } - - /** - * Returns whether the notification processor should ignore errors when interacting with GitHub - * @return ignoreError - * @since 2.3.2 - */ - public boolean isIgnoreError() { - return ignoreError; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "GitHubNotificationRequest{" + - "context='" + context + '\'' + - ", url='" + url + '\'' + - ", message='" + message + '\'' + - ", state=" + state + - ", ignoreError=" + ignoreError + - '}'; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GitHubNotificationRequest that = (GitHubNotificationRequest) o; - - if (ignoreError != that.ignoreError) return false; - if (!Objects.equals(context, that.context)) return false; - if (!Objects.equals(url, that.url)) return false; - if (!Objects.equals(message, that.message)) return false; - return state == that.state; - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - int result = context != null ? context.hashCode() : 0; - result = 31 * result + (url != null ? url.hashCode() : 0); - result = 31 * result + (message != null ? message.hashCode() : 0); - result = 31 * result + (state != null ? state.hashCode() : 0); - result = 31 * result + (ignoreError ? 1 : 0); - return result; - } + private final String context; + private final String url; + private final String message; + private final GHCommitState state; + private final boolean ignoreError; + + /** @since 2.3.2 */ + private GitHubNotificationRequest( + String context, String url, String message, GHCommitState state, boolean ignoreError) { + this.context = context; + this.url = url; + this.message = message; + this.state = state; + this.ignoreError = ignoreError; + } + + public static GitHubNotificationRequest build( + String context, String url, String message, GHCommitState state, boolean ignoreError) { + return new GitHubNotificationRequest(context, url, message, state, ignoreError); + } + + /** + * Returns the context label to be used for a notification + * + * @return context + * @since 2.3.2 + */ + public String getContext() { + return context; + } + + /** + * Returns the URL to be supplied with a notification + * + * @return url + * @since 2.3.2 + */ + public String getUrl() { + return url; + } + + /** + * Returns the message for a notification + * + * @return message + * @since 2.3.2 + */ + public String getMessage() { + return message; + } + + /** + * Returns the commit state of a notification + * + * @return state + * @since 2.3.2 + */ + public GHCommitState getState() { + return state; + } + + /** + * Returns whether the notification processor should ignore errors when interacting with GitHub + * + * @return ignoreError + * @since 2.3.2 + */ + public boolean isIgnoreError() { + return ignoreError; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "GitHubNotificationRequest{" + + "context='" + + context + + '\'' + + ", url='" + + url + + '\'' + + ", message='" + + message + + '\'' + + ", state=" + + state + + ", ignoreError=" + + ignoreError + + '}'; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GitHubNotificationRequest that = (GitHubNotificationRequest) o; + + if (ignoreError != that.ignoreError) return false; + if (!Objects.equals(context, that.context)) return false; + if (!Objects.equals(url, that.url)) return false; + if (!Objects.equals(message, that.message)) return false; + return state == that.state; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = context != null ? context.hashCode() : 0; + result = 31 * result + (url != null ? url.hashCode() : 0); + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + (state != null ? state.hashCode() : 0); + result = 31 * result + (ignoreError ? 1 : 0); + return result; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java index 9db32fa54..85dcd42e6 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgMetadataAction.java @@ -37,106 +37,93 @@ import org.kohsuke.stapler.Stapler; /** - * Invisible {@link AvatarMetadataAction} property that retains information about GitHub organization. + * Invisible {@link AvatarMetadataAction} property that retains information about GitHub + * organization. * * @author Kohsuke Kawaguchi */ public class GitHubOrgMetadataAction extends AvatarMetadataAction { - @CheckForNull - private final String avatar; - - public GitHubOrgMetadataAction(@NonNull GHUser org) throws IOException { - this(org.getAvatarUrl()); - } - - public GitHubOrgMetadataAction(@CheckForNull String avatar) { - this.avatar = Util.fixEmpty(avatar); - } - - public GitHubOrgMetadataAction(@NonNull GitHubOrgMetadataAction that) { - this(that.getAvatar()); - } - - private Object readResolve() throws ObjectStreamException { - if (avatar != null && StringUtils.isBlank(avatar)) - return new GitHubOrgMetadataAction(this); - return this; - } - - @CheckForNull - public String getAvatar() { - return Util.fixEmpty(avatar); - } - - /** - * {@inheritDoc} - */ - @Override - public String getAvatarImageOf(String size) { - if (avatar == null) { - // fall back to the generic github org icon - String image = avatarIconClassNameImageOf(getAvatarIconClassName(), size); - return image != null - ? image - : (Stapler.getCurrentRequest().getContextPath() + Hudson.RESOURCE_PATH - + "/plugin/github-branch-source/images/" + size + "/github-logo.png"); - } else { - String[] xy = size.split("x"); - if (xy.length == 0) return avatar; - if (avatar.contains("?")) return avatar + "&s=" + xy[0]; - else return avatar + "?s=" + xy[0]; - } + @CheckForNull private final String avatar; + + public GitHubOrgMetadataAction(@NonNull GHUser org) throws IOException { + this(org.getAvatarUrl()); + } + + public GitHubOrgMetadataAction(@CheckForNull String avatar) { + this.avatar = Util.fixEmpty(avatar); + } + + public GitHubOrgMetadataAction(@NonNull GitHubOrgMetadataAction that) { + this(that.getAvatar()); + } + + private Object readResolve() throws ObjectStreamException { + if (avatar != null && StringUtils.isBlank(avatar)) return new GitHubOrgMetadataAction(this); + return this; + } + + @CheckForNull + public String getAvatar() { + return Util.fixEmpty(avatar); + } + + /** {@inheritDoc} */ + @Override + public String getAvatarImageOf(String size) { + if (avatar == null) { + // fall back to the generic github org icon + String image = avatarIconClassNameImageOf(getAvatarIconClassName(), size); + return image != null + ? image + : (Stapler.getCurrentRequest().getContextPath() + + Hudson.RESOURCE_PATH + + "/plugin/github-branch-source/images/" + + size + + "/github-logo.png"); + } else { + String[] xy = size.split("x"); + if (xy.length == 0) return avatar; + if (avatar.contains("?")) return avatar + "&s=" + xy[0]; + else return avatar + "?s=" + xy[0]; } - - /** - * {@inheritDoc} - */ - @Override - public String getAvatarIconClassName() { - return avatar == null ? "icon-github-logo" : null; + } + + /** {@inheritDoc} */ + @Override + public String getAvatarIconClassName() { + return avatar == null ? "icon-github-logo" : null; + } + + /** {@inheritDoc} */ + @Override + public String getAvatarDescription() { + return Messages.GitHubOrgMetadataAction_IconDescription(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - /** - * {@inheritDoc} - */ - @Override - public String getAvatarDescription() { - return Messages.GitHubOrgMetadataAction_IconDescription(); + if (o == null || getClass() != o.getClass()) { + return false; } - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - GitHubOrgMetadataAction that = (GitHubOrgMetadataAction) o; - - return Objects.equals(avatar, that.avatar); - } + GitHubOrgMetadataAction that = (GitHubOrgMetadataAction) o; - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return (avatar != null ? avatar.hashCode() : 0); - } + return Objects.equals(avatar, that.avatar); + } - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "GitHubOrgMetadataAction{" + - ", avatar='" + avatar + '\'' + - "}"; - } + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (avatar != null ? avatar.hashCode() : 0); + } + /** {@inheritDoc} */ + @Override + public String toString() { + return "GitHubOrgMetadataAction{" + ", avatar='" + avatar + '\'' + "}"; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHook.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHook.java index d38ecfb28..d027766d0 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHook.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHook.java @@ -40,94 +40,108 @@ import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; -/** - * Manages the GitHub organization webhook. - */ +/** Manages the GitHub organization webhook. */ public class GitHubOrgWebHook { - private static final Logger LOGGER = Logger.getLogger(GitHubOrgWebHook.class.getName()); - private static final List EVENTS = Arrays.asList(GHEvent.REPOSITORY, GHEvent.PUSH, GHEvent.PULL_REQUEST, GHEvent.PULL_REQUEST_REVIEW_COMMENT); + private static final Logger LOGGER = Logger.getLogger(GitHubOrgWebHook.class.getName()); + private static final List EVENTS = + Arrays.asList( + GHEvent.REPOSITORY, + GHEvent.PUSH, + GHEvent.PULL_REQUEST, + GHEvent.PULL_REQUEST_REVIEW_COMMENT); - public static void register(GitHub hub, String orgName) throws IOException { - String rootUrl = System.getProperty("jenkins.hook.url"); - if (rootUrl == null) { - rootUrl = Jenkins.get().getRootUrl(); - } - if (rootUrl == null) { - return; + public static void register(GitHub hub, String orgName) throws IOException { + String rootUrl = System.getProperty("jenkins.hook.url"); + if (rootUrl == null) { + rootUrl = Jenkins.get().getRootUrl(); + } + if (rootUrl == null) { + return; + } + GHUser u = hub.getUser(orgName); + FileBoolean orghook = new FileBoolean(getTrackingFile(orgName)); + if (orghook.isOff()) { + try { + GHOrganization org = hub.getOrganization(orgName); + String url = rootUrl + "github-webhook/"; + boolean found = false; + for (GHHook hook : org.getHooks()) { + if (hook.getConfig().get("url").equals(url)) { + found = !hook.getEvents().containsAll(EVENTS); + break; + } } - GHUser u = hub.getUser(orgName); - FileBoolean orghook = new FileBoolean(getTrackingFile(orgName)); - if (orghook.isOff()) { - try { - GHOrganization org = hub.getOrganization(orgName); - String url = rootUrl + "github-webhook/"; - boolean found = false; - for (GHHook hook : org.getHooks()) { - if (hook.getConfig().get("url").equals(url)) { - found = !hook.getEvents().containsAll(EVENTS); - break; - } - } - if (!found) { - org.createWebHook(new URL(url), EVENTS); - LOGGER.log(Level.INFO, "A webhook was registered for the organization {0}", org.getHtmlUrl()); - // keep trying until the hook gets successfully installed - // if the user doesn't have the proper permission, this will cause - // a repeated failure, but this code doesn't execute too often. - } - orghook.on(); - } catch (FileNotFoundException e) { - LOGGER.log(Level.WARNING, "Failed to register GitHub Org hook to {0} (missing permissions?): {1}", - new Object[]{u.getHtmlUrl(), e.getMessage()}); - LOGGER.log(Level.FINE, null, e); - } catch (RateLimitExceededException e) { - LOGGER.log(Level.WARNING, "Failed to register GitHub Org hook to {0}: {1}", - new Object[]{u.getHtmlUrl(), e.getMessage()}); - LOGGER.log(Level.FINE, null, e); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to register GitHub Org hook to " + u.getHtmlUrl(), e); - } + if (!found) { + org.createWebHook(new URL(url), EVENTS); + LOGGER.log( + Level.INFO, "A webhook was registered for the organization {0}", org.getHtmlUrl()); + // keep trying until the hook gets successfully installed + // if the user doesn't have the proper permission, this will cause + // a repeated failure, but this code doesn't execute too often. } + orghook.on(); + } catch (FileNotFoundException e) { + LOGGER.log( + Level.WARNING, + "Failed to register GitHub Org hook to {0} (missing permissions?): {1}", + new Object[] {u.getHtmlUrl(), e.getMessage()}); + LOGGER.log(Level.FINE, null, e); + } catch (RateLimitExceededException e) { + LOGGER.log( + Level.WARNING, + "Failed to register GitHub Org hook to {0}: {1}", + new Object[] {u.getHtmlUrl(), e.getMessage()}); + LOGGER.log(Level.FINE, null, e); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to register GitHub Org hook to " + u.getHtmlUrl(), e); + } } + } - private static File getTrackingFile(String orgName) { - return new File(Jenkins.get().getRootDir(), "github-webhooks/GitHubOrgHook." + orgName); - } + private static File getTrackingFile(String orgName) { + return new File(Jenkins.get().getRootDir(), "github-webhooks/GitHubOrgHook." + orgName); + } - public static void deregister(GitHub hub, String orgName) throws IOException { - String rootUrl = Jenkins.get().getRootUrl(); - if (rootUrl == null) { - return; - } - GHUser u = hub.getUser(orgName); - FileBoolean orghook = new FileBoolean(getTrackingFile(orgName)); - if (orghook.isOn()) { - try { - GHOrganization org = hub.getOrganization(orgName); - String url = rootUrl + "github-webhook/"; - for (GHHook hook : org.getHooks()) { - if (hook.getConfig().get("url").equals(url)) { - hook.delete(); - LOGGER.log(Level.INFO, "A webhook was deregistered for the organization {0}", org.getHtmlUrl()); - // keep trying until the hook gets successfully uninstalled - // if the user doesn't have the proper permission, this will cause - // a repeated failure, but this code doesn't execute too often. - } - } - orghook.off(); - } catch (FileNotFoundException e) { - LOGGER.log(Level.WARNING, "Failed to deregister GitHub Org hook to {0} (missing permissions?): {1}", - new Object[]{u.getHtmlUrl(), e.getMessage()}); - LOGGER.log(Level.FINE, null, e); - } catch (RateLimitExceededException e) { - LOGGER.log(Level.WARNING, "Failed to deregister GitHub Org hook to {0}: {1}", - new Object[]{u.getHtmlUrl(), e.getMessage()}); - LOGGER.log(Level.FINE, null, e); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to deregister GitHub Org hook to " + u.getHtmlUrl(), e); - } + public static void deregister(GitHub hub, String orgName) throws IOException { + String rootUrl = Jenkins.get().getRootUrl(); + if (rootUrl == null) { + return; + } + GHUser u = hub.getUser(orgName); + FileBoolean orghook = new FileBoolean(getTrackingFile(orgName)); + if (orghook.isOn()) { + try { + GHOrganization org = hub.getOrganization(orgName); + String url = rootUrl + "github-webhook/"; + for (GHHook hook : org.getHooks()) { + if (hook.getConfig().get("url").equals(url)) { + hook.delete(); + LOGGER.log( + Level.INFO, + "A webhook was deregistered for the organization {0}", + org.getHtmlUrl()); + // keep trying until the hook gets successfully uninstalled + // if the user doesn't have the proper permission, this will cause + // a repeated failure, but this code doesn't execute too often. + } } + orghook.off(); + } catch (FileNotFoundException e) { + LOGGER.log( + Level.WARNING, + "Failed to deregister GitHub Org hook to {0} (missing permissions?): {1}", + new Object[] {u.getHtmlUrl(), e.getMessage()}); + LOGGER.log(Level.FINE, null, e); + } catch (RateLimitExceededException e) { + LOGGER.log( + Level.WARNING, + "Failed to deregister GitHub Org hook to {0}: {1}", + new Object[] {u.getHtmlUrl(), e.getMessage()}); + LOGGER.log(Level.FINE, null, e); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to deregister GitHub Org hook to " + u.getHtmlUrl(), e); + } } - + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPermissionsSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPermissionsSource.java index c17f0f809..c62d57590 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPermissionsSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPermissionsSource.java @@ -33,13 +33,13 @@ * @since 2.2.2 */ public abstract class GitHubPermissionsSource { - /** - * Fetches the permissions of the supplied username. - * - * @param username the username. - * @return the permissions. - * @throws IOException if there was an IO error. - * @throws InterruptedException if interrupted. - */ - public abstract GHPermissionType fetch(String username) throws IOException, InterruptedException; + /** + * Fetches the permissions of the supplied username. + * + * @param username the username. + * @return the permissions. + * @throws IOException if there was an IO error. + * @throws InterruptedException if interrupted. + */ + public abstract GHPermissionType fetch(String username) throws IOException, InterruptedException; } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPullRequestFilter.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPullRequestFilter.java index 60e2b1e48..b93f065ac 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPullRequestFilter.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubPullRequestFilter.java @@ -39,42 +39,33 @@ * @since 2.0.0 */ public class GitHubPullRequestFilter extends ViewJobFilter { - /** - * Our constructor. - */ - @DataBoundConstructor - public GitHubPullRequestFilter() { - } + /** Our constructor. */ + @DataBoundConstructor + public GitHubPullRequestFilter() {} - /** - * {@inheritDoc} - */ - @Override - public List filter(List added, List all, View filteringView) { - for (TopLevelItem item:all) { - if (added.contains(item)) { - continue; - } - if (SCMHead.HeadByItem.findHead(item) instanceof PullRequestSCMHead) { - added.add(item); - } - } - return added; + /** {@inheritDoc} */ + @Override + public List filter( + List added, List all, View filteringView) { + for (TopLevelItem item : all) { + if (added.contains(item)) { + continue; + } + if (SCMHead.HeadByItem.findHead(item) instanceof PullRequestSCMHead) { + added.add(item); + } } + return added; + } - /** - * Our descriptor. - */ - @Extension(optional = true) - public static class DescriptorImpl extends Descriptor { + /** Our descriptor. */ + @Extension(optional = true) + public static class DescriptorImpl extends Descriptor { - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.GitHubPullRequestFilter_DisplayName(); - } + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.GitHubPullRequestFilter_DisplayName(); } - + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoMetadataAction.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoMetadataAction.java index 467c64fb4..f0bffb586 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoMetadataAction.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoMetadataAction.java @@ -33,50 +33,39 @@ */ public class GitHubRepoMetadataAction extends AvatarMetadataAction { - /** - * {@inheritDoc} - */ - @Override - public String getAvatarIconClassName() { - return "icon-github-repo"; - } - - /** - * {@inheritDoc} - */ - @Override - public String getAvatarDescription() { - return Messages.GitHubRepoMetadataAction_IconDescription(); - } + /** {@inheritDoc} */ + @Override + public String getAvatarIconClassName() { + return "icon-github-repo"; + } - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - return true; + /** {@inheritDoc} */ + @Override + public String getAvatarDescription() { + return Messages.GitHubRepoMetadataAction_IconDescription(); + } + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return 0; + if (o == null || getClass() != o.getClass()) { + return false; } + return true; + } - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return "GitHubRepoMetadataAction{}"; - } + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "GitHubRepoMetadataAction{}"; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java index f74c86c5f..68cda612f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimator.java @@ -3,40 +3,42 @@ import com.cloudbees.plugins.credentials.common.StandardCredentials; import hudson.Extension; import hudson.model.Item; +import jenkins.plugins.git.GitToolChooser; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import jenkins.plugins.git.GitToolChooser; - -import java.io.IOException; public class GitHubRepoSizeEstimator { - /** - * This extension intends to perform a GET request without any credentials on the provided repository URL - * to return the size of repository. - */ - @Extension - public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySizeAPI { + /** + * This extension intends to perform a GET request without any credentials on the provided + * repository URL to return the size of repository. + */ + @Extension + public static class RepositorySizeGithubAPI extends GitToolChooser.RepositorySizeAPI { - @Override - public boolean isApplicableTo(String repoUrl, Item context, String credentialsId) { - StandardCredentials credentials = Connector.lookupScanCredentials(context, repoUrl, credentialsId); - try { - GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); - GitHub gitHub = Connector.connect(info.getApiUri(), credentials); - gitHub.checkApiUrlValidity(); - } catch (Exception e) { - return false; - } - return true; - } + @Override + public boolean isApplicableTo(String repoUrl, Item context, String credentialsId) { + StandardCredentials credentials = + Connector.lookupScanCredentials(context, repoUrl, credentialsId); + try { + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); + GitHub gitHub = Connector.connect(info.getApiUri(), credentials); + gitHub.checkApiUrlValidity(); + } catch (Exception e) { + return false; + } + return true; + } - @Override - public Long getSizeOfRepository(String repoUrl, Item context, String credentialsId) throws Exception { - StandardCredentials credentials = Connector.lookupScanCredentials(context, repoUrl, credentialsId); - GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); - GitHub github = Connector.connect(info.getApiUri(), credentials); - GHRepository ghRepository = github.getRepository(info.getRepoOwner() + '/' + info.getRepository()); - return (long) ghRepository.getSize(); - } + @Override + public Long getSizeOfRepository(String repoUrl, Item context, String credentialsId) + throws Exception { + StandardCredentials credentials = + Connector.lookupScanCredentials(context, repoUrl, credentialsId); + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repoUrl); + GitHub github = Connector.connect(info.getApiUri(), credentials); + GHRepository ghRepository = + github.getRepository(info.getRepoOwner() + '/' + info.getRepository()); + return (long) ghRepository.getSize(); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryEventSubscriber.java index 508c44231..7e248adc0 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryEventSubscriber.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryEventSubscriber.java @@ -23,6 +23,11 @@ */ package org.jenkinsci.plugins.github_branch_source; +import static com.google.common.collect.Sets.immutableEnumSet; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.WARNING; +import static org.kohsuke.github.GHEvent.REPOSITORY; + import com.cloudbees.jenkins.GitHubRepositoryName; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -47,119 +52,120 @@ import org.kohsuke.github.GHEventPayload; import org.kohsuke.github.GitHub; -import static com.google.common.collect.Sets.immutableEnumSet; -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.WARNING; -import static org.kohsuke.github.GHEvent.REPOSITORY; - -/** - * This subscriber manages {@link org.kohsuke.github.GHEvent} REPOSITORY. - */ +/** This subscriber manages {@link org.kohsuke.github.GHEvent} REPOSITORY. */ @Extension public class GitHubRepositoryEventSubscriber extends GHEventsSubscriber { - private static final Logger LOGGER = Logger.getLogger(GitHubRepositoryEventSubscriber.class.getName()); - private static final Pattern REPOSITORY_NAME_PATTERN = Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); - + private static final Logger LOGGER = + Logger.getLogger(GitHubRepositoryEventSubscriber.class.getName()); + private static final Pattern REPOSITORY_NAME_PATTERN = + Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); - @Override - protected boolean isApplicable(@Nullable Item item) { - if (item instanceof SCMNavigatorOwner) { - for (SCMNavigator navigator : ((SCMNavigatorOwner) item).getSCMNavigators()) { - if (navigator instanceof GitHubSCMNavigator) { - return true; // TODO allow navigators to opt-out - } - } + @Override + protected boolean isApplicable(@Nullable Item item) { + if (item instanceof SCMNavigatorOwner) { + for (SCMNavigator navigator : ((SCMNavigatorOwner) item).getSCMNavigators()) { + if (navigator instanceof GitHubSCMNavigator) { + return true; // TODO allow navigators to opt-out } - return false; + } } + return false; + } - /** - * @return set with only REPOSITORY event - */ - @Override - protected Set events() { - return immutableEnumSet(REPOSITORY); - } + /** @return set with only REPOSITORY event */ + @Override + protected Set events() { + return immutableEnumSet(REPOSITORY); + } - @Override - protected void onEvent(GHSubscriberEvent event) { - try { - final GHEventPayload.Repository p = GitHub.offline() - .parseEventPayload(new StringReader(event.getPayload()), GHEventPayload.Repository.class); - String action = p.getAction(); - String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); - LOGGER.log(Level.FINE, "Received {0} for {1} from {2}", - new Object[]{event.getGHEvent(), repoUrl, event.getOrigin()} - ); - boolean fork = p.getRepository().isFork(); - Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); - if (matcher.matches()) { - final GitHubRepositoryName repo = GitHubRepositoryName.create(repoUrl); - if (repo == null) { - LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl); - return; - } - if (!"created".equals(action)) { - LOGGER.log(FINE, "Repository {0} was {1} not created, will be ignored", - new Object[]{repo.getRepositoryName(), action}); - return; - } - if (!fork) { - LOGGER.log(FINE, "Repository {0} was created but it is empty, will be ignored", - repo.getRepositoryName()); - return; - } - final NewSCMSourceEvent e = new NewSCMSourceEvent(event.getTimestamp(), event.getOrigin(), p, repo); - // Delaying the indexing for some seconds to avoid GitHub cache - SCMSourceEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); - } else { - LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl); - } - } catch (IOException e) { - LogRecord lr = new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); - lr.setParameters(new Object[]{event.getGHEvent(), event.getOrigin(), event.getPayload()}); - lr.setThrown(e); - LOGGER.log(lr); + @Override + protected void onEvent(GHSubscriberEvent event) { + try { + final GHEventPayload.Repository p = + GitHub.offline() + .parseEventPayload( + new StringReader(event.getPayload()), GHEventPayload.Repository.class); + String action = p.getAction(); + String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); + LOGGER.log( + Level.FINE, + "Received {0} for {1} from {2}", + new Object[] {event.getGHEvent(), repoUrl, event.getOrigin()}); + boolean fork = p.getRepository().isFork(); + Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); + if (matcher.matches()) { + final GitHubRepositoryName repo = GitHubRepositoryName.create(repoUrl); + if (repo == null) { + LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl); + return; + } + if (!"created".equals(action)) { + LOGGER.log( + FINE, + "Repository {0} was {1} not created, will be ignored", + new Object[] {repo.getRepositoryName(), action}); + return; } + if (!fork) { + LOGGER.log( + FINE, + "Repository {0} was created but it is empty, will be ignored", + repo.getRepositoryName()); + return; + } + final NewSCMSourceEvent e = + new NewSCMSourceEvent(event.getTimestamp(), event.getOrigin(), p, repo); + // Delaying the indexing for some seconds to avoid GitHub cache + SCMSourceEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + } else { + LOGGER.log(WARNING, "Malformed repository URL {0}", repoUrl); + } + } catch (IOException e) { + LogRecord lr = + new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); + lr.setParameters(new Object[] {event.getGHEvent(), event.getOrigin(), event.getPayload()}); + lr.setThrown(e); + LOGGER.log(lr); } + } - private static class NewSCMSourceEvent extends SCMSourceEvent { - private final String repoHost; - private final String repoOwner; - private final String repository; + private static class NewSCMSourceEvent extends SCMSourceEvent { + private final String repoHost; + private final String repoOwner; + private final String repository; - public NewSCMSourceEvent(long timestamp, String origin, GHEventPayload.Repository event, - GitHubRepositoryName repo) { - super(Type.CREATED, timestamp, event, origin); - this.repoHost = repo.getHost(); - this.repoOwner = event.getRepository().getOwnerName(); - this.repository = event.getRepository().getName(); - } + public NewSCMSourceEvent( + long timestamp, String origin, GHEventPayload.Repository event, GitHubRepositoryName repo) { + super(Type.CREATED, timestamp, event, origin); + this.repoHost = repo.getHost(); + this.repoOwner = event.getRepository().getOwnerName(); + this.repository = event.getRepository().getName(); + } - private boolean isApiMatch(String apiUri) { - return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); - } + private boolean isApiMatch(String apiUri) { + return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); + } - @Override - public boolean isMatch(@NonNull SCMNavigator navigator) { - return navigator instanceof GitHubSCMNavigator - && isApiMatch(((GitHubSCMNavigator) navigator).getApiUri()) - && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); - } + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + return navigator instanceof GitHubSCMNavigator + && isApiMatch(((GitHubSCMNavigator) navigator).getApiUri()) + && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); + } - @Override - public boolean isMatch(@NonNull SCMSource source) { - return source instanceof GitHubSCMSource - && isApiMatch(((GitHubSCMSource) source).getApiUri()) - && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) - && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()); - } + @Override + public boolean isMatch(@NonNull SCMSource source) { + return source instanceof GitHubSCMSource + && isApiMatch(((GitHubSCMSource) source).getApiUri()) + && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) + && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()); + } - @NonNull - @Override - public String getSourceName() { - return repository; - } + @NonNull + @Override + public String getSourceName() { + return repository; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java index c54aefaca..9b99cc39e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubRepositoryInfo.java @@ -24,102 +24,98 @@ package org.jenkinsci.plugins.github_branch_source; -import edu.umd.cs.findbugs.annotations.NonNull; -import org.apache.commons.lang.StringUtils; +import static org.apache.commons.lang.StringUtils.removeEnd; +import static org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.GITHUB_COM; +import edu.umd.cs.findbugs.annotations.NonNull; import java.net.MalformedURLException; import java.net.URL; - -import static org.apache.commons.lang.StringUtils.removeEnd; -import static org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.GITHUB_COM; +import org.apache.commons.lang.StringUtils; /** * Used to compute values for GitHubSCMSource from a user-specified repository URL. - * - * TODO: Is it possible to compute the API URI from just a repository URL, or not because of the possibility of proxies, etc.? - * Is it worth making a guess based on the specified host? + * + *

TODO: Is it possible to compute the API URI from just a repository URL, or not because of the + * possibility of proxies, etc.? Is it worth making a guess based on the specified host? */ class GitHubRepositoryInfo { - private static final String GITHUB_API_URL = "api.github.com"; - @NonNull - private final String apiUri; + private static final String GITHUB_API_URL = "api.github.com"; + @NonNull private final String apiUri; - @NonNull - private final String repoOwner; + @NonNull private final String repoOwner; - @NonNull - private final String repository; + @NonNull private final String repository; - @NonNull - private final String repositoryUrl; + @NonNull private final String repositoryUrl; - private GitHubRepositoryInfo(String apiUri, String repoOwner, String repository, String repositoryUrl) { - this.apiUri = apiUri; - this.repoOwner = repoOwner; - this.repository = repository; - this.repositoryUrl = repositoryUrl; - } + private GitHubRepositoryInfo( + String apiUri, String repoOwner, String repository, String repositoryUrl) { + this.apiUri = apiUri; + this.repoOwner = repoOwner; + this.repository = repository; + this.repositoryUrl = repositoryUrl; + } - public String getApiUri() { - return apiUri; - } + public String getApiUri() { + return apiUri; + } - public String getRepoOwner() { - return repoOwner; - } + public String getRepoOwner() { + return repoOwner; + } - public String getRepository() { - return repository; - } + public String getRepository() { + return repository; + } - public String getRepositoryUrl() { - return repositoryUrl; - } + public String getRepositoryUrl() { + return repositoryUrl; + } - @NonNull - public static GitHubRepositoryInfo forRepositoryUrl(@NonNull String repositoryUrl) { - String trimmedRepoUrl = repositoryUrl.trim(); - if (StringUtils.isBlank(trimmedRepoUrl)) { - throw new IllegalArgumentException("Repository URL must not be empty"); - } - URL url; - try { - url = new URL(trimmedRepoUrl); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } - if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { - throw new IllegalArgumentException("Invalid repository URL scheme (must be HTTPS or HTTP): " + url.getProtocol()); - } - String apiUri = guessApiUri(url); - String[] pathParts = StringUtils.removeStart(url.getPath(), "/").split("/"); - if (pathParts.length != 2) { - throw new IllegalArgumentException("Invalid repository URL: " + repositoryUrl); - } else { - String repoOwner = pathParts[0]; - String repository = removeEnd(pathParts[1], ".git"); - return new GitHubRepositoryInfo(apiUri, repoOwner, repository, repositoryUrl.trim()); - } + @NonNull + public static GitHubRepositoryInfo forRepositoryUrl(@NonNull String repositoryUrl) { + String trimmedRepoUrl = repositoryUrl.trim(); + if (StringUtils.isBlank(trimmedRepoUrl)) { + throw new IllegalArgumentException("Repository URL must not be empty"); } - - private static String guessApiUri(URL repositoryUrl) { - StringBuilder sb = new StringBuilder(); - sb.append(repositoryUrl.getProtocol()); - sb.append("://"); - boolean isGitHub = GITHUB_COM.equals(repositoryUrl.getHost()); - if(isGitHub){ - sb.append(GITHUB_API_URL); - }else { - sb.append(repositoryUrl.getHost()); - } - if (repositoryUrl.getPort() != -1) { - sb.append(':'); - sb.append(repositoryUrl.getPort()); - } - if(!isGitHub) { - sb.append('/').append(GitHubSCMBuilder.API_V3); - } - return GitHubConfiguration.normalizeApiUri(sb.toString()); + URL url; + try { + url = new URL(trimmedRepoUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); } - + if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { + throw new IllegalArgumentException( + "Invalid repository URL scheme (must be HTTPS or HTTP): " + url.getProtocol()); + } + String apiUri = guessApiUri(url); + String[] pathParts = StringUtils.removeStart(url.getPath(), "/").split("/"); + if (pathParts.length != 2) { + throw new IllegalArgumentException("Invalid repository URL: " + repositoryUrl); + } else { + String repoOwner = pathParts[0]; + String repository = removeEnd(pathParts[1], ".git"); + return new GitHubRepositoryInfo(apiUri, repoOwner, repository, repositoryUrl.trim()); + } + } + + private static String guessApiUri(URL repositoryUrl) { + StringBuilder sb = new StringBuilder(); + sb.append(repositoryUrl.getProtocol()); + sb.append("://"); + boolean isGitHub = GITHUB_COM.equals(repositoryUrl.getHost()); + if (isGitHub) { + sb.append(GITHUB_API_URL); + } else { + sb.append(repositoryUrl.getHost()); + } + if (repositoryUrl.getPort() != -1) { + sb.append(':'); + sb.append(repositoryUrl.getPort()); + } + if (!isGitHub) { + sb.append('/').append(GitHubSCMBuilder.API_V3); + } + return GitHubConfiguration.normalizeApiUri(sb.toString()); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java index d8a5b2f79..16f4e3607 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilder.java @@ -60,292 +60,279 @@ */ public class GitHubSCMBuilder extends GitSCMBuilder { - /** - * Singleton instance of {@link HttpsRepositoryUriResolver}. - */ - static final HttpsRepositoryUriResolver HTTPS = new HttpsRepositoryUriResolver(); - /** - * Singleton instance of {@link SshRepositoryUriResolver}. - */ - static final SshRepositoryUriResolver SSH = new SshRepositoryUriResolver(); - /** - * The GitHub API suffix for GitHub Server. - */ - static final String API_V3 = "api/v3"; - /** - * The context within which credentials should be resolved. - */ - @CheckForNull - private final SCMSourceOwner context; - /** - * The API URL - */ - @NonNull - private final String apiUri; - /** - * The repository owner. - */ - @NonNull - private final String repoOwner; - /** - * The repository name. - */ - @NonNull - private final String repository; - /** - * The definitive HTML user-facing URL of the repository (as provided by the GitHub API) if available. - */ - @CheckForNull - private final URL repositoryUrl; - /** - * The repository name. - */ - @NonNull - private RepositoryUriResolver uriResolver = GitHubSCMBuilder.HTTPS; + /** Singleton instance of {@link HttpsRepositoryUriResolver}. */ + static final HttpsRepositoryUriResolver HTTPS = new HttpsRepositoryUriResolver(); + /** Singleton instance of {@link SshRepositoryUriResolver}. */ + static final SshRepositoryUriResolver SSH = new SshRepositoryUriResolver(); + /** The GitHub API suffix for GitHub Server. */ + static final String API_V3 = "api/v3"; + /** The context within which credentials should be resolved. */ + @CheckForNull private final SCMSourceOwner context; + /** The API URL */ + @NonNull private final String apiUri; + /** The repository owner. */ + @NonNull private final String repoOwner; + /** The repository name. */ + @NonNull private final String repository; + /** + * The definitive HTML user-facing URL of the repository (as provided by the GitHub API) if + * available. + */ + @CheckForNull private final URL repositoryUrl; + /** The repository name. */ + @NonNull private RepositoryUriResolver uriResolver = GitHubSCMBuilder.HTTPS; - /** - * Constructor. - * - * @param source the {@link GitHubSCMSource}. - * @param head the {@link SCMHead} - * @param revision the (optional) {@link SCMRevision} - */ - public GitHubSCMBuilder(@NonNull GitHubSCMSource source, - @NonNull SCMHead head, @CheckForNull SCMRevision revision) { - super(head, revision, /*dummy value*/guessRemote(source), source.getCredentialsId()); - this.context = source.getOwner(); - apiUri = StringUtils.defaultIfBlank(source.getApiUri(), GitHubServerConfig.GITHUB_URL); - repoOwner = source.getRepoOwner(); - repository = source.getRepository(); - repositoryUrl = source.getResolvedRepositoryUrl(); - // now configure the ref specs - withoutRefSpecs(); - String repoUrl; - if (head instanceof PullRequestSCMHead) { - PullRequestSCMHead h = (PullRequestSCMHead) head; - withRefSpec("+refs/pull/" + h.getId() + "/head:refs/remotes/@{remote}/" + head - .getName()); - repoUrl = repositoryUrl(h.getSourceOwner(), h.getSourceRepo()); - } else if (head instanceof TagSCMHead) { - withRefSpec("+refs/tags/" + head.getName() + ":refs/tags/" + head.getName()); - repoUrl = repositoryUrl(repoOwner, repository); - } else { - withRefSpec("+refs/heads/" + head.getName() + ":refs/remotes/@{remote}/" + head.getName()); - repoUrl = repositoryUrl(repoOwner, repository); - } - // pre-configure the browser - if (repoUrl != null) { - withBrowser(new GithubWeb(repoUrl)); - } - withCredentials(credentialsId(), null); + /** + * Constructor. + * + * @param source the {@link GitHubSCMSource}. + * @param head the {@link SCMHead} + * @param revision the (optional) {@link SCMRevision} + */ + public GitHubSCMBuilder( + @NonNull GitHubSCMSource source, @NonNull SCMHead head, @CheckForNull SCMRevision revision) { + super(head, revision, /*dummy value*/ guessRemote(source), source.getCredentialsId()); + this.context = source.getOwner(); + apiUri = StringUtils.defaultIfBlank(source.getApiUri(), GitHubServerConfig.GITHUB_URL); + repoOwner = source.getRepoOwner(); + repository = source.getRepository(); + repositoryUrl = source.getResolvedRepositoryUrl(); + // now configure the ref specs + withoutRefSpecs(); + String repoUrl; + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead h = (PullRequestSCMHead) head; + withRefSpec("+refs/pull/" + h.getId() + "/head:refs/remotes/@{remote}/" + head.getName()); + repoUrl = repositoryUrl(h.getSourceOwner(), h.getSourceRepo()); + } else if (head instanceof TagSCMHead) { + withRefSpec("+refs/tags/" + head.getName() + ":refs/tags/" + head.getName()); + repoUrl = repositoryUrl(repoOwner, repository); + } else { + withRefSpec("+refs/heads/" + head.getName() + ":refs/remotes/@{remote}/" + head.getName()); + repoUrl = repositoryUrl(repoOwner, repository); } - - /** - * Tries to guess the HTTPS URL of the Git repository. - * - * @param source the source. - * @return the (possibly incorrect) best guess at the Git repository URL. - */ - private static String guessRemote(GitHubSCMSource source) { - String apiUri = StringUtils.removeEnd(source.getApiUri(), "/"); - if (StringUtils.isBlank(apiUri) || GitHubServerConfig.GITHUB_URL.equals(apiUri)) { - apiUri = "https://github.com"; - } else { - apiUri = StringUtils.removeEnd(apiUri, "/"+API_V3); - } - return apiUri + "/" + source.getRepoOwner() + "/" + source.getRepository() + ".git"; + // pre-configure the browser + if (repoUrl != null) { + withBrowser(new GithubWeb(repoUrl)); } + withCredentials(credentialsId(), null); + } - /** - * Tries as best as possible to guess the repository HTML url to use with {@link GithubWeb}. - * - * @param owner the owner. - * @param repo the repository. - * @return the HTML url of the repository or {@code null} if we could not determine the answer. - */ - @CheckForNull - public final String repositoryUrl(String owner, String repo) { - if (repositoryUrl != null) { - if (repoOwner.equals(owner) && repository.equals(repo)) { - return repositoryUrl.toExternalForm(); - } - // hack! - return repositoryUrl.toExternalForm().replace(repoOwner + "/" + repository, owner + "/" + repo); - } - if (StringUtils.isBlank(apiUri) || GitHubServerConfig.GITHUB_URL.equals(apiUri)) { - return "https://github.com/" + owner + "/" + repo; - } - if (StringUtils.endsWith(StringUtils.removeEnd(apiUri, "/"), "/"+API_V3)) { - return StringUtils.removeEnd(StringUtils.removeEnd(apiUri, "/"), API_V3) + owner + "/" + repo; - } - return null; + /** + * Tries to guess the HTTPS URL of the Git repository. + * + * @param source the source. + * @return the (possibly incorrect) best guess at the Git repository URL. + */ + private static String guessRemote(GitHubSCMSource source) { + String apiUri = StringUtils.removeEnd(source.getApiUri(), "/"); + if (StringUtils.isBlank(apiUri) || GitHubServerConfig.GITHUB_URL.equals(apiUri)) { + apiUri = "https://github.com"; + } else { + apiUri = StringUtils.removeEnd(apiUri, "/" + API_V3); } + return apiUri + "/" + source.getRepoOwner() + "/" + source.getRepository() + ".git"; + } - /** - * Returns a {@link RepositoryUriResolver} according to credentials configuration. - * - * @return a {@link RepositoryUriResolver} - */ - @NonNull - public final RepositoryUriResolver uriResolver() { - return uriResolver; + /** + * Tries as best as possible to guess the repository HTML url to use with {@link GithubWeb}. + * + * @param owner the owner. + * @param repo the repository. + * @return the HTML url of the repository or {@code null} if we could not determine the answer. + */ + @CheckForNull + public final String repositoryUrl(String owner, String repo) { + if (repositoryUrl != null) { + if (repoOwner.equals(owner) && repository.equals(repo)) { + return repositoryUrl.toExternalForm(); + } + // hack! + return repositoryUrl + .toExternalForm() + .replace(repoOwner + "/" + repository, owner + "/" + repo); + } + if (StringUtils.isBlank(apiUri) || GitHubServerConfig.GITHUB_URL.equals(apiUri)) { + return "https://github.com/" + owner + "/" + repo; } + if (StringUtils.endsWith(StringUtils.removeEnd(apiUri, "/"), "/" + API_V3)) { + return StringUtils.removeEnd(StringUtils.removeEnd(apiUri, "/"), API_V3) + owner + "/" + repo; + } + return null; + } - /** - * Configures the {@link IdCredentials#getId()} of the {@link Credentials} to use when connecting to the - * {@link #remote()} - * - * @param credentialsId the {@link IdCredentials#getId()} of the {@link Credentials} to use when connecting to - * the {@link #remote()} or {@code null} to let the git client choose between providing its own - * credentials or connecting anonymously. - * @param uriResolver the {@link RepositoryUriResolver} of the {@link Credentials} to use or {@code null} - * to detect the the protocol based on the credentialsId. Defaults to HTTP if credentials are - * {@code null}. Enables support for blank SSH credentials. - * @return {@code this} for method chaining. - */ - @NonNull - public GitHubSCMBuilder withCredentials(String credentialsId, RepositoryUriResolver uriResolver) { - if (uriResolver == null) { - uriResolver = uriResolver(context, apiUri, credentialsId); - } + /** + * Returns a {@link RepositoryUriResolver} according to credentials configuration. + * + * @return a {@link RepositoryUriResolver} + */ + @NonNull + public final RepositoryUriResolver uriResolver() { + return uriResolver; + } - this.uriResolver = uriResolver; - return withCredentials(credentialsId); + /** + * Configures the {@link IdCredentials#getId()} of the {@link Credentials} to use when connecting + * to the {@link #remote()} + * + * @param credentialsId the {@link IdCredentials#getId()} of the {@link Credentials} to use when + * connecting to the {@link #remote()} or {@code null} to let the git client choose between + * providing its own credentials or connecting anonymously. + * @param uriResolver the {@link RepositoryUriResolver} of the {@link Credentials} to use or + * {@code null} to detect the the protocol based on the credentialsId. Defaults to HTTP if + * credentials are {@code null}. Enables support for blank SSH credentials. + * @return {@code this} for method chaining. + */ + @NonNull + public GitHubSCMBuilder withCredentials(String credentialsId, RepositoryUriResolver uriResolver) { + if (uriResolver == null) { + uriResolver = uriResolver(context, apiUri, credentialsId); } - /** - * Returns a {@link RepositoryUriResolver} according to credentials configuration. - * - * @param context the context within which to resolve the credentials. - * @param apiUri the API url - * @param credentialsId the credentials. - * @return a {@link RepositoryUriResolver} - */ - @NonNull - public static RepositoryUriResolver uriResolver(@CheckForNull Item context, @NonNull String apiUri, - @CheckForNull String credentialsId) { - if (credentialsId == null) { - return HTTPS; - } else { - StandardCredentials credentials = CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials( - StandardCredentials.class, - context, - context instanceof Queue.Task - ? ((Queue.Task) context).getDefaultAuthentication() - : ACL.SYSTEM, - URIRequirementBuilder.create() - .withHostname(RepositoryUriResolver.hostnameFromApiUri(apiUri)) - .build() - ), - CredentialsMatchers.allOf( - CredentialsMatchers.withId(credentialsId), - CredentialsMatchers.instanceOf(StandardCredentials.class) - ) - ); - if (credentials instanceof SSHUserPrivateKey) { - return SSH; - } else { - // Defaults to HTTP/HTTPS - return HTTPS; - } - } + this.uriResolver = uriResolver; + return withCredentials(credentialsId); + } + + /** + * Returns a {@link RepositoryUriResolver} according to credentials configuration. + * + * @param context the context within which to resolve the credentials. + * @param apiUri the API url + * @param credentialsId the credentials. + * @return a {@link RepositoryUriResolver} + */ + @NonNull + public static RepositoryUriResolver uriResolver( + @CheckForNull Item context, @NonNull String apiUri, @CheckForNull String credentialsId) { + if (credentialsId == null) { + return HTTPS; + } else { + StandardCredentials credentials = + CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + StandardCredentials.class, + context, + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + URIRequirementBuilder.create() + .withHostname(RepositoryUriResolver.hostnameFromApiUri(apiUri)) + .build()), + CredentialsMatchers.allOf( + CredentialsMatchers.withId(credentialsId), + CredentialsMatchers.instanceOf(StandardCredentials.class))); + if (credentials instanceof SSHUserPrivateKey) { + return SSH; + } else { + // Defaults to HTTP/HTTPS + return HTTPS; + } } + } - /** - * Updates the {@link GitSCMBuilder#withRemote(String)} based on the current {@link #head()} and - * {@link #revision()}. - * - * Will be called automatically by {@link #build()} but exposed in case the correct remote is required after - * changing the {@link #withCredentials(String)}. - * - * @return {@code this} for method chaining. - */ - @NonNull - public final GitHubSCMBuilder withGitHubRemote() { - withRemote(uriResolver().getRepositoryUri(apiUri, repoOwner, repository)); - final SCMHead h = head(); - String repoUrl; - if (h instanceof PullRequestSCMHead) { - final PullRequestSCMHead head = (PullRequestSCMHead) h; - repoUrl = repositoryUrl(head.getSourceOwner(), head.getSourceRepo()); - } else { - repoUrl = repositoryUrl(repoOwner, repository); - } - if (repoUrl != null) { - withBrowser(new GithubWeb(repoUrl)); - } - return this; + /** + * Updates the {@link GitSCMBuilder#withRemote(String)} based on the current {@link #head()} and + * {@link #revision()}. + * + *

Will be called automatically by {@link #build()} but exposed in case the correct remote is + * required after changing the {@link #withCredentials(String)}. + * + * @return {@code this} for method chaining. + */ + @NonNull + public final GitHubSCMBuilder withGitHubRemote() { + withRemote(uriResolver().getRepositoryUri(apiUri, repoOwner, repository)); + final SCMHead h = head(); + String repoUrl; + if (h instanceof PullRequestSCMHead) { + final PullRequestSCMHead head = (PullRequestSCMHead) h; + repoUrl = repositoryUrl(head.getSourceOwner(), head.getSourceRepo()); + } else { + repoUrl = repositoryUrl(repoOwner, repository); + } + if (repoUrl != null) { + withBrowser(new GithubWeb(repoUrl)); } + return this; + } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public GitSCM build() { - final SCMHead h = head(); - final SCMRevision r = revision(); - try { - withGitHubRemote(); + /** {@inheritDoc} */ + @NonNull + @Override + public GitSCM build() { + final SCMHead h = head(); + final SCMRevision r = revision(); + try { + withGitHubRemote(); - if (h instanceof PullRequestSCMHead) { - PullRequestSCMHead head = (PullRequestSCMHead) h; - if (head.isMerge()) { - // add the target branch to ensure that the revision we want to merge is also available - String name = head.getTarget().getName(); - String localName = "remotes/" + remoteName() + "/" + name; - Set localNames = new HashSet<>(); - boolean match = false; - String targetSrc = Constants.R_HEADS + name; - String targetDst = Constants.R_REMOTES + remoteName() + "/" + name; - for (RefSpec b : asRefSpecs()) { - String dst = b.getDestination(); - assert dst.startsWith(Constants.R_REFS) - : "All git references must start with refs/"; - if (targetSrc.equals(b.getSource())) { - if (targetDst.equals(dst)) { - match = true; - } else { - // pick up the configured destination name - localName = dst.substring(Constants.R_REFS.length()); - match = true; - } - } else { - localNames.add(dst.substring(Constants.R_REFS.length())); - } - } - if (!match) { - if (localNames.contains(localName)) { - // conflict with intended name - localName = "remotes/" + remoteName() + "/upstream-" + name; - } - if (localNames.contains(localName)) { - // conflict with intended alternative name - localName = "remotes/" + remoteName() + "/pr-" + head.getNumber() + "-upstream-" + name; - } - if (localNames.contains(localName)) { - // ok we're just going to mangle our way to something that works - Random entropy = new Random(); - while (localNames.contains(localName)) { - localName = "remotes/" + remoteName() + "/pr-" + head.getNumber() + "-upstream-" + name - + "-" + Integer.toHexString(entropy.nextInt(Integer.MAX_VALUE)); - } - } - withRefSpec("+refs/heads/" + name + ":refs/" + localName); - } - withExtension(new MergeWithGitSCMExtension(localName, - r instanceof PullRequestSCMRevision ? ((PullRequestSCMRevision) r).getBaseHash() : null)); - } - if (r instanceof PullRequestSCMRevision) { - PullRequestSCMRevision rev = (PullRequestSCMRevision) r; - withRevision(new AbstractGitSCMSource.SCMRevisionImpl(head, rev.getPullHash())); - } + if (h instanceof PullRequestSCMHead) { + PullRequestSCMHead head = (PullRequestSCMHead) h; + if (head.isMerge()) { + // add the target branch to ensure that the revision we want to merge is also available + String name = head.getTarget().getName(); + String localName = "remotes/" + remoteName() + "/" + name; + Set localNames = new HashSet<>(); + boolean match = false; + String targetSrc = Constants.R_HEADS + name; + String targetDst = Constants.R_REMOTES + remoteName() + "/" + name; + for (RefSpec b : asRefSpecs()) { + String dst = b.getDestination(); + assert dst.startsWith(Constants.R_REFS) : "All git references must start with refs/"; + if (targetSrc.equals(b.getSource())) { + if (targetDst.equals(dst)) { + match = true; + } else { + // pick up the configured destination name + localName = dst.substring(Constants.R_REFS.length()); + match = true; + } + } else { + localNames.add(dst.substring(Constants.R_REFS.length())); + } + } + if (!match) { + if (localNames.contains(localName)) { + // conflict with intended name + localName = "remotes/" + remoteName() + "/upstream-" + name; + } + if (localNames.contains(localName)) { + // conflict with intended alternative name + localName = + "remotes/" + remoteName() + "/pr-" + head.getNumber() + "-upstream-" + name; } - return super.build(); - } finally { - withHead(h); - withRevision(r); + if (localNames.contains(localName)) { + // ok we're just going to mangle our way to something that works + Random entropy = new Random(); + while (localNames.contains(localName)) { + localName = + "remotes/" + + remoteName() + + "/pr-" + + head.getNumber() + + "-upstream-" + + name + + "-" + + Integer.toHexString(entropy.nextInt(Integer.MAX_VALUE)); + } + } + withRefSpec("+refs/heads/" + name + ":refs/" + localName); + } + withExtension( + new MergeWithGitSCMExtension( + localName, + r instanceof PullRequestSCMRevision + ? ((PullRequestSCMRevision) r).getBaseHash() + : null)); + } + if (r instanceof PullRequestSCMRevision) { + PullRequestSCMRevision rev = (PullRequestSCMRevision) r; + withRevision(new AbstractGitSCMSource.SCMRevisionImpl(head, rev.getPullHash())); } + } + return super.build(); + } finally { + withHead(h); + withRevision(r); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFile.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFile.java index e768a629f..bc9d91dbc 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFile.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFile.java @@ -39,159 +39,170 @@ class GitHubSCMFile extends SCMFile { - private TypeInfo info; - private final GitHubClosable closable; - private final GHRepository repo; - private final String ref; - private transient Object metadata; - private transient boolean resolved; - - GitHubSCMFile(GitHubClosable closable, GHRepository repo, String ref) { - super(); - this.closable = closable; - type(Type.DIRECTORY); - info = TypeInfo.DIRECTORY_ASSUMED; // we have not resolved the metadata yet - this.repo = repo; - this.ref = ref; + private TypeInfo info; + private final GitHubClosable closable; + private final GHRepository repo; + private final String ref; + private transient Object metadata; + private transient boolean resolved; + + GitHubSCMFile(GitHubClosable closable, GHRepository repo, String ref) { + super(); + this.closable = closable; + type(Type.DIRECTORY); + info = TypeInfo.DIRECTORY_ASSUMED; // we have not resolved the metadata yet + this.repo = repo; + this.ref = ref; + } + + private GitHubSCMFile(@NonNull GitHubSCMFile parent, String name, TypeInfo info) { + super(parent, name); + this.closable = parent.closable; + this.info = info; + this.repo = parent.repo; + this.ref = parent.ref; + } + + private GitHubSCMFile(@NonNull GitHubSCMFile parent, String name, GHContent metadata) { + super(parent, name); + this.closable = parent.closable; + this.repo = parent.repo; + this.ref = parent.ref; + if (metadata.isDirectory()) { + info = TypeInfo.DIRECTORY_CONFIRMED; + // we have not listed the children yet, but we know it is a directory + } else { + info = TypeInfo.NON_DIRECTORY_CONFIRMED; + this.metadata = metadata; + resolved = true; } + } - private GitHubSCMFile(@NonNull GitHubSCMFile parent, String name, TypeInfo info) { - super(parent, name); - this.closable = parent.closable; - this.info = info; - this.repo = parent.repo; - this.ref = parent.ref; + private void checkOpen() throws IOException { + if (!closable.isOpen() || (!resolved && repo == null)) { + throw new IOException("Closed"); } - - private GitHubSCMFile(@NonNull GitHubSCMFile parent, String name, GHContent metadata) { - super(parent, name); - this.closable = parent.closable; - this.repo = parent.repo; - this.ref = parent.ref; - if (metadata.isDirectory()) { + } + + private Object metadata() throws IOException { + if (metadata == null && !resolved) { + try { + switch (info) { + case DIRECTORY_ASSUMED: + metadata = + repo.getDirectoryContent( + getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); info = TypeInfo.DIRECTORY_CONFIRMED; - // we have not listed the children yet, but we know it is a directory - } else { - info = TypeInfo.NON_DIRECTORY_CONFIRMED; - this.metadata = metadata; resolved = true; - } - } - - private void checkOpen() throws IOException { - if (!closable.isOpen() || (!resolved && repo == null)) { - throw new IOException("Closed"); - } - } - - private Object metadata() throws IOException { - if (metadata == null && !resolved) { + break; + case DIRECTORY_CONFIRMED: + metadata = + repo.getDirectoryContent( + getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); + resolved = true; + break; + case NON_DIRECTORY_CONFIRMED: + metadata = + repo.getFileContent( + getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); + resolved = true; + break; + case UNRESOLVED: + checkOpen(); try { - switch (info) { - case DIRECTORY_ASSUMED: - metadata = repo.getDirectoryContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - info = TypeInfo.DIRECTORY_CONFIRMED; - resolved = true; - break; - case DIRECTORY_CONFIRMED: - metadata = repo.getDirectoryContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - resolved = true; - break; - case NON_DIRECTORY_CONFIRMED: - metadata = repo.getFileContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - resolved = true; - break; - case UNRESOLVED: - checkOpen(); - try { - metadata = repo.getFileContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - info = TypeInfo.NON_DIRECTORY_CONFIRMED; - resolved = true; - } catch (IOException e) { - // Upcoming version of github-api hoists JsonMappingException up one level - // Support both the old and the new structure - if (e.getCause() instanceof JsonMappingException - || e.getCause() != null && e.getCause().getCause() instanceof JsonMappingException ) { - metadata = repo.getDirectoryContent(getPath(), - ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - info = TypeInfo.DIRECTORY_CONFIRMED; - resolved = true; - } else { - throw e; - } - } - break; - } - } catch (FileNotFoundException e) { - metadata = null; + metadata = + repo.getFileContent( + getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); + info = TypeInfo.NON_DIRECTORY_CONFIRMED; + resolved = true; + } catch (IOException e) { + // Upcoming version of github-api hoists JsonMappingException up one level + // Support both the old and the new structure + if (e.getCause() instanceof JsonMappingException + || e.getCause() != null + && e.getCause().getCause() instanceof JsonMappingException) { + metadata = + repo.getDirectoryContent( + getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); + info = TypeInfo.DIRECTORY_CONFIRMED; resolved = true; + } else { + throw e; + } } + break; } - return metadata; + } catch (FileNotFoundException e) { + metadata = null; + resolved = true; + } } - - @NonNull - @Override - protected SCMFile newChild(String name, boolean assumeIsDirectory) { - return new GitHubSCMFile(this, name, assumeIsDirectory ? TypeInfo.DIRECTORY_ASSUMED: TypeInfo.UNRESOLVED); + return metadata; + } + + @NonNull + @Override + protected SCMFile newChild(String name, boolean assumeIsDirectory) { + return new GitHubSCMFile( + this, name, assumeIsDirectory ? TypeInfo.DIRECTORY_ASSUMED : TypeInfo.UNRESOLVED); + } + + @NonNull + @Override + public Iterable children() throws IOException { + checkOpen(); + List content = + repo.getDirectoryContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); + List result = new ArrayList<>(content.size()); + for (GHContent c : content) { + result.add(new GitHubSCMFile(this, c.getName(), c)); } - - @NonNull - @Override - public Iterable children() throws IOException { - checkOpen(); - List content = repo.getDirectoryContent(getPath(), ref.indexOf('/') == -1 ? ref : Constants.R_REFS + ref); - List result = new ArrayList<>(content.size()); - for (GHContent c : content) { - result.add(new GitHubSCMFile(this, c.getName(), c)); - } - return result; + return result; + } + + @Override + public long lastModified() throws IOException, InterruptedException { + // TODO see if we can find a way to implement it + return 0L; + } + + @NonNull + @Override + protected Type type() throws IOException, InterruptedException { + Object metadata = metadata(); + if (metadata instanceof List) { + return Type.DIRECTORY; } - - @Override - public long lastModified() throws IOException, InterruptedException { - // TODO see if we can find a way to implement it - return 0L; - } - - @NonNull - @Override - protected Type type() throws IOException, InterruptedException { - Object metadata = metadata(); - if (metadata instanceof List) { - return Type.DIRECTORY; - } - if (metadata instanceof GHContent) { - GHContent content = (GHContent) metadata; - if ("symlink".equals(content.getType())) { - return Type.LINK; - } - if (content.isFile()) { - return Type.REGULAR_FILE; - } - return Type.OTHER; - } - return Type.NONEXISTENT; + if (metadata instanceof GHContent) { + GHContent content = (GHContent) metadata; + if ("symlink".equals(content.getType())) { + return Type.LINK; + } + if (content.isFile()) { + return Type.REGULAR_FILE; + } + return Type.OTHER; } - - @NonNull - @Override - public InputStream content() throws IOException, InterruptedException { - Object metadata = metadata(); - if (metadata instanceof List) { - throw new IOException("Directory"); - } - if (metadata instanceof GHContent) { - return ((GHContent)metadata).read(); - } - throw new FileNotFoundException(getPath()); + return Type.NONEXISTENT; + } + + @NonNull + @Override + public InputStream content() throws IOException, InterruptedException { + Object metadata = metadata(); + if (metadata instanceof List) { + throw new IOException("Directory"); } - - private enum TypeInfo { - UNRESOLVED, - DIRECTORY_ASSUMED, - DIRECTORY_CONFIRMED, - NON_DIRECTORY_CONFIRMED; + if (metadata instanceof GHContent) { + return ((GHContent) metadata).read(); } - + throw new FileNotFoundException(getPath()); + } + + private enum TypeInfo { + UNRESOLVED, + DIRECTORY_ASSUMED, + DIRECTORY_CONFIRMED, + NON_DIRECTORY_CONFIRMED; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java index ec29fa7ce..7f37df602 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java @@ -32,14 +32,12 @@ import hudson.model.Item; import hudson.plugins.git.GitSCM; import hudson.scm.SCM; - +import hudson.scm.SCMDescriptor; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Objects; - -import hudson.scm.SCMDescriptor; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.plugins.git.GitTagSCMRevision; import jenkins.scm.api.SCMFile; @@ -52,277 +50,264 @@ import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHRef; import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHUser; import org.kohsuke.github.GHTagObject; +import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; -import org.kohsuke.github.HttpException; -/** - * Implements {@link SCMFileSystem} for GitHub. - */ +/** Implements {@link SCMFileSystem} for GitHub. */ public class GitHubSCMFileSystem extends SCMFileSystem implements GitHubClosable { - private final GitHub gitHub; - private final GHRepository repo; - private final String ref; - private boolean open; + private final GitHub gitHub; + private final GHRepository repo; + private final String ref; + private boolean open; - /** - * Constructor. - * - * @param gitHub the {@link GitHub} - * @param repo the {@link GHRepository} - * @param refName the ref name, e.g. {@code heads/branchName}, {@code tags/tagName}, {@code pull/N/head} or the SHA. - * @param rev the optional revision. - * @throws IOException if I/O errors occur. - */ - protected GitHubSCMFileSystem(GitHub gitHub, GHRepository repo, String refName, @CheckForNull SCMRevision rev) throws IOException { - super(rev); - this.gitHub = gitHub; - this.open = true; - this.repo = repo; - if (rev != null) { - if (rev.getHead() instanceof PullRequestSCMHead) { - PullRequestSCMRevision prRev = (PullRequestSCMRevision) rev; - PullRequestSCMHead pr = (PullRequestSCMHead) prRev.getHead(); - if (pr.isMerge()) { - this.ref = prRev.getMergeHash(); - } else { - this.ref = prRev.getPullHash(); - } - } else if (rev instanceof AbstractGitSCMSource.SCMRevisionImpl) { - this.ref = ((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(); - } else { - this.ref = refName; - } + /** + * Constructor. + * + * @param gitHub the {@link GitHub} + * @param repo the {@link GHRepository} + * @param refName the ref name, e.g. {@code heads/branchName}, {@code tags/tagName}, {@code + * pull/N/head} or the SHA. + * @param rev the optional revision. + * @throws IOException if I/O errors occur. + */ + protected GitHubSCMFileSystem( + GitHub gitHub, GHRepository repo, String refName, @CheckForNull SCMRevision rev) + throws IOException { + super(rev); + this.gitHub = gitHub; + this.open = true; + this.repo = repo; + if (rev != null) { + if (rev.getHead() instanceof PullRequestSCMHead) { + PullRequestSCMRevision prRev = (PullRequestSCMRevision) rev; + PullRequestSCMHead pr = (PullRequestSCMHead) prRev.getHead(); + if (pr.isMerge()) { + this.ref = prRev.getMergeHash(); } else { - this.ref = refName; + this.ref = prRev.getPullHash(); } + } else if (rev instanceof AbstractGitSCMSource.SCMRevisionImpl) { + this.ref = ((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(); + } else { + this.ref = refName; + } + } else { + this.ref = refName; } + } - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - synchronized (this) { - if (!open) { - return; - } - open = false; - } - Connector.release(gitHub); + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + synchronized (this) { + if (!open) { + return; + } + open = false; } + Connector.release(gitHub); + } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ + @Override + public synchronized boolean isOpen() { + return open; + } + + /** {@inheritDoc} */ + @Override + public long lastModified() throws IOException { + return repo.getCommit(ref).getCommitDate().getTime(); + } + + /** {@inheritDoc} */ + @Override + public boolean changesSince(SCMRevision revision, @NonNull OutputStream changeLogStream) + throws UnsupportedOperationException, IOException, InterruptedException { + if (Objects.equals(getRevision(), revision)) { + // special case where somebody is asking one of two stupid questions: + // 1. what has changed between the latest and the latest + // 2. what has changed between the current revision and the current revision + return false; + } + int count = 0; + FastDateFormat iso = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZ"); + StringBuilder log = new StringBuilder(1024); + String endHash; + if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { + endHash = + ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash().toLowerCase(Locale.ENGLISH); + } else { + endHash = null; + } + // this is the format expected by GitSCM, so we need to format each GHCommit with the same + // format + // commit %H%ntree %T%nparent %P%nauthor %aN <%aE> %ai%ncommitter %cN <%cE> + // %ci%n%n%w(76,4,4)%s%n%n%b + for (GHCommit commit : repo.queryCommits().from(ref).pageSize(GitSCM.MAX_CHANGELOG).list()) { + if (commit.getSHA1().toLowerCase(Locale.ENGLISH).equals(endHash)) { + break; + } + log.setLength(0); + log.append("commit ").append(commit.getSHA1()).append('\n'); + log.append("tree ").append(commit.getTree().getSha()).append('\n'); + log.append("parent"); + for (String parent : commit.getParentSHA1s()) { + log.append(' ').append(parent); + } + log.append('\n'); + GHCommit.ShortInfo info = commit.getCommitShortInfo(); + log.append("author ") + .append(info.getAuthor().getName()) + .append(" <") + .append(info.getAuthor().getEmail()) + .append("> ") + .append(iso.format(info.getAuthoredDate())) + .append('\n'); + log.append("committer ") + .append(info.getCommitter().getName()) + .append(" <") + .append(info.getCommitter().getEmail()) + .append("> ") + .append(iso.format(info.getCommitDate())) + .append('\n'); + log.append('\n'); + String msg = info.getMessage(); + if (msg.endsWith("\r\n")) { + msg = msg.substring(0, msg.length() - 2); + } else if (msg.endsWith("\n")) { + msg = msg.substring(0, msg.length() - 1); + } + msg = msg.replace("\r\n", "\n").replace("\r", "\n").replace("\n", "\n "); + log.append(" ").append(msg).append('\n'); + changeLogStream.write(log.toString().getBytes(StandardCharsets.UTF_8)); + changeLogStream.flush(); + count++; + if (count >= GitSCM.MAX_CHANGELOG) { + break; + } + } + return count > 0; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public SCMFile getRoot() { + return new GitHubSCMFile(this, repo, ref); + } + + @Extension + public static class BuilderImpl extends SCMFileSystem.Builder { + + /** {@inheritDoc} */ @Override - public synchronized boolean isOpen() { - return open; + public boolean supports(SCM source) { + // TODO implement a GitHubSCM so we can work for those + return false; } - /** - * {@inheritDoc} - */ @Override - public long lastModified() throws IOException { - return repo.getCommit(ref).getCommitDate().getTime(); + protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) { + // TODO implement a GitHubSCM so we can work for those + return false; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override - public boolean changesSince(SCMRevision revision, @NonNull OutputStream changeLogStream) - throws UnsupportedOperationException, IOException, InterruptedException { - if (Objects.equals(getRevision(), revision)) { - // special case where somebody is asking one of two stupid questions: - // 1. what has changed between the latest and the latest - // 2. what has changed between the current revision and the current revision - return false; - } - int count = 0; - FastDateFormat iso = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZ"); - StringBuilder log = new StringBuilder(1024); - String endHash; - if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { - endHash = ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash().toLowerCase(Locale.ENGLISH); - } else { - endHash = null; - } - // this is the format expected by GitSCM, so we need to format each GHCommit with the same format - // commit %H%ntree %T%nparent %P%nauthor %aN <%aE> %ai%ncommitter %cN <%cE> %ci%n%n%w(76,4,4)%s%n%n%b - for (GHCommit commit: repo.queryCommits().from(ref).pageSize(GitSCM.MAX_CHANGELOG).list()) { - if (commit.getSHA1().toLowerCase(Locale.ENGLISH).equals(endHash)) { - break; - } - log.setLength(0); - log.append("commit ").append(commit.getSHA1()).append('\n'); - log.append("tree ").append(commit.getTree().getSha()).append('\n'); - log.append("parent"); - for (String parent: commit.getParentSHA1s()) { - log.append(' ').append(parent); - } - log.append('\n'); - GHCommit.ShortInfo info = commit.getCommitShortInfo(); - log.append("author ") - .append(info.getAuthor().getName()) - .append(" <") - .append(info.getAuthor().getEmail()) - .append("> ") - .append(iso.format(info.getAuthoredDate())) - .append('\n'); - log.append("committer ") - .append(info.getCommitter().getName()) - .append(" <") - .append(info.getCommitter().getEmail()) - .append("> ") - .append(iso.format(info.getCommitDate())) - .append('\n'); - log.append('\n'); - String msg = info.getMessage(); - if (msg.endsWith("\r\n")) { - msg = msg.substring(0, msg.length() - 2); - } else if (msg.endsWith("\n")) { - msg = msg.substring(0, msg.length() - 1); - } - msg = msg.replace("\r\n", "\n").replace("\r", "\n").replace("\n", "\n "); - log.append(" ").append(msg).append('\n'); - changeLogStream.write(log.toString().getBytes(StandardCharsets.UTF_8)); - changeLogStream.flush(); - count++; - if (count >= GitSCM.MAX_CHANGELOG) { - break; - } - } - return count > 0; + public boolean supports(SCMSource source) { + return source instanceof GitHubSCMSource; } - /** - * {@inheritDoc} - */ - @NonNull @Override - public SCMFile getRoot() { - return new GitHubSCMFile(this, repo, ref); + protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) { + return scmSourceDescriptor instanceof GitHubSCMSource.DescriptorImpl; } - @Extension - public static class BuilderImpl extends SCMFileSystem.Builder { + /** {@inheritDoc} */ + @Override + public SCMFileSystem build( + @NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) { + return null; + } - /** - * {@inheritDoc} - */ - @Override - public boolean supports(SCM source) { - // TODO implement a GitHubSCM so we can work for those - return false; - } + /** {@inheritDoc} */ + @Override + public SCMFileSystem build( + @NonNull SCMSource source, @NonNull SCMHead head, @CheckForNull SCMRevision rev) + throws IOException, InterruptedException { + GitHubSCMSource src = (GitHubSCMSource) source; + String apiUri = src.getApiUri(); + StandardCredentials credentials = + Connector.lookupScanCredentials( + (Item) src.getOwner(), apiUri, src.getScanCredentialsId()); - @Override - protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) { - // TODO implement a GitHubSCM so we can work for those - return false; - } + // Github client and validation + GitHub github = Connector.connect(apiUri, credentials); + try { + String refName; - /** - * {@inheritDoc} - */ - @Override - public boolean supports(SCMSource source) { - return source instanceof GitHubSCMSource; + if (head instanceof BranchSCMHead) { + refName = "heads/" + head.getName(); + } else if (head instanceof GitHubTagSCMHead) { + refName = "tags/" + head.getName(); + } else if (head instanceof PullRequestSCMHead) { + refName = null; + if (rev instanceof PullRequestSCMRevision) { + PullRequestSCMRevision prRev = (PullRequestSCMRevision) rev; + if (((PullRequestSCMHead) head).isMerge()) { + if (prRev.getMergeHash() == null) { + return null; + } + prRev.validateMergeHash(); + } + } else { + return null; + } + } else { + return null; } - @Override - protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) { - return scmSourceDescriptor instanceof GitHubSCMSource.DescriptorImpl; + GHUser user = github.getUser(src.getRepoOwner()); + if (user == null) { + return null; } - - /** - * {@inheritDoc} - */ - @Override - public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) { - return null; + GHRepository repo = user.getRepository(src.getRepository()); + if (repo == null) { + return null; } - /** - * {@inheritDoc} - */ - @Override - public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @CheckForNull SCMRevision rev) - throws IOException, InterruptedException { - GitHubSCMSource src = (GitHubSCMSource) source; - String apiUri = src.getApiUri(); - StandardCredentials credentials = - Connector.lookupScanCredentials((Item)src.getOwner(), apiUri, src.getScanCredentialsId()); - - // Github client and validation - GitHub github = Connector.connect(apiUri, credentials); - try { - String refName; - - if (head instanceof BranchSCMHead) { - refName = "heads/" + head.getName(); - } else if (head instanceof GitHubTagSCMHead) { - refName = "tags/" + head.getName(); - } else if (head instanceof PullRequestSCMHead) { - refName = null; - if (rev instanceof PullRequestSCMRevision) { - PullRequestSCMRevision prRev = (PullRequestSCMRevision) rev; - if (((PullRequestSCMHead)head).isMerge()) { - if (prRev.getMergeHash() == null) { - return null; - } - prRev.validateMergeHash(); - } - } else { - return null; - } - } else { - return null; - } - - GHUser user = github.getUser(src.getRepoOwner()); - if (user == null) { - return null; - } - GHRepository repo = user.getRepository(src.getRepository()); - if (repo == null) { - return null; - } - - - if (rev == null) { - GHRef ref = repo.getRef(refName); - if ("tag".equalsIgnoreCase(ref.getObject().getType())) { - GHTagObject tag = repo.getTagObject(ref.getObject().getSha()); - if (head instanceof GitHubTagSCMHead) { - rev = new GitTagSCMRevision((GitHubTagSCMHead) head, tag.getObject().getSha()); - } else { - // we should never get here, but just in case, we have the information to construct - // the correct head, so let's do that - rev = new GitTagSCMRevision( - new GitHubTagSCMHead(head.getName(), - tag.getTagger().getDate().getTime()), tag.getObject().getSha() - ); - } - } else { - rev = new AbstractGitSCMSource.SCMRevisionImpl(head, ref.getObject().getSha()); - } - } - - // Instead of calling release in many case and skipping this one case - // Make another call to connect() for this case - // and always release the existing instance as part of finally block. - // The result is the same but with far fewer code paths calling release(). - GitHub fileSystemGitHub = Connector.connect(apiUri, credentials); - return new GitHubSCMFileSystem(fileSystemGitHub, repo, refName, rev); - } catch (IOException | RuntimeException e) { - throw e; - } finally { - Connector.release(github); + if (rev == null) { + GHRef ref = repo.getRef(refName); + if ("tag".equalsIgnoreCase(ref.getObject().getType())) { + GHTagObject tag = repo.getTagObject(ref.getObject().getSha()); + if (head instanceof GitHubTagSCMHead) { + rev = new GitTagSCMRevision((GitHubTagSCMHead) head, tag.getObject().getSha()); + } else { + // we should never get here, but just in case, we have the information to construct + // the correct head, so let's do that + rev = + new GitTagSCMRevision( + new GitHubTagSCMHead(head.getName(), tag.getTagger().getDate().getTime()), + tag.getObject().getSha()); } + } else { + rev = new AbstractGitSCMSource.SCMRevisionImpl(head, ref.getObject().getSha()); + } } + + // Instead of calling release in many case and skipping this one case + // Make another call to connect() for this case + // and always release the existing instance as part of finally block. + // The result is the same but with far fewer code paths calling release(). + GitHub fileSystemGitHub = Connector.connect(apiUri, credentials); + return new GitHubSCMFileSystem(fileSystemGitHub, repo, refName, rev); + } catch (IOException | RuntimeException e) { + throw e; + } finally { + Connector.release(github); + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java index d17717aa0..d44adb13e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid; + import com.cloudbees.jenkins.GitHubWebHook; import com.cloudbees.plugins.credentials.CredentialsNameProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; @@ -99,1617 +102,1804 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid; - public class GitHubSCMNavigator extends SCMNavigator { - /** - * The owner of the repositories to navigate. - */ - @NonNull - private final String repoOwner; - - /** - * The API endpoint for the GitHub server. - */ - @CheckForNull - private String apiUri; - /** - * The credentials to use when accessing {@link #apiUri} (and also the default credentials to use for checking out). - */ - @CheckForNull - private String credentialsId; - /** - * The behavioural traits to apply. - */ - @NonNull - private List>> traits; - - /** - * Legacy configuration field - * - * @deprecated use {@link #credentialsId}. - */ - @Deprecated - private transient String scanCredentialsId; - /** - * Legacy configuration field - * - * @deprecated use {@link SSHCheckoutTrait}. - */ - @Deprecated - private transient String checkoutCredentialsId; - /** - * Legacy configuration field - * - * @deprecated use {@link RegexSCMSourceFilterTrait}. - */ - @Deprecated - private transient String pattern; - /** - * Legacy configuration field - * - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ - @Deprecated - private String includes; - /** - * Legacy configuration field - * - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ - @Deprecated - private String excludes; - /** - * Legacy configuration field - * - * @deprecated use {@link BranchDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildOriginBranch; - /** - * Legacy configuration field - * - * @deprecated use {@link BranchDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildOriginBranchWithPR; - /** - * Legacy configuration field - * - * @deprecated use {@link OriginPullRequestDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildOriginPRMerge; - /** - * Legacy configuration field - * - * @deprecated use {@link OriginPullRequestDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildOriginPRHead; - /** - * Legacy configuration field - * - * @deprecated use {@link ForkPullRequestDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildForkPRMerge; - /** - * Legacy configuration field - * - * @deprecated use {@link ForkPullRequestDiscoveryTrait}. - */ - @Deprecated - private transient Boolean buildForkPRHead; - - /** - * Constructor. - * - * @param repoOwner the owner of the repositories to navigate. - * @since 2.2.0 - */ - @DataBoundConstructor - public GitHubSCMNavigator(String repoOwner) { - this.repoOwner = StringUtils.defaultString(repoOwner); - this.traits = new ArrayList<>(); + /** The owner of the repositories to navigate. */ + @NonNull private final String repoOwner; + + /** The API endpoint for the GitHub server. */ + @CheckForNull private String apiUri; + /** + * The credentials to use when accessing {@link #apiUri} (and also the default credentials to use + * for checking out). + */ + @CheckForNull private String credentialsId; + /** The behavioural traits to apply. */ + @NonNull private List>> traits; + + /** + * Legacy configuration field + * + * @deprecated use {@link #credentialsId}. + */ + @Deprecated private transient String scanCredentialsId; + /** + * Legacy configuration field + * + * @deprecated use {@link SSHCheckoutTrait}. + */ + @Deprecated private transient String checkoutCredentialsId; + /** + * Legacy configuration field + * + * @deprecated use {@link RegexSCMSourceFilterTrait}. + */ + @Deprecated private transient String pattern; + /** + * Legacy configuration field + * + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated private String includes; + /** + * Legacy configuration field + * + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated private String excludes; + /** + * Legacy configuration field + * + * @deprecated use {@link BranchDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildOriginBranch; + /** + * Legacy configuration field + * + * @deprecated use {@link BranchDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildOriginBranchWithPR; + /** + * Legacy configuration field + * + * @deprecated use {@link OriginPullRequestDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildOriginPRMerge; + /** + * Legacy configuration field + * + * @deprecated use {@link OriginPullRequestDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildOriginPRHead; + /** + * Legacy configuration field + * + * @deprecated use {@link ForkPullRequestDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildForkPRMerge; + /** + * Legacy configuration field + * + * @deprecated use {@link ForkPullRequestDiscoveryTrait}. + */ + @Deprecated private transient Boolean buildForkPRHead; + + /** + * Constructor. + * + * @param repoOwner the owner of the repositories to navigate. + * @since 2.2.0 + */ + @DataBoundConstructor + public GitHubSCMNavigator(String repoOwner) { + this.repoOwner = StringUtils.defaultString(repoOwner); + this.traits = new ArrayList<>(); + } + + /** + * Legacy constructor. + * + * @param apiUri the API endpoint for the GitHub server. + * @param repoOwner the owner of the repositories to navigate. + * @param scanCredentialsId the credentials to use when accessing {@link #apiUri} (and also the + * default credentials to use for checking out). + * @param checkoutCredentialsId the credentials to use when checking out. + * @deprecated use {@link #GitHubSCMNavigator(String)}, {@link #setApiUri(String)}, {@link + * #setCredentialsId(String)} and {@link SSHCheckoutTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public GitHubSCMNavigator( + String apiUri, String repoOwner, String scanCredentialsId, String checkoutCredentialsId) { + this(repoOwner); + setCredentialsId(scanCredentialsId); + setApiUri(apiUri); + // legacy constructor means legacy defaults + this.traits = new ArrayList<>(); + this.traits.add(new BranchDiscoveryTrait(true, true)); + this.traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + if (!GitHubSCMSource.DescriptorImpl.SAME.equals(checkoutCredentialsId)) { + traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); } - - /** - * Legacy constructor. - * - * @param apiUri the API endpoint for the GitHub server. - * @param repoOwner the owner of the repositories to navigate. - * @param scanCredentialsId the credentials to use when accessing {@link #apiUri} (and also the default - * credentials to use for checking out). - * @param checkoutCredentialsId the credentials to use when checking out. - * @deprecated use {@link #GitHubSCMNavigator(String)}, {@link #setApiUri(String)}, - * {@link #setCredentialsId(String)} and {@link SSHCheckoutTrait} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public GitHubSCMNavigator(String apiUri, String repoOwner, String scanCredentialsId, String checkoutCredentialsId) { - this(repoOwner); - setCredentialsId(scanCredentialsId); - setApiUri(apiUri); - // legacy constructor means legacy defaults - this.traits = new ArrayList<>(); - this.traits.add(new BranchDiscoveryTrait(true, true)); - this.traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), - new ForkPullRequestDiscoveryTrait.TrustPermission())); - if (!GitHubSCMSource.DescriptorImpl.SAME.equals(checkoutCredentialsId)) { - traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); + } + + /** + * Gets the API endpoint for the GitHub server. + * + * @return the API endpoint for the GitHub server. + */ + @CheckForNull + public String getApiUri() { + return apiUri; + } + + /** + * Sets the API endpoint for the GitHub server. + * + * @param apiUri the API endpoint for the GitHub server. + * @since 2.2.0 + */ + @DataBoundSetter + public void setApiUri(String apiUri) { + if (isBlank(apiUri)) { + this.apiUri = GitHubServerConfig.GITHUB_URL; + } else { + this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); + } + } + + /** + * Gets the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link + * #apiUri} (and also the default credentials to use for checking out). + * + * @return the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link + * #apiUri} (and also the default credentials to use for checking out). + * @since 2.2.0 + */ + @CheckForNull + public String getCredentialsId() { + return credentialsId; + } + + /** + * Sets the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link + * #apiUri} (and also the default credentials to use for checking out). + * + * @param credentialsId the {@link StandardCredentials#getId()} of the credentials to use when + * accessing {@link #apiUri} (and also the default credentials to use for checking out). + * @since 2.2.0 + */ + @DataBoundSetter + public void setCredentialsId(@CheckForNull String credentialsId) { + this.credentialsId = Util.fixEmpty(credentialsId); + } + + /** + * Gets the name of the owner who's repositories will be navigated. + * + * @return the name of the owner who's repositories will be navigated. + */ + @NonNull + public String getRepoOwner() { + return repoOwner; + } + + /** + * Gets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} + * instances it discovers. + * + * @return the behavioural traits. + */ + @NonNull + public List>> getTraits() { + return Collections.unmodifiableList(traits); + } + + /** + * Sets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} + * instances it discovers. The new traits will take affect on the next navigation through any of + * the {@link #visitSources(SCMSourceObserver)} overloads or {@link #visitSource(String, + * SCMSourceObserver)}. + * + * @param traits the new behavioural traits. + */ + @SuppressWarnings("unchecked") + @DataBoundSetter + public void setTraits(@CheckForNull SCMTrait[] traits) { + // the reduced generics in the method signature are a workaround for JENKINS-26535 + this.traits = new ArrayList<>(); + if (traits != null) { + for (SCMTrait trait : traits) { + this.traits.add(trait); + } + } + } + + /** + * Sets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} + * instances it discovers. The new traits will take affect on the next navigation through any of + * the {@link #visitSources(SCMSourceObserver)} overloads or {@link #visitSource(String, + * SCMSourceObserver)}. + * + * @param traits the new behavioural traits. + */ + @Override + public void setTraits(@CheckForNull List>> traits) { + this.traits = traits != null ? new ArrayList<>(traits) : new ArrayList<>(); + } + + /** Use defaults for old settings. */ + @SuppressWarnings("ConstantConditions") + @SuppressFBWarnings( + value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", + justification = "Only non-null after we set them here!") + private Object readResolve() { + if (scanCredentialsId != null) { + credentialsId = scanCredentialsId; + } + if (traits == null) { + boolean buildOriginBranch = this.buildOriginBranch == null || this.buildOriginBranch; + boolean buildOriginBranchWithPR = + this.buildOriginBranchWithPR == null || this.buildOriginBranchWithPR; + boolean buildOriginPRMerge = this.buildOriginPRMerge != null && this.buildOriginPRMerge; + boolean buildOriginPRHead = this.buildOriginPRHead != null && this.buildOriginPRHead; + boolean buildForkPRMerge = this.buildForkPRMerge == null || this.buildForkPRMerge; + boolean buildForkPRHead = this.buildForkPRHead != null && this.buildForkPRHead; + List>> traits = new ArrayList<>(); + if (buildOriginBranch || buildOriginBranchWithPR) { + traits.add(new BranchDiscoveryTrait(buildOriginBranch, buildOriginBranchWithPR)); + } + if (buildOriginPRMerge || buildOriginPRHead) { + EnumSet s = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + if (buildOriginPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } + if (buildOriginPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); } + traits.add(new OriginPullRequestDiscoveryTrait(s)); + } + if (buildForkPRMerge || buildForkPRHead) { + EnumSet s = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + if (buildForkPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } + if (buildForkPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } + traits.add( + new ForkPullRequestDiscoveryTrait( + s, new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + if (checkoutCredentialsId != null + && !DescriptorImpl.SAME.equals(checkoutCredentialsId) + && !checkoutCredentialsId.equals(scanCredentialsId)) { + traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); + } + if ((includes != null && !"*".equals(includes)) + || (excludes != null && !"".equals(excludes))) { + traits.add( + new WildcardSCMHeadFilterTrait( + StringUtils.defaultIfBlank(includes, "*"), + StringUtils.defaultIfBlank(excludes, ""))); + } + if (pattern != null && !".*".equals(pattern)) { + traits.add(new RegexSCMSourceFilterTrait(pattern)); + } + this.traits = traits; } - - /** - * Gets the API endpoint for the GitHub server. - * - * @return the API endpoint for the GitHub server. - */ - @CheckForNull - public String getApiUri() { - return apiUri; + if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { + setApiUri(apiUri); } - - /** - * Sets the API endpoint for the GitHub server. - * - * @param apiUri the API endpoint for the GitHub server. - * @since 2.2.0 - */ - @DataBoundSetter - public void setApiUri(String apiUri) { - if(isBlank(apiUri)){ - this.apiUri = GitHubServerConfig.GITHUB_URL; - }else { - this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); + return this; + } + + /** + * Legacy getter. + * + * @return {@link #getCredentialsId()}. + * @deprecated use {@link #getCredentialsId()}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @CheckForNull + public String getScanCredentialsId() { + return credentialsId; + } + + /** + * Legacy setter. + * + * @param scanCredentialsId the credentials. + * @deprecated use {@link #setCredentialsId(String)} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setScanCredentialsId(@CheckForNull String scanCredentialsId) { + this.credentialsId = scanCredentialsId; + } + + /** + * Legacy getter. + * + * @return {@link WildcardSCMHeadFilterTrait#getIncludes()} + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @NonNull + public String getIncludes() { + for (SCMTrait trait : traits) { + if (trait instanceof WildcardSCMHeadFilterTrait) { + return ((WildcardSCMHeadFilterTrait) trait).getIncludes(); + } + } + return "*"; + } + + /** + * Legacy getter. + * + * @return {@link WildcardSCMHeadFilterTrait#getExcludes()} + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @NonNull + public String getExcludes() { + for (SCMTrait trait : traits) { + if (trait instanceof WildcardSCMHeadFilterTrait) { + return ((WildcardSCMHeadFilterTrait) trait).getExcludes(); + } + } + return ""; + } + + /** + * Legacy setter. + * + * @param includes see {@link WildcardSCMHeadFilterTrait#WildcardSCMHeadFilterTrait(String, + * String)} + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setIncludes(@NonNull String includes) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof WildcardSCMHeadFilterTrait) { + WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; + if ("*".equals(includes) && "".equals(existing.getExcludes())) { + traits.remove(i); + } else { + traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes())); + } + return; + } + } + if (!"*".equals(includes)) { + traits.add(new WildcardSCMHeadFilterTrait(includes, "")); + } + } + + /** + * Legacy setter. + * + * @param excludes see {@link WildcardSCMHeadFilterTrait#WildcardSCMHeadFilterTrait(String, + * String)} + * @deprecated use {@link WildcardSCMHeadFilterTrait}. + */ + @Deprecated + @Restricted(NoExternalUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setExcludes(@NonNull String excludes) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof WildcardSCMHeadFilterTrait) { + WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; + if ("*".equals(existing.getIncludes()) && "".equals(excludes)) { + traits.remove(i); + } else { + traits.set(i, new WildcardSCMHeadFilterTrait(existing.getIncludes(), excludes)); + } + return; + } + } + if (!"".equals(excludes)) { + traits.add(new WildcardSCMHeadFilterTrait("*", excludes)); + } + } + + /** + * Legacy getter. + * + * @return {@link BranchDiscoveryTrait#isBuildBranch()}. + * @deprecated use {@link BranchDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginBranch() { + for (SCMTrait trait : traits) { + if (trait instanceof BranchDiscoveryTrait) { + return ((BranchDiscoveryTrait) trait).isBuildBranch(); + } + } + return false; + } + + /** + * Legacy setter. + * + * @param buildOriginBranch see {@link BranchDiscoveryTrait#BranchDiscoveryTrait(boolean, + * boolean)}. + * @deprecated use {@link BranchDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginBranch(boolean buildOriginBranch) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof BranchDiscoveryTrait) { + BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; + if (buildOriginBranch || previous.isBuildBranchesWithPR()) { + traits.set( + i, new BranchDiscoveryTrait(buildOriginBranch, previous.isBuildBranchesWithPR())); + } else { + traits.remove(i); + } + return; + } + } + if (buildOriginBranch) { + traits.add(new BranchDiscoveryTrait(buildOriginBranch, false)); + } + } + + /** + * Legacy getter. + * + * @return {@link BranchDiscoveryTrait#isBuildBranchesWithPR()}. + * @deprecated use {@link BranchDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginBranchWithPR() { + for (SCMTrait trait : traits) { + if (trait instanceof BranchDiscoveryTrait) { + return ((BranchDiscoveryTrait) trait).isBuildBranchesWithPR(); + } + } + return false; + } + + /** + * Legacy setter. + * + * @param buildOriginBranchWithPR see {@link BranchDiscoveryTrait#BranchDiscoveryTrait(boolean, + * boolean)}. + * @deprecated use {@link BranchDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginBranchWithPR(boolean buildOriginBranchWithPR) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof BranchDiscoveryTrait) { + BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; + if (buildOriginBranchWithPR || previous.isBuildBranch()) { + traits.set( + i, new BranchDiscoveryTrait(previous.isBuildBranch(), buildOriginBranchWithPR)); + } else { + traits.remove(i); + } + return; + } + } + if (buildOriginBranchWithPR) { + traits.add(new BranchDiscoveryTrait(false, buildOriginBranchWithPR)); + } + } + + /** + * Legacy getter. + * + * @return {@link OriginPullRequestDiscoveryTrait#getStrategies()}. + * @deprecated use {@link OriginPullRequestDiscoveryTrait#getStrategies()} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginPRMerge() { + for (SCMTrait trait : traits) { + if (trait instanceof OriginPullRequestDiscoveryTrait) { + return ((OriginPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.MERGE); + } + } + return false; + } + + /** + * Legacy setter. + * + * @param buildOriginPRMerge see {@link + * OriginPullRequestDiscoveryTrait#OriginPullRequestDiscoveryTrait(Set)}. + * @deprecated use {@link OriginPullRequestDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginPRMerge(boolean buildOriginPRMerge) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof OriginPullRequestDiscoveryTrait) { + Set s = + ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); + if (buildOriginPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } else { + s.remove(ChangeRequestCheckoutStrategy.MERGE); } + traits.set(i, new OriginPullRequestDiscoveryTrait(s)); + return; + } } - - /** - * Gets the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link #apiUri} (and also - * the default credentials to use for checking out). - * - * @return the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link #apiUri} (and - * also the default credentials to use for checking out). - * @since 2.2.0 - */ - @CheckForNull - public String getCredentialsId() { - return credentialsId; + if (buildOriginPRMerge) { + traits.add( + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); } - - /** - * Sets the {@link StandardCredentials#getId()} of the credentials to use when accessing {@link #apiUri} (and also - * the default credentials to use for checking out). - * - * @param credentialsId the {@link StandardCredentials#getId()} of the credentials to use when accessing - * {@link #apiUri} (and also the default credentials to use for checking out). - * @since 2.2.0 - */ - @DataBoundSetter - public void setCredentialsId(@CheckForNull String credentialsId) { - this.credentialsId = Util.fixEmpty(credentialsId); + } + + /** + * Legacy getter. + * + * @return {@link OriginPullRequestDiscoveryTrait#getStrategies()}. + * @deprecated use {@link OriginPullRequestDiscoveryTrait#getStrategies()} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginPRHead() { + for (SCMTrait trait : traits) { + if (trait instanceof OriginPullRequestDiscoveryTrait) { + return ((OriginPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.HEAD); + } } - - /** - * Gets the name of the owner who's repositories will be navigated. - * @return the name of the owner who's repositories will be navigated. - */ - @NonNull - public String getRepoOwner() { - return repoOwner; + return false; + } + + /** + * Legacy setter. + * + * @param buildOriginPRHead see {@link + * OriginPullRequestDiscoveryTrait#OriginPullRequestDiscoveryTrait(Set)}. + * @deprecated use {@link OriginPullRequestDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginPRHead(boolean buildOriginPRHead) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof OriginPullRequestDiscoveryTrait) { + Set s = + ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); + if (buildOriginPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } else { + s.remove(ChangeRequestCheckoutStrategy.HEAD); + } + traits.set(i, new OriginPullRequestDiscoveryTrait(s)); + return; + } + } + if (buildOriginPRHead) { + traits.add( + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); + } + } + + /** + * Legacy getter. + * + * @return {@link ForkPullRequestDiscoveryTrait#getStrategies()}. + * @deprecated use {@link ForkPullRequestDiscoveryTrait#getStrategies()} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildForkPRMerge() { + for (SCMTrait trait : traits) { + if (trait instanceof ForkPullRequestDiscoveryTrait) { + return ((ForkPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.MERGE); + } + } + return false; + } + + /** + * Legacy setter. + * + * @param buildForkPRMerge see {@link + * ForkPullRequestDiscoveryTrait#ForkPullRequestDiscoveryTrait(Set, SCMHeadAuthority)}. + * @deprecated use {@link ForkPullRequestDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildForkPRMerge(boolean buildForkPRMerge) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof ForkPullRequestDiscoveryTrait) { + ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; + Set s = forkTrait.getStrategies(); + if (buildForkPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } else { + s.remove(ChangeRequestCheckoutStrategy.MERGE); + } + traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); + return; + } + } + if (buildForkPRMerge) { + traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + } + + /** + * Legacy getter. + * + * @return {@link ForkPullRequestDiscoveryTrait#getStrategies()}. + * @deprecated use {@link ForkPullRequestDiscoveryTrait#getStrategies()} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildForkPRHead() { + for (SCMTrait trait : traits) { + if (trait instanceof ForkPullRequestDiscoveryTrait) { + return ((ForkPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.HEAD); + } + } + return false; + } + + /** + * Legacy setter. + * + * @param buildForkPRHead see {@link + * ForkPullRequestDiscoveryTrait#ForkPullRequestDiscoveryTrait(Set, SCMHeadAuthority)}. + * @deprecated use {@link ForkPullRequestDiscoveryTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildForkPRHead(boolean buildForkPRHead) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof ForkPullRequestDiscoveryTrait) { + ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; + Set s = forkTrait.getStrategies(); + if (buildForkPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } else { + s.remove(ChangeRequestCheckoutStrategy.HEAD); + } + traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); + return; + } + } + if (buildForkPRHead) { + traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + } + + /** + * Legacy getter. + * + * @return {@link SSHCheckoutTrait#getCredentialsId()} with some mangling to preserve legacy + * behaviour. + * @deprecated use {@link SSHCheckoutTrait} + */ + @CheckForNull + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public String getCheckoutCredentialsId() { + for (SCMTrait trait : traits) { + if (trait instanceof SSHCheckoutTrait) { + return StringUtils.defaultString( + ((SSHCheckoutTrait) trait).getCredentialsId(), + GitHubSCMSource.DescriptorImpl.ANONYMOUS); + } + } + return DescriptorImpl.SAME; + } + + /** + * Legacy getter. + * + * @return {@link RegexSCMSourceFilterTrait#getRegex()}. + * @deprecated use {@link RegexSCMSourceFilterTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public String getPattern() { + for (SCMTrait trait : traits) { + if (trait instanceof RegexSCMSourceFilterTrait) { + return ((RegexSCMSourceFilterTrait) trait).getRegex(); + } + } + return ".*"; + } + + /** + * Legacy setter. + * + * @param pattern see {@link RegexSCMSourceFilterTrait#RegexSCMSourceFilterTrait(String)}. + * @deprecated use {@link RegexSCMSourceFilterTrait} + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setPattern(String pattern) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof RegexSCMSourceFilterTrait) { + if (".*".equals(pattern)) { + traits.remove(i); + } else { + traits.set(i, new RegexSCMSourceFilterTrait(pattern)); + } + return; + } + } + if (!".*".equals(pattern)) { + traits.add(new RegexSCMSourceFilterTrait(pattern)); + } + } + + /** {@inheritDoc} */ + @NonNull + @Override + protected String id() { + return StringUtils.defaultIfBlank(apiUri, GitHubSCMSource.GITHUB_URL) + "::" + repoOwner; + } + + /** {@inheritDoc} */ + @Override + public void visitSources(SCMSourceObserver observer) throws IOException, InterruptedException { + Set includes = observer.getIncludes(); + if (includes != null && includes.size() == 1) { + // optimize for the single source case + visitSource(includes.iterator().next(), observer); + return; } + TaskListener listener = observer.getListener(); - /** - * Gets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} instances it - * discovers. - * - * @return the behavioural traits. - */ - @NonNull - public List>> getTraits() { - return Collections.unmodifiableList(traits); + // Input data validation + if (repoOwner.isEmpty()) { + throw new AbortException("Must specify user or organization"); } - /** - * Sets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} instances it - * discovers. The new traits will take affect on the next navigation through any of the - * {@link #visitSources(SCMSourceObserver)} overloads or {@link #visitSource(String, SCMSourceObserver)}. - * - * @param traits the new behavioural traits. - */ - @SuppressWarnings("unchecked") - @DataBoundSetter - public void setTraits(@CheckForNull SCMTrait[] traits) { - // the reduced generics in the method signature are a workaround for JENKINS-26535 - this.traits = new ArrayList<>(); - if (traits != null) { - for (SCMTrait trait : traits) { - this.traits.add(trait); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId); + + // Github client and validation + GitHub github = Connector.connect(apiUri, credentials); + try { + Connector.checkConnectionValidity(apiUri, listener, credentials, github); + Connector.configureLocalRateLimitChecker(listener, github); + + // Input data validation + if (credentials != null && !isCredentialValid(github)) { + String message = + String.format( + "Invalid scan credentials %s to connect to %s, skipping", + CredentialsNameProvider.name(credentials), + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); + throw new AbortException(message); + } + + GitHubSCMNavigatorContext gitHubSCMNavigatorContext = + new GitHubSCMNavigatorContext().withTraits(traits); + + try (GitHubSCMNavigatorRequest request = + gitHubSCMNavigatorContext.newRequest(this, observer)) { + SourceFactory sourceFactory = new SourceFactory(request); + WitnessImpl witness = new WitnessImpl(listener); + + boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; + if (github.isAnonymous()) { + listener + .getLogger() + .format( + "Connecting to %s with no credentials, anonymous access%n", + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); + } else if (!githubAppAuthentication) { + GHMyself myself; + try { + // Requires an authenticated access + myself = github.getMyself(); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } + if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("Looking up repositories of myself %s", repoOwner))); + for (GHRepository repo : myself.listRepositories(100)) { + if (!repo.getOwnerName().equals(repoOwner)) { + continue; // ignore repos in other orgs when using GHMyself + } + + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", repo.getName()))); + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } + } + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; + } + } + + GHOrganization org = getGhOrganization(github); + if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("Looking up repositories of organization %s", repoOwner))); + final Iterable repositories; + if (StringUtils.isNotBlank(gitHubSCMNavigatorContext.getTeamSlug())) { + // get repositories for selected team + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Looking up repositories for team %s", + gitHubSCMNavigatorContext.getTeamSlug()))); + repositories = + org.getTeamBySlug(gitHubSCMNavigatorContext.getTeamSlug()) + .listRepositories() + .withPageSize(100); + } else { + repositories = org.listRepositories(100); + } + for (GHRepository repo : repositories) { + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + // exclude archived repositories + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", repo.getName()))); + + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); } + } + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; } + + GHUser user = null; + try { + user = github.getUser(repoOwner); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } catch (FileNotFoundException fnf) { + // the user may not exist... ok to ignore + } + if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { + listener.getLogger().format("Looking up repositories of user %s%n%n", repoOwner); + for (GHRepository repo : user.listRepositories(100)) { + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } + } + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; + } + + throw new AbortException( + repoOwner + " does not correspond to a known GitHub User Account or Organization"); + } + } finally { + Connector.release(github); + } + } + + private GHOrganization getGhOrganization(final GitHub github) throws IOException { + try { + return github.getOrganization(repoOwner); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } catch (FileNotFoundException fnf) { + // may be an user... ok to ignore + } + return null; + } + + /** {@inheritDoc} */ + @Override + public void visitSource(String sourceName, SCMSourceObserver observer) + throws IOException, InterruptedException { + TaskListener listener = observer.getListener(); + + // Input data validation + if (repoOwner.isEmpty()) { + throw new AbortException("Must specify user or organization"); } - /** - * Sets the behavioural traits that are applied to this navigator and any {@link GitHubSCMSource} instances it - * discovers. The new traits will take affect on the next navigation through any of the - * {@link #visitSources(SCMSourceObserver)} overloads or {@link #visitSource(String, SCMSourceObserver)}. - * - * @param traits the new behavioural traits. - */ - @Override - public void setTraits(@CheckForNull List>> traits) { - this.traits = traits != null ? new ArrayList<>(traits) : new ArrayList<>(); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId); + // Github client and validation + GitHub github; + try { + github = Connector.connect(apiUri, credentials); + } catch (HttpException e) { + throw new AbortException(e.getMessage()); } - /** - * Use defaults for old settings. - */ - @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification="Only non-null after we set them here!") - private Object readResolve() { - if (scanCredentialsId != null) { - credentialsId = scanCredentialsId; - } - if (traits == null) { - boolean buildOriginBranch = this.buildOriginBranch == null || this.buildOriginBranch; - boolean buildOriginBranchWithPR = this.buildOriginBranchWithPR == null || this.buildOriginBranchWithPR; - boolean buildOriginPRMerge = this.buildOriginPRMerge != null && this.buildOriginPRMerge; - boolean buildOriginPRHead = this.buildOriginPRHead != null && this.buildOriginPRHead; - boolean buildForkPRMerge = this.buildForkPRMerge == null || this.buildForkPRMerge; - boolean buildForkPRHead = this.buildForkPRHead != null && this.buildForkPRHead; - List>> traits = new ArrayList<>(); - if (buildOriginBranch || buildOriginBranchWithPR) { - traits.add(new BranchDiscoveryTrait(buildOriginBranch, buildOriginBranchWithPR)); - } - if (buildOriginPRMerge || buildOriginPRHead) { - EnumSet s = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - if (buildOriginPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } - if (buildOriginPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } - traits.add(new OriginPullRequestDiscoveryTrait(s)); - } - if (buildForkPRMerge || buildForkPRHead) { - EnumSet s = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - if (buildForkPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } - if (buildForkPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } - traits.add(new ForkPullRequestDiscoveryTrait(s, new ForkPullRequestDiscoveryTrait.TrustPermission())); - } - if (checkoutCredentialsId != null - && !DescriptorImpl.SAME.equals(checkoutCredentialsId) - && !checkoutCredentialsId.equals(scanCredentialsId)) { - traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); - } - if ((includes != null && !"*".equals(includes)) || (excludes != null && !"".equals(excludes))) { - traits.add(new WildcardSCMHeadFilterTrait( - StringUtils.defaultIfBlank(includes, "*"), - StringUtils.defaultIfBlank(excludes, ""))); + try { + // Input data validation + if (credentials != null && !isCredentialValid(github)) { + String message = + String.format( + "Invalid scan credentials %s to connect to %s, skipping", + CredentialsNameProvider.name(credentials), + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); + throw new AbortException(message); + } + + GitHubSCMNavigatorContext gitHubSCMNavigatorContext = + new GitHubSCMNavigatorContext().withTraits(traits); + + try (GitHubSCMNavigatorRequest request = + gitHubSCMNavigatorContext.newRequest(this, observer)) { + SourceFactory sourceFactory = new SourceFactory(request); + WitnessImpl witness = new WitnessImpl(listener); + + boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; + if (github.isAnonymous()) { + listener + .getLogger() + .format( + "Connecting to %s with no credentials, anonymous access%n", + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); + } else if (!githubAppAuthentication) { + listener + .getLogger() + .format( + "Connecting to %s using %s%n", + apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri, + CredentialsNameProvider.name(credentials)); + GHMyself myself; + try { + // Requires an authenticated access + myself = github.getMyself(); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } + if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { + listener + .getLogger() + .format("Looking up %s repository of myself %s%n%n", sourceName, repoOwner); + GHRepository repo = myself.getRepository(sourceName); + if (repo != null && repo.getOwnerName().equals(repoOwner)) { + + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", repo.getName()))); + + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } } - if (pattern != null && !".*".equals(pattern)) { - traits.add(new RegexSCMSourceFilterTrait(pattern)); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; + } + } + + GHOrganization org = getGhOrganization(github); + if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { + listener + .getLogger() + .format("Looking up %s repository of organization %s%n%n", sourceName, repoOwner); + GHRepository repo = org.getRepository(sourceName); + if (repo != null) { + + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + } else if (StringUtils.isNotBlank(gitHubSCMNavigatorContext.getTeamSlug()) + && !isRepositoryVisibleToTeam(org, repo, gitHubSCMNavigatorContext.getTeamSlug())) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is not in team %s", + repo.getName(), gitHubSCMNavigatorContext.getTeamSlug()))); + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", repo.getName()))); + + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); } - this.traits = traits; - } - if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { - setApiUri(apiUri); + } + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; } - return this; - } - /** - * Legacy getter. - * - * @return {@link #getCredentialsId()}. - * @deprecated use {@link #getCredentialsId()}. - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @CheckForNull - public String getScanCredentialsId() { - return credentialsId; + GHUser user = null; + try { + user = github.getUser(repoOwner); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } catch (FileNotFoundException fnf) { + // the user may not exist... ok to ignore + } + if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { + listener + .getLogger() + .format("Looking up %s repository of user %s%n%n", sourceName, repoOwner); + GHRepository repo = user.getRepository(sourceName); + if (repo != null) { + + if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is archived", repo.getName()))); + + } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() + && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { + // exclude repositories which are missing one or more of the specified topics + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is missing one or more of the following topics: '%s'", + repo.getName(), gitHubSCMNavigatorContext.getTopics()))); + } else if (!repo.isPrivate() + && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { + witness.record(repo.getName(), false); + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is public", repo.getName()))); + + } else if (request.process(repo.getName(), sourceFactory, null, witness)) { + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "%d repositories were processed (query completed)", + witness.getCount()))); + } + } + listener + .getLogger() + .println( + GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format("%d repositories were processed", witness.getCount()))); + return; + } + + throw new AbortException( + repoOwner + " does not correspond to a known GitHub User Account or Organization"); + } + } finally { + Connector.release(github); } - - /** - * Legacy setter. - * - * @param scanCredentialsId the credentials. - * @deprecated use {@link #setCredentialsId(String)} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setScanCredentialsId(@CheckForNull String scanCredentialsId) { - this.credentialsId = scanCredentialsId; + } + + private boolean isRepositoryVisibleToTeam(GHOrganization org, GHRepository repo, String teamSlug) + throws IOException { + final Iterable repositories = + org.getTeamBySlug(teamSlug).listRepositories().withPageSize(100); + for (GHRepository item : repositories) { + if (repo.getFullName().equals(item.getFullName())) { + return true; + } + } + return false; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public List retrieveActions( + @NonNull SCMNavigatorOwner owner, + @CheckForNull SCMNavigatorEvent event, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + // TODO when we have support for trusted events, use the details from event if event was from + // trusted source + listener.getLogger().printf("Looking up details of %s...%n", getRepoOwner()); + List result = new ArrayList<>(); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId); + GitHub hub = Connector.connect(getApiUri(), credentials); + try { + Connector.configureLocalRateLimitChecker(listener, hub); + GHUser u = hub.getUser(getRepoOwner()); + String objectUrl = u.getHtmlUrl() == null ? null : u.getHtmlUrl().toExternalForm(); + result.add(new ObjectMetadataAction(Util.fixEmpty(u.getName()), null, objectUrl)); + result.add(new GitHubOrgMetadataAction(u)); + result.add(new GitHubLink("icon-github-logo", u.getHtmlUrl())); + if (objectUrl == null) { + listener.getLogger().println("Organization URL: unspecified"); + } else { + listener + .getLogger() + .printf( + "Organization URL: %s%n", + HyperlinkNote.encodeTo( + objectUrl, StringUtils.defaultIfBlank(u.getName(), objectUrl))); + } + return result; + } finally { + Connector.release(hub); } + } + + /** {@inheritDoc} */ + @Override + public void afterSave(@NonNull SCMNavigatorOwner owner) { + GitHubWebHook.get().registerHookFor(owner); + try { + // FIXME MINOR HACK ALERT + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId); + GitHub hub = Connector.connect(getApiUri(), credentials); + try { + GitHubOrgWebHook.register(hub, repoOwner); + } finally { + Connector.release(hub); + } + } catch (IOException e) { + DescriptorImpl.LOGGER.log(Level.WARNING, e.getMessage(), e); + } + } + + @Symbol("github") + @Extension + public static class DescriptorImpl extends SCMNavigatorDescriptor implements IconSpec { + + private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); - /** - * Legacy getter. - * - * @return {@link WildcardSCMHeadFilterTrait#getIncludes()} - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @NonNull - public String getIncludes() { - for (SCMTrait trait : traits) { - if (trait instanceof WildcardSCMHeadFilterTrait) { - return ((WildcardSCMHeadFilterTrait) trait).getIncludes(); - } - } - return "*"; - } + public static final String defaultIncludes = "*"; - /** - * Legacy getter. - * - * @return {@link WildcardSCMHeadFilterTrait#getExcludes()} - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @NonNull - public String getExcludes() { - for (SCMTrait trait : traits) { - if (trait instanceof WildcardSCMHeadFilterTrait) { - return ((WildcardSCMHeadFilterTrait) trait).getExcludes(); - } - } - return ""; - } + public static final String defaultExcludes = ""; + + public static final String SAME = GitHubSCMSource.DescriptorImpl.SAME; - /** - * Legacy setter. - * - * @param includes see {@link WildcardSCMHeadFilterTrait#WildcardSCMHeadFilterTrait(String, String)} - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @DataBoundSetter - public void setIncludes(@NonNull String includes) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof WildcardSCMHeadFilterTrait) { - WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; - if ("*".equals(includes) && "".equals(existing.getExcludes())) { - traits.remove(i); - } else { - traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes())); - } - return; - } - } - if (!"*".equals(includes)) { - traits.add(new WildcardSCMHeadFilterTrait(includes, "")); - } - } + public static final boolean defaultBuildOriginBranch = true; - /** - * Legacy setter. - * - * @param excludes see {@link WildcardSCMHeadFilterTrait#WildcardSCMHeadFilterTrait(String, String)} - * @deprecated use {@link WildcardSCMHeadFilterTrait}. - */ @Deprecated - @Restricted(NoExternalUse.class) + @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @DataBoundSetter - public void setExcludes(@NonNull String excludes) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof WildcardSCMHeadFilterTrait) { - WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; - if ("*".equals(existing.getIncludes()) && "".equals(excludes)) { - traits.remove(i); - } else { - traits.set(i, new WildcardSCMHeadFilterTrait(existing.getIncludes(), excludes)); - } - return; - } - } - if (!"".equals(excludes)) { - traits.add(new WildcardSCMHeadFilterTrait("*", excludes)); - } - } + public static final boolean defaultBuildOriginBranchWithPR = true; - /** - * Legacy getter. - * @return {@link BranchDiscoveryTrait#isBuildBranch()}. - * @deprecated use {@link BranchDiscoveryTrait} - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - public boolean getBuildOriginBranch() { - for (SCMTrait trait : traits) { - if (trait instanceof BranchDiscoveryTrait) { - return ((BranchDiscoveryTrait) trait).isBuildBranch(); - } - } - return false; - } + public static final boolean defaultBuildOriginPRMerge = false; - /** - * Legacy setter. - * - * @param buildOriginBranch see {@link BranchDiscoveryTrait#BranchDiscoveryTrait(boolean, boolean)}. - * @deprecated use {@link BranchDiscoveryTrait} - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginBranch(boolean buildOriginBranch) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof BranchDiscoveryTrait) { - BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; - if (buildOriginBranch || previous.isBuildBranchesWithPR()) { - traits.set(i, new BranchDiscoveryTrait(buildOriginBranch, previous.isBuildBranchesWithPR())); - } else { - traits.remove(i); - } - return; - } - } - if (buildOriginBranch) { - traits.add(new BranchDiscoveryTrait(buildOriginBranch, false)); - } - } + public static final boolean defaultBuildOriginPRHead = false; - /** - * Legacy getter. - * - * @return {@link BranchDiscoveryTrait#isBuildBranchesWithPR()}. - * @deprecated use {@link BranchDiscoveryTrait} - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - public boolean getBuildOriginBranchWithPR() { - for (SCMTrait trait : traits) { - if (trait instanceof BranchDiscoveryTrait) { - return ((BranchDiscoveryTrait) trait).isBuildBranchesWithPR(); - } - } - return false; - } + public static final boolean defaultBuildForkPRMerge = false; - /** - * Legacy setter. - * - * @param buildOriginBranchWithPR see {@link BranchDiscoveryTrait#BranchDiscoveryTrait(boolean, boolean)}. - * @deprecated use {@link BranchDiscoveryTrait} - */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginBranchWithPR(boolean buildOriginBranchWithPR) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof BranchDiscoveryTrait) { - BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; - if (buildOriginBranchWithPR || previous.isBuildBranch()) { - traits.set(i, new BranchDiscoveryTrait(previous.isBuildBranch(), buildOriginBranchWithPR)); - } else { - traits.remove(i); - } - return; - } - } - if (buildOriginBranchWithPR) { - traits.add(new BranchDiscoveryTrait(false, buildOriginBranchWithPR)); - } + public static final boolean defaultBuildForkPRHead = false; + + @Inject private GitHubSCMSource.DescriptorImpl delegate; + + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.GitHubSCMNavigator_Pronoun(); } - /** - * Legacy getter. - * - * @return {@link OriginPullRequestDiscoveryTrait#getStrategies()}. - * @deprecated use {@link OriginPullRequestDiscoveryTrait#getStrategies()} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginPRMerge() { - for (SCMTrait trait : traits) { - if (trait instanceof OriginPullRequestDiscoveryTrait) { - return ((OriginPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.MERGE); - } - } - return false; + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.GitHubSCMNavigator_DisplayName(); } - /** - * Legacy setter. - * - * @param buildOriginPRMerge see {@link OriginPullRequestDiscoveryTrait#OriginPullRequestDiscoveryTrait(Set)}. - * @deprecated use {@link OriginPullRequestDiscoveryTrait} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginPRMerge(boolean buildOriginPRMerge) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof OriginPullRequestDiscoveryTrait) { - Set s = ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); - if (buildOriginPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } else { - s.remove(ChangeRequestCheckoutStrategy.MERGE); - } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } - } - if (buildOriginPRMerge) { - traits.add(new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); - } + /** {@inheritDoc} */ + @Override + public String getDescription() { + return Messages.GitHubSCMNavigator_Description(); } - /** - * Legacy getter. - * - * @return {@link OriginPullRequestDiscoveryTrait#getStrategies()}. - * @deprecated use {@link OriginPullRequestDiscoveryTrait#getStrategies()} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginPRHead() { - for (SCMTrait trait : traits) { - if (trait instanceof OriginPullRequestDiscoveryTrait) { - return ((OriginPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.HEAD); - } - } - return false; + /** {@inheritDoc} */ + @Override + public String getIconFilePathPattern() { + return "plugin/github-branch-source/images/:size/github-scmnavigator.png"; + } + /** {@inheritDoc} */ + @Override + public String getIconClassName() { + return "icon-github-scm-navigator"; } - /** - * Legacy setter. - * - * @param buildOriginPRHead see {@link OriginPullRequestDiscoveryTrait#OriginPullRequestDiscoveryTrait(Set)}. - * @deprecated use {@link OriginPullRequestDiscoveryTrait} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginPRHead(boolean buildOriginPRHead) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof OriginPullRequestDiscoveryTrait) { - Set s = ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); - if (buildOriginPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } else { - s.remove(ChangeRequestCheckoutStrategy.HEAD); - } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } - } - if (buildOriginPRHead) { - traits.add(new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); - } + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + public SCMNavigator newInstance(String name) { + GitHubSCMNavigator navigator = new GitHubSCMNavigator(name); + navigator.setTraits(getTraitsDefaults()); + navigator.setApiUri(GitHubServerConfig.GITHUB_URL); + return navigator; } - /** - * Legacy getter. - * - * @return {@link ForkPullRequestDiscoveryTrait#getStrategies()}. - * @deprecated use {@link ForkPullRequestDiscoveryTrait#getStrategies()} - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildForkPRMerge() { - for (SCMTrait trait : traits) { - if (trait instanceof ForkPullRequestDiscoveryTrait) { - return ((ForkPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.MERGE); - } - } - return false; + /** {@inheritDoc} */ + @NonNull + @Override + protected SCMSourceCategory[] createCategories() { + return new SCMSourceCategory[] { + new UncategorizedSCMSourceCategory(Messages._GitHubSCMNavigator_UncategorizedCategory()) + // TODO add support for forks + }; } /** - * Legacy setter. + * Validates the selected credentials. * - * @param buildForkPRMerge see {@link ForkPullRequestDiscoveryTrait#ForkPullRequestDiscoveryTrait(Set, SCMHeadAuthority)}. - * @deprecated use {@link ForkPullRequestDiscoveryTrait} + * @param context the context. + * @param apiUri the end-point. + * @param credentialsId the credentials. + * @return validation results. + * @since 2.2.0 */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildForkPRMerge(boolean buildForkPRMerge) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof ForkPullRequestDiscoveryTrait) { - ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; - Set s = forkTrait.getStrategies(); - if (buildForkPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } else { - s.remove(ChangeRequestCheckoutStrategy.MERGE); - } - traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); - return; - } - } - if (buildForkPRMerge) { - traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), - new ForkPullRequestDiscoveryTrait.TrustPermission())); - } + @RequirePOST + @Restricted(NoExternalUse.class) // stapler + public FormValidation doCheckCredentialsId( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId) { + return Connector.checkScanCredentials(context, apiUri, credentialsId); } /** - * Legacy getter. + * Populates the drop-down list of credentials. * - * @return {@link ForkPullRequestDiscoveryTrait#getStrategies()}. - * @deprecated use {@link ForkPullRequestDiscoveryTrait#getStrategies()} + * @param context the context. + * @param apiUri the end-point. + * @param credentialsId the existing selection; + * @return the drop-down list. + * @since 2.2.0 */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildForkPRHead() { - for (SCMTrait trait : traits) { - if (trait instanceof ForkPullRequestDiscoveryTrait) { - return ((ForkPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.HEAD); - } - } - return false; + @Restricted(NoExternalUse.class) // stapler + public ListBoxModel doFillCredentialsIdItems( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId) { + if (context == null + ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + : !context.hasPermission(Item.EXTENDED_READ)) { + return new StandardListBoxModel().includeCurrentValue(credentialsId); + } + return Connector.listScanCredentials(context, apiUri); } /** - * Legacy setter. + * Returns the available GitHub endpoint items. * - * @param buildForkPRHead see - * {@link ForkPullRequestDiscoveryTrait#ForkPullRequestDiscoveryTrait(Set, SCMHeadAuthority)}. - * @deprecated use {@link ForkPullRequestDiscoveryTrait} + * @return the available GitHub endpoint items. */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildForkPRHead(boolean buildForkPRHead) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof ForkPullRequestDiscoveryTrait) { - ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; - Set s = forkTrait.getStrategies(); - if (buildForkPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } else { - s.remove(ChangeRequestCheckoutStrategy.HEAD); - } - traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); - return; - } - } - if (buildForkPRHead) { - traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), - new ForkPullRequestDiscoveryTrait.TrustPermission())); - } + @Restricted(NoExternalUse.class) // stapler + @SuppressWarnings("unused") // stapler + public ListBoxModel doFillApiUriItems() { + return getPossibleApiUriItems(); } - /** - * Legacy getter. - * - * @return {@link SSHCheckoutTrait#getCredentialsId()} with some mangling to preserve legacy behaviour. - * @deprecated use {@link SSHCheckoutTrait} - */ - @CheckForNull - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public String getCheckoutCredentialsId() { - for (SCMTrait trait : traits) { - if (trait instanceof SSHCheckoutTrait) { - return StringUtils.defaultString( - ((SSHCheckoutTrait) trait).getCredentialsId(), - GitHubSCMSource.DescriptorImpl.ANONYMOUS - ); - } - } - return DescriptorImpl.SAME; + static ListBoxModel getPossibleApiUriItems() { + ListBoxModel result = new ListBoxModel(); + result.add("GitHub", ""); + for (Endpoint e : GitHubConfiguration.get().getEndpoints()) { + result.add( + e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")", + e.getApiUri()); + } + return result; } /** - * Legacy getter. + * Returns {@code true} if there is more than one GitHub endpoint configured, and consequently + * the UI should provide the ability to select the endpoint. * - * @return {@link RegexSCMSourceFilterTrait#getRegex()}. - * @deprecated use {@link RegexSCMSourceFilterTrait} + * @return {@code true} if there is more than one GitHub endpoint configured. */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public String getPattern() { - for (SCMTrait trait : traits) { - if (trait instanceof RegexSCMSourceFilterTrait) { - return ((RegexSCMSourceFilterTrait) trait).getRegex(); - } - } - return ".*"; + @SuppressWarnings("unused") // jelly + public boolean isApiUriSelectable() { + return !GitHubConfiguration.get().getEndpoints().isEmpty(); } /** - * Legacy setter. + * Returns the {@link SCMTraitDescriptor} instances grouped into categories. * - * @param pattern see {@link RegexSCMSourceFilterTrait#RegexSCMSourceFilterTrait(String)}. - * @deprecated use {@link RegexSCMSourceFilterTrait} + * @return the categorized list of {@link SCMTraitDescriptor} instances. + * @since 2.2.0 */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setPattern(String pattern) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof RegexSCMSourceFilterTrait) { - if (".*".equals(pattern)) { - traits.remove(i); - } else { - traits.set(i, new RegexSCMSourceFilterTrait(pattern)); - } - return; + @SuppressWarnings("unused") // jelly + public List>> getTraitsDescriptorLists() { + GitHubSCMSource.DescriptorImpl sourceDescriptor = + Jenkins.get().getDescriptorByType(GitHubSCMSource.DescriptorImpl.class); + List> all = new ArrayList<>(); + all.addAll( + SCMNavigatorTrait._for( + this, GitHubSCMNavigatorContext.class, GitHubSCMSourceBuilder.class)); + all.addAll(SCMSourceTrait._for(sourceDescriptor, GitHubSCMSourceContext.class, null)); + all.addAll(SCMSourceTrait._for(sourceDescriptor, null, GitHubSCMBuilder.class)); + Set> dedup = new HashSet<>(); + for (Iterator> iterator = all.iterator(); iterator.hasNext(); ) { + SCMTraitDescriptor d = iterator.next(); + if (dedup.contains(d) || d instanceof GitBrowserSCMSourceTrait.DescriptorImpl) { + // remove any we have seen already and ban the browser configuration as it will always be + // github + iterator.remove(); + } else { + dedup.add(d); + } + } + List>> result = new ArrayList<>(); + NamedArrayList.select( + all, + "Repositories", + new NamedArrayList.Predicate>() { + @Override + public boolean test(SCMTraitDescriptor scmTraitDescriptor) { + return scmTraitDescriptor instanceof SCMNavigatorTraitDescriptor; } - } - if (!".*".equals(pattern)) { - traits.add(new RegexSCMSourceFilterTrait(pattern)); - } + }, + true, + result); + NamedArrayList.select( + all, + Messages.GitHubSCMNavigator_withinRepository(), + NamedArrayList.anyOf( + NamedArrayList.withAnnotation(Discovery.class), + NamedArrayList.withAnnotation(Selection.class)), + true, + result); + NamedArrayList.select(all, Messages.GitHubSCMNavigator_general(), null, true, result); + return result; } - /** - * {@inheritDoc} - */ + @SuppressWarnings("unused") // jelly @NonNull - @Override - protected String id() { - return StringUtils.defaultIfBlank(apiUri, GitHubSCMSource.GITHUB_URL) + "::" + repoOwner; + public List>> getTraitsDefaults() { + return new ArrayList<>(delegate.getTraitsDefaults()); } - /** - * {@inheritDoc} - */ - @Override - public void visitSources(SCMSourceObserver observer) throws IOException, InterruptedException { - Set includes = observer.getIncludes(); - if (includes != null && includes.size() == 1) { - // optimize for the single source case - visitSource(includes.iterator().next(), observer); - return; - } - TaskListener listener = observer.getListener(); - - // Input data validation - if (repoOwner.isEmpty()) { - throw new AbortException("Must specify user or organization"); - } - - StandardCredentials credentials = Connector.lookupScanCredentials((Item)observer.getContext(), apiUri, - credentialsId); - - // Github client and validation - GitHub github = Connector.connect(apiUri, credentials); - try { - Connector.checkConnectionValidity(apiUri, listener, credentials, github); - Connector.configureLocalRateLimitChecker(listener, github); - - // Input data validation - if (credentials != null && !isCredentialValid(github)) { - String message = String.format("Invalid scan credentials %s to connect to %s, skipping", - CredentialsNameProvider.name(credentials), - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); - throw new AbortException(message); - } - - GitHubSCMNavigatorContext gitHubSCMNavigatorContext = new GitHubSCMNavigatorContext().withTraits(traits); - - try (GitHubSCMNavigatorRequest request = gitHubSCMNavigatorContext.newRequest(this, observer)) { - SourceFactory sourceFactory = new SourceFactory(request); - WitnessImpl witness = new WitnessImpl(listener); - - boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; - if (github.isAnonymous()) { - listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); - } else if (!githubAppAuthentication) { - GHMyself myself; - try { - // Requires an authenticated access - myself = github.getMyself(); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } - if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Looking up repositories of myself %s", repoOwner - ))); - for (GHRepository repo : myself.listRepositories(100)) { - if (!repo.getOwnerName().equals(repoOwner)) { - continue; // ignore repos in other orgs when using GHMyself - } - - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is public", repo.getName()))); - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount() - ))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount() - ))); - return; - } - } - - GHOrganization org = getGhOrganization(github); - if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Looking up repositories of organization %s", repoOwner))); - final Iterable repositories; - if (StringUtils.isNotBlank(gitHubSCMNavigatorContext.getTeamSlug())) { - // get repositories for selected team - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Looking up repositories for team %s", gitHubSCMNavigatorContext.getTeamSlug()))); - repositories = org.getTeamBySlug(gitHubSCMNavigatorContext.getTeamSlug()).listRepositories().withPageSize(100); - } else { - repositories = org.listRepositories(100); - } - for (GHRepository repo : repositories) { - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - // exclude archived repositories - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is public", repo.getName()))); - - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount()))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount()))); - return; - } - - GHUser user = null; - try { - user = github.getUser(repoOwner); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } catch (FileNotFoundException fnf) { - // the user may not exist... ok to ignore - } - if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { - listener.getLogger().format("Looking up repositories of user %s%n%n", repoOwner); - for (GHRepository repo : user.listRepositories(100)) { - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount() - ))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount() - ))); - return; - } - - throw new AbortException( - repoOwner + " does not correspond to a known GitHub User Account or Organization"); - } - } finally { - Connector.release(github); - } + static { + IconSet.icons.addIcon( + new Icon( + "icon-github-scm-navigator icon-sm", + "plugin/github-branch-source/images/16x16/github-scmnavigator.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-scm-navigator icon-md", + "plugin/github-branch-source/images/24x24/github-scmnavigator.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-scm-navigator icon-lg", + "plugin/github-branch-source/images/32x32/github-scmnavigator.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-scm-navigator icon-xlg", + "plugin/github-branch-source/images/48x48/github-scmnavigator.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon( + "icon-github-logo icon-sm", + "plugin/github-branch-source/images/16x16/github-logo.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-logo icon-md", + "plugin/github-branch-source/images/24x24/github-logo.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-logo icon-lg", + "plugin/github-branch-source/images/32x32/github-logo.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-logo icon-xlg", + "plugin/github-branch-source/images/48x48/github-logo.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon( + "icon-github-repo icon-sm", + "plugin/github-branch-source/images/16x16/github-repo.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-repo icon-md", + "plugin/github-branch-source/images/24x24/github-repo.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-repo icon-lg", + "plugin/github-branch-source/images/32x32/github-repo.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-repo icon-xlg", + "plugin/github-branch-source/images/48x48/github-repo.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon( + "icon-github-branch icon-sm", + "plugin/github-branch-source/images/16x16/github-branch.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-branch icon-md", + "plugin/github-branch-source/images/24x24/github-branch.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-branch icon-lg", + "plugin/github-branch-source/images/32x32/github-branch.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon( + "icon-github-branch icon-xlg", + "plugin/github-branch-source/images/48x48/github-branch.png", + Icon.ICON_XLARGE_STYLE)); } + } - private GHOrganization getGhOrganization(final GitHub github) throws IOException { - try { - return github.getOrganization(repoOwner); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } catch (FileNotFoundException fnf) { - // may be an user... ok to ignore - } - return null; - } + /** A {@link SCMNavigatorRequest.Witness} that counts how many sources have been observed. */ + private static class WitnessImpl implements SCMNavigatorRequest.Witness { + /** The count of repositories matches. */ + @GuardedBy("this") + private int count; + /** The listener to log to. */ + @NonNull private final TaskListener listener; /** - * {@inheritDoc} + * Constructor. + * + * @param listener the listener to log to. */ - @Override - public void visitSource(String sourceName, SCMSourceObserver observer) - throws IOException, InterruptedException { - TaskListener listener = observer.getListener(); - - // Input data validation - if (repoOwner.isEmpty()) { - throw new AbortException("Must specify user or organization"); - } - - StandardCredentials credentials = - Connector.lookupScanCredentials((Item)observer.getContext(), apiUri, credentialsId); - - // Github client and validation - GitHub github; - try { - github = Connector.connect(apiUri, credentials); - } catch (HttpException e) { - throw new AbortException(e.getMessage()); - } - - try { - // Input data validation - if (credentials != null && !isCredentialValid(github)) { - String message = String.format("Invalid scan credentials %s to connect to %s, skipping", - CredentialsNameProvider.name(credentials), - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); - throw new AbortException(message); - } - - GitHubSCMNavigatorContext gitHubSCMNavigatorContext = new GitHubSCMNavigatorContext().withTraits(traits); - - try (GitHubSCMNavigatorRequest request = gitHubSCMNavigatorContext.newRequest(this, observer)) { - SourceFactory sourceFactory = new SourceFactory(request); - WitnessImpl witness = new WitnessImpl(listener); - - boolean githubAppAuthentication = credentials instanceof GitHubAppCredentials; - if (github.isAnonymous()) { - listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", - apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri); - } else if (!githubAppAuthentication) { - listener.getLogger() - .format("Connecting to %s using %s%n", apiUri == null ? GitHubSCMSource.GITHUB_URL : apiUri, - CredentialsNameProvider.name(credentials)); - GHMyself myself; - try { - // Requires an authenticated access - myself = github.getMyself(); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } - if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { - listener.getLogger().format("Looking up %s repository of myself %s%n%n", sourceName, repoOwner); - GHRepository repo = myself.getRepository(sourceName); - if (repo != null && repo.getOwnerName().equals(repoOwner)) { - - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is public", repo.getName()))); - - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount() - ))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount() - ))); - return; - } - } - - GHOrganization org = getGhOrganization(github); - if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { - listener.getLogger() - .format("Looking up %s repository of organization %s%n%n", sourceName, repoOwner); - GHRepository repo = org.getRepository(sourceName); - if (repo != null) { - - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - } else if (StringUtils.isNotBlank(gitHubSCMNavigatorContext.getTeamSlug()) && !isRepositoryVisibleToTeam(org, repo, gitHubSCMNavigatorContext.getTeamSlug()) ) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is not in team %s", - repo.getName(), - gitHubSCMNavigatorContext.getTeamSlug()))); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is public", repo.getName()))); - - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount() - ))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount() - ))); - return; - } - - GHUser user = null; - try { - user = github.getUser(repoOwner); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } catch (FileNotFoundException fnf) { - // the user may not exist... ok to ignore - } - if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { - listener.getLogger().format("Looking up %s repository of user %s%n%n", sourceName, repoOwner); - GHRepository repo = user.getRepository(sourceName); - if (repo != null) { - - if (repo.isArchived() && gitHubSCMNavigatorContext.isExcludeArchivedRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is archived", repo.getName()))); - - } else if (!gitHubSCMNavigatorContext.getTopics().isEmpty() && !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) { - // exclude repositories which are missing one or more of the specified topics - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is missing one or more of the following topics: '%s'", repo.getName(), gitHubSCMNavigatorContext.getTopics()))); - } else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) { - witness.record(repo.getName(), false); - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "Skipping repository %s because it is public", repo.getName()))); - - } else if (request.process(repo.getName(), sourceFactory, null, witness)) { - listener.getLogger() - .println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed (query completed)", witness.getCount() - ))); - } - } - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), String.format( - "%d repositories were processed", witness.getCount() - ))); - return; - } - - throw new AbortException( - repoOwner + " does not correspond to a known GitHub User Account or Organization"); - } - } finally { - Connector.release(github); - } - } - - private boolean isRepositoryVisibleToTeam(GHOrganization org, GHRepository repo, String teamSlug) throws IOException { - final Iterable repositories = org.getTeamBySlug(teamSlug).listRepositories() - .withPageSize(100); - for (GHRepository item : repositories) { - if (repo.getFullName().equals(item.getFullName())) { - return true; - } - } - return false; + public WitnessImpl(@NonNull TaskListener listener) { + this.listener = listener; } - /** - * {@inheritDoc} - */ - @NonNull + /** {@inheritDoc} */ @Override - public List retrieveActions(@NonNull SCMNavigatorOwner owner, - @CheckForNull SCMNavigatorEvent event, - @NonNull TaskListener listener) throws IOException, InterruptedException { - // TODO when we have support for trusted events, use the details from event if event was from trusted source - listener.getLogger().printf("Looking up details of %s...%n", getRepoOwner()); - List result = new ArrayList<>(); - StandardCredentials credentials = Connector.lookupScanCredentials((Item)owner, getApiUri(), credentialsId); - GitHub hub = Connector.connect(getApiUri(), credentials); - try { - Connector.configureLocalRateLimitChecker(listener, hub); - GHUser u = hub.getUser(getRepoOwner()); - String objectUrl = u.getHtmlUrl() == null ? null : u.getHtmlUrl().toExternalForm(); - result.add(new ObjectMetadataAction( - Util.fixEmpty(u.getName()), - null, - objectUrl) - ); - result.add(new GitHubOrgMetadataAction(u)); - result.add(new GitHubLink("icon-github-logo", u.getHtmlUrl())); - if (objectUrl == null) { - listener.getLogger().println("Organization URL: unspecified"); - } else { - listener.getLogger().printf("Organization URL: %s%n", - HyperlinkNote.encodeTo(objectUrl, StringUtils.defaultIfBlank(u.getName(), objectUrl))); - } - return result; - } finally { - Connector.release(hub); - } + public void record(@NonNull String name, boolean isMatch) { + if (isMatch) { + listener.getLogger().format("Proposing %s%n", name); + synchronized (this) { + count++; + } + } else { + listener.getLogger().format("Ignoring %s%n", name); + } } /** - * {@inheritDoc} + * Returns the count of repositories matches. + * + * @return the count of repositories matches. */ - @Override - public void afterSave(@NonNull SCMNavigatorOwner owner) { - GitHubWebHook.get().registerHookFor(owner); - try { - // FIXME MINOR HACK ALERT - StandardCredentials credentials = Connector.lookupScanCredentials((Item)owner, getApiUri(), credentialsId); - GitHub hub = Connector.connect(getApiUri(), credentials); - try { - GitHubOrgWebHook.register(hub, repoOwner); - } finally { - Connector.release(hub); - } - } catch (IOException e) { - DescriptorImpl.LOGGER.log(Level.WARNING, e.getMessage(), e); - } + public synchronized int getCount() { + return count; } + } - @Symbol("github") - @Extension - public static class DescriptorImpl extends SCMNavigatorDescriptor implements IconSpec { - - private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultIncludes = "*"; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultExcludes = ""; - public static final String SAME = GitHubSCMSource.DescriptorImpl.SAME; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginBranch = true; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginBranchWithPR = true; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRMerge = false; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRHead = false; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRMerge = false; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRHead = false; - - @Inject private GitHubSCMSource.DescriptorImpl delegate; - - /** - * {@inheritDoc} - */ - @Override - public String getPronoun() { - return Messages.GitHubSCMNavigator_Pronoun(); - } - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.GitHubSCMNavigator_DisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public String getDescription() { - return Messages.GitHubSCMNavigator_Description(); - } - - /** - * {@inheritDoc} - */ - @Override - public String getIconFilePathPattern() { - return "plugin/github-branch-source/images/:size/github-scmnavigator.png"; - } - - /** - * {@inheritDoc} - */ - @Override - public String getIconClassName() { - return "icon-github-scm-navigator"; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @Override - public SCMNavigator newInstance(String name) { - GitHubSCMNavigator navigator = new GitHubSCMNavigator(name); - navigator.setTraits(getTraitsDefaults()); - navigator.setApiUri(GitHubServerConfig.GITHUB_URL); - return navigator; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - protected SCMSourceCategory[] createCategories() { - return new SCMSourceCategory[]{ - new UncategorizedSCMSourceCategory(Messages._GitHubSCMNavigator_UncategorizedCategory()) - // TODO add support for forks - }; - } - - /** - * Validates the selected credentials. - * - * @param context the context. - * @param apiUri the end-point. - * @param credentialsId the credentials. - * @return validation results. - * @since 2.2.0 - */ - @RequirePOST - @Restricted(NoExternalUse.class) // stapler - public FormValidation doCheckCredentialsId(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String credentialsId) { - return Connector.checkScanCredentials(context, apiUri, credentialsId); - } - - /** - * Populates the drop-down list of credentials. - * - * @param context the context. - * @param apiUri the end-point. - * @param credentialsId the existing selection; - * @return the drop-down list. - * @since 2.2.0 - */ - @Restricted(NoExternalUse.class) // stapler - public ListBoxModel doFillCredentialsIdItems(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String credentialsId) { - if (context == null - ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) - : !context.hasPermission(Item.EXTENDED_READ)) { - return new StandardListBoxModel().includeCurrentValue(credentialsId); - } - return Connector.listScanCredentials(context, apiUri); - } - - /** - * Returns the available GitHub endpoint items. - * - * @return the available GitHub endpoint items. - */ - @Restricted(NoExternalUse.class) // stapler - @SuppressWarnings("unused") // stapler - public ListBoxModel doFillApiUriItems() { - return getPossibleApiUriItems(); - } - - static ListBoxModel getPossibleApiUriItems() { - ListBoxModel result = new ListBoxModel(); - result.add("GitHub", ""); - for (Endpoint e : GitHubConfiguration.get().getEndpoints()) { - result.add(e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")", - e.getApiUri()); - } - return result; - } - - /** - * Returns {@code true} if there is more than one GitHub endpoint configured, and consequently the UI should - * provide the ability to select the endpoint. - * - * @return {@code true} if there is more than one GitHub endpoint configured. - */ - @SuppressWarnings("unused") // jelly - public boolean isApiUriSelectable() { - return !GitHubConfiguration.get().getEndpoints().isEmpty(); - } - - /** - * Returns the {@link SCMTraitDescriptor} instances grouped into categories. - * - * @return the categorized list of {@link SCMTraitDescriptor} instances. - * @since 2.2.0 - */ - @SuppressWarnings("unused") // jelly - public List>> getTraitsDescriptorLists() { - GitHubSCMSource.DescriptorImpl sourceDescriptor = - Jenkins.get().getDescriptorByType(GitHubSCMSource.DescriptorImpl.class); - List> all = new ArrayList<>(); - all.addAll(SCMNavigatorTrait._for(this, GitHubSCMNavigatorContext.class, GitHubSCMSourceBuilder.class)); - all.addAll(SCMSourceTrait._for(sourceDescriptor, GitHubSCMSourceContext.class, null)); - all.addAll(SCMSourceTrait._for(sourceDescriptor, null, GitHubSCMBuilder.class)); - Set> dedup = new HashSet<>(); - for (Iterator> iterator = all.iterator(); iterator.hasNext(); ) { - SCMTraitDescriptor d = iterator.next(); - if (dedup.contains(d) - || d instanceof GitBrowserSCMSourceTrait.DescriptorImpl) { - // remove any we have seen already and ban the browser configuration as it will always be github - iterator.remove(); - } else { - dedup.add(d); - } - } - List>> result = new ArrayList<>(); - NamedArrayList.select(all, "Repositories", new NamedArrayList.Predicate>() { - @Override - public boolean test(SCMTraitDescriptor scmTraitDescriptor) { - return scmTraitDescriptor instanceof SCMNavigatorTraitDescriptor; - } - }, - true, result); - NamedArrayList.select(all, Messages.GitHubSCMNavigator_withinRepository(), NamedArrayList.anyOf(NamedArrayList.withAnnotation(Discovery.class),NamedArrayList.withAnnotation(Selection.class)), - true, result); - NamedArrayList.select(all, Messages.GitHubSCMNavigator_general(), null, true, result); - return result; - } - - @SuppressWarnings("unused") // jelly - @NonNull - public List>> getTraitsDefaults() { - return new ArrayList<>(delegate.getTraitsDefaults()); - } - - static { - IconSet.icons.addIcon( - new Icon("icon-github-scm-navigator icon-sm", - "plugin/github-branch-source/images/16x16/github-scmnavigator.png", - Icon.ICON_SMALL_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-scm-navigator icon-md", - "plugin/github-branch-source/images/24x24/github-scmnavigator.png", - Icon.ICON_MEDIUM_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-scm-navigator icon-lg", - "plugin/github-branch-source/images/32x32/github-scmnavigator.png", - Icon.ICON_LARGE_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-scm-navigator icon-xlg", - "plugin/github-branch-source/images/48x48/github-scmnavigator.png", - Icon.ICON_XLARGE_STYLE)); - - IconSet.icons.addIcon( - new Icon("icon-github-logo icon-sm", - "plugin/github-branch-source/images/16x16/github-logo.png", - Icon.ICON_SMALL_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-logo icon-md", - "plugin/github-branch-source/images/24x24/github-logo.png", - Icon.ICON_MEDIUM_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-logo icon-lg", - "plugin/github-branch-source/images/32x32/github-logo.png", - Icon.ICON_LARGE_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-logo icon-xlg", - "plugin/github-branch-source/images/48x48/github-logo.png", - Icon.ICON_XLARGE_STYLE)); - - IconSet.icons.addIcon( - new Icon("icon-github-repo icon-sm", - "plugin/github-branch-source/images/16x16/github-repo.png", - Icon.ICON_SMALL_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-repo icon-md", - "plugin/github-branch-source/images/24x24/github-repo.png", - Icon.ICON_MEDIUM_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-repo icon-lg", - "plugin/github-branch-source/images/32x32/github-repo.png", - Icon.ICON_LARGE_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-repo icon-xlg", - "plugin/github-branch-source/images/48x48/github-repo.png", - Icon.ICON_XLARGE_STYLE)); - - IconSet.icons.addIcon( - new Icon("icon-github-branch icon-sm", - "plugin/github-branch-source/images/16x16/github-branch.png", - Icon.ICON_SMALL_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-branch icon-md", - "plugin/github-branch-source/images/24x24/github-branch.png", - Icon.ICON_MEDIUM_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-branch icon-lg", - "plugin/github-branch-source/images/32x32/github-branch.png", - Icon.ICON_LARGE_STYLE)); - IconSet.icons.addIcon( - new Icon("icon-github-branch icon-xlg", - "plugin/github-branch-source/images/48x48/github-branch.png", - Icon.ICON_XLARGE_STYLE)); - } - } + /** Our {@link SCMNavigatorRequest.SourceLambda}. */ + private class SourceFactory implements SCMNavigatorRequest.SourceLambda { + /** The request. */ + private final GitHubSCMNavigatorRequest request; /** - * A {@link SCMNavigatorRequest.Witness} that counts how many sources have been observed. + * Constructor. + * + * @param request the request to decorate {@link SCMSource} instances with. */ - private static class WitnessImpl implements SCMNavigatorRequest.Witness { - /** - * The count of repositories matches. - */ - @GuardedBy("this") - private int count; - /** - * The listener to log to. - */ - @NonNull - private final TaskListener listener; - - /** - * Constructor. - * - * @param listener the listener to log to. - */ - public WitnessImpl(@NonNull TaskListener listener) { - this.listener = listener; - } - - /** - * {@inheritDoc} - */ - @Override - public void record(@NonNull String name, boolean isMatch) { - if (isMatch) { - listener.getLogger().format("Proposing %s%n", name); - synchronized (this) { - count++; - } - } else { - listener.getLogger().format("Ignoring %s%n", name); - } - } - - /** - * Returns the count of repositories matches. - * - * @return the count of repositories matches. - */ - public synchronized int getCount() { - return count; - } + public SourceFactory(GitHubSCMNavigatorRequest request) { + this.request = request; } - /** - * Our {@link SCMNavigatorRequest.SourceLambda}. - */ - private class SourceFactory implements SCMNavigatorRequest.SourceLambda { - /** - * The request. - */ - private final GitHubSCMNavigatorRequest request; - - /** - * Constructor. - * - * @param request the request to decorate {@link SCMSource} instances with. - */ - public SourceFactory(GitHubSCMNavigatorRequest request) { - this.request = request; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public SCMSource create(@NonNull String name) { - return new GitHubSCMSourceBuilder(getId() + "::" + name, apiUri, credentialsId, repoOwner, name) - .withRequest(request) - .build(); - } + /** {@inheritDoc} */ + @NonNull + @Override + public SCMSource create(@NonNull String name) { + return new GitHubSCMSourceBuilder( + getId() + "::" + name, apiUri, credentialsId, repoOwner, name) + .withRequest(request) + .build(); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorContext.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorContext.java index cb2dfa6ee..8042ce681 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorContext.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorContext.java @@ -23,10 +23,9 @@ */ package org.jenkinsci.plugins.github_branch_source; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; - -import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.scm.api.SCMNavigator; import jenkins.scm.api.SCMSourceObserver; import jenkins.scm.api.trait.SCMNavigatorContext; @@ -36,92 +35,74 @@ * * @since 2.2.0 */ -public class GitHubSCMNavigatorContext extends SCMNavigatorContext { - - /** - * The team name of the repositories to navigate. - */ - private String teamSlug = ""; - - /** - * The topic which the repositories must have. - */ - private ArrayList topics = new ArrayList(); - - /** - * If true, archived repositories will be ignored. - */ - private boolean excludeArchivedRepositories; - - /** - * If true, public repositories will be ignored. - */ - private boolean excludePublicRepositories; - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public GitHubSCMNavigatorRequest newRequest(@NonNull SCMNavigator navigator, @NonNull SCMSourceObserver observer) { - return new GitHubSCMNavigatorRequest(navigator, this, observer); - } - - /** - * Sets the name of the team who's repositories will be navigated. - */ - void setTeamSlug(String teamSlug) { - this.teamSlug = teamSlug; - } - - /** - * Gets the name of the team who's repositories will be navigated. - * @return teamSlug - */ - public String getTeamSlug() { - return teamSlug; - } - - /** - * Sets the topics which the repositories must have. - */ - public void setTopics(ArrayList topics) { - this.topics = topics; - } - - /** - * Gets the topics which the repositories must have. - * @return topics - */ - public List getTopics() { - return topics; - } - - /** - * @return True if archived repositories should be ignored, false if they should be included. - */ - public boolean isExcludeArchivedRepositories() { - return excludeArchivedRepositories; - } - - /** - * @return True if public repositories should be ignored, false if they should be included. - */ - public boolean isExcludePublicRepositories() { - return excludePublicRepositories; - } - - /** - * @param excludeArchivedRepositories Set true to exclude archived repositories - */ - public void setExcludeArchivedRepositories(boolean excludeArchivedRepositories) { - this.excludeArchivedRepositories = excludeArchivedRepositories; - } - - /** - * @param excludePublicRepositories Set true to exclude public repositories - */ - public void setExcludePublicRepositories(boolean excludePublicRepositories) { - this.excludePublicRepositories = excludePublicRepositories; - } +public class GitHubSCMNavigatorContext + extends SCMNavigatorContext { + + /** The team name of the repositories to navigate. */ + private String teamSlug = ""; + + /** The topic which the repositories must have. */ + private ArrayList topics = new ArrayList(); + + /** If true, archived repositories will be ignored. */ + private boolean excludeArchivedRepositories; + + /** If true, public repositories will be ignored. */ + private boolean excludePublicRepositories; + + /** {@inheritDoc} */ + @NonNull + @Override + public GitHubSCMNavigatorRequest newRequest( + @NonNull SCMNavigator navigator, @NonNull SCMSourceObserver observer) { + return new GitHubSCMNavigatorRequest(navigator, this, observer); + } + + /** Sets the name of the team who's repositories will be navigated. */ + void setTeamSlug(String teamSlug) { + this.teamSlug = teamSlug; + } + + /** + * Gets the name of the team who's repositories will be navigated. + * + * @return teamSlug + */ + public String getTeamSlug() { + return teamSlug; + } + + /** Sets the topics which the repositories must have. */ + public void setTopics(ArrayList topics) { + this.topics = topics; + } + + /** + * Gets the topics which the repositories must have. + * + * @return topics + */ + public List getTopics() { + return topics; + } + + /** @return True if archived repositories should be ignored, false if they should be included. */ + public boolean isExcludeArchivedRepositories() { + return excludeArchivedRepositories; + } + + /** @return True if public repositories should be ignored, false if they should be included. */ + public boolean isExcludePublicRepositories() { + return excludePublicRepositories; + } + + /** @param excludeArchivedRepositories Set true to exclude archived repositories */ + public void setExcludeArchivedRepositories(boolean excludeArchivedRepositories) { + this.excludeArchivedRepositories = excludeArchivedRepositories; + } + + /** @param excludePublicRepositories Set true to exclude public repositories */ + public void setExcludePublicRepositories(boolean excludePublicRepositories) { + this.excludePublicRepositories = excludePublicRepositories; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorRequest.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorRequest.java index 54ec32a44..5d1f3a9bc 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorRequest.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorRequest.java @@ -34,16 +34,17 @@ * @since 2.2.0 */ public class GitHubSCMNavigatorRequest extends SCMNavigatorRequest { - /** - * Constructor. - * - * @param source the source. - * @param context the context. - * @param observer the observer. - */ - protected GitHubSCMNavigatorRequest(@NonNull SCMNavigator source, - @NonNull GitHubSCMNavigatorContext context, - @NonNull SCMSourceObserver observer) { - super(source, context, observer); - } + /** + * Constructor. + * + * @param source the source. + * @param context the context. + * @param observer the observer. + */ + protected GitHubSCMNavigatorRequest( + @NonNull SCMNavigator source, + @NonNull GitHubSCMNavigatorContext context, + @NonNull SCMSourceObserver observer) { + super(source, context, observer); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java index a47360385..3f0a1d11b 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbe.java @@ -28,6 +28,10 @@ import com.cloudbees.plugins.credentials.common.StandardCredentials; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.logging.Logger; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.scm.api.SCMFile; import jenkins.scm.api.SCMHead; @@ -41,157 +45,155 @@ import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; -import java.util.logging.Logger; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") class GitHubSCMProbe extends SCMProbe implements GitHubClosable { - private static final long serialVersionUID = 1L; - private static final Logger LOG = Logger.getLogger(GitHubSCMProbe.class.getName()); - private final SCMRevision revision; - private final transient GitHub gitHub; - private final transient GHRepository repo; - private final String ref; - private final String name; - private transient boolean open = true; + private static final long serialVersionUID = 1L; + private static final Logger LOG = Logger.getLogger(GitHubSCMProbe.class.getName()); + private final SCMRevision revision; + private final transient GitHub gitHub; + private final transient GHRepository repo; + private final String ref; + private final String name; + private transient boolean open = true; - public GitHubSCMProbe(String apiUri, - StandardCredentials credentials, - GHRepository repo, - SCMHead head, - SCMRevision revision) throws IOException { - this.gitHub = Connector.connect(apiUri, credentials); - this.revision = revision; - this.repo = repo; - this.name = head.getName(); - if (head instanceof PullRequestSCMHead) { - PullRequestSCMHead pr = (PullRequestSCMHead) head; - this.ref = "pull/" + pr.getNumber() + (pr.isMerge() ? "/merge" : "/head"); - } else if (head instanceof GitHubTagSCMHead){ - this.ref = "tags/" + head.getName(); - } else { - this.ref = "heads/" + head.getName(); - } + public GitHubSCMProbe( + String apiUri, + StandardCredentials credentials, + GHRepository repo, + SCMHead head, + SCMRevision revision) + throws IOException { + this.gitHub = Connector.connect(apiUri, credentials); + this.revision = revision; + this.repo = repo; + this.name = head.getName(); + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead pr = (PullRequestSCMHead) head; + this.ref = "pull/" + pr.getNumber() + (pr.isMerge() ? "/merge" : "/head"); + } else if (head instanceof GitHubTagSCMHead) { + this.ref = "tags/" + head.getName(); + } else { + this.ref = "heads/" + head.getName(); } + } - @Override - public void close() throws IOException { - if (gitHub == null || repo == null) { - return; - } - synchronized (this) { - if (!open) { - return; - } - open = false; - } - Connector.release(gitHub); + @Override + public void close() throws IOException { + if (gitHub == null || repo == null) { + return; } - - private synchronized void checkOpen() throws IOException { - if (!open) { - throw new IOException("Closed"); - } - if (repo == null) { - throw new IOException("No connection available"); - } + synchronized (this) { + if (!open) { + return; + } + open = false; } + Connector.release(gitHub); + } - @Override - public String name() { - return name; + private synchronized void checkOpen() throws IOException { + if (!open) { + throw new IOException("Closed"); } - - @Override - public long lastModified() { - if (repo == null) { - return 0L; - } - synchronized (this) { - if (!open) { - return 0L; - } - } - if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { - try { - GHCommit commit = repo.getCommit(((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash()); - return commit.getCommitDate().getTime(); - } catch (IOException e) { - // ignore - } - } else if (revision == null) { - try { - GHRef ref = repo.getRef(this.ref); - GHCommit commit = repo.getCommit(ref.getObject().getSha()); - return commit.getCommitDate().getTime(); - } catch (IOException e) { - // ignore - } - } - return 0; + if (repo == null) { + throw new IOException("No connection available"); } + } - @NonNull - @Override - public SCMProbeStat stat(@NonNull String path) throws IOException { - checkOpen(); - try { - int index = path.lastIndexOf('/') + 1; - List directoryContent = repo.getDirectoryContent(path.substring(0, index), Constants.R_REFS + ref); - for (GHContent content : directoryContent) { - if (content.getPath().equals(path)) { - if (content.isFile()) { - return SCMProbeStat.fromType(SCMFile.Type.REGULAR_FILE); - } else if (content.isDirectory()) { - return SCMProbeStat.fromType(SCMFile.Type.DIRECTORY); - } else if ("symlink".equals(content.getType())) { - return SCMProbeStat.fromType(SCMFile.Type.LINK); - } else { - return SCMProbeStat.fromType(SCMFile.Type.OTHER); - } - } - } - for (GHContent content : directoryContent) { - if (content.getPath().equalsIgnoreCase(path)) { - return SCMProbeStat.fromAlternativePath(content.getPath()); - } - } - } catch (FileNotFoundException fnf) { - // means that does not exist and this is handled below this try/catch block. - } - return SCMProbeStat.fromType(SCMFile.Type.NONEXISTENT); + @Override + public String name() { + return name; + } + + @Override + public long lastModified() { + if (repo == null) { + return 0L; + } + synchronized (this) { + if (!open) { + return 0L; + } } + if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { + try { + GHCommit commit = + repo.getCommit(((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash()); + return commit.getCommitDate().getTime(); + } catch (IOException e) { + // ignore + } + } else if (revision == null) { + try { + GHRef ref = repo.getRef(this.ref); + GHCommit commit = repo.getCommit(ref.getObject().getSha()); + return commit.getCommitDate().getTime(); + } catch (IOException e) { + // ignore + } + } + return 0; + } - @Override - public SCMFile getRoot() { - if (repo == null) { - return null; - } - synchronized (this) { - if (!open) { - return null; - } + @NonNull + @Override + public SCMProbeStat stat(@NonNull String path) throws IOException { + checkOpen(); + try { + int index = path.lastIndexOf('/') + 1; + List directoryContent = + repo.getDirectoryContent(path.substring(0, index), Constants.R_REFS + ref); + for (GHContent content : directoryContent) { + if (content.getPath().equals(path)) { + if (content.isFile()) { + return SCMProbeStat.fromType(SCMFile.Type.REGULAR_FILE); + } else if (content.isDirectory()) { + return SCMProbeStat.fromType(SCMFile.Type.DIRECTORY); + } else if ("symlink".equals(content.getType())) { + return SCMProbeStat.fromType(SCMFile.Type.LINK); + } else { + return SCMProbeStat.fromType(SCMFile.Type.OTHER); + } } - String ref; - if (revision != null) { - if (revision.getHead() instanceof PullRequestSCMHead) { - ref = this.ref; - } else if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl){ - ref = ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(); - } else { - ref = this.ref; - } - } else { - ref = this.ref; + } + for (GHContent content : directoryContent) { + if (content.getPath().equalsIgnoreCase(path)) { + return SCMProbeStat.fromAlternativePath(content.getPath()); } - return new GitHubSCMFile(this, repo, ref); + } + } catch (FileNotFoundException fnf) { + // means that does not exist and this is handled below this try/catch block. } + return SCMProbeStat.fromType(SCMFile.Type.NONEXISTENT); + } - @Override - public synchronized boolean isOpen() { - return open; + @Override + public SCMFile getRoot() { + if (repo == null) { + return null; } + synchronized (this) { + if (!open) { + return null; + } + } + String ref; + if (revision != null) { + if (revision.getHead() instanceof PullRequestSCMHead) { + ref = this.ref; + } else if (revision instanceof AbstractGitSCMSource.SCMRevisionImpl) { + ref = ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(); + } else { + ref = this.ref; + } + } else { + ref = this.ref; + } + return new GitHubSCMFile(this, repo, ref); + } + + @Override + public synchronized boolean isOpen() { + return open; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index e0fbc746f..7cf00e4b3 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -24,6 +24,13 @@ package org.jenkinsci.plugins.github_branch_source; +import static hudson.Functions.isWindows; +import static hudson.model.Items.XSTREAM2; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang.StringUtils.removeEnd; +import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid; +import static org.jenkinsci.plugins.github_branch_source.GitHubSCMBuilder.API_V3; + import com.cloudbees.jenkins.GitHubWebHook; import com.cloudbees.plugins.credentials.CredentialsNameProvider; import com.cloudbees.plugins.credentials.CredentialsProvider; @@ -69,8 +76,8 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.TreeSet; import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -137,2668 +144,2800 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; - -import static hudson.Functions.isWindows; -import static hudson.model.Items.XSTREAM2; -import static org.apache.commons.lang.StringUtils.isBlank; -import static org.apache.commons.lang.StringUtils.removeEnd; -import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid; -import static org.jenkinsci.plugins.github_branch_source.GitHubSCMBuilder.API_V3; - import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.interceptor.RequirePOST; public class GitHubSCMSource extends AbstractGitSCMSource { - public static final String VALID_GITHUB_REPO_NAME = "^[0-9A-Za-z._-]+$"; - public static final String VALID_GITHUB_USER_NAME = "^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$"; - public static final String VALID_GIT_SHA1 = "^[a-fA-F0-9]{40}$"; - public static final String GITHUB_URL = GitHubServerConfig.GITHUB_URL; - public static final String GITHUB_COM = "github.com"; - private static final Logger LOGGER = Logger.getLogger(GitHubSCMSource.class.getName()); - private static final String R_PULL = Constants.R_REFS + "pull/"; - /** - * How long to delay events received from GitHub in order to allow the API caches to sync. - */ - private static /*mostly final*/ int eventDelaySeconds = - Math.min(300, Math.max(0, Integer.getInteger(GitHubSCMSource.class.getName() + ".eventDelaySeconds", 5))); - /** - * How big (in megabytes) an on-disk cache to keep of GitHub API responses. Cache is per repo, per credentials. - */ - private static /*mostly final*/ int cacheSize = - Math.min(1024, Math.max(0, Integer.getInteger(GitHubSCMSource.class.getName() + ".cacheSize", isWindows() ? 0 : 20))); - /** - * Lock to guard access to the {@link #pullRequestSourceMap} field and prevent concurrent GitHub queries during - * a 1.x to 2.2.0+ upgrade. - * - * @since 2.2.0 - */ - private static final Object pullRequestSourceMapLock = new Object(); - - ////////////////////////////////////////////////////////////////////// - // Configuration fields - ////////////////////////////////////////////////////////////////////// - - /** - * The GitHub end-point. Defaults to {@link #GITHUB_URL}. - */ - @NonNull - private String apiUri; - - /** - * Credentials for GitHub API; currently only supports username/password (personal access token). - * @since 2.2.0 - */ - @CheckForNull - private String credentialsId; - - /** - * The repository owner. - */ - @NonNull - private final String repoOwner; - - /** - * The repository - */ - @NonNull - private final String repository; - - /** - * HTTPS URL for the repository, if specified by the user. - */ - @CheckForNull - private final String repositoryUrl; - - /** - * The behaviours to apply to this source. - * @since 2.2.0 - */ - @NonNull - private List traits; + public static final String VALID_GITHUB_REPO_NAME = "^[0-9A-Za-z._-]+$"; + public static final String VALID_GITHUB_USER_NAME = + "^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$"; + public static final String VALID_GIT_SHA1 = "^[a-fA-F0-9]{40}$"; + public static final String GITHUB_URL = GitHubServerConfig.GITHUB_URL; + public static final String GITHUB_COM = "github.com"; + private static final Logger LOGGER = Logger.getLogger(GitHubSCMSource.class.getName()); + private static final String R_PULL = Constants.R_REFS + "pull/"; + /** How long to delay events received from GitHub in order to allow the API caches to sync. */ + private static /*mostly final*/ int eventDelaySeconds = + Math.min( + 300, + Math.max( + 0, Integer.getInteger(GitHubSCMSource.class.getName() + ".eventDelaySeconds", 5))); + /** + * How big (in megabytes) an on-disk cache to keep of GitHub API responses. Cache is per repo, per + * credentials. + */ + private static /*mostly final*/ int cacheSize = + Math.min( + 1024, + Math.max( + 0, + Integer.getInteger( + GitHubSCMSource.class.getName() + ".cacheSize", isWindows() ? 0 : 20))); + /** + * Lock to guard access to the {@link #pullRequestSourceMap} field and prevent concurrent GitHub + * queries during a 1.x to 2.2.0+ upgrade. + * + * @since 2.2.0 + */ + private static final Object pullRequestSourceMapLock = new Object(); + + ////////////////////////////////////////////////////////////////////// + // Configuration fields + ////////////////////////////////////////////////////////////////////// + + /** The GitHub end-point. Defaults to {@link #GITHUB_URL}. */ + @NonNull private String apiUri; + + /** + * Credentials for GitHub API; currently only supports username/password (personal access token). + * + * @since 2.2.0 + */ + @CheckForNull private String credentialsId; + + /** The repository owner. */ + @NonNull private final String repoOwner; + + /** The repository */ + @NonNull private final String repository; + + /** HTTPS URL for the repository, if specified by the user. */ + @CheckForNull private final String repositoryUrl; + + /** + * The behaviours to apply to this source. + * + * @since 2.2.0 + */ + @NonNull private List traits; + + ////////////////////////////////////////////////////////////////////// + // Legacy Configuration fields + ////////////////////////////////////////////////////////////////////// + + /** Legacy field. */ + @Deprecated private transient String scanCredentialsId; + /** Legacy field. */ + @Deprecated private transient String checkoutCredentialsId; + /** Legacy field. */ + @Deprecated private String includes; + /** Legacy field. */ + @Deprecated private String excludes; + /** Legacy field. */ + @Deprecated private transient Boolean buildOriginBranch; + /** Legacy field. */ + @Deprecated private transient Boolean buildOriginBranchWithPR; + /** Legacy field. */ + @Deprecated private transient Boolean buildOriginPRMerge; + /** Legacy field. */ + @Deprecated private transient Boolean buildOriginPRHead; + /** Legacy field. */ + @Deprecated private transient Boolean buildForkPRMerge; + /** Legacy field. */ + @Deprecated private transient Boolean buildForkPRHead; + + ////////////////////////////////////////////////////////////////////// + // Run-time cached state + ////////////////////////////////////////////////////////////////////// + + /** + * Cache of the official repository HTML URL as reported by {@link GitHub#getRepository(String)}. + */ + @CheckForNull private transient URL resolvedRepositoryUrl; + /** The collaborator names used to determine if pull requests are from trusted authors */ + @CheckForNull private transient Set collaboratorNames; + /** Cache of details of the repository. */ + @CheckForNull private transient GHRepository ghRepository; + + /** The cache of {@link ObjectMetadataAction} instances for each open PR. */ + @NonNull + private transient /*effectively final*/ Map + pullRequestMetadataCache; + /** The cache of {@link ObjectMetadataAction} instances for each open PR. */ + @NonNull + private transient /*effectively final*/ Map + pullRequestContributorCache; + + /** + * Used during upgrade from 1.x to 2.2.0+ only. + * + * @see #retrievePullRequestSource(int) + * @see PullRequestSCMHead.FixMetadata + * @see PullRequestSCMHead.FixMetadataMigration + * @since 2.2.0 + */ + @CheckForNull // normally null except during a migration from 1.x + private transient /*effectively final*/ Map pullRequestSourceMap; + + /** + * Constructor, defaults to {@link #GITHUB_URL} as the end-point, and anonymous access, does not + * default any {@link SCMSourceTrait} behaviours. + * + * @param repoOwner the repository owner. + * @param repository the repository name. + * @param repositoryUrl HTML URL for the repository. If specified, takes precedence over repoOwner + * and repository. + * @param configuredByUrl Whether to use repositoryUrl or repoOwner/repository for configuration. + * @throws IllegalArgumentException if repositoryUrl is specified but invalid. + * @since 2.2.0 + */ + // configuredByUrl is used to decide which radioBlock in the UI the user had selected when they + // submitted the form. + @DataBoundConstructor + public GitHubSCMSource( + String repoOwner, String repository, String repositoryUrl, boolean configuredByUrl) { + if (!configuredByUrl) { + this.apiUri = GITHUB_URL; + this.repoOwner = repoOwner; + this.repository = repository; + this.repositoryUrl = null; + } else { + GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repositoryUrl); + this.apiUri = info.getApiUri(); + this.repoOwner = info.getRepoOwner(); + this.repository = info.getRepository(); + this.repositoryUrl = info.getRepositoryUrl(); + } + pullRequestMetadataCache = new ConcurrentHashMap<>(); + pullRequestContributorCache = new ConcurrentHashMap<>(); + this.traits = new ArrayList<>(); + } + + /** + * Legacy constructor. + * + * @param repoOwner the repository owner. + * @param repository the repository name. + * @since 2.2.0 + */ + @Deprecated + public GitHubSCMSource(String repoOwner, String repository) { + this(repoOwner, repository, null, false); + } + + /** + * Legacy constructor. + * + * @param id the source id. + * @param apiUri the GitHub endpoint. + * @param checkoutCredentialsId the checkout credentials id or {@link DescriptorImpl#SAME} or + * {@link DescriptorImpl#ANONYMOUS}. + * @param scanCredentialsId the scan credentials id or {@code null}. + * @param repoOwner the repository owner. + * @param repository the repository name. + */ + @Deprecated + public GitHubSCMSource( + @CheckForNull String id, + @CheckForNull String apiUri, + @NonNull String checkoutCredentialsId, + @CheckForNull String scanCredentialsId, + @NonNull String repoOwner, + @NonNull String repository) { + this(repoOwner, repository, null, false); + setId(id); + setApiUri(apiUri); + setCredentialsId(scanCredentialsId); + // legacy constructor means legacy defaults + this.traits = new ArrayList<>(); + this.traits.add(new BranchDiscoveryTrait(true, true)); + this.traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + if (!DescriptorImpl.SAME.equals(checkoutCredentialsId)) { + traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); + } + } + + @Restricted(NoExternalUse.class) + public boolean isConfiguredByUrl() { + return repositoryUrl != null; + } + + /** + * Returns the GitHub API end-point. + * + * @return the GitHub API end-point. + */ + @NonNull + public String getApiUri() { + return apiUri; + } + + /** + * Sets the GitHub API end-point. + * + * @param apiUri the GitHub API end-point or {@code null} if {@link #GITHUB_URL}. + * @since 2.2.0 + */ + @DataBoundSetter + public void setApiUri(@CheckForNull String apiUri) { + // JENKINS-58862 + // If repositoryUrl is set, we don't want to set it again. + if (this.repositoryUrl != null) { + return; + } + apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); + if (apiUri == null) { + apiUri = GITHUB_URL; + } + this.apiUri = apiUri; + } + + /** + * Forces the apiUri to a specific value. FOR TESTING ONLY. + * + * @param apiUri the api uri + */ + void forceApiUri(@Nonnull String apiUri) { + this.apiUri = apiUri; + } + + /** + * Gets the credentials used to access the GitHub REST API (also used as the default credentials + * for checking out sources. + * + * @return the credentials used to access the GitHub REST API or {@code null} to access + * anonymously + */ + @Override + @CheckForNull + public String getCredentialsId() { + return credentialsId; + } + + /** + * Sets the credentials used to access the GitHub REST API (also used as the default credentials + * for checking out sources. + * + * @param credentialsId the credentials used to access the GitHub REST API or {@code null} to + * access anonymously + * @since 2.2.0 + */ + @DataBoundSetter + public void setCredentialsId(@CheckForNull String credentialsId) { + this.credentialsId = Util.fixEmpty(credentialsId); + } + + /** + * Gets the repository owner. + * + * @return the repository owner. + */ + @Exported + @NonNull + public String getRepoOwner() { + return repoOwner; + } + + /** + * Gets the repository name. + * + * @return the repository name. + */ + @Exported + @NonNull + public String getRepository() { + return repository; + } + + /** + * Gets the repository URL as specified by the user. + * + * @return the repository URL as specified by the user. + */ + @Restricted(NoExternalUse.class) + @NonNull // Always returns a value so that users can always use the URL-based configuration when + // reconfiguring. + public String getRepositoryUrl() { + if (repositoryUrl != null) { + return repositoryUrl; + } else { + if (GITHUB_URL.equals(apiUri)) return "https://github.com/" + repoOwner + '/' + repository; + else return String.format("%s%s/%s", removeEnd(apiUri, API_V3), repoOwner, repository); + } + } + + /** + * {@inheritDoc} + * + * @since 2.2.0 + */ + @Override + public List getTraits() { + return traits; + } + + /** + * Sets the behaviours that are applied to this {@link GitHubSCMSource}. + * + * @param traits the behaviours that are to be applied. + */ + @DataBoundSetter + public void setTraits(@CheckForNull List traits) { + this.traits = new ArrayList<>(Util.fixNull(traits)); + } + + /** Use defaults for old settings. */ + @SuppressWarnings("ConstantConditions") + @SuppressFBWarnings( + value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", + justification = "Only non-null after we set them here!") + private Object readResolve() { + if (scanCredentialsId != null) { + credentialsId = scanCredentialsId; + } + if (pullRequestMetadataCache == null) { + pullRequestMetadataCache = new ConcurrentHashMap<>(); + } + if (pullRequestContributorCache == null) { + pullRequestContributorCache = new ConcurrentHashMap<>(); + } + if (traits == null) { + boolean buildOriginBranch = this.buildOriginBranch == null || this.buildOriginBranch; + boolean buildOriginBranchWithPR = + this.buildOriginBranchWithPR == null || this.buildOriginBranchWithPR; + boolean buildOriginPRMerge = this.buildOriginPRMerge != null && this.buildOriginPRMerge; + boolean buildOriginPRHead = this.buildOriginPRHead != null && this.buildOriginPRHead; + boolean buildForkPRMerge = this.buildForkPRMerge == null || this.buildForkPRMerge; + boolean buildForkPRHead = this.buildForkPRHead != null && this.buildForkPRHead; + List traits = new ArrayList<>(); + if (buildOriginBranch || buildOriginBranchWithPR) { + traits.add(new BranchDiscoveryTrait(buildOriginBranch, buildOriginBranchWithPR)); + } + if (buildOriginPRMerge || buildOriginPRHead) { + EnumSet s = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + if (buildOriginPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } + if (buildOriginPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } + traits.add(new OriginPullRequestDiscoveryTrait(s)); + } + if (buildForkPRMerge || buildForkPRHead) { + EnumSet s = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + if (buildForkPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } + if (buildForkPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } + traits.add( + new ForkPullRequestDiscoveryTrait( + s, new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + if (!"*".equals(includes) || !"".equals(excludes)) { + traits.add(new WildcardSCMHeadFilterTrait(includes, excludes)); + } + if (checkoutCredentialsId != null + && !DescriptorImpl.SAME.equals(checkoutCredentialsId) + && !checkoutCredentialsId.equals(scanCredentialsId)) { + traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); + } + this.traits = traits; + } + if (isBlank(apiUri)) { + setApiUri(GITHUB_URL); + } else if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { + setApiUri(apiUri); + } + return this; + } + + /** + * Returns how long to delay events received from GitHub in order to allow the API caches to sync. + * + * @return how long to delay events received from GitHub in order to allow the API caches to sync. + */ + public static int getEventDelaySeconds() { + return eventDelaySeconds; + } + + /** + * Sets how long to delay events received from GitHub in order to allow the API caches to sync. + * + * @param eventDelaySeconds number of seconds to delay, will be restricted into a value within the + * range {@code [0,300]} inclusive + */ + @Restricted(NoExternalUse.class) // to allow configuration from system groovy console + public static void setEventDelaySeconds(int eventDelaySeconds) { + GitHubSCMSource.eventDelaySeconds = Math.min(300, Math.max(0, eventDelaySeconds)); + } + + /** + * Returns how many megabytes of on-disk cache to maintain per GitHub API URL per credentials. + * + * @return how many megabytes of on-disk cache to maintain per GitHub API URL per credentials. + */ + public static int getCacheSize() { + return cacheSize; + } + + /** + * Sets how long to delay events received from GitHub in order to allow the API caches to sync. + * + * @param cacheSize how many megabytes of on-disk cache to maintain per GitHub API URL per + * credentials, will be restricted into a value within the range {@code [0,1024]} inclusive. + */ + @Restricted(NoExternalUse.class) // to allow configuration from system groovy console + public static void setCacheSize(int cacheSize) { + GitHubSCMSource.cacheSize = Math.min(1024, Math.max(0, cacheSize)); + } + + /** {@inheritDoc} */ + @Override + public String getRemote() { + return GitHubSCMBuilder.uriResolver(getOwner(), apiUri, credentialsId) + .getRepositoryUri(apiUri, repoOwner, repository); + } + + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.GitHubSCMSource_Pronoun(); + } + + /** + * Returns a {@link RepositoryUriResolver} according to credentials configuration. + * + * @return a {@link RepositoryUriResolver} + * @deprecated use {@link GitHubSCMBuilder#uriResolver()} or {@link + * GitHubSCMBuilder#uriResolver(Item, String, String)}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public RepositoryUriResolver getUriResolver() { + return GitHubSCMBuilder.uriResolver(getOwner(), apiUri, credentialsId); + } + + @Restricted(NoExternalUse.class) + @RestrictedSince("2.2.0") + @Deprecated + @CheckForNull + public String getScanCredentialsId() { + return credentialsId; + } + + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @Deprecated + public void setScanCredentialsId(@CheckForNull String credentialsId) { + this.credentialsId = credentialsId; + } + + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @Deprecated + @CheckForNull + public String getCheckoutCredentialsId() { + for (SCMSourceTrait trait : traits) { + if (trait instanceof SSHCheckoutTrait) { + return StringUtils.defaultString( + ((SSHCheckoutTrait) trait).getCredentialsId(), + GitHubSCMSource.DescriptorImpl.ANONYMOUS); + } + } + return DescriptorImpl.SAME; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setIncludes(@NonNull String includes) { + for (int i = 0; i < traits.size(); i++) { + SCMSourceTrait trait = traits.get(i); + if (trait instanceof WildcardSCMHeadFilterTrait) { + WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; + if ("*".equals(includes) && "".equals(existing.getExcludes())) { + traits.remove(i); + } else { + traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes())); + } + return; + } + } + if (!"*".equals(includes)) { + traits.add(new WildcardSCMHeadFilterTrait(includes, "")); + } + } + + @Deprecated + @Restricted(NoExternalUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setExcludes(@NonNull String excludes) { + for (int i = 0; i < traits.size(); i++) { + SCMSourceTrait trait = traits.get(i); + if (trait instanceof WildcardSCMHeadFilterTrait) { + WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; + if ("*".equals(existing.getIncludes()) && "".equals(excludes)) { + traits.remove(i); + } else { + traits.set(i, new WildcardSCMHeadFilterTrait(existing.getIncludes(), excludes)); + } + return; + } + } + if (!"".equals(excludes)) { + traits.add(new WildcardSCMHeadFilterTrait("*", excludes)); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginBranch() { + for (SCMTrait trait : traits) { + if (trait instanceof BranchDiscoveryTrait) { + return ((BranchDiscoveryTrait) trait).isBuildBranch(); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginBranch(boolean buildOriginBranch) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof BranchDiscoveryTrait) { + BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; + if (buildOriginBranch || previous.isBuildBranchesWithPR()) { + traits.set( + i, new BranchDiscoveryTrait(buildOriginBranch, previous.isBuildBranchesWithPR())); + } else { + traits.remove(i); + } + return; + } + } + if (buildOriginBranch) { + traits.add(new BranchDiscoveryTrait(buildOriginBranch, false)); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginBranchWithPR() { + for (SCMTrait trait : traits) { + if (trait instanceof BranchDiscoveryTrait) { + return ((BranchDiscoveryTrait) trait).isBuildBranchesWithPR(); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginBranchWithPR(boolean buildOriginBranchWithPR) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof BranchDiscoveryTrait) { + BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; + if (buildOriginBranchWithPR || previous.isBuildBranch()) { + traits.set( + i, new BranchDiscoveryTrait(previous.isBuildBranch(), buildOriginBranchWithPR)); + } else { + traits.remove(i); + } + return; + } + } + if (buildOriginBranchWithPR) { + traits.add(new BranchDiscoveryTrait(false, buildOriginBranchWithPR)); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginPRMerge() { + for (SCMTrait trait : traits) { + if (trait instanceof OriginPullRequestDiscoveryTrait) { + return ((OriginPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.MERGE); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginPRMerge(boolean buildOriginPRMerge) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof OriginPullRequestDiscoveryTrait) { + Set s = + ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); + if (buildOriginPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } else { + s.remove(ChangeRequestCheckoutStrategy.MERGE); + } + traits.set(i, new OriginPullRequestDiscoveryTrait(s)); + return; + } + } + if (buildOriginPRMerge) { + traits.add( + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildOriginPRHead() { + for (SCMTrait trait : traits) { + if (trait instanceof OriginPullRequestDiscoveryTrait) { + return ((OriginPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.HEAD); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildOriginPRHead(boolean buildOriginPRHead) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof OriginPullRequestDiscoveryTrait) { + Set s = + ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); + if (buildOriginPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } else { + s.remove(ChangeRequestCheckoutStrategy.HEAD); + } + traits.set(i, new OriginPullRequestDiscoveryTrait(s)); + return; + } + } + if (buildOriginPRHead) { + traits.add( + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildForkPRMerge() { + for (SCMTrait trait : traits) { + if (trait instanceof ForkPullRequestDiscoveryTrait) { + return ((ForkPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.MERGE); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildForkPRMerge(boolean buildForkPRMerge) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof ForkPullRequestDiscoveryTrait) { + ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; + Set s = forkTrait.getStrategies(); + if (buildForkPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); + } else { + s.remove(ChangeRequestCheckoutStrategy.MERGE); + } + traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); + return; + } + } + if (buildForkPRMerge) { + traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public boolean getBuildForkPRHead() { + for (SCMTrait trait : traits) { + if (trait instanceof ForkPullRequestDiscoveryTrait) { + return ((ForkPullRequestDiscoveryTrait) trait) + .getStrategies() + .contains(ChangeRequestCheckoutStrategy.HEAD); + } + } + return false; + } + + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @DataBoundSetter + public void setBuildForkPRHead(boolean buildForkPRHead) { + for (int i = 0; i < traits.size(); i++) { + SCMTrait trait = traits.get(i); + if (trait instanceof ForkPullRequestDiscoveryTrait) { + ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; + Set s = forkTrait.getStrategies(); + if (buildForkPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); + } else { + s.remove(ChangeRequestCheckoutStrategy.HEAD); + } + traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); + return; + } + } + if (buildForkPRHead) { + traits.add( + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), + new ForkPullRequestDiscoveryTrait.TrustPermission())); + } + } + + @Override + protected final void retrieve( + @CheckForNull SCMSourceCriteria criteria, + @NonNull SCMHeadObserver observer, + @CheckForNull SCMHeadEvent event, + @NonNull final TaskListener listener) + throws IOException, InterruptedException { + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + // Github client and validation + final GitHub github = Connector.connect(apiUri, credentials); + try { + Connector.configureLocalRateLimitChecker(listener, github); + + try { + // Input data validation + Connector.checkConnectionValidity(apiUri, listener, credentials, github); + + // Input data validation + if (isBlank(repository)) { + throw new AbortException("No repository selected, skipping"); + } + + String fullName = repoOwner + "/" + repository; + ghRepository = github.getRepository(fullName); + final GHRepository ghRepository = this.ghRepository; + listener + .getLogger() + .format( + "Examining %s%n", + HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); + resolvedRepositoryUrl = ghRepository.getHtmlUrl(); + try (final GitHubSCMSourceRequest request = + new GitHubSCMSourceContext(criteria, observer) + .withTraits(traits) + .newRequest(this, listener)) { + // populate the request with its data sources + request.setGitHub(github); + request.setRepository(ghRepository); + if (request.isFetchPRs()) { + request.setPullRequests(new LazyPullRequests(request, ghRepository)); + } + if (request.isFetchBranches()) { + request.setBranches(new LazyBranches(request, ghRepository)); + } + if (request.isFetchTags()) { + request.setTags(new LazyTags(request, ghRepository)); + } + request.setCollaboratorNames( + new LazyContributorNames(request, listener, github, ghRepository, credentials)); + request.setPermissionsSource( + new GitHubPermissionsSource() { + @Override + public GHPermissionType fetch(String username) + throws IOException, InterruptedException { + return ghRepository.getPermission(username); + } + }); + + if (request.isFetchBranches() && !request.isComplete()) { + listener.getLogger().format("%n Checking branches...%n"); + int count = 0; + for (final GHBranch branch : request.getBranches()) { + count++; + String branchName = branch.getName(); + listener + .getLogger() + .format( + "%n Checking branch %s%n", + HyperlinkNote.encodeTo( + resolvedRepositoryUrl + "/tree/" + branchName, branchName)); + BranchSCMHead head = new BranchSCMHead(branchName); + if (request.process( + head, + new SCMRevisionImpl(head, branch.getSHA1()), + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull BranchSCMHead head, @Nullable SCMRevisionImpl revisionInfo) + throws IOException, InterruptedException { + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, head, revisionInfo); + } + }, + new CriteriaWitness(listener))) { + listener + .getLogger() + .format("%n %d branches were processed (query completed)%n", count); + break; + } + } + listener.getLogger().format("%n %d branches were processed%n", count); + } + if (request.isFetchPRs() && !request.isComplete()) { + listener.getLogger().format("%n Checking pull-requests...%n"); + int count = 0; + int errorCount = 0; + Map> strategies = request.getPRStrategies(); + + // JENKINS-56996 + // PRs are one the most error prone areas for scans + // Branches and tags are contained only the current repo, PRs go across forks + // FileNotFoundException can occur in a number of situations + // When this happens, it is not ideal behavior but it is better to let the PR be + // orphaned + // and the orphan strategy control the result than for this error to stop scanning + // (For Org scanning this is particularly important.) + // If some more general IO exception is thrown, we will still fail. + + validatePullRequests(request); + for (final GHPullRequest pr : request.getPullRequests()) { + int number = pr.getNumber(); + try { + retrievePullRequest( + apiUri, credentials, ghRepository, pr, strategies, request, listener); + } catch (FileNotFoundException e) { + listener.getLogger().format("%n Error while processing pull request %d%n", number); + Functions.printStackTrace(e, listener.getLogger()); + errorCount++; + } + count++; + } + listener.getLogger().format("%n %d pull requests were processed%n", count); + if (errorCount > 0) { + listener + .getLogger() + .format("%n %d pull requests encountered errors and were orphaned.%n", count); + } + } + if (request.isFetchTags() && !request.isComplete()) { + listener.getLogger().format("%n Checking tags...%n"); + int count = 0; + for (final GHRef tag : request.getTags()) { + String tagName = tag.getRef(); + if (!tagName.startsWith(Constants.R_TAGS)) { + // should never happen, but if it does we should skip + continue; + } + tagName = tagName.substring(Constants.R_TAGS.length()); + count++; + listener + .getLogger() + .format( + "%n Checking tag %s%n", + HyperlinkNote.encodeTo(resolvedRepositoryUrl + "/tree/" + tagName, tagName)); + long tagDate = 0L; + String sha = tag.getObject().getSha(); + if ("tag".equalsIgnoreCase(tag.getObject().getType())) { + // annotated tag object + try { + GHTagObject tagObject = request.getRepository().getTagObject(sha); + tagDate = tagObject.getTagger().getDate().getTime(); + // we want the sha of the tagged commit not the tag object + sha = tagObject.getObject().getSha(); + } catch (IOException e) { + // ignore, if the tag doesn't exist, the probe will handle that correctly + // we just need enough of a date value to allow for probing + } + } else { + try { + GHCommit commit = request.getRepository().getCommit(sha); + tagDate = commit.getCommitDate().getTime(); + } catch (IOException e) { + // ignore, if the tag doesn't exist, the probe will handle that correctly + // we just need enough of a date value to allow for probing + } + } + GitHubTagSCMHead head = new GitHubTagSCMHead(tagName, tagDate); + if (request.process( + head, + new GitTagSCMRevision(head, sha), + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull GitHubTagSCMHead head, @Nullable GitTagSCMRevision revisionInfo) + throws IOException, InterruptedException { + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, head, revisionInfo); + } + }, + new CriteriaWitness(listener))) { + listener + .getLogger() + .format("%n %d tags were processed (query completed)%n", count); + break; + } + } + listener.getLogger().format("%n %d tags were processed%n", count); + } + } + listener.getLogger().format("%nFinished examining %s%n%n", fullName); + } catch (WrappedException e) { + try { + e.unwrap(); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } + } + } finally { + Connector.release(github); + } + } + + private static void validatePullRequests(GitHubSCMSourceRequest request) { + // JENKINS-56996 + // This method handles the case where there would be an error + // while finding a user inside the PR iterator. + // Once this is done future iterations over PR use a cached list. + // We could do this at the same time as processing each PR, but + // this is clearer and safer. + Iterator iterator = request.getPullRequests().iterator(); + while (iterator.hasNext()) { + try { + try { + iterator.next(); + } catch (NoSuchElementException e) { + break; + } catch (WrappedException wrapped) { + wrapped.unwrap(); + } + } catch (FileNotFoundException e) { + // File not found exceptions are ignorable + } catch (IOException | InterruptedException e) { + throw new WrappedException(e); + } + } + } + + private static void retrievePullRequest( + final String apiUri, + final StandardCredentials credentials, + @NonNull final GHRepository ghRepository, + @NonNull final GHPullRequest pr, + @NonNull final Map> strategies, + @NonNull final GitHubSCMSourceRequest request, + @NonNull final TaskListener listener) + throws IOException, InterruptedException { + + int number = pr.getNumber(); + listener + .getLogger() + .format( + "%n Checking pull request %s%n", + HyperlinkNote.encodeTo(pr.getHtmlUrl().toString(), "#" + number)); + boolean fork = !ghRepository.getOwner().equals(pr.getHead().getUser()); + if (strategies.get(fork).isEmpty()) { + if (fork) { + listener.getLogger().format(" Submitted from fork, skipping%n%n"); + } else { + listener.getLogger().format(" Submitted from origin repository, skipping%n%n"); + } + return; + } + for (final ChangeRequestCheckoutStrategy strategy : strategies.get(fork)) { + final String branchName; + if (strategies.get(fork).size() == 1) { + branchName = "PR-" + number; + } else { + branchName = "PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH); + } + + // PR details only needed for merge PRs + if (strategy == ChangeRequestCheckoutStrategy.MERGE) { + // The probe github will be closed along with the probe. + final GitHub gitHub = Connector.connect(apiUri, credentials); + try { + ensureDetailedGHPullRequest(pr, listener, gitHub, ghRepository); + } finally { + Connector.release(gitHub); + } + } - ////////////////////////////////////////////////////////////////////// - // Legacy Configuration fields - ////////////////////////////////////////////////////////////////////// + if (request.process( + new PullRequestSCMHead(pr, branchName, strategy == ChangeRequestCheckoutStrategy.MERGE), + null, + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull PullRequestSCMHead head, @Nullable Void revisionInfo) + throws IOException, InterruptedException { + boolean trusted = request.isTrusted(head); + if (!trusted) { + listener.getLogger().format(" (not from a trusted source)%n"); + } + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, trusted ? head : head.getTarget(), null); + } + }, + new SCMSourceRequest.LazyRevisionLambda() { + @NonNull + @Override + public SCMRevision create(@NonNull PullRequestSCMHead head, @Nullable Void ignored) + throws IOException, InterruptedException { + + return createPullRequestSCMRevision(pr, head, listener, ghRepository); + } + }, + new MergabilityWitness(pr, strategy, listener), + new CriteriaWitness(listener))) { + listener.getLogger().format("%n Pull request %d processed (query completed)%n", number); + } + } + } + + @NonNull + @Override + protected Set retrieveRevisions(@NonNull TaskListener listener, Item retrieveContext) + throws IOException, InterruptedException { + StandardCredentials credentials = + Connector.lookupScanCredentials(retrieveContext, apiUri, credentialsId); + // Github client and validation + final GitHub github = Connector.connect(apiUri, credentials); + try { + Connector.configureLocalRateLimitChecker(listener, github); + Set result = new TreeSet<>(); + + try { + // Input data validation + Connector.checkConnectionValidity(apiUri, listener, credentials, github); + + // Input data validation + if (isBlank(repository)) { + throw new AbortException("No repository selected, skipping"); + } + + String fullName = repoOwner + "/" + repository; + ghRepository = github.getRepository(fullName); + final GHRepository ghRepository = this.ghRepository; + listener + .getLogger() + .format( + "Listing %s%n", + HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); + resolvedRepositoryUrl = ghRepository.getHtmlUrl(); + GitHubSCMSourceContext context = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()).withTraits(traits); + boolean wantBranches = context.wantBranches(); + boolean wantTags = context.wantTags(); + boolean wantPRs = context.wantPRs(); + boolean wantSinglePRs = + context.forkPRStrategies().size() == 1 || context.originPRStrategies().size() == 1; + boolean wantMultiPRs = + context.forkPRStrategies().size() > 1 || context.originPRStrategies().size() > 1; + Set strategies = new TreeSet<>(); + strategies.addAll(context.forkPRStrategies()); + strategies.addAll(context.originPRStrategies()); + for (GHRef ref : ghRepository.listRefs()) { + String name = ref.getRef(); + if (name.startsWith(Constants.R_HEADS) && wantBranches) { + String branchName = name.substring(Constants.R_HEADS.length()); + listener + .getLogger() + .format( + "%n Found branch %s%n", + HyperlinkNote.encodeTo( + resolvedRepositoryUrl + "/tree/" + branchName, branchName)); + result.add(branchName); + continue; + } + if (name.startsWith(R_PULL) && wantPRs) { + int index = name.indexOf('/', R_PULL.length()); + if (index != -1) { + String number = name.substring(R_PULL.length(), index); + listener + .getLogger() + .format( + "%n Found pull request %s%n", + HyperlinkNote.encodeTo( + resolvedRepositoryUrl + "/pull/" + number, "#" + number)); + // we are allowed to return "invalid" names so if the user has configured, say + // origin as single strategy and fork as multiple strategies + // we will return PR-5, PR-5-merge and PR-5-head in the result set + // and leave it up to the call to retrieve to determine exactly + // whether the name is actually valid and resolve the correct SCMHead type + // + // this allows this method to avoid an API call for every PR in order to + // determine if the PR is an origin or a fork PR and allows us to just + // use the single (set) of calls to get all refs + if (wantSinglePRs) { + result.add("PR-" + number); + } + if (wantMultiPRs) { + for (ChangeRequestCheckoutStrategy strategy : strategies) { + result.add("PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH)); + } + } + } + continue; + } + if (name.startsWith(Constants.R_TAGS) && wantTags) { + String tagName = name.substring(Constants.R_TAGS.length()); + listener + .getLogger() + .format( + "%n Found tag %s%n", + HyperlinkNote.encodeTo(resolvedRepositoryUrl + "/tree/" + tagName, tagName)); + result.add(tagName); + continue; + } + } + listener.getLogger().format("%nFinished listing %s%n%n", fullName); + } catch (WrappedException e) { + try { + e.unwrap(); + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } + } + return result; + } finally { + Connector.release(github); + } + } + + @Override + protected SCMRevision retrieve( + @NonNull String headName, @NonNull TaskListener listener, Item retrieveContext) + throws IOException, InterruptedException { + StandardCredentials credentials = + Connector.lookupScanCredentials(retrieveContext, apiUri, credentialsId); + // Github client and validation + final GitHub github = Connector.connect(apiUri, credentials); + try { + Connector.configureLocalRateLimitChecker(listener, github); + // Input data validation + if (isBlank(repository)) { + throw new AbortException("No repository selected, skipping"); + } + + String fullName = repoOwner + "/" + repository; + ghRepository = github.getRepository(fullName); + final GHRepository ghRepository = this.ghRepository; + listener + .getLogger() + .format( + "Examining %s%n", + HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); + GitHubSCMSourceContext context = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()).withTraits(traits); + Matcher prMatcher = Pattern.compile("^PR-(\\d+)(?:-(.*))?$").matcher(headName); + if (prMatcher.matches()) { + // it's a looking very much like a PR + int number = Integer.parseInt(prMatcher.group(1)); + listener + .getLogger() + .format("Attempting to resolve %s as pull request %d%n", headName, number); + try { + GHPullRequest pr = ghRepository.getPullRequest(number); + if (pr != null) { + boolean fork = !ghRepository.getOwner().equals(pr.getHead().getUser()); + Set strategies; + if (context.wantPRs()) { + strategies = fork ? context.forkPRStrategies() : context.originPRStrategies(); + } else { + // if not configured, we go with merge + strategies = EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); + } + ChangeRequestCheckoutStrategy strategy; + if (prMatcher.group(2) == null) { + if (strategies.size() == 1) { + strategy = strategies.iterator().next(); + } else { + // invalid name + listener + .getLogger() + .format( + "Resolved %s as pull request %d but indeterminate checkout strategy, " + + "please try %s or %s%n", + headName, + number, + headName + "-" + ChangeRequestCheckoutStrategy.HEAD.name(), + headName + "-" + ChangeRequestCheckoutStrategy.MERGE.name()); + return null; + } + } else { + strategy = null; + for (ChangeRequestCheckoutStrategy s : strategies) { + if (s.name().toLowerCase(Locale.ENGLISH).equals(prMatcher.group(2))) { + strategy = s; + break; + } + } + if (strategy == null) { + // invalid name; + listener + .getLogger() + .format( + "Resolved %s as pull request %d but unknown checkout strategy %s, " + + "please try %s or %s%n", + headName, + number, + prMatcher.group(2), + headName + "-" + ChangeRequestCheckoutStrategy.HEAD.name(), + headName + "-" + ChangeRequestCheckoutStrategy.MERGE.name()); + return null; + } + } + PullRequestSCMHead head = + new PullRequestSCMHead( + pr, headName, strategy == ChangeRequestCheckoutStrategy.MERGE); + if (head.isMerge()) { + ensureDetailedGHPullRequest(pr, listener, github, ghRepository); + } + PullRequestSCMRevision prRev = + createPullRequestSCMRevision(pr, head, listener, ghRepository); + + switch (strategy) { + case MERGE: + try { + prRev.validateMergeHash(); + } catch (AbortException e) { + listener + .getLogger() + .format( + "Resolved %s as pull request %d: %s.%n%n", + headName, number, e.getMessage()); + return null; + } + listener + .getLogger() + .format( + "Resolved %s as pull request %d at revision %s merged onto %s as %s%n", + headName, + number, + prRev.getPullHash(), + prRev.getBaseHash(), + prRev.getMergeHash()); + break; + default: + listener + .getLogger() + .format( + "Resolved %s as pull request %d at revision %s%n", + headName, number, prRev.getPullHash()); + break; + } + return prRev; + } else { + listener + .getLogger() + .format("Could not resolve %s as pull request %d%n", headName, number); + } + } catch (FileNotFoundException e) { + // maybe some ****er created a branch or a tag called PR-_ + listener + .getLogger() + .format("Could not resolve %s as pull request %d%n", headName, number); + } + } + try { + listener.getLogger().format("Attempting to resolve %s as a branch%n", headName); + GHBranch branch = ghRepository.getBranch(headName); + if (branch != null) { + listener + .getLogger() + .format( + "Resolved %s as branch %s at revision %s%n", + headName, branch.getName(), branch.getSHA1()); + return new SCMRevisionImpl(new BranchSCMHead(headName), branch.getSHA1()); + } + } catch (FileNotFoundException e) { + // maybe it's a tag + } + try { + listener.getLogger().format("Attempting to resolve %s as a tag%n", headName); + GHRef tag = ghRepository.getRef("tags/" + headName); + if (tag != null) { + long tagDate = 0L; + String tagSha = tag.getObject().getSha(); + if ("tag".equalsIgnoreCase(tag.getObject().getType())) { + // annotated tag object + try { + GHTagObject tagObject = ghRepository.getTagObject(tagSha); + tagDate = tagObject.getTagger().getDate().getTime(); + } catch (IOException e) { + // ignore, if the tag doesn't exist, the probe will handle that correctly + // we just need enough of a date value to allow for probing + } + } else { + try { + GHCommit commit = ghRepository.getCommit(tagSha); + tagDate = commit.getCommitDate().getTime(); + } catch (IOException e) { + // ignore, if the tag doesn't exist, the probe will handle that correctly + // we just need enough of a date value to allow for probing + } + } + listener + .getLogger() + .format("Resolved %s as tag %s at revision %s%n", headName, headName, tagSha); + return new GitTagSCMRevision(new GitHubTagSCMHead(headName, tagDate), tagSha); + } + } catch (FileNotFoundException e) { + // ok it doesn't exist + } + listener.error("Could not resolve %s", headName); + + // TODO try and resolve as a revision, but right now we'd need to know what branch the + // revision belonged to + // once GitSCMSource has support for arbitrary refs, we could just use that... but given that + // GitHubSCMBuilder constructs the refspec based on the branch name, without a specific + // "arbitrary ref" + // SCMHead subclass we cannot do anything here + return null; + } finally { + Connector.release(github); + } + } + + @NonNull + private Set updateCollaboratorNames( + @NonNull TaskListener listener, + @CheckForNull StandardCredentials credentials, + @NonNull GHRepository ghRepository) + throws IOException { + if (credentials == null && (apiUri == null || GITHUB_URL.equals(apiUri))) { + // anonymous access to GitHub will never get list of collaborators and will + // burn an API call, so no point in even trying + listener.getLogger().println("Anonymous cannot query list of collaborators, assuming none"); + return collaboratorNames = Collections.emptySet(); + } else { + try { + return collaboratorNames = new HashSet<>(ghRepository.getCollaboratorNames()); + } catch (FileNotFoundException e) { + // not permitted + listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); + return collaboratorNames = Collections.emptySet(); + } catch (HttpException e) { + if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED + || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { + listener + .getLogger() + .println("Not permitted to query list of collaborators, assuming none"); + return collaboratorNames = Collections.emptySet(); + } else { + throw e; + } + } + } + } + + private static class WrappedException extends RuntimeException { + + public WrappedException(Throwable cause) { + super(cause); + } + + public void unwrap() throws IOException, InterruptedException { + Throwable cause = getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } + if (cause instanceof InterruptedException) { + throw (InterruptedException) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw this; + } + } + + @NonNull + @Override + protected SCMProbe createProbe(@NonNull SCMHead head, @CheckForNull final SCMRevision revision) + throws IOException { + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + // Github client and validation + GitHub github = Connector.connect(apiUri, credentials); + try { + String fullName = repoOwner + "/" + repository; + final GHRepository repo = github.getRepository(fullName); + return new GitHubSCMProbe(apiUri, credentials, repo, head, revision); + } catch (IOException | RuntimeException | Error e) { + throw e; + } finally { + Connector.release(github); + } + } + + @Override + @CheckForNull + protected SCMRevision retrieve(SCMHead head, TaskListener listener) + throws IOException, InterruptedException { + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + + // Github client and validation + GitHub github = Connector.connect(apiUri, credentials); + try { + try { + Connector.checkConnectionValidity(apiUri, listener, credentials, github); + Connector.configureLocalRateLimitChecker(listener, github); + String fullName = repoOwner + "/" + repository; + ghRepository = github.getRepository(fullName); + final GHRepository ghRepository = this.ghRepository; + resolvedRepositoryUrl = ghRepository.getHtmlUrl(); + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead prhead = (PullRequestSCMHead) head; + GHPullRequest pr = ghRepository.getPullRequest(prhead.getNumber()); + if (prhead.isMerge()) { + ensureDetailedGHPullRequest(pr, listener, github, ghRepository); + } + PullRequestSCMRevision prRev = + createPullRequestSCMRevision(pr, prhead, listener, ghRepository); + prRev.validateMergeHash(); + return prRev; + } else if (head instanceof GitHubTagSCMHead) { + GitHubTagSCMHead tagHead = (GitHubTagSCMHead) head; + GHRef tag = ghRepository.getRef("tags/" + tagHead.getName()); + String sha = tag.getObject().getSha(); + if ("tag".equalsIgnoreCase(tag.getObject().getType())) { + // annotated tag object + GHTagObject tagObject = ghRepository.getTagObject(sha); + // we want the sha of the tagged commit not the tag object + sha = tagObject.getObject().getSha(); + } + return new GitTagSCMRevision(tagHead, sha); + } else { + return new SCMRevisionImpl( + head, ghRepository.getRef("heads/" + head.getName()).getObject().getSha()); + } + } catch (RateLimitExceededException rle) { + throw new AbortException(rle.getMessage()); + } + } finally { + Connector.release(github); + } + } + + private static PullRequestSCMRevision createPullRequestSCMRevision( + GHPullRequest pr, PullRequestSCMHead prhead, TaskListener listener, GHRepository ghRepository) + throws IOException, InterruptedException { + String baseHash = pr.getBase().getSha(); + String prHeadHash = pr.getHead().getSha(); + String mergeHash = null; + + if (prhead.isMerge()) { + if (Boolean.FALSE.equals(pr.getMergeable())) { + mergeHash = PullRequestSCMRevision.NOT_MERGEABLE_HASH; + } else if (Boolean.TRUE.equals(pr.getMergeable())) { + String proposedMergeHash = pr.getMergeCommitSha(); + GHCommit commit = null; + try { + commit = ghRepository.getCommit(proposedMergeHash); + } catch (FileNotFoundException e) { + listener + .getLogger() + .format( + "Pull request %s : github merge_commit_sha not found (%s). Close and reopen the PR to reset its merge hash.%n", + pr.getNumber(), proposedMergeHash); + } catch (IOException e) { + throw new AbortException( + "Error while retrieving pull request " + + pr.getNumber() + + " merge hash : " + + e.toString()); + } + + if (commit != null) { + List parents = commit.getParentSHA1s(); + // Merge commits always merge against the most recent base commit they can detect. + if (parents.size() != 2) { + listener + .getLogger() + .format( + "WARNING: Invalid github merge_commit_sha for pull request %s : merge commit %s with parents - %s.%n", + pr.getNumber(), proposedMergeHash, StringUtils.join(parents, "+")); + } else if (!parents.contains(prHeadHash)) { + // This is maintains the existing behavior from pre-2.5.x when the merge_commit_sha is + // out of sync from the requested prHead + listener + .getLogger() + .format( + "WARNING: Invalid github merge_commit_sha for pull request %s : Head commit %s does match merge commit %s with parents - %s.%n", + pr.getNumber(), prHeadHash, proposedMergeHash, StringUtils.join(parents, "+")); + } else { + // We found a merge_commit_sha with 2 parents and one matches the prHeadHash + // Use the other parent hash as the base. This keeps the merge hash in sync with head + // and base. + // It is possible that head or base hash will not exist in their branch by the time we + // build + // This is be true (and cause a failure) regardless of how we determine the commits. + mergeHash = proposedMergeHash; + baseHash = prHeadHash.equals(parents.get(0)) ? parents.get(1) : parents.get(0); + } + } + } + + // Merge PR jobs always merge against the most recent base branch commit they can detect. + // For an invalid merge_commit_sha, we need to query for most recent base commit separately + if (mergeHash == null) { + baseHash = ghRepository.getRef("heads/" + pr.getBase().getRef()).getObject().getSha(); + } + } + + return new PullRequestSCMRevision(prhead, baseHash, prHeadHash, mergeHash); + } + + private static void ensureDetailedGHPullRequest( + GHPullRequest pr, TaskListener listener, GitHub github, GHRepository ghRepository) + throws IOException, InterruptedException { + final long sleep = 1000; + int retryCountdown = 4; + + while (pr.getMergeable() == null && retryCountdown > 1) { + listener + .getLogger() + .format( + "Waiting for GitHub to create a merge commit for pull request %d. Retrying %d more times...%n", + pr.getNumber(), retryCountdown); + retryCountdown -= 1; + Thread.sleep(sleep); + } + } + + @Override + public SCM build(SCMHead head, SCMRevision revision) { + return new GitHubSCMBuilder(this, head, revision).withTraits(traits).build(); + } + + @CheckForNull + /*package*/ URL getResolvedRepositoryUrl() { + return resolvedRepositoryUrl; + } + + @Deprecated // TODO remove once migration from 1.x is no longer supported + PullRequestSource retrievePullRequestSource(int number) { + // we use a big honking great lock to prevent concurrent requests to github during job loading + Map pullRequestSourceMap; + synchronized (pullRequestSourceMapLock) { + pullRequestSourceMap = this.pullRequestSourceMap; + if (pullRequestSourceMap == null) { + this.pullRequestSourceMap = pullRequestSourceMap = new HashMap<>(); + if (StringUtils.isNotBlank(repository)) { + String fullName = repoOwner + "/" + repository; + LOGGER.log(Level.INFO, "Getting remote pull requests from {0}", fullName); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + LogTaskListener listener = new LogTaskListener(LOGGER, Level.INFO); + try { + GitHub github = Connector.connect(apiUri, credentials); + try { + Connector.configureLocalRateLimitChecker(listener, github); + ghRepository = github.getRepository(fullName); + LOGGER.log(Level.INFO, "Got remote pull requests from {0}", fullName); + int n = 0; + for (GHPullRequest pr : + ghRepository.queryPullRequests().state(GHIssueState.OPEN).list()) { + GHRepository repository = pr.getHead().getRepository(); + // JENKINS-41246 repository may be null for deleted forks + pullRequestSourceMap.put( + pr.getNumber(), + new PullRequestSource( + repository == null ? null : repository.getOwnerName(), + repository == null ? null : repository.getName(), + pr.getHead().getRef())); + n++; + } + } finally { + Connector.release(github); + } + } catch (IOException | InterruptedException e) { + LOGGER.log( + Level.WARNING, + "Could not get all pull requests from " + fullName + ", there may be rebuilds", + e); + } + } + } + return pullRequestSourceMap.get(number); + } + } + + /** + * Retained to migrate legacy configuration. + * + * @deprecated use {@link MergeWithGitSCMExtension}. + */ + @Restricted(NoExternalUse.class) + @RestrictedSince("2.2.0") + @Deprecated + private static class MergeWith extends GitSCMExtension { + private final String baseName; + private final String baseHash; + + private MergeWith(String baseName, String baseHash) { + this.baseName = baseName; + this.baseHash = baseHash; + } + + private Object readResolve() throws ObjectStreamException { + return new MergeWithGitSCMExtension("remotes/origin/" + baseName, baseHash); + } + } + + @Override + public SCMRevision getTrustedRevision(SCMRevision revision, final TaskListener listener) + throws IOException, InterruptedException { + if (revision instanceof PullRequestSCMRevision) { + PullRequestSCMHead head = (PullRequestSCMHead) revision.getHead(); + + try (GitHubSCMSourceRequest request = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()) + .withTraits(traits) + .newRequest(this, listener)) { + if (collaboratorNames != null) { + request.setCollaboratorNames(collaboratorNames); + } else { + request.setCollaboratorNames(new DeferredContributorNames(request, listener)); + } + request.setPermissionsSource(new DeferredPermissionsSource(listener)); + if (request.isTrusted(head)) { + return revision; + } + } catch (WrappedException wrapped) { + try { + wrapped.unwrap(); + } catch (HttpException e) { + listener + .getLogger() + .format("It seems %s is unreachable, assuming no trusted collaborators%n", apiUri); + collaboratorNames = Collections.singleton(repoOwner); + } + } + PullRequestSCMRevision rev = (PullRequestSCMRevision) revision; + listener + .getLogger() + .format( + "Loading trusted files from base branch %s at %s rather than %s%n", + head.getTarget().getName(), rev.getBaseHash(), rev.getPullHash()); + return new SCMRevisionImpl(head.getTarget(), rev.getBaseHash()); + } + return revision; + } + + /** {@inheritDoc} */ + protected boolean isCategoryEnabled(@NonNull SCMHeadCategory category) { + for (SCMSourceTrait trait : traits) { + if (trait.isCategoryEnabled(category)) { + return true; + } + } + return false; + } + + /** {@inheritDoc} */ + @NonNull + @Override + protected List retrieveActions( + @NonNull SCMHead head, @CheckForNull SCMHeadEvent event, @NonNull TaskListener listener) + throws IOException, InterruptedException { + // TODO when we have support for trusted events, use the details from event if event was from + // trusted source + List result = new ArrayList<>(); + SCMSourceOwner owner = getOwner(); + if (owner instanceof Actionable) { + GitHubLink repoLink = ((Actionable) owner).getAction(GitHubLink.class); + if (repoLink != null) { + String url; + ObjectMetadataAction metadataAction; + if (head instanceof PullRequestSCMHead) { + // pull request to this repository + int number = ((PullRequestSCMHead) head).getNumber(); + url = repoLink.getUrl() + "/pull/" + number; + metadataAction = pullRequestMetadataCache.get(number); + if (metadataAction == null) { + // best effort + metadataAction = new ObjectMetadataAction(null, null, url); + } + ContributorMetadataAction contributor = pullRequestContributorCache.get(number); + if (contributor != null) { + result.add(contributor); + } + } else { + // branch in this repository + url = repoLink.getUrl() + "/tree/" + head.getName(); + metadataAction = new ObjectMetadataAction(head.getName(), null, url); + } + result.add(new GitHubLink("icon-github-branch", url)); + result.add(metadataAction); + } + if (head instanceof BranchSCMHead) { + for (GitHubDefaultBranch p : ((Actionable) owner).getActions(GitHubDefaultBranch.class)) { + if (StringUtils.equals(getRepoOwner(), p.getRepoOwner()) + && StringUtils.equals(repository, p.getRepository()) + && StringUtils.equals(p.getDefaultBranch(), head.getName())) { + result.add(new PrimaryInstanceMetadataAction()); + break; + } + } + } + } + return result; + } + + /** {@inheritDoc} */ + @NonNull + @Override + protected List retrieveActions( + @CheckForNull SCMSourceEvent event, @NonNull TaskListener listener) throws IOException { + // TODO when we have support for trusted events, use the details from event if event was from + // trusted source + List result = new ArrayList<>(); + result.add(new GitHubRepoMetadataAction()); + String repository = this.repository; + + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + GitHub hub = Connector.connect(apiUri, credentials); + try { + Connector.checkConnectionValidity(apiUri, listener, credentials, hub); + try { + ghRepository = hub.getRepository(getRepoOwner() + '/' + repository); + resolvedRepositoryUrl = ghRepository.getHtmlUrl(); + } catch (FileNotFoundException e) { + throw new AbortException( + String.format( + "Invalid scan credentials when using %s to connect to %s/%s on %s", + credentials == null + ? "anonymous access" + : CredentialsNameProvider.name(credentials), + repoOwner, + repository, + apiUri)); + } + result.add( + new ObjectMetadataAction( + null, ghRepository.getDescription(), Util.fixEmpty(ghRepository.getHomepage()))); + result.add(new GitHubLink("icon-github-repo", ghRepository.getHtmlUrl())); + if (StringUtils.isNotBlank(ghRepository.getDefaultBranch())) { + result.add( + new GitHubDefaultBranch(getRepoOwner(), repository, ghRepository.getDefaultBranch())); + } + return result; + } finally { + Connector.release(hub); + } + } + + /** {@inheritDoc} */ + @Override + public void afterSave() { + SCMSourceOwner owner = getOwner(); + if (owner != null) { + GitHubWebHook.get().registerHookFor(owner); + } + } + + @Symbol("github") + @Extension + public static class DescriptorImpl extends SCMSourceDescriptor implements CustomDescribableModel { - /** - * Legacy field. - */ - @Deprecated - private transient String scanCredentialsId; - /** - * Legacy field. - */ @Deprecated - private transient String checkoutCredentialsId; - /** - * Legacy field. - */ - @Deprecated - private String includes; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final String defaultIncludes = "*"; + @Deprecated - private String excludes; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final String defaultExcludes = ""; + + public static final String ANONYMOUS = "ANONYMOUS"; + public static final String SAME = "SAME"; + // Prior to JENKINS-33161 the unconditional behavior was to build fork PRs plus origin branches, + // and try to build a merge revision for PRs. @Deprecated - private transient Boolean buildOriginBranch; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildOriginBranch = true; + @Deprecated - private transient Boolean buildOriginBranchWithPR; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildOriginBranchWithPR = true; + @Deprecated - private transient Boolean buildOriginPRMerge; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildOriginPRMerge = false; + @Deprecated - private transient Boolean buildOriginPRHead; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildOriginPRHead = false; + @Deprecated - private transient Boolean buildForkPRMerge; - /** - * Legacy field. - */ + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildForkPRMerge = true; + @Deprecated - private transient Boolean buildForkPRHead; + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final boolean defaultBuildForkPRHead = false; - ////////////////////////////////////////////////////////////////////// - // Run-time cached state - ////////////////////////////////////////////////////////////////////// + @Initializer(before = InitMilestone.PLUGINS_STARTED) + public static void addAliases() { + XSTREAM2.addCompatibilityAlias( + "org.jenkinsci.plugins.github_branch_source.OriginGitHubSCMSource", + GitHubSCMSource.class); + } - /** - * Cache of the official repository HTML URL as reported by {@link GitHub#getRepository(String)}. - */ - @CheckForNull - private transient URL resolvedRepositoryUrl; - /** - * The collaborator names used to determine if pull requests are from trusted authors - */ - @CheckForNull - private transient Set collaboratorNames; - /** - * Cache of details of the repository. - */ - @CheckForNull - private transient GHRepository ghRepository; + @Override + public String getDisplayName() { + return Messages.GitHubSCMSource_DisplayName(); + } - /** - * The cache of {@link ObjectMetadataAction} instances for each open PR. - */ - @NonNull - private transient /*effectively final*/ Map pullRequestMetadataCache; - /** - * The cache of {@link ObjectMetadataAction} instances for each open PR. - */ - @NonNull - private transient /*effectively final*/ Map pullRequestContributorCache; + @Nonnull + public Map customInstantiate(@Nonnull Map arguments) { + Map arguments2 = new TreeMap<>(arguments); + arguments2.remove("repositoryUrl"); + arguments2.remove("configuredByUrl"); + return arguments2; + } - /** - * Used during upgrade from 1.x to 2.2.0+ only. - * - * @see #retrievePullRequestSource(int) - * @see PullRequestSCMHead.FixMetadata - * @see PullRequestSCMHead.FixMetadataMigration - * @since 2.2.0 - */ - @CheckForNull // normally null except during a migration from 1.x - private transient /*effectively final*/ Map pullRequestSourceMap; + @Nonnull + public UninstantiatedDescribable customUninstantiate(@Nonnull UninstantiatedDescribable ud) { + Map scmArguments = new TreeMap<>(ud.getArguments()); + scmArguments.remove("repositoryUrl"); + scmArguments.remove("configuredByUrl"); + return ud.withArguments(scmArguments); + } - /** - * Constructor, defaults to {@link #GITHUB_URL} as the end-point, and anonymous access, does not default any - * {@link SCMSourceTrait} behaviours. - * - * @param repoOwner the repository owner. - * @param repository the repository name. - * @param repositoryUrl HTML URL for the repository. If specified, takes precedence over repoOwner and repository. - * @param configuredByUrl Whether to use repositoryUrl or repoOwner/repository for configuration. - * @throws IllegalArgumentException if repositoryUrl is specified but invalid. - * @since 2.2.0 - */ - // configuredByUrl is used to decide which radioBlock in the UI the user had selected when they submitted the form. - @DataBoundConstructor - public GitHubSCMSource(String repoOwner, String repository, String repositoryUrl, boolean configuredByUrl) { - if (!configuredByUrl) { - this.apiUri = GITHUB_URL; - this.repoOwner = repoOwner; - this.repository = repository; - this.repositoryUrl = null; - } else { - GitHubRepositoryInfo info = GitHubRepositoryInfo.forRepositoryUrl(repositoryUrl); - this.apiUri = info.getApiUri(); - this.repoOwner = info.getRepoOwner(); - this.repository = info.getRepository(); - this.repositoryUrl = info.getRepositoryUrl(); - } - pullRequestMetadataCache = new ConcurrentHashMap<>(); - pullRequestContributorCache = new ConcurrentHashMap<>(); - this.traits = new ArrayList<>(); + public ListBoxModel doFillCredentialsIdItems( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId) { + if (context == null + ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + : !context.hasPermission(Item.EXTENDED_READ)) { + return new StandardListBoxModel().includeCurrentValue(credentialsId); + } + return Connector.listScanCredentials(context, apiUri); } - /** - * Legacy constructor. - * - * @param repoOwner the repository owner. - * @param repository the repository name. - * @since 2.2.0 - */ - @Deprecated - public GitHubSCMSource(String repoOwner, String repository) { - this(repoOwner, repository, null, false); + @RequirePOST + @Restricted(NoExternalUse.class) + public FormValidation doCheckCredentialsId( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String value) { + return Connector.checkScanCredentials(context, apiUri, value); } - /** - * Legacy constructor. - * @param id the source id. - * @param apiUri the GitHub endpoint. - * @param checkoutCredentialsId the checkout credentials id or {@link DescriptorImpl#SAME} or - * {@link DescriptorImpl#ANONYMOUS}. - * @param scanCredentialsId the scan credentials id or {@code null}. - * @param repoOwner the repository owner. - * @param repository the repository name. - */ - @Deprecated - public GitHubSCMSource(@CheckForNull String id, @CheckForNull String apiUri, - @NonNull String checkoutCredentialsId, - @CheckForNull String scanCredentialsId, @NonNull String repoOwner, - @NonNull String repository) { - this(repoOwner, repository, null, false); - setId(id); - setApiUri(apiUri); - setCredentialsId(scanCredentialsId); - // legacy constructor means legacy defaults - this.traits = new ArrayList<>(); - this.traits.add(new BranchDiscoveryTrait(true, true)); - this.traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustPermission())); - if (!DescriptorImpl.SAME.equals(checkoutCredentialsId)) { - traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); + @RequirePOST + @Restricted(NoExternalUse.class) + public FormValidation doValidateRepositoryUrlAndCredentials( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String repositoryUrl, + @QueryParameter String credentialsId) { + if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + || context != null && !context.hasPermission(Item.EXTENDED_READ)) { + return FormValidation.error( + "Unable to validate repository information"); // not supposed to be seeing this form + } + if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { + return FormValidation.error( + "Unable to validate repository information"); // not permitted to try connecting with + // these credentials + } + GitHubRepositoryInfo info; + + try { + info = GitHubRepositoryInfo.forRepositoryUrl(repositoryUrl); + } catch (IllegalArgumentException e) { + return FormValidation.error(e, e.getMessage()); + } + + StandardCredentials credentials = + Connector.lookupScanCredentials(context, info.getApiUri(), credentialsId); + StringBuilder sb = new StringBuilder(); + try { + GitHub github = Connector.connect(info.getApiUri(), credentials); + try { + if (github.isCredentialValid()) { + sb.append("Credentials ok."); + } + + GHRepository repo = + github.getRepository(info.getRepoOwner() + "/" + info.getRepository()); + if (repo != null) { + sb.append(" Connected to "); + sb.append(repo.getHtmlUrl()); + sb.append("."); + } + } finally { + Connector.release(github); } + } catch (IOException e) { + return FormValidation.error(e, "Error validating repository information. " + sb.toString()); + } + return FormValidation.ok(sb.toString()); } @Restricted(NoExternalUse.class) - public boolean isConfiguredByUrl() { - return repositoryUrl != null; + public FormValidation doCheckIncludes(@QueryParameter String value) { + if (value.isEmpty()) { + return FormValidation.warning( + Messages.GitHubSCMSource_did_you_mean_to_use_to_match_all_branches()); + } + return FormValidation.ok(); } - /** - * Returns the GitHub API end-point. - * - * @return the GitHub API end-point. - */ - @NonNull - public String getApiUri() { - return apiUri; + @RequirePOST + @Restricted(NoExternalUse.class) + public FormValidation doCheckScanCredentialsId( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String scanCredentialsId) { + return doCheckCredentialsId(context, apiUri, scanCredentialsId); } - /** - * Sets the GitHub API end-point. - * - * @param apiUri the GitHub API end-point or {@code null} if {@link #GITHUB_URL}. - * @since 2.2.0 - */ - @DataBoundSetter - public void setApiUri(@CheckForNull String apiUri) { - // JENKINS-58862 - // If repositoryUrl is set, we don't want to set it again. - if (this.repositoryUrl != null) { - return; - } - apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); - if (apiUri == null) { - apiUri = GITHUB_URL; - } - this.apiUri = apiUri; + @Restricted(NoExternalUse.class) + public FormValidation doCheckBuildOriginBranchWithPR( + @QueryParameter boolean buildOriginBranch, + @QueryParameter boolean buildOriginBranchWithPR, + @QueryParameter boolean buildOriginPRMerge, + @QueryParameter boolean buildOriginPRHead, + @QueryParameter boolean buildForkPRMerge, + @QueryParameter boolean buildForkPRHead) { + if (buildOriginBranch + && !buildOriginBranchWithPR + && !buildOriginPRMerge + && !buildOriginPRHead + && !buildForkPRMerge + && !buildForkPRHead) { + // TODO in principle we could make doRetrieve populate originBranchesWithPR without actually + // including any PRs, but it would be more work and probably never wanted anyway. + return FormValidation.warning( + "If you are not building any PRs, all origin branches will be built."); + } + return FormValidation.ok(); } - /** - * Forces the apiUri to a specific value. - * FOR TESTING ONLY. - * - * @param apiUri the api uri - */ - void forceApiUri(@Nonnull String apiUri) { - this.apiUri = apiUri; + @Restricted(NoExternalUse.class) + public FormValidation doCheckBuildOriginPRHead( + @QueryParameter boolean buildOriginBranchWithPR, + @QueryParameter boolean buildOriginPRMerge, + @QueryParameter boolean buildOriginPRHead) { + if (buildOriginBranchWithPR && buildOriginPRHead) { + return FormValidation.warning( + "Redundant to build an origin PR both as a branch and as an unmerged PR."); + } + if (buildOriginPRMerge && buildOriginPRHead) { + return FormValidation.ok( + "Merged vs. unmerged PRs will be distinguished in the job name (*-merge vs. *-head)."); + } + return FormValidation.ok(); } - /** - * Gets the credentials used to access the GitHub REST API (also used as the default credentials for checking out - * sources. - * @return the credentials used to access the GitHub REST API or {@code null} to access anonymously - */ - @Override - @CheckForNull - public String getCredentialsId() { - return credentialsId; - } + @Restricted(NoExternalUse.class) + public FormValidation + doCheckBuildForkPRHead /* web method name controls UI position of message; we want this at the bottom */( + @QueryParameter boolean buildOriginBranch, + @QueryParameter boolean buildOriginBranchWithPR, + @QueryParameter boolean buildOriginPRMerge, + @QueryParameter boolean buildOriginPRHead, + @QueryParameter boolean buildForkPRMerge, + @QueryParameter boolean buildForkPRHead) { + if (!buildOriginBranch + && !buildOriginBranchWithPR + && !buildOriginPRMerge + && !buildOriginPRHead + && !buildForkPRMerge + && !buildForkPRHead) { + return FormValidation.warning("You need to build something!"); + } + if (buildForkPRMerge && buildForkPRHead) { + return FormValidation.ok( + "Merged vs. unmerged PRs will be distinguished in the job name (*-merge vs. *-head)."); + } + return FormValidation.ok(); + } + + public ListBoxModel doFillApiUriItems() { + ListBoxModel result = new ListBoxModel(); + result.add("GitHub", ""); + for (Endpoint e : GitHubConfiguration.get().getEndpoints()) { + result.add( + e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")", + e.getApiUri()); + } + return result; + } + + public boolean isApiUriSelectable() { + return !GitHubConfiguration.get().getEndpoints().isEmpty(); + } + + @RequirePOST + public ListBoxModel doFillOrganizationItems( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId) + throws IOException { + if (credentialsId == null) { + return new ListBoxModel(); + } + if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + || context != null && !context.hasPermission(Item.EXTENDED_READ)) { + return new ListBoxModel(); // not supposed to be seeing this form + } + if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { + return new ListBoxModel(); // not permitted to try connecting with these credentials + } + try { + StandardCredentials credentials = + Connector.lookupScanCredentials(context, apiUri, credentialsId); + GitHub github = Connector.connect(apiUri, credentials); + try { + if (!github.isAnonymous()) { + ListBoxModel model = new ListBoxModel(); + for (Map.Entry entry : github.getMyOrganizations().entrySet()) { + model.add(entry.getKey(), entry.getValue().getAvatarUrl()); + } + return model; + } + } finally { + Connector.release(github); + } + } catch (FillErrorResponse e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new FillErrorResponse(e.getMessage(), false); + } + throw new FillErrorResponse( + Messages.GitHubSCMSource_CouldNotConnectionGithub(credentialsId), true); + } + + @RequirePOST + public ListBoxModel doFillRepositoryItems( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId, + @QueryParameter String repoOwner, + @QueryParameter boolean configuredByUrl) + throws IOException { + if (configuredByUrl) { + return new ListBoxModel(); // Using the URL-based configuration, don't scan for + // repositories. + } + repoOwner = Util.fixEmptyAndTrim(repoOwner); + if (repoOwner == null) { + return new ListBoxModel(); + } + if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + || context != null && !context.hasPermission(Item.EXTENDED_READ)) { + return new ListBoxModel(); // not supposed to be seeing this form + } + if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { + return new ListBoxModel(); // not permitted to try connecting with these credentials + } + try { + StandardCredentials credentials = + Connector.lookupScanCredentials(context, apiUri, credentialsId); + GitHub github = Connector.connect(apiUri, credentials); + try { - /** - * Sets the credentials used to access the GitHub REST API (also used as the default credentials for checking out - * sources. - * - * @param credentialsId the credentials used to access the GitHub REST API or {@code null} to access anonymously - * @since 2.2.0 - */ - @DataBoundSetter - public void setCredentialsId(@CheckForNull String credentialsId) { - this.credentialsId = Util.fixEmpty(credentialsId); + if (!github.isAnonymous()) { + GHMyself myself; + try { + myself = github.getMyself(); + } catch (IllegalStateException e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); + throw new FillErrorResponse(e.getMessage(), false); + } catch (IOException e) { + LogRecord lr = + new LogRecord( + Level.WARNING, + "Exception retrieving the repositories of the owner {0} on {1} with credentials {2}"); + lr.setThrown(e); + lr.setParameters( + new Object[] { + repoOwner, + apiUri, + credentials == null + ? "anonymous access" + : CredentialsNameProvider.name(credentials) + }); + LOGGER.log(lr); + throw new FillErrorResponse(e.getMessage(), false); + } + if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { + Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (GHRepository repo : + myself.listRepositories(100, GHMyself.RepositoryListFilter.ALL)) { + result.add(repo.getName()); + } + return nameAndValueModel(result); + } + } + + GHOrganization org = null; + try { + org = github.getOrganization(repoOwner); + } catch (FileNotFoundException fnf) { + LOGGER.log(Level.FINE, "There is not any GH Organization named {0}", repoOwner); + } catch (IOException e) { + LogRecord lr = + new LogRecord( + Level.WARNING, + "Exception retrieving the repositories of the organization {0} on {1} with credentials {2}"); + lr.setThrown(e); + lr.setParameters( + new Object[] { + repoOwner, + apiUri, + credentials == null + ? "anonymous access" + : CredentialsNameProvider.name(credentials) + }); + LOGGER.log(lr); + throw new FillErrorResponse(e.getMessage(), false); + } + if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { + Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + LOGGER.log( + Level.FINE, + "as {0} looking for repositories in {1}", + new Object[] {credentialsId, repoOwner}); + for (GHRepository repo : org.listRepositories(100)) { + LOGGER.log( + Level.FINE, + "as {0} found {1}/{2}", + new Object[] {credentialsId, repoOwner, repo.getName()}); + result.add(repo.getName()); + } + LOGGER.log( + Level.FINE, + "as {0} result of {1} is {2}", + new Object[] {credentialsId, repoOwner, result}); + return nameAndValueModel(result); + } + + GHUser user = null; + try { + user = github.getUser(repoOwner); + } catch (FileNotFoundException fnf) { + LOGGER.log(Level.FINE, "There is not any GH User named {0}", repoOwner); + } catch (IOException e) { + LogRecord lr = + new LogRecord( + Level.WARNING, + "Exception retrieving the repositories of the user {0} on {1} with credentials {2}"); + lr.setThrown(e); + lr.setParameters( + new Object[] { + repoOwner, + apiUri, + credentials == null + ? "anonymous access" + : CredentialsNameProvider.name(credentials) + }); + LOGGER.log(lr); + throw new FillErrorResponse(e.getMessage(), false); + } + if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { + Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (GHRepository repo : user.listRepositories(100)) { + result.add(repo.getName()); + } + return nameAndValueModel(result); + } + } finally { + Connector.release(github); + } + } catch (FillErrorResponse e) { + throw e; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw new FillErrorResponse(e.getMessage(), false); + } + throw new FillErrorResponse(Messages.GitHubSCMSource_NoMatchingOwner(repoOwner), true); } - /** - * Gets the repository owner. - * @return the repository owner. + * Creates a list box model from a list of values. ({@link + * ListBoxModel#ListBoxModel(Collection)} takes {@link hudson.util.ListBoxModel.Option}s, not + * {@link String}s, and those are not {@link Comparable}.) */ - @Exported - @NonNull - public String getRepoOwner() { - return repoOwner; + private static ListBoxModel nameAndValueModel(Collection items) { + ListBoxModel model = new ListBoxModel(); + for (String item : items) { + model.add(item); + } + return model; + } + + public List>> getTraitsDescriptorLists() { + List> all = new ArrayList<>(); + all.addAll(SCMSourceTrait._for(this, GitHubSCMSourceContext.class, null)); + all.addAll(SCMSourceTrait._for(this, null, GitHubSCMBuilder.class)); + Set> dedup = new HashSet<>(); + for (Iterator> iterator = all.iterator(); iterator.hasNext(); ) { + SCMTraitDescriptor d = iterator.next(); + if (dedup.contains(d) || d instanceof GitBrowserSCMSourceTrait.DescriptorImpl) { + // remove any we have seen already and ban the browser configuration as it will always be + // github + iterator.remove(); + } else { + dedup.add(d); + } + } + List>> result = new ArrayList<>(); + NamedArrayList.select( + all, + Messages.GitHubSCMNavigator_withinRepository(), + NamedArrayList.anyOf( + NamedArrayList.withAnnotation(Discovery.class), + NamedArrayList.withAnnotation(Selection.class)), + true, + result); + NamedArrayList.select(all, Messages.GitHubSCMNavigator_general(), null, true, result); + return result; + } + + public List getTraitsDefaults() { + return Arrays.asList( // TODO finalize + new BranchDiscoveryTrait(true, false), + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE)), + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustPermission())); } - /** - * Gets the repository name. - * @return the repository name. - */ - @Exported @NonNull - public String getRepository() { - return repository; + @Override + protected SCMHeadCategory[] createCategories() { + return new SCMHeadCategory[] { + new UncategorizedSCMHeadCategory(Messages._GitHubSCMSource_UncategorizedCategory()), + new ChangeRequestSCMHeadCategory(Messages._GitHubSCMSource_ChangeRequestCategory()), + new TagSCMHeadCategory(Messages._GitHubSCMSource_TagCategory()) + }; } + } - /** - * Gets the repository URL as specified by the user. - * @return the repository URL as specified by the user. - */ - @Restricted(NoExternalUse.class) - @NonNull // Always returns a value so that users can always use the URL-based configuration when reconfiguring. - public String getRepositoryUrl() { - if (repositoryUrl != null) { - return repositoryUrl; - } else { - if (GITHUB_URL.equals(apiUri)) - return "https://github.com/" + repoOwner + '/' + repository; - else - return String.format("%s%s/%s", removeEnd(apiUri, API_V3), repoOwner, repository); - } + @Restricted(NoExternalUse.class) + class LazyPullRequests extends LazyIterable implements Closeable { + private final GitHubSCMSourceRequest request; + private final GHRepository repo; + private Set pullRequestMetadataKeys = new HashSet<>(); + private boolean fullScanRequested = false; + private boolean iterationCompleted = false; + + public LazyPullRequests(GitHubSCMSourceRequest request, GHRepository repo) { + this.request = request; + this.repo = repo; } - /** - * {@inheritDoc} - * @since 2.2.0 - */ @Override - public List getTraits() { - return traits; + protected Iterable create() { + try { + Set prs = request.getRequestedPullRequestNumbers(); + if (prs != null && prs.size() == 1) { + Integer number = prs.iterator().next(); + request.listener().getLogger().format("%n Getting remote pull request #%d...%n", number); + GHPullRequest pullRequest = repo.getPullRequest(number); + if (pullRequest.getState() != GHIssueState.OPEN) { + return Collections.emptyList(); + } + return new CacheUpdatingIterable(Collections.singletonList(pullRequest)); + } + Set branchNames = request.getRequestedOriginBranchNames(); + if (branchNames != null + && branchNames.size() == 1) { // TODO flag to check PRs are all origin PRs + // if we were including multiple PRs and they are not all from the same origin branch + // then branchNames would have a size > 1 therefore if the size is 1 we must only + // be after PRs that come from this named branch + String branchName = branchNames.iterator().next(); + request + .listener() + .getLogger() + .format("%n Getting remote pull requests from branch %s...%n", branchName); + return new CacheUpdatingIterable( + repo.queryPullRequests() + .state(GHIssueState.OPEN) + .head(repo.getOwnerName() + ":" + branchName) + .list()); + } + request.listener().getLogger().format("%n Getting remote pull requests...%n"); + fullScanRequested = true; + return new CacheUpdatingIterable( + LazyPullRequests.this.repo.queryPullRequests().state(GHIssueState.OPEN).list()); + } catch (IOException e) { + throw new GitHubSCMSource.WrappedException(e); + } } - /** - * Sets the behaviours that are applied to this {@link GitHubSCMSource}. - * @param traits the behaviours that are to be applied. - */ - @DataBoundSetter - public void setTraits(@CheckForNull List traits) { - this.traits = new ArrayList<>(Util.fixNull(traits)); + @Override + public void close() throws IOException { + if (fullScanRequested && iterationCompleted) { + // we needed a full scan and the scan was completed, so trim the cache entries + pullRequestMetadataCache.keySet().retainAll(pullRequestMetadataKeys); + pullRequestContributorCache.keySet().retainAll(pullRequestMetadataKeys); + if (Jenkins.get().getInitLevel().compareTo(InitMilestone.JOB_LOADED) > 0) { + // synchronization should be cheap as only writers would be looking for this just to + // write null + synchronized (pullRequestSourceMapLock) { + pullRequestSourceMap = null; // all data has to have been migrated + } + } + } + } + + private class CacheUpdatingIterable extends SinglePassIterable { + /** + * A map of all fully populated {@link GHUser} entries we have fetched, keyed by {@link + * GHUser#getLogin()}. + */ + private Map users = new HashMap<>(); + + CacheUpdatingIterable(Iterable delegate) { + super(delegate); + } + + @Override + public void observe(GHPullRequest pr) { + int number = pr.getNumber(); + GHUser user = null; + try { + user = pr.getUser(); + if (users.containsKey(user.getLogin())) { + // looked up this user already + user = users.get(user.getLogin()); + } + ContributorMetadataAction contributor = + new ContributorMetadataAction(user.getLogin(), user.getName(), user.getEmail()); + pullRequestContributorCache.put(number, contributor); + // store the populated user record now that we have it + users.put(user.getLogin(), user); + } catch (FileNotFoundException e) { + // If file not found for user, warn but keep going + request + .listener() + .getLogger() + .format( + "%n Could not find user %s for pull request %d.%n", + user == null ? "null" : user.getLogin(), number); + throw new WrappedException(e); + } catch (IOException e) { + throw new WrappedException(e); + } + + pullRequestMetadataCache.put( + number, + new ObjectMetadataAction( + pr.getTitle(), pr.getBody(), pr.getHtmlUrl().toExternalForm())); + pullRequestMetadataKeys.add(number); + } + + @Override + public void completed() { + // we have completed a full iteration of the PRs from the delegate + iterationCompleted = true; + } + } + } + + @Restricted(NoExternalUse.class) + static class LazyBranches extends LazyIterable { + private final GitHubSCMSourceRequest request; + private final GHRepository repo; + + public LazyBranches(GitHubSCMSourceRequest request, GHRepository repo) { + this.request = request; + this.repo = repo; } - /** - * Use defaults for old settings. - */ - @SuppressWarnings("ConstantConditions") - @SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification="Only non-null after we set them here!") - private Object readResolve() { - if (scanCredentialsId != null) { - credentialsId = scanCredentialsId; - } - if (pullRequestMetadataCache == null) { - pullRequestMetadataCache = new ConcurrentHashMap<>(); - } - if (pullRequestContributorCache == null) { - pullRequestContributorCache = new ConcurrentHashMap<>(); - } - if (traits == null) { - boolean buildOriginBranch = this.buildOriginBranch == null || this.buildOriginBranch; - boolean buildOriginBranchWithPR = this.buildOriginBranchWithPR == null || this.buildOriginBranchWithPR; - boolean buildOriginPRMerge = this.buildOriginPRMerge != null && this.buildOriginPRMerge; - boolean buildOriginPRHead = this.buildOriginPRHead != null && this.buildOriginPRHead; - boolean buildForkPRMerge = this.buildForkPRMerge == null || this.buildForkPRMerge; - boolean buildForkPRHead = this.buildForkPRHead != null && this.buildForkPRHead; - List traits = new ArrayList<>(); - if (buildOriginBranch || buildOriginBranchWithPR) { - traits.add(new BranchDiscoveryTrait(buildOriginBranch, buildOriginBranchWithPR)); - } - if (buildOriginPRMerge || buildOriginPRHead) { - EnumSet s = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - if (buildOriginPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); + @Override + protected Iterable create() { + try { + Set branchNames = request.getRequestedOriginBranchNames(); + if (branchNames != null && branchNames.size() == 1) { + String branchName = branchNames.iterator().next(); + request.listener().getLogger().format("%n Getting remote branch %s...%n", branchName); + try { + GHBranch branch = repo.getBranch(branchName); + return Collections.singletonList(branch); + } catch (FileNotFoundException e) { + // branch does not currently exist + return Collections.emptyList(); + } + } + request.listener().getLogger().format("%n Getting remote branches...%n"); + // local optimization: always try the default branch first in any search + List values = new ArrayList<>(repo.getBranches().values()); + final String defaultBranch = StringUtils.defaultIfBlank(repo.getDefaultBranch(), "master"); + Collections.sort( + values, + new Comparator() { + @Override + public int compare(GHBranch o1, GHBranch o2) { + if (defaultBranch.equals(o1.getName())) { + return -1; } - if (buildOriginPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); + if (defaultBranch.equals(o2.getName())) { + return 1; } - traits.add(new OriginPullRequestDiscoveryTrait(s)); + return 0; + } + }); + return values; + } catch (IOException e) { + throw new GitHubSCMSource.WrappedException(e); + } + } + } + + @Restricted(NoExternalUse.class) + static class LazyTags extends LazyIterable { + private final GitHubSCMSourceRequest request; + private final GHRepository repo; + + public LazyTags(GitHubSCMSourceRequest request, GHRepository repo) { + this.request = request; + this.repo = repo; + } + + @Override + protected Iterable create() { + try { + final Set tagNames = request.getRequestedTagNames(); + if (tagNames != null && tagNames.size() == 1) { + String tagName = tagNames.iterator().next(); + request.listener().getLogger().format("%n Getting remote tag %s...%n", tagName); + try { + // Do not blow up if the tag is not present + GHRef tag = repo.getRef("tags/" + tagName); + return Collections.singletonList(tag); + } catch (FileNotFoundException e) { + // branch does not currently exist + return Collections.emptyList(); + } catch (Error e) { + if (e.getCause() instanceof GHFileNotFoundException) { + return Collections.emptyList(); } - if (buildForkPRMerge || buildForkPRHead) { - EnumSet s = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - if (buildForkPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } - if (buildForkPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } - traits.add(new ForkPullRequestDiscoveryTrait(s, new ForkPullRequestDiscoveryTrait.TrustPermission())); - } - if (!"*".equals(includes) || !"".equals(excludes)) { - traits.add(new WildcardSCMHeadFilterTrait(includes, excludes)); - } - if (checkoutCredentialsId != null - && !DescriptorImpl.SAME.equals(checkoutCredentialsId) - && !checkoutCredentialsId.equals(scanCredentialsId)) { - traits.add(new SSHCheckoutTrait(checkoutCredentialsId)); - } - this.traits = traits; - } - if (isBlank(apiUri)) { - setApiUri(GITHUB_URL); - } else if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { - setApiUri(apiUri); - } - return this; - } - - /** - * Returns how long to delay events received from GitHub in order to allow the API caches to sync. - * - * @return how long to delay events received from GitHub in order to allow the API caches to sync. - */ - public static int getEventDelaySeconds() { - return eventDelaySeconds; - } - - /** - * Sets how long to delay events received from GitHub in order to allow the API caches to sync. - * - * @param eventDelaySeconds number of seconds to delay, will be restricted into a value within the range - * {@code [0,300]} inclusive - */ - @Restricted(NoExternalUse.class) // to allow configuration from system groovy console - public static void setEventDelaySeconds(int eventDelaySeconds) { - GitHubSCMSource.eventDelaySeconds = Math.min(300, Math.max(0, eventDelaySeconds)); - } - - /** - * Returns how many megabytes of on-disk cache to maintain per GitHub API URL per credentials. - * - * @return how many megabytes of on-disk cache to maintain per GitHub API URL per credentials. - */ - public static int getCacheSize() { - return cacheSize; - } - - /** - * Sets how long to delay events received from GitHub in order to allow the API caches to sync. - * - * @param cacheSize how many megabytes of on-disk cache to maintain per GitHub API URL per credentials, - * will be restricted into a value within the range {@code [0,1024]} inclusive. - */ - @Restricted(NoExternalUse.class) // to allow configuration from system groovy console - public static void setCacheSize(int cacheSize) { - GitHubSCMSource.cacheSize = Math.min(1024, Math.max(0, cacheSize)); - } - - /** - * {@inheritDoc} - */ - @Override - public String getRemote() { - return GitHubSCMBuilder.uriResolver(getOwner(), apiUri, credentialsId) - .getRepositoryUri(apiUri, repoOwner, repository); - } - - /** - * {@inheritDoc} - */ - @Override - public String getPronoun() { - return Messages.GitHubSCMSource_Pronoun(); - } - - /** - * Returns a {@link RepositoryUriResolver} according to credentials configuration. - * - * @return a {@link RepositoryUriResolver} - * @deprecated use {@link GitHubSCMBuilder#uriResolver()} or {@link GitHubSCMBuilder#uriResolver(Item, String, String)}. - */ - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public RepositoryUriResolver getUriResolver() { - return GitHubSCMBuilder.uriResolver( - getOwner(), - apiUri, - credentialsId - ); - } - - @Restricted(NoExternalUse.class) - @RestrictedSince("2.2.0") - @Deprecated - @CheckForNull - public String getScanCredentialsId() { - return credentialsId; - } - - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @Deprecated - public void setScanCredentialsId(@CheckForNull String credentialsId) { - this.credentialsId = credentialsId; - } - - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @Deprecated - @CheckForNull - public String getCheckoutCredentialsId() { - for (SCMSourceTrait trait : traits) { - if (trait instanceof SSHCheckoutTrait) { - return StringUtils.defaultString( - ((SSHCheckoutTrait) trait).getCredentialsId(), - GitHubSCMSource.DescriptorImpl.ANONYMOUS - ); - } - } - return DescriptorImpl.SAME; - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setIncludes(@NonNull String includes) { - for (int i = 0; i < traits.size(); i++) { - SCMSourceTrait trait = traits.get(i); - if (trait instanceof WildcardSCMHeadFilterTrait) { - WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; - if ("*".equals(includes) && "".equals(existing.getExcludes())) { - traits.remove(i); - } else { - traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes())); - } - return; - } - } - if (!"*".equals(includes)) { - traits.add(new WildcardSCMHeadFilterTrait(includes, "")); - } - } - - @Deprecated - @Restricted(NoExternalUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setExcludes(@NonNull String excludes) { - for (int i = 0; i < traits.size(); i++) { - SCMSourceTrait trait = traits.get(i); - if (trait instanceof WildcardSCMHeadFilterTrait) { - WildcardSCMHeadFilterTrait existing = (WildcardSCMHeadFilterTrait) trait; - if ("*".equals(existing.getIncludes()) && "".equals(excludes)) { - traits.remove(i); - } else { - traits.set(i, new WildcardSCMHeadFilterTrait(existing.getIncludes(), excludes)); - } - return; - } - } - if (!"".equals(excludes)) { - traits.add(new WildcardSCMHeadFilterTrait("*", excludes)); - } - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginBranch() { - for (SCMTrait trait : traits) { - if (trait instanceof BranchDiscoveryTrait) { - return ((BranchDiscoveryTrait) trait).isBuildBranch(); - } - } - return false; - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginBranch(boolean buildOriginBranch) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof BranchDiscoveryTrait) { - BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; - if (buildOriginBranch || previous.isBuildBranchesWithPR()) { - traits.set(i, new BranchDiscoveryTrait(buildOriginBranch, previous.isBuildBranchesWithPR())); - } else { - traits.remove(i); - } - return; - } - } - if (buildOriginBranch) { - traits.add(new BranchDiscoveryTrait(buildOriginBranch, false)); - } - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginBranchWithPR() { - for (SCMTrait trait : traits) { - if (trait instanceof BranchDiscoveryTrait) { - return ((BranchDiscoveryTrait) trait).isBuildBranchesWithPR(); - } - } - return false; - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginBranchWithPR(boolean buildOriginBranchWithPR) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof BranchDiscoveryTrait) { - BranchDiscoveryTrait previous = (BranchDiscoveryTrait) trait; - if (buildOriginBranchWithPR || previous.isBuildBranch()) { - traits.set(i, new BranchDiscoveryTrait(previous.isBuildBranch(), buildOriginBranchWithPR)); - } else { - traits.remove(i); + throw e; + } + } + request.listener().getLogger().format("%n Getting remote tags...%n"); + // GitHub will give a 404 if the repository does not have any tags + // we could rework the code that iterates to expect the 404, but that + // would mean leaking the strange behaviour in every trait that consults the list + // of tags. (And GitHub API is probably correct in throwing the GHFileNotFoundException + // from a PagedIterable, so we don't want to fix that) + // + // Instead we just return a wrapped iterator that does the right thing. + final Iterable iterable = repo.listRefs("tags"); + return new Iterable() { + @Override + public Iterator iterator() { + final Iterator iterator; + try { + iterator = iterable.iterator(); + } catch (Error e) { + if (e.getCause() instanceof GHFileNotFoundException) { + return Collections.emptyIterator(); + } + throw e; + } + return new Iterator() { + boolean hadAtLeastOne; + boolean hasNone; + + @Override + public boolean hasNext() { + try { + boolean hasNext = iterator.hasNext(); + hadAtLeastOne = hadAtLeastOne || hasNext; + return hasNext; + } catch (Error e) { + // pre https://github.com/kohsuke/github-api/commit + // /a17ce04552ddd3f6bd8210c740184e6c7ad13ae4 + // we at least got the cause, even if wrapped in an Error + if (e.getCause() instanceof GHFileNotFoundException) { + return false; + } + throw e; + } catch (GHException e) { + // JENKINS-52397 I have no clue why https://github.com/kohsuke/github-api/commit + // /a17ce04552ddd3f6bd8210c740184e6c7ad13ae4 does what it does, but it makes + // it rather difficult to distinguish between a network outage and the file + // not found. + if (hadAtLeastOne) { + throw e; + } + try { + hasNone = hasNone || repo.getRefs("tags").length == 0; + if (hasNone) return false; + throw e; + } catch (FileNotFoundException e1) { + hasNone = true; + return false; + } catch (IOException e1) { + e.addSuppressed(e1); + throw e; + } } - return; - } - } - if (buildOriginBranchWithPR) { - traits.add(new BranchDiscoveryTrait(false, buildOriginBranchWithPR)); - } - } + } - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginPRMerge() { - for (SCMTrait trait : traits) { - if (trait instanceof OriginPullRequestDiscoveryTrait) { - return ((OriginPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.MERGE); - } - } - return false; - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginPRMerge(boolean buildOriginPRMerge) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof OriginPullRequestDiscoveryTrait) { - Set s = ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); - if (buildOriginPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } else { - s.remove(ChangeRequestCheckoutStrategy.MERGE); + @Override + public GHRef next() { + if (!hasNext()) { + throw new NoSuchElementException(); } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } - } - if (buildOriginPRMerge) { - traits.add(new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); - } - } - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildOriginPRHead() { - for (SCMTrait trait : traits) { - if (trait instanceof OriginPullRequestDiscoveryTrait) { - return ((OriginPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.HEAD); - } - } - return false; - - } + return iterator.next(); + } - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildOriginPRHead(boolean buildOriginPRHead) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof OriginPullRequestDiscoveryTrait) { - Set s = ((OriginPullRequestDiscoveryTrait) trait).getStrategies(); - if (buildOriginPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } else { - s.remove(ChangeRequestCheckoutStrategy.HEAD); - } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } - } - if (buildOriginPRHead) { - traits.add(new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); - } + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; + } + }; + } catch (IOException e) { + throw new GitHubSCMSource.WrappedException(e); + } } + } - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildForkPRMerge() { - for (SCMTrait trait : traits) { - if (trait instanceof ForkPullRequestDiscoveryTrait) { - return ((ForkPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.MERGE); - } - } - return false; - } + private static class CriteriaWitness implements SCMSourceRequest.Witness { + private final TaskListener listener; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildForkPRMerge(boolean buildForkPRMerge) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof ForkPullRequestDiscoveryTrait) { - ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; - Set s = forkTrait.getStrategies(); - if (buildForkPRMerge) { - s.add(ChangeRequestCheckoutStrategy.MERGE); - } else { - s.remove(ChangeRequestCheckoutStrategy.MERGE); - } - traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); - return; - } - } - if (buildForkPRMerge) { - traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), - new ForkPullRequestDiscoveryTrait.TrustPermission())); - } + public CriteriaWitness(TaskListener listener) { + this.listener = listener; } - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public boolean getBuildForkPRHead() { - for (SCMTrait trait : traits) { - if (trait instanceof ForkPullRequestDiscoveryTrait) { - return ((ForkPullRequestDiscoveryTrait) trait).getStrategies() - .contains(ChangeRequestCheckoutStrategy.HEAD); - } - } - return false; + @Override + public void record(@NonNull SCMHead head, SCMRevision revision, boolean isMatch) { + if (isMatch) { + listener.getLogger().format(" Met criteria%n"); + } else { + listener.getLogger().format(" Does not meet criteria%n"); + } } + } + private static class MergabilityWitness + implements SCMSourceRequest.Witness { + private final GHPullRequest pr; + private final ChangeRequestCheckoutStrategy strategy; + private final TaskListener listener; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - @DataBoundSetter - public void setBuildForkPRHead(boolean buildForkPRHead) { - for (int i = 0; i < traits.size(); i++) { - SCMTrait trait = traits.get(i); - if (trait instanceof ForkPullRequestDiscoveryTrait) { - ForkPullRequestDiscoveryTrait forkTrait = (ForkPullRequestDiscoveryTrait) trait; - Set s = forkTrait.getStrategies(); - if (buildForkPRHead) { - s.add(ChangeRequestCheckoutStrategy.HEAD); - } else { - s.remove(ChangeRequestCheckoutStrategy.HEAD); - } - traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); - return; - } - } - if (buildForkPRHead) { - traits.add(new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), - new ForkPullRequestDiscoveryTrait.TrustPermission())); - } + public MergabilityWitness( + GHPullRequest pr, ChangeRequestCheckoutStrategy strategy, TaskListener listener) { + this.pr = pr; + this.strategy = strategy; + this.listener = listener; } @Override - protected final void retrieve(@CheckForNull SCMSourceCriteria criteria, - @NonNull SCMHeadObserver observer, - @CheckForNull SCMHeadEvent event, - @NonNull final TaskListener listener) throws IOException, InterruptedException { - StandardCredentials credentials = Connector.lookupScanCredentials((Item)getOwner(), apiUri, credentialsId); - // Github client and validation - final GitHub github = Connector.connect(apiUri, credentials); + public void record( + @NonNull PullRequestSCMHead head, PullRequestSCMRevision revision, boolean isMatch) { + if (isMatch) { + Boolean mergeable; try { - Connector.configureLocalRateLimitChecker(listener, github); - - try { - // Input data validation - Connector.checkConnectionValidity(apiUri, listener, credentials, github); - - // Input data validation - if (isBlank(repository)) { - throw new AbortException("No repository selected, skipping"); - } - - String fullName = repoOwner + "/" + repository; - ghRepository = github.getRepository(fullName); - final GHRepository ghRepository = this.ghRepository; - listener.getLogger().format("Examining %s%n", - HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); - resolvedRepositoryUrl = ghRepository.getHtmlUrl(); - try (final GitHubSCMSourceRequest request = new GitHubSCMSourceContext(criteria, observer) - .withTraits(traits) - .newRequest(this, listener)) { - // populate the request with its data sources - request.setGitHub(github); - request.setRepository(ghRepository); - if (request.isFetchPRs()) { - request.setPullRequests(new LazyPullRequests(request, ghRepository)); - } - if (request.isFetchBranches()) { - request.setBranches(new LazyBranches(request, ghRepository)); - } - if (request.isFetchTags()) { - request.setTags(new LazyTags(request, ghRepository)); - } - request.setCollaboratorNames(new LazyContributorNames(request, listener, github, ghRepository, credentials)); - request.setPermissionsSource(new GitHubPermissionsSource() { - @Override - public GHPermissionType fetch(String username) throws IOException, InterruptedException { - return ghRepository.getPermission(username); - } - }); - - if (request.isFetchBranches() && !request.isComplete()) { - listener.getLogger().format("%n Checking branches...%n"); - int count = 0; - for (final GHBranch branch : request.getBranches()) { - count++; - String branchName = branch.getName(); - listener.getLogger().format("%n Checking branch %s%n", HyperlinkNote - .encodeTo(resolvedRepositoryUrl + "/tree/" + branchName, branchName)); - BranchSCMHead head = new BranchSCMHead(branchName); - if (request.process(head, new SCMRevisionImpl(head, branch.getSHA1()), - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create(@NonNull BranchSCMHead head, - @Nullable SCMRevisionImpl revisionInfo) - throws IOException, InterruptedException { - return new GitHubSCMProbe( - apiUri, - credentials, - ghRepository, - head, - revisionInfo); - } - }, new CriteriaWitness(listener))) { - listener.getLogger().format("%n %d branches were processed (query completed)%n", count); - break; - } - } - listener.getLogger().format("%n %d branches were processed%n", count); - } - if (request.isFetchPRs() && !request.isComplete()) { - listener.getLogger().format("%n Checking pull-requests...%n"); - int count = 0; - int errorCount = 0; - Map> strategies = request.getPRStrategies(); - - // JENKINS-56996 - // PRs are one the most error prone areas for scans - // Branches and tags are contained only the current repo, PRs go across forks - // FileNotFoundException can occur in a number of situations - // When this happens, it is not ideal behavior but it is better to let the PR be orphaned - // and the orphan strategy control the result than for this error to stop scanning - // (For Org scanning this is particularly important.) - // If some more general IO exception is thrown, we will still fail. - - validatePullRequests(request); - for (final GHPullRequest pr : request.getPullRequests()) { - int number = pr.getNumber(); - try { - retrievePullRequest(apiUri, credentials, ghRepository, pr, strategies, request, listener); - } catch (FileNotFoundException e) { - listener.getLogger().format("%n Error while processing pull request %d%n", number); - Functions.printStackTrace(e, listener.getLogger()); - errorCount++; - } - count++; - } - listener.getLogger().format("%n %d pull requests were processed%n", count); - if (errorCount > 0 ) { - listener.getLogger().format("%n %d pull requests encountered errors and were orphaned.%n", count); - } - } - if (request.isFetchTags() && !request.isComplete()) { - listener.getLogger().format("%n Checking tags...%n"); - int count = 0; - for (final GHRef tag : request.getTags()) { - String tagName = tag.getRef(); - if (!tagName.startsWith(Constants.R_TAGS)) { - // should never happen, but if it does we should skip - continue; - } - tagName = tagName.substring(Constants.R_TAGS.length()); - count++; - listener.getLogger().format("%n Checking tag %s%n", HyperlinkNote - .encodeTo(resolvedRepositoryUrl + "/tree/" + tagName, tagName)); - long tagDate = 0L; - String sha = tag.getObject().getSha(); - if ("tag".equalsIgnoreCase(tag.getObject().getType())) { - // annotated tag object - try { - GHTagObject tagObject = request.getRepository().getTagObject(sha); - tagDate = tagObject.getTagger().getDate().getTime(); - // we want the sha of the tagged commit not the tag object - sha = tagObject.getObject().getSha(); - } catch (IOException e) { - // ignore, if the tag doesn't exist, the probe will handle that correctly - // we just need enough of a date value to allow for probing - } - } else { - try { - GHCommit commit = request.getRepository().getCommit(sha); - tagDate = commit.getCommitDate().getTime(); - } catch (IOException e) { - // ignore, if the tag doesn't exist, the probe will handle that correctly - // we just need enough of a date value to allow for probing - } - } - GitHubTagSCMHead head = new GitHubTagSCMHead(tagName, tagDate); - if (request.process(head, new GitTagSCMRevision(head, sha), - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create(@NonNull GitHubTagSCMHead head, - @Nullable GitTagSCMRevision revisionInfo) - throws IOException, InterruptedException { - return new GitHubSCMProbe( - apiUri, - credentials, - ghRepository, - head, - revisionInfo); - } - }, new CriteriaWitness(listener))) { - listener.getLogger() - .format("%n %d tags were processed (query completed)%n", count); - break; - } - } - listener.getLogger().format("%n %d tags were processed%n", count); - } - } - listener.getLogger().format("%nFinished examining %s%n%n", fullName); - } catch (WrappedException e) { - try { - e.unwrap(); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } - } - } finally { - Connector.release(github); - } - } - - private static void validatePullRequests(GitHubSCMSourceRequest request) { - // JENKINS-56996 - // This method handles the case where there would be an error - // while finding a user inside the PR iterator. - // Once this is done future iterations over PR use a cached list. - // We could do this at the same time as processing each PR, but - // this is clearer and safer. - Iterator iterator = request.getPullRequests().iterator(); - while (iterator.hasNext()) { - try { - try { - iterator.next(); - } catch (NoSuchElementException e) { - break; - } catch (WrappedException wrapped) { - wrapped.unwrap(); - } - } catch (FileNotFoundException e) { - // File not found exceptions are ignorable - } catch (IOException | InterruptedException e) { - throw new WrappedException(e); - } - } + mergeable = pr.getMergeable(); + } catch (IOException e) { + throw new GitHubSCMSource.WrappedException(e); + } + if (Boolean.FALSE.equals(mergeable)) { + switch (strategy) { + case MERGE: + listener.getLogger().format(" Not mergeable, build likely to fail%n"); + break; + default: + listener.getLogger().format(" Not mergeable, but will be built anyway%n"); + break; + } + } + } + } + } + + private class LazyContributorNames extends LazySet { + private final GitHubSCMSourceRequest request; + private final TaskListener listener; + private final GitHub github; + private final GHRepository repo; + private final StandardCredentials credentials; + + public LazyContributorNames( + GitHubSCMSourceRequest request, + TaskListener listener, + GitHub github, + GHRepository repo, + StandardCredentials credentials) { + this.request = request; + this.listener = listener; + this.github = github; + this.repo = repo; + this.credentials = credentials; + } + + /** {@inheritDoc} */ + @NonNull + @Override + protected Set create() { + try { + return updateCollaboratorNames(listener, credentials, repo); + } catch (IOException e) { + throw new WrappedException(e); + } } + } - private static void retrievePullRequest( - final String apiUri, - final StandardCredentials credentials, - @NonNull final GHRepository ghRepository, - @NonNull final GHPullRequest pr, - @NonNull final Map> strategies, - @NonNull final GitHubSCMSourceRequest request, - @NonNull final TaskListener listener) - throws IOException, InterruptedException { - - int number = pr.getNumber(); - listener.getLogger().format("%n Checking pull request %s%n", - HyperlinkNote.encodeTo(pr.getHtmlUrl().toString(), "#" + number)); - boolean fork = !ghRepository.getOwner().equals(pr.getHead().getUser()); - if (strategies.get(fork).isEmpty()) { - if (fork) { - listener.getLogger().format(" Submitted from fork, skipping%n%n"); - } else { - listener.getLogger().format(" Submitted from origin repository, skipping%n%n"); - } - return; - } - for (final ChangeRequestCheckoutStrategy strategy : strategies.get(fork)) { - final String branchName; - if (strategies.get(fork).size() == 1) { - branchName = "PR-" + number; - } else { - branchName = "PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH); - } - - // PR details only needed for merge PRs - if (strategy == ChangeRequestCheckoutStrategy.MERGE) { - // The probe github will be closed along with the probe. - final GitHub gitHub = Connector.connect(apiUri, credentials); - try { - ensureDetailedGHPullRequest(pr, listener, gitHub, ghRepository); - } - finally { - Connector.release(gitHub); - } - } + private class DeferredContributorNames extends LazySet { + private final GitHubSCMSourceRequest request; + private final TaskListener listener; - if (request.process(new PullRequestSCMHead( - pr, branchName, strategy == ChangeRequestCheckoutStrategy.MERGE - ), - null, - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create(@NonNull PullRequestSCMHead head, - @Nullable Void revisionInfo) - throws IOException, InterruptedException { - boolean trusted = request.isTrusted(head); - if (!trusted) { - listener.getLogger().format(" (not from a trusted source)%n"); - } - return new GitHubSCMProbe( - apiUri, credentials, - ghRepository, trusted ? head : head.getTarget(), null); - } - }, - new SCMSourceRequest.LazyRevisionLambda() { - @NonNull - @Override - public SCMRevision create(@NonNull PullRequestSCMHead head, - @Nullable Void ignored) - throws IOException, InterruptedException { - - return createPullRequestSCMRevision(pr, head, listener, ghRepository); - } - }, - new MergabilityWitness(pr, strategy, listener), - new CriteriaWitness(listener) - )) { - listener.getLogger().format( - "%n Pull request %d processed (query completed)%n", - number - ); - } - } + public DeferredContributorNames(GitHubSCMSourceRequest request, TaskListener listener) { + this.request = request; + this.listener = listener; } - + /** {@inheritDoc} */ @NonNull @Override - protected Set retrieveRevisions(@NonNull TaskListener listener, Item retrieveContext) throws IOException, InterruptedException { - StandardCredentials credentials = Connector.lookupScanCredentials(retrieveContext, apiUri, credentialsId); - // Github client and validation - final GitHub github = Connector.connect(apiUri, credentials); + protected Set create() { + if (collaboratorNames != null) { + return collaboratorNames; + } + listener + .getLogger() + .format( + "Connecting to %s to obtain list of collaborators for %s/%s%n", + apiUri, repoOwner, repository); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + // Github client and validation + try { + GitHub github = Connector.connect(apiUri, credentials); try { - Connector.configureLocalRateLimitChecker(listener, github); - Set result = new TreeSet<>(); - - try { - // Input data validation - Connector.checkConnectionValidity(apiUri, listener, credentials, github); - - // Input data validation - if (isBlank(repository)) { - throw new AbortException("No repository selected, skipping"); - } + Connector.configureLocalRateLimitChecker(listener, github); + + // Input data validation + Connector.checkConnectionValidity(apiUri, listener, credentials, github); + // Input data validation + String credentialsName = + credentials == null ? "anonymous access" : CredentialsNameProvider.name(credentials); + if (credentials != null && !isCredentialValid(github)) { + listener + .getLogger() + .format( + "Invalid scan credentials %s to connect to %s, " + + "assuming no trusted collaborators%n", + credentialsName, apiUri); + collaboratorNames = Collections.singleton(repoOwner); + } else { + if (!github.isAnonymous()) { + listener.getLogger().format("Connecting to %s using %s%n", apiUri, credentialsName); + } else { + listener + .getLogger() + .format("Connecting to %s with no credentials, anonymous access%n", apiUri); + } - String fullName = repoOwner + "/" + repository; - ghRepository = github.getRepository(fullName); - final GHRepository ghRepository = this.ghRepository; - listener.getLogger().format("Listing %s%n", - HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); - resolvedRepositoryUrl = ghRepository.getHtmlUrl(); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(traits); - boolean wantBranches = context.wantBranches(); - boolean wantTags = context.wantTags(); - boolean wantPRs = context.wantPRs(); - boolean wantSinglePRs = context.forkPRStrategies().size() == 1 || context.originPRStrategies().size() == 1; - boolean wantMultiPRs = context.forkPRStrategies().size() > 1 || context.originPRStrategies().size() > 1; - Set strategies = new TreeSet<>(); - strategies.addAll(context.forkPRStrategies()); - strategies.addAll(context.originPRStrategies()); - for (GHRef ref: ghRepository.listRefs()) { - String name = ref.getRef(); - if (name.startsWith(Constants.R_HEADS) && wantBranches) { - String branchName = name.substring(Constants.R_HEADS.length()); - listener.getLogger().format("%n Found branch %s%n", HyperlinkNote - .encodeTo(resolvedRepositoryUrl + "/tree/" + branchName, branchName)); - result.add(branchName); - continue; - } - if (name.startsWith(R_PULL) && wantPRs) { - int index = name.indexOf('/', R_PULL.length()); - if (index != -1) { - String number = name.substring(R_PULL.length(), index); - listener.getLogger().format("%n Found pull request %s%n", HyperlinkNote - .encodeTo(resolvedRepositoryUrl + "/pull/" + number, "#" + number)); - // we are allowed to return "invalid" names so if the user has configured, say - // origin as single strategy and fork as multiple strategies - // we will return PR-5, PR-5-merge and PR-5-head in the result set - // and leave it up to the call to retrieve to determine exactly - // whether the name is actually valid and resolve the correct SCMHead type - // - // this allows this method to avoid an API call for every PR in order to - // determine if the PR is an origin or a fork PR and allows us to just - // use the single (set) of calls to get all refs - if (wantSinglePRs) { - result.add("PR-" + number); - } - if (wantMultiPRs) { - for (ChangeRequestCheckoutStrategy strategy: strategies) { - result.add("PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH)); - } - } - } - continue; - } - if (name.startsWith(Constants.R_TAGS) && wantTags) { - String tagName = name.substring(Constants.R_TAGS.length()); - listener.getLogger().format("%n Found tag %s%n", HyperlinkNote - .encodeTo(resolvedRepositoryUrl + "/tree/" + tagName, tagName)); - result.add(tagName); - continue; - } - } - listener.getLogger().format("%nFinished listing %s%n%n", fullName); - } catch (WrappedException e) { - try { - e.unwrap(); - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } + // Input data validation + if (isBlank(getRepository())) { + collaboratorNames = Collections.singleton(repoOwner); + } else { + String fullName = repoOwner + "/" + repository; + ghRepository = github.getRepository(fullName); + resolvedRepositoryUrl = ghRepository.getHtmlUrl(); + return new LazyContributorNames(request, listener, github, ghRepository, credentials); } - return result; + } + return collaboratorNames; } finally { - Connector.release(github); + Connector.release(github); } + } catch (IOException | InterruptedException e) { + throw new WrappedException(e); + } } + } - @Override - protected SCMRevision retrieve(@NonNull String headName, @NonNull TaskListener listener, Item retrieveContext) - throws IOException, InterruptedException { - StandardCredentials credentials = Connector.lookupScanCredentials(retrieveContext, apiUri, credentialsId); - // Github client and validation - final GitHub github = Connector.connect(apiUri, credentials); - try { - Connector.configureLocalRateLimitChecker(listener, github); - // Input data validation - if (isBlank(repository)) { - throw new AbortException("No repository selected, skipping"); - } + private class DeferredPermissionsSource extends GitHubPermissionsSource implements Closeable { - String fullName = repoOwner + "/" + repository; - ghRepository = github.getRepository(fullName); - final GHRepository ghRepository = this.ghRepository; - listener.getLogger().format("Examining %s%n", - HyperlinkNote.encodeTo(ghRepository.getHtmlUrl().toString(), fullName)); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(traits); - Matcher prMatcher = Pattern.compile("^PR-(\\d+)(?:-(.*))?$").matcher(headName); - if (prMatcher.matches()) { - // it's a looking very much like a PR - int number = Integer.parseInt(prMatcher.group(1)); - listener.getLogger().format("Attempting to resolve %s as pull request %d%n", headName, number); - try { - GHPullRequest pr = ghRepository.getPullRequest(number); - if (pr != null) { - boolean fork = !ghRepository.getOwner().equals(pr.getHead().getUser()); - Set strategies; - if (context.wantPRs()) { - strategies = fork - ? context.forkPRStrategies() - : context.originPRStrategies(); - } else { - // if not configured, we go with merge - strategies = EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); - } - ChangeRequestCheckoutStrategy strategy; - if (prMatcher.group(2) == null) { - if (strategies.size() == 1) { - strategy = strategies.iterator().next(); - } else { - // invalid name - listener.getLogger().format( - "Resolved %s as pull request %d but indeterminate checkout strategy, " - + "please try %s or %s%n", - headName, - number, - headName + "-" + ChangeRequestCheckoutStrategy.HEAD.name(), - headName + "-" + ChangeRequestCheckoutStrategy.MERGE.name() - ); - return null; - } - } else { - strategy = null; - for (ChangeRequestCheckoutStrategy s: strategies) { - if (s.name().toLowerCase(Locale.ENGLISH).equals(prMatcher.group(2))) { - strategy = s; - break; - } - } - if (strategy == null) { - // invalid name; - listener.getLogger().format( - "Resolved %s as pull request %d but unknown checkout strategy %s, " - + "please try %s or %s%n", - headName, - number, - prMatcher.group(2), - headName + "-" + ChangeRequestCheckoutStrategy.HEAD.name(), - headName + "-" + ChangeRequestCheckoutStrategy.MERGE.name() - ); - return null; - } - } - PullRequestSCMHead head = new PullRequestSCMHead( - pr, headName, strategy == ChangeRequestCheckoutStrategy.MERGE - ); - if (head.isMerge()) { - ensureDetailedGHPullRequest(pr, listener, github, ghRepository); - } - PullRequestSCMRevision prRev = createPullRequestSCMRevision(pr, head, listener, ghRepository); - - switch (strategy) { - case MERGE: - try { - prRev.validateMergeHash(); - } catch (AbortException e) { - listener.getLogger().format("Resolved %s as pull request %d: %s.%n%n", - headName, - number, - e.getMessage()); - return null; - } - listener.getLogger().format( - "Resolved %s as pull request %d at revision %s merged onto %s as %s%n", - headName, - number, - prRev.getPullHash(), - prRev.getBaseHash(), - prRev.getMergeHash() - ); - break; - default: - listener.getLogger().format( - "Resolved %s as pull request %d at revision %s%n", - headName, - number, - prRev.getPullHash() - ); - break; - } - return prRev; - } else { - listener.getLogger().format( - "Could not resolve %s as pull request %d%n", - headName, - number - ); - } - } catch (FileNotFoundException e) { - // maybe some ****er created a branch or a tag called PR-_ - listener.getLogger().format( - "Could not resolve %s as pull request %d%n", - headName, - number - ); - } - } - try { - listener.getLogger().format("Attempting to resolve %s as a branch%n", headName); - GHBranch branch = ghRepository.getBranch(headName); - if (branch != null) { - listener.getLogger().format("Resolved %s as branch %s at revision %s%n", headName, branch.getName(), branch.getSHA1()); - return new SCMRevisionImpl(new BranchSCMHead(headName), branch.getSHA1()); - } - } catch (FileNotFoundException e) { - // maybe it's a tag - } - try { - listener.getLogger().format("Attempting to resolve %s as a tag%n", headName); - GHRef tag = ghRepository.getRef("tags/" + headName); - if (tag != null) { - long tagDate = 0L; - String tagSha = tag.getObject().getSha(); - if ("tag".equalsIgnoreCase(tag.getObject().getType())) { - // annotated tag object - try { - GHTagObject tagObject = ghRepository.getTagObject(tagSha); - tagDate = tagObject.getTagger().getDate().getTime(); - } catch (IOException e) { - // ignore, if the tag doesn't exist, the probe will handle that correctly - // we just need enough of a date value to allow for probing - } - } else { - try { - GHCommit commit = ghRepository.getCommit(tagSha); - tagDate = commit.getCommitDate().getTime(); - } catch (IOException e) { - // ignore, if the tag doesn't exist, the probe will handle that correctly - // we just need enough of a date value to allow for probing - } - } - listener.getLogger().format("Resolved %s as tag %s at revision %s%n", headName, headName, - tagSha); - return new GitTagSCMRevision(new GitHubTagSCMHead(headName, tagDate), tagSha); - } - } catch (FileNotFoundException e) { - // ok it doesn't exist - } - listener.error("Could not resolve %s", headName); + private final TaskListener listener; + private GitHub github; + private GHRepository repo; - // TODO try and resolve as a revision, but right now we'd need to know what branch the revision belonged to - // once GitSCMSource has support for arbitrary refs, we could just use that... but given that - // GitHubSCMBuilder constructs the refspec based on the branch name, without a specific "arbitrary ref" - // SCMHead subclass we cannot do anything here - return null; - } finally { - Connector.release(github); - } + public DeferredPermissionsSource(TaskListener listener) { + this.listener = listener; } - @NonNull - private Set updateCollaboratorNames(@NonNull TaskListener listener, @CheckForNull StandardCredentials credentials, - @NonNull GHRepository ghRepository) - throws IOException { - if (credentials == null && (apiUri == null || GITHUB_URL.equals(apiUri))) { - // anonymous access to GitHub will never get list of collaborators and will - // burn an API call, so no point in even trying - listener.getLogger().println("Anonymous cannot query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); - } else { - try { - return collaboratorNames = new HashSet<>(ghRepository.getCollaboratorNames()); - } catch (FileNotFoundException e) { - // not permitted - listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); - } catch (HttpException e) { - if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED - || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { - listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); - } else { - throw e; - } - } - } + @Override + public GHPermissionType fetch(String username) throws IOException, InterruptedException { + if (repo == null) { + listener + .getLogger() + .format( + "Connecting to %s to check permissions of obtain list of %s for %s/%s%n", + apiUri, username, repoOwner, repository); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); + github = Connector.connect(apiUri, credentials); + String fullName = repoOwner + "/" + repository; + repo = github.getRepository(fullName); + } + return repo.getPermission(username); } - private static class WrappedException extends RuntimeException { - - public WrappedException(Throwable cause) { - super(cause); - } - - public void unwrap() throws IOException, InterruptedException { - Throwable cause = getCause(); - if (cause instanceof IOException) { - throw (IOException) cause; - } - if (cause instanceof InterruptedException) { - throw (InterruptedException) cause; - } - if (cause instanceof RuntimeException) { - throw (RuntimeException) cause; - } - throw this; - } - - } - - @NonNull - @Override - protected SCMProbe createProbe(@NonNull SCMHead head, @CheckForNull final SCMRevision revision) throws IOException { - StandardCredentials credentials = Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); - // Github client and validation - GitHub github = Connector.connect(apiUri, credentials); - try { - String fullName = repoOwner + "/" + repository; - final GHRepository repo = github.getRepository(fullName); - return new GitHubSCMProbe(apiUri, credentials, repo, head, revision); - } catch (IOException | RuntimeException | Error e) { - throw e; - } finally { - Connector.release(github); - } - - } - - @Override - @CheckForNull - protected SCMRevision retrieve(SCMHead head, TaskListener listener) throws IOException, InterruptedException { - StandardCredentials credentials = Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); - - // Github client and validation - GitHub github = Connector.connect(apiUri, credentials); - try { - try { - Connector.checkConnectionValidity(apiUri, listener, credentials, github); - Connector.configureLocalRateLimitChecker(listener, github); - String fullName = repoOwner + "/" + repository; - ghRepository = github.getRepository(fullName); - final GHRepository ghRepository = this.ghRepository; - resolvedRepositoryUrl = ghRepository.getHtmlUrl(); - if (head instanceof PullRequestSCMHead) { - PullRequestSCMHead prhead = (PullRequestSCMHead) head; - GHPullRequest pr = ghRepository.getPullRequest(prhead.getNumber()); - if (prhead.isMerge()) { - ensureDetailedGHPullRequest(pr, listener, github, ghRepository); - } - PullRequestSCMRevision prRev = createPullRequestSCMRevision(pr, prhead, listener, ghRepository); - prRev.validateMergeHash(); - return prRev; - } else if (head instanceof GitHubTagSCMHead) { - GitHubTagSCMHead tagHead = (GitHubTagSCMHead) head; - GHRef tag = ghRepository.getRef("tags/" + tagHead.getName()); - String sha = tag.getObject().getSha(); - if ("tag".equalsIgnoreCase(tag.getObject().getType())) { - // annotated tag object - GHTagObject tagObject = ghRepository.getTagObject(sha); - // we want the sha of the tagged commit not the tag object - sha = tagObject.getObject().getSha(); - } - return new GitTagSCMRevision(tagHead, sha); - } else { - return new SCMRevisionImpl(head, ghRepository.getRef("heads/" + head.getName()).getObject().getSha()); - } - } catch (RateLimitExceededException rle) { - throw new AbortException(rle.getMessage()); - } - } finally { - Connector.release(github); - } - } - - private static PullRequestSCMRevision createPullRequestSCMRevision(GHPullRequest pr, PullRequestSCMHead prhead, TaskListener listener, GHRepository ghRepository) throws IOException, InterruptedException { - String baseHash = pr.getBase().getSha(); - String prHeadHash = pr.getHead().getSha(); - String mergeHash = null; - - if (prhead.isMerge()) { - if (Boolean.FALSE.equals(pr.getMergeable())) { - mergeHash = PullRequestSCMRevision.NOT_MERGEABLE_HASH; - } else if (Boolean.TRUE.equals(pr.getMergeable())) { - String proposedMergeHash = pr.getMergeCommitSha(); - GHCommit commit = null; - try { - commit = ghRepository.getCommit(proposedMergeHash); - } catch (FileNotFoundException e) { - listener.getLogger().format("Pull request %s : github merge_commit_sha not found (%s). Close and reopen the PR to reset its merge hash.%n", - pr.getNumber(), - proposedMergeHash); - } catch (IOException e) { - throw new AbortException("Error while retrieving pull request " + pr.getNumber() + " merge hash : " + e.toString()); - } - - if (commit != null) { - List parents = commit.getParentSHA1s(); - // Merge commits always merge against the most recent base commit they can detect. - if (parents.size() != 2) { - listener.getLogger().format("WARNING: Invalid github merge_commit_sha for pull request %s : merge commit %s with parents - %s.%n", - pr.getNumber(), - proposedMergeHash, - StringUtils.join(parents, "+")); - } else if (!parents.contains(prHeadHash)) { - // This is maintains the existing behavior from pre-2.5.x when the merge_commit_sha is out of sync from the requested prHead - listener.getLogger().format("WARNING: Invalid github merge_commit_sha for pull request %s : Head commit %s does match merge commit %s with parents - %s.%n", - pr.getNumber(), - prHeadHash, - proposedMergeHash, - StringUtils.join(parents, "+")); - } else { - // We found a merge_commit_sha with 2 parents and one matches the prHeadHash - // Use the other parent hash as the base. This keeps the merge hash in sync with head and base. - // It is possible that head or base hash will not exist in their branch by the time we build - // This is be true (and cause a failure) regardless of how we determine the commits. - mergeHash = proposedMergeHash; - baseHash = prHeadHash.equals(parents.get(0)) ? parents.get(1) : parents.get(0); - } - } - } - - // Merge PR jobs always merge against the most recent base branch commit they can detect. - // For an invalid merge_commit_sha, we need to query for most recent base commit separately - if (mergeHash == null) { - baseHash = ghRepository.getRef("heads/" + pr.getBase().getRef()).getObject().getSha(); - } - } - - return new PullRequestSCMRevision(prhead, baseHash, prHeadHash, mergeHash); - } - - private static void ensureDetailedGHPullRequest(GHPullRequest pr, TaskListener listener, GitHub github, GHRepository ghRepository) throws IOException, InterruptedException { - final long sleep = 1000; - int retryCountdown = 4; - - while (pr.getMergeable() == null && retryCountdown > 1) { - listener.getLogger().format( - "Waiting for GitHub to create a merge commit for pull request %d. Retrying %d more times...%n", - pr.getNumber(), - retryCountdown); - retryCountdown -= 1; - Thread.sleep(sleep); - } - - - } - - @Override - public SCM build(SCMHead head, SCMRevision revision) { - return new GitHubSCMBuilder(this, head, revision).withTraits(traits).build(); - } - - @CheckForNull - /*package*/ URL getResolvedRepositoryUrl() { - return resolvedRepositoryUrl; - } - - @Deprecated // TODO remove once migration from 1.x is no longer supported - PullRequestSource retrievePullRequestSource(int number) { - // we use a big honking great lock to prevent concurrent requests to github during job loading - Map pullRequestSourceMap; - synchronized (pullRequestSourceMapLock) { - pullRequestSourceMap = this.pullRequestSourceMap; - if (pullRequestSourceMap == null) { - this.pullRequestSourceMap = pullRequestSourceMap = new HashMap<>(); - if (StringUtils.isNotBlank(repository)) { - String fullName = repoOwner + "/" + repository; - LOGGER.log(Level.INFO, "Getting remote pull requests from {0}", fullName); - StandardCredentials credentials = - Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); - LogTaskListener listener = new LogTaskListener(LOGGER, Level.INFO); - try { - GitHub github = Connector.connect(apiUri, credentials); - try { - Connector.configureLocalRateLimitChecker(listener, github); - ghRepository = github.getRepository(fullName); - LOGGER.log(Level.INFO, "Got remote pull requests from {0}", fullName); - int n = 0; - for (GHPullRequest pr: ghRepository.queryPullRequests().state(GHIssueState.OPEN).list()) { - GHRepository repository = pr.getHead().getRepository(); - // JENKINS-41246 repository may be null for deleted forks - pullRequestSourceMap.put(pr.getNumber(), new PullRequestSource( - repository == null ? null : repository.getOwnerName(), - repository == null ? null : repository.getName(), - pr.getHead().getRef())); - n++; - } - } finally { - Connector.release(github); - } - } catch (IOException | InterruptedException e) { - LOGGER.log(Level.WARNING, - "Could not get all pull requests from " + fullName + ", there may be rebuilds", e); - } - } - } - return pullRequestSourceMap.get(number); - } - } - - /** - * Retained to migrate legacy configuration. - * @deprecated use {@link MergeWithGitSCMExtension}. - */ - @Restricted(NoExternalUse.class) - @RestrictedSince("2.2.0") - @Deprecated - private static class MergeWith extends GitSCMExtension { - private final String baseName; - private final String baseHash; - - private MergeWith(String baseName, String baseHash) { - this.baseName = baseName; - this.baseHash = baseHash; - } - - private Object readResolve() throws ObjectStreamException { - return new MergeWithGitSCMExtension("remotes/origin/"+baseName, baseHash); - } - } - @Override - public SCMRevision getTrustedRevision(SCMRevision revision, final TaskListener listener) - throws IOException, InterruptedException { - if (revision instanceof PullRequestSCMRevision) { - PullRequestSCMHead head = (PullRequestSCMHead) revision.getHead(); - - try (GitHubSCMSourceRequest request = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(traits) - .newRequest(this, listener)) { - if (collaboratorNames != null) { - request.setCollaboratorNames(collaboratorNames); - } else { - request.setCollaboratorNames(new DeferredContributorNames(request, listener)); - } - request.setPermissionsSource(new DeferredPermissionsSource(listener)); - if (request.isTrusted(head)) { - return revision; - } - } catch (WrappedException wrapped) { - try { - wrapped.unwrap(); - } catch (HttpException e) { - listener.getLogger() - .format("It seems %s is unreachable, assuming no trusted collaborators%n", - apiUri); - collaboratorNames = Collections.singleton(repoOwner); - } - } - PullRequestSCMRevision rev = (PullRequestSCMRevision) revision; - listener.getLogger().format("Loading trusted files from base branch %s at %s rather than %s%n", - head.getTarget().getName(), rev.getBaseHash(), rev.getPullHash()); - return new SCMRevisionImpl(head.getTarget(), rev.getBaseHash()); - } - return revision; - } - - /** - * {@inheritDoc} - */ - protected boolean isCategoryEnabled(@NonNull SCMHeadCategory category) { - for (SCMSourceTrait trait : traits) { - if (trait.isCategoryEnabled(category)) { - return true; - } - } - return false; - } - - /** - * {@inheritDoc} - */ - @NonNull @Override - protected List retrieveActions(@NonNull SCMHead head, - @CheckForNull SCMHeadEvent event, - @NonNull TaskListener listener) throws IOException, InterruptedException { - // TODO when we have support for trusted events, use the details from event if event was from trusted source - List result = new ArrayList<>(); - SCMSourceOwner owner = getOwner(); - if (owner instanceof Actionable) { - GitHubLink repoLink = ((Actionable) owner).getAction(GitHubLink.class); - if (repoLink != null) { - String url; - ObjectMetadataAction metadataAction; - if (head instanceof PullRequestSCMHead) { - // pull request to this repository - int number = ((PullRequestSCMHead) head).getNumber(); - url = repoLink.getUrl() + "/pull/" + number; - metadataAction = pullRequestMetadataCache.get(number); - if (metadataAction == null) { - // best effort - metadataAction = new ObjectMetadataAction(null, null, url); - } - ContributorMetadataAction contributor = pullRequestContributorCache.get(number); - if (contributor != null) { - result.add(contributor); - } - } else { - // branch in this repository - url = repoLink.getUrl() + "/tree/" + head.getName(); - metadataAction = new ObjectMetadataAction(head.getName(), null, url); - } - result.add(new GitHubLink("icon-github-branch", url)); - result.add(metadataAction); - } - if (head instanceof BranchSCMHead) { - for (GitHubDefaultBranch p : ((Actionable) owner).getActions(GitHubDefaultBranch.class)) { - if (StringUtils.equals(getRepoOwner(), p.getRepoOwner()) - && StringUtils.equals(repository, p.getRepository()) - && StringUtils.equals(p.getDefaultBranch(), head.getName())) { - result.add(new PrimaryInstanceMetadataAction()); - break; - } - } - } - } - return result; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - protected List retrieveActions(@CheckForNull SCMSourceEvent event, - @NonNull TaskListener listener) throws IOException { - // TODO when we have support for trusted events, use the details from event if event was from trusted source - List result = new ArrayList<>(); - result.add(new GitHubRepoMetadataAction()); - String repository = this.repository; - - StandardCredentials credentials = Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId); - GitHub hub = Connector.connect(apiUri, credentials); - try { - Connector.checkConnectionValidity(apiUri, listener, credentials, hub); - try { - ghRepository = hub.getRepository(getRepoOwner() + '/' + repository); - resolvedRepositoryUrl = ghRepository.getHtmlUrl(); - } catch (FileNotFoundException e) { - throw new AbortException( - String.format("Invalid scan credentials when using %s to connect to %s/%s on %s", - credentials == null ? "anonymous access" : CredentialsNameProvider.name(credentials), repoOwner, repository, apiUri)); - } - result.add(new ObjectMetadataAction(null, ghRepository.getDescription(), Util.fixEmpty(ghRepository.getHomepage()))); - result.add(new GitHubLink("icon-github-repo", ghRepository.getHtmlUrl())); - if (StringUtils.isNotBlank(ghRepository.getDefaultBranch())) { - result.add(new GitHubDefaultBranch(getRepoOwner(), repository, ghRepository.getDefaultBranch())); - } - return result; - } finally { - Connector.release(hub); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void afterSave() { - SCMSourceOwner owner = getOwner(); - if (owner != null) { - GitHubWebHook.get().registerHookFor(owner); - } - } - - @Symbol("github") - @Extension - public static class DescriptorImpl extends SCMSourceDescriptor implements CustomDescribableModel { - - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultIncludes = "*"; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultExcludes = ""; - public static final String ANONYMOUS = "ANONYMOUS"; - public static final String SAME = "SAME"; - // Prior to JENKINS-33161 the unconditional behavior was to build fork PRs plus origin branches, and try to build a merge revision for PRs. - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginBranch = true; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginBranchWithPR = true; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRMerge = false; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRHead = false; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRMerge = true; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRHead = false; - - @Initializer(before = InitMilestone.PLUGINS_STARTED) - public static void addAliases() { - XSTREAM2.addCompatibilityAlias("org.jenkinsci.plugins.github_branch_source.OriginGitHubSCMSource", GitHubSCMSource.class); - } - - @Override - public String getDisplayName() { - return Messages.GitHubSCMSource_DisplayName(); - } - - @Nonnull - public Map customInstantiate(@Nonnull Map arguments) { - Map arguments2 = new TreeMap<>(arguments); - arguments2.remove("repositoryUrl"); - arguments2.remove("configuredByUrl"); - return arguments2; - } - - @Nonnull - public UninstantiatedDescribable customUninstantiate(@Nonnull UninstantiatedDescribable ud) { - Map scmArguments = new TreeMap<>(ud.getArguments()); - scmArguments.remove("repositoryUrl"); - scmArguments.remove("configuredByUrl"); - return ud.withArguments(scmArguments); - } - - public ListBoxModel doFillCredentialsIdItems(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String credentialsId) { - if (context == null - ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) - : !context.hasPermission(Item.EXTENDED_READ)) { - return new StandardListBoxModel().includeCurrentValue(credentialsId); - } - return Connector.listScanCredentials(context, apiUri); - } - - @RequirePOST - @Restricted(NoExternalUse.class) - public FormValidation doCheckCredentialsId(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String value) { - return Connector.checkScanCredentials(context, apiUri, value); - } - - @RequirePOST - @Restricted(NoExternalUse.class) - public FormValidation doValidateRepositoryUrlAndCredentials(@CheckForNull @AncestorInPath Item context, - @QueryParameter String repositoryUrl, - @QueryParameter String credentialsId) { - if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) || - context != null && !context.hasPermission(Item.EXTENDED_READ)) { - return FormValidation.error("Unable to validate repository information"); // not supposed to be seeing this form - } - if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { - return FormValidation.error("Unable to validate repository information"); // not permitted to try connecting with these credentials - } - GitHubRepositoryInfo info; - - try { - info = GitHubRepositoryInfo.forRepositoryUrl(repositoryUrl); - } catch (IllegalArgumentException e) { - return FormValidation.error(e, e.getMessage()); - } - - StandardCredentials credentials = Connector.lookupScanCredentials(context, info.getApiUri(), credentialsId); - StringBuilder sb = new StringBuilder(); - try { - GitHub github = Connector.connect(info.getApiUri(), credentials); - try { - if (github.isCredentialValid()){ - sb.append("Credentials ok."); - } - - GHRepository repo = github.getRepository(info.getRepoOwner() + "/" + info.getRepository()); - if (repo != null) { - sb.append(" Connected to "); - sb.append(repo.getHtmlUrl()); - sb.append("."); - } - } finally { - Connector.release(github); - } - } catch (IOException e) { - return FormValidation.error(e, "Error validating repository information. " + sb.toString()); - } - return FormValidation.ok(sb.toString()); - } - - @Restricted(NoExternalUse.class) - public FormValidation doCheckIncludes(@QueryParameter String value) { - if (value.isEmpty()) { - return FormValidation.warning(Messages.GitHubSCMSource_did_you_mean_to_use_to_match_all_branches()); - } - return FormValidation.ok(); - } - - @RequirePOST - @Restricted(NoExternalUse.class) - public FormValidation doCheckScanCredentialsId(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String scanCredentialsId) { - return doCheckCredentialsId(context, apiUri, scanCredentialsId); - } - - @Restricted(NoExternalUse.class) - public FormValidation doCheckBuildOriginBranchWithPR( - @QueryParameter boolean buildOriginBranch, - @QueryParameter boolean buildOriginBranchWithPR, - @QueryParameter boolean buildOriginPRMerge, - @QueryParameter boolean buildOriginPRHead, - @QueryParameter boolean buildForkPRMerge, - @QueryParameter boolean buildForkPRHead - ) { - if (buildOriginBranch && !buildOriginBranchWithPR && !buildOriginPRMerge && !buildOriginPRHead && !buildForkPRMerge && !buildForkPRHead) { - // TODO in principle we could make doRetrieve populate originBranchesWithPR without actually including any PRs, but it would be more work and probably never wanted anyway. - return FormValidation.warning("If you are not building any PRs, all origin branches will be built."); - } - return FormValidation.ok(); - } - - @Restricted(NoExternalUse.class) - public FormValidation doCheckBuildOriginPRHead(@QueryParameter boolean buildOriginBranchWithPR, @QueryParameter boolean buildOriginPRMerge, @QueryParameter boolean buildOriginPRHead) { - if (buildOriginBranchWithPR && buildOriginPRHead) { - return FormValidation.warning("Redundant to build an origin PR both as a branch and as an unmerged PR."); - } - if (buildOriginPRMerge && buildOriginPRHead) { - return FormValidation.ok("Merged vs. unmerged PRs will be distinguished in the job name (*-merge vs. *-head)."); - } - return FormValidation.ok(); - } - - @Restricted(NoExternalUse.class) - public FormValidation doCheckBuildForkPRHead/* web method name controls UI position of message; we want this at the bottom */( - @QueryParameter boolean buildOriginBranch, - @QueryParameter boolean buildOriginBranchWithPR, - @QueryParameter boolean buildOriginPRMerge, - @QueryParameter boolean buildOriginPRHead, - @QueryParameter boolean buildForkPRMerge, - @QueryParameter boolean buildForkPRHead - ) { - if (!buildOriginBranch && !buildOriginBranchWithPR && !buildOriginPRMerge && !buildOriginPRHead && !buildForkPRMerge && !buildForkPRHead) { - return FormValidation.warning("You need to build something!"); - } - if (buildForkPRMerge && buildForkPRHead) { - return FormValidation.ok("Merged vs. unmerged PRs will be distinguished in the job name (*-merge vs. *-head)."); - } - return FormValidation.ok(); - } - - public ListBoxModel doFillApiUriItems() { - ListBoxModel result = new ListBoxModel(); - result.add("GitHub", ""); - for (Endpoint e : GitHubConfiguration.get().getEndpoints()) { - result.add(e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")", - e.getApiUri()); - } - return result; - } - - public boolean isApiUriSelectable() { - return !GitHubConfiguration.get().getEndpoints().isEmpty(); - } - - - @RequirePOST - public ListBoxModel doFillOrganizationItems(@CheckForNull @AncestorInPath Item context, @QueryParameter String apiUri, - @QueryParameter String credentialsId) throws IOException { - if (credentialsId == null) { - return new ListBoxModel(); - } - if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) || - context != null && !context.hasPermission(Item.EXTENDED_READ)) { - return new ListBoxModel(); // not supposed to be seeing this form - } - if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { - return new ListBoxModel(); // not permitted to try connecting with these credentials - } - try { - StandardCredentials credentials = Connector.lookupScanCredentials(context, apiUri, credentialsId); - GitHub github = Connector.connect(apiUri, credentials); - try { - if (!github.isAnonymous()) { - ListBoxModel model = new ListBoxModel(); - for (Map.Entry entry : github.getMyOrganizations().entrySet()) { - model.add(entry.getKey(), entry.getValue().getAvatarUrl()); - } - return model; - } - } finally { - Connector.release(github); - } - } - catch (FillErrorResponse e) { - throw e; - } catch (Throwable e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - throw new FillErrorResponse(e.getMessage(), false); - } - throw new FillErrorResponse(Messages.GitHubSCMSource_CouldNotConnectionGithub(credentialsId),true); - } - @RequirePOST - public ListBoxModel doFillRepositoryItems(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String credentialsId, - @QueryParameter String repoOwner, - @QueryParameter boolean configuredByUrl) throws IOException { - if (configuredByUrl) { - return new ListBoxModel(); // Using the URL-based configuration, don't scan for repositories. - } - repoOwner = Util.fixEmptyAndTrim(repoOwner); - if (repoOwner == null) { - return new ListBoxModel(); - } - if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) || - context != null && !context.hasPermission(Item.EXTENDED_READ)) { - return new ListBoxModel(); // not supposed to be seeing this form - } - if (context != null && !context.hasPermission(CredentialsProvider.USE_ITEM)) { - return new ListBoxModel(); // not permitted to try connecting with these credentials - } - try { - StandardCredentials credentials = Connector.lookupScanCredentials(context, apiUri, credentialsId); - GitHub github = Connector.connect(apiUri, credentials); - try { - - if (!github.isAnonymous()) { - GHMyself myself; - try { - myself = github.getMyself(); - } catch (IllegalStateException e) { - LOGGER.log(Level.WARNING, e.getMessage(), e); - throw new FillErrorResponse(e.getMessage(), false); - } catch (IOException e) { - LogRecord lr = new LogRecord(Level.WARNING, - "Exception retrieving the repositories of the owner {0} on {1} with credentials {2}"); - lr.setThrown(e); - lr.setParameters(new Object[]{ - repoOwner, apiUri, - credentials == null - ? "anonymous access" - : CredentialsNameProvider.name(credentials) - }); - LOGGER.log(lr); - throw new FillErrorResponse(e.getMessage(), false); - } - if (myself != null && repoOwner.equalsIgnoreCase(myself.getLogin())) { - Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - for (GHRepository repo : myself.listRepositories(100, GHMyself.RepositoryListFilter.ALL)) { - result.add(repo.getName()); - } - return nameAndValueModel(result); - } - } - - GHOrganization org = null; - try { - org = github.getOrganization(repoOwner); - } catch (FileNotFoundException fnf) { - LOGGER.log(Level.FINE, "There is not any GH Organization named {0}", repoOwner); - } catch (IOException e) { - LogRecord lr = new LogRecord(Level.WARNING, - "Exception retrieving the repositories of the organization {0} on {1} with credentials {2}"); - lr.setThrown(e); - lr.setParameters(new Object[]{ - repoOwner, apiUri, - credentials == null - ? "anonymous access" - : CredentialsNameProvider.name(credentials) - }); - LOGGER.log(lr); - throw new FillErrorResponse(e.getMessage(), false); - } - if (org != null && repoOwner.equalsIgnoreCase(org.getLogin())) { - Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - LOGGER.log(Level.FINE, "as {0} looking for repositories in {1}", - new Object[]{credentialsId, repoOwner}); - for (GHRepository repo : org.listRepositories(100)) { - LOGGER.log(Level.FINE, "as {0} found {1}/{2}", - new Object[]{credentialsId, repoOwner, repo.getName()}); - result.add(repo.getName()); - } - LOGGER.log(Level.FINE, "as {0} result of {1} is {2}", - new Object[]{credentialsId, repoOwner, result}); - return nameAndValueModel(result); - } - - GHUser user = null; - try { - user = github.getUser(repoOwner); - } catch (FileNotFoundException fnf) { - LOGGER.log(Level.FINE, "There is not any GH User named {0}", repoOwner); - } catch (IOException e) { - LogRecord lr = new LogRecord(Level.WARNING, - "Exception retrieving the repositories of the user {0} on {1} with credentials {2}"); - lr.setThrown(e); - lr.setParameters(new Object[]{ - repoOwner, apiUri, - credentials == null - ? "anonymous access" - : CredentialsNameProvider.name(credentials) - }); - LOGGER.log(lr); - throw new FillErrorResponse(e.getMessage(), false); - } - if (user != null && repoOwner.equalsIgnoreCase(user.getLogin())) { - Set result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - for (GHRepository repo : user.listRepositories(100)) { - result.add(repo.getName()); - } - return nameAndValueModel(result); - } - } finally { - Connector.release(github); - } - } catch (FillErrorResponse e) { - throw e; - } catch (Throwable e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - throw new FillErrorResponse(e.getMessage(), false); - } - throw new FillErrorResponse(Messages.GitHubSCMSource_NoMatchingOwner(repoOwner), true); - } - /** - * Creates a list box model from a list of values. - * ({@link ListBoxModel#ListBoxModel(Collection)} takes {@link hudson.util.ListBoxModel.Option}s, - * not {@link String}s, and those are not {@link Comparable}.) - */ - private static ListBoxModel nameAndValueModel(Collection items) { - ListBoxModel model = new ListBoxModel(); - for (String item : items) { - model.add(item); - } - return model; - } - - public List>> getTraitsDescriptorLists() { - List> all = new ArrayList<>(); - all.addAll(SCMSourceTrait._for(this, GitHubSCMSourceContext.class, null)); - all.addAll(SCMSourceTrait._for(this, null, GitHubSCMBuilder.class)); - Set> dedup = new HashSet<>(); - for (Iterator> iterator = all.iterator(); iterator.hasNext(); ) { - SCMTraitDescriptor d = iterator.next(); - if (dedup.contains(d) - || d instanceof GitBrowserSCMSourceTrait.DescriptorImpl) { - // remove any we have seen already and ban the browser configuration as it will always be github - iterator.remove(); - } else { - dedup.add(d); - } - } - List>> result = new ArrayList<>(); - NamedArrayList.select(all, Messages.GitHubSCMNavigator_withinRepository(), NamedArrayList - .anyOf(NamedArrayList.withAnnotation(Discovery.class), - NamedArrayList.withAnnotation(Selection.class)), - true, result); - NamedArrayList.select(all, Messages.GitHubSCMNavigator_general(), null, true, result); - return result; - } - - public List getTraitsDefaults() { - return Arrays.asList( // TODO finalize - new BranchDiscoveryTrait(true, false), - new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE)), - new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustPermission()) - ); - } - - @NonNull - @Override - protected SCMHeadCategory[] createCategories() { - return new SCMHeadCategory[]{ - new UncategorizedSCMHeadCategory(Messages._GitHubSCMSource_UncategorizedCategory()), - new ChangeRequestSCMHeadCategory(Messages._GitHubSCMSource_ChangeRequestCategory()), - new TagSCMHeadCategory(Messages._GitHubSCMSource_TagCategory()) - }; - } - - } - - @Restricted(NoExternalUse.class) - class LazyPullRequests extends LazyIterable implements Closeable { - private final GitHubSCMSourceRequest request; - private final GHRepository repo; - private Set pullRequestMetadataKeys = new HashSet<>(); - private boolean fullScanRequested = false; - private boolean iterationCompleted = false; - - public LazyPullRequests(GitHubSCMSourceRequest request, GHRepository repo) { - this.request = request; - this.repo = repo; - } - - @Override - protected Iterable create() { - try { - Set prs = request.getRequestedPullRequestNumbers(); - if (prs != null && prs.size() == 1) { - Integer number = prs.iterator().next(); - request.listener().getLogger().format("%n Getting remote pull request #%d...%n", number); - GHPullRequest pullRequest = repo.getPullRequest(number); - if (pullRequest.getState() != GHIssueState.OPEN) { - return Collections.emptyList(); - } - return new CacheUpdatingIterable(Collections.singletonList(pullRequest)); - } - Set branchNames = request.getRequestedOriginBranchNames(); - if (branchNames != null && branchNames.size() == 1) { // TODO flag to check PRs are all origin PRs - // if we were including multiple PRs and they are not all from the same origin branch - // then branchNames would have a size > 1 therefore if the size is 1 we must only - // be after PRs that come from this named branch - String branchName = branchNames.iterator().next(); - request.listener().getLogger().format( - "%n Getting remote pull requests from branch %s...%n", branchName - ); - return new CacheUpdatingIterable(repo.queryPullRequests() - .state(GHIssueState.OPEN) - .head(repo.getOwnerName() + ":" + branchName) - .list()); - } - request.listener().getLogger().format("%n Getting remote pull requests...%n"); - fullScanRequested = true; - return new CacheUpdatingIterable(LazyPullRequests.this.repo.queryPullRequests() - .state(GHIssueState.OPEN) - .list()); - } catch (IOException e) { - throw new GitHubSCMSource.WrappedException(e); - } - } - - @Override - public void close() throws IOException { - if (fullScanRequested && iterationCompleted) { - // we needed a full scan and the scan was completed, so trim the cache entries - pullRequestMetadataCache.keySet().retainAll(pullRequestMetadataKeys); - pullRequestContributorCache.keySet().retainAll(pullRequestMetadataKeys); - if (Jenkins.get().getInitLevel().compareTo(InitMilestone.JOB_LOADED) > 0) { - // synchronization should be cheap as only writers would be looking for this just to - // write null - synchronized (pullRequestSourceMapLock) { - pullRequestSourceMap = null; // all data has to have been migrated - } - } - } - } - - private class CacheUpdatingIterable extends SinglePassIterable { - /** - * A map of all fully populated {@link GHUser} entries we have fetched, keyed by {@link GHUser#getLogin()}. - */ - private Map users = new HashMap<>(); - CacheUpdatingIterable(Iterable delegate) { - super(delegate); - } - - @Override - public void observe(GHPullRequest pr) { - int number = pr.getNumber(); - GHUser user = null; - try { - user = pr.getUser(); - if (users.containsKey(user.getLogin())) { - // looked up this user already - user = users.get(user.getLogin()); - } - ContributorMetadataAction contributor = new ContributorMetadataAction( - user.getLogin(), - user.getName(), - user.getEmail()); - pullRequestContributorCache.put(number, contributor); - // store the populated user record now that we have it - users.put(user.getLogin(), user); - } catch (FileNotFoundException e) { - // If file not found for user, warn but keep going - request.listener().getLogger().format("%n Could not find user %s for pull request %d.%n", - user == null ? "null" : user.getLogin(), number); - throw new WrappedException(e); - } catch (IOException e) { - throw new WrappedException(e); - } - - pullRequestMetadataCache.put(number, - new ObjectMetadataAction( - pr.getTitle(), - pr.getBody(), - pr.getHtmlUrl().toExternalForm() - ) - ); - pullRequestMetadataKeys.add(number); - } - - @Override - public void completed() { - // we have completed a full iteration of the PRs from the delegate - iterationCompleted = true; - } - } - } - - @Restricted(NoExternalUse.class) - static class LazyBranches extends LazyIterable { - private final GitHubSCMSourceRequest request; - private final GHRepository repo; - - public LazyBranches(GitHubSCMSourceRequest request, GHRepository repo) { - this.request = request; - this.repo = repo; - } - - @Override - protected Iterable create() { - try { - Set branchNames = request.getRequestedOriginBranchNames(); - if (branchNames != null && branchNames.size() == 1) { - String branchName = branchNames.iterator().next(); - request.listener().getLogger().format("%n Getting remote branch %s...%n", branchName); - try { - GHBranch branch = repo.getBranch(branchName); - return Collections.singletonList(branch); - } catch (FileNotFoundException e) { - // branch does not currently exist - return Collections.emptyList(); - } - } - request.listener().getLogger().format("%n Getting remote branches...%n"); - // local optimization: always try the default branch first in any search - List values = new ArrayList<>(repo.getBranches().values()); - final String defaultBranch = StringUtils.defaultIfBlank(repo.getDefaultBranch(), "master"); - Collections.sort(values, new Comparator() { - @Override - public int compare(GHBranch o1, GHBranch o2) { - if (defaultBranch.equals(o1.getName())) { - return -1; - } - if (defaultBranch.equals(o2.getName())) { - return 1; - } - return 0; - } - }); - return values; - } catch (IOException e) { - throw new GitHubSCMSource.WrappedException(e); - } - } - } - - @Restricted(NoExternalUse.class) - static class LazyTags extends LazyIterable { - private final GitHubSCMSourceRequest request; - private final GHRepository repo; - - public LazyTags(GitHubSCMSourceRequest request, GHRepository repo) { - this.request = request; - this.repo = repo; - } - - @Override - protected Iterable create() { - try { - final Set tagNames = request.getRequestedTagNames(); - if (tagNames != null && tagNames.size() == 1) { - String tagName = tagNames.iterator().next(); - request.listener().getLogger().format("%n Getting remote tag %s...%n", tagName); - try { - // Do not blow up if the tag is not present - GHRef tag = repo.getRef("tags/" + tagName); - return Collections.singletonList(tag); - } catch (FileNotFoundException e) { - // branch does not currently exist - return Collections.emptyList(); - }catch (Error e) { - if (e.getCause() instanceof GHFileNotFoundException) { - return Collections.emptyList(); - } - throw e; - } - } - request.listener().getLogger().format("%n Getting remote tags...%n"); - // GitHub will give a 404 if the repository does not have any tags - // we could rework the code that iterates to expect the 404, but that - // would mean leaking the strange behaviour in every trait that consults the list - // of tags. (And GitHub API is probably correct in throwing the GHFileNotFoundException - // from a PagedIterable, so we don't want to fix that) - // - // Instead we just return a wrapped iterator that does the right thing. - final Iterable iterable = repo.listRefs("tags"); - return new Iterable() { - @Override - public Iterator iterator() { - final Iterator iterator; - try { - iterator = iterable.iterator(); - } catch (Error e) { - if (e.getCause() instanceof GHFileNotFoundException) { - return Collections.emptyIterator(); - } - throw e; - } - return new Iterator() { - boolean hadAtLeastOne; - boolean hasNone; - - @Override - public boolean hasNext() { - try { - boolean hasNext = iterator.hasNext(); - hadAtLeastOne = hadAtLeastOne || hasNext; - return hasNext; - } catch (Error e) { - // pre https://github.com/kohsuke/github-api/commit - // /a17ce04552ddd3f6bd8210c740184e6c7ad13ae4 - // we at least got the cause, even if wrapped in an Error - if (e.getCause() instanceof GHFileNotFoundException) { - return false; - } - throw e; - } catch (GHException e) { - // JENKINS-52397 I have no clue why https://github.com/kohsuke/github-api/commit - // /a17ce04552ddd3f6bd8210c740184e6c7ad13ae4 does what it does, but it makes - // it rather difficult to distinguish between a network outage and the file - // not found. - if (hadAtLeastOne) { - throw e; - } - try { - hasNone = hasNone || repo.getRefs("tags").length == 0; - if (hasNone) return false; - throw e; - } catch (FileNotFoundException e1) { - hasNone = true; - return false; - } catch (IOException e1) { - e.addSuppressed(e1); - throw e; - } - } - } - - @Override - public GHRef next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - return iterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - }; - } - }; - } catch (IOException e) { - throw new GitHubSCMSource.WrappedException(e); - } - } - } - - private static class CriteriaWitness implements SCMSourceRequest.Witness { - private final TaskListener listener; - - public CriteriaWitness(TaskListener listener) { - this.listener = listener; - } - - @Override - public void record(@NonNull SCMHead head, SCMRevision revision, boolean isMatch) { - if (isMatch) { - listener.getLogger().format(" Met criteria%n"); - } else { - listener.getLogger().format(" Does not meet criteria%n"); - } - } - } - - private static class MergabilityWitness - implements SCMSourceRequest.Witness { - private final GHPullRequest pr; - private final ChangeRequestCheckoutStrategy strategy; - private final TaskListener listener; - - public MergabilityWitness(GHPullRequest pr, ChangeRequestCheckoutStrategy strategy, TaskListener listener) { - this.pr = pr; - this.strategy = strategy; - this.listener = listener; - } - - @Override - public void record(@NonNull PullRequestSCMHead head, - PullRequestSCMRevision revision, boolean isMatch) { - if (isMatch) { - Boolean mergeable; - try { - mergeable = pr.getMergeable(); - } catch (IOException e) { - throw new GitHubSCMSource.WrappedException(e); - } - if (Boolean.FALSE.equals(mergeable)) { - switch (strategy) { - case MERGE: - listener.getLogger().format(" Not mergeable, build likely to fail%n"); - break; - default: - listener.getLogger().format(" Not mergeable, but will be built anyway%n"); - break; - } - } - } - } - } - - private class LazyContributorNames extends LazySet { - private final GitHubSCMSourceRequest request; - private final TaskListener listener; - private final GitHub github; - private final GHRepository repo; - private final StandardCredentials credentials; - - public LazyContributorNames(GitHubSCMSourceRequest request, - TaskListener listener, GitHub github, GHRepository repo, - StandardCredentials credentials) { - this.request = request; - this.listener = listener; - this.github = github; - this.repo = repo; - this.credentials = credentials; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - protected Set create() { - try { - return updateCollaboratorNames(listener, credentials, repo); - } catch (IOException e) { - throw new WrappedException(e); - } - } - } - - private class DeferredContributorNames extends LazySet { - private final GitHubSCMSourceRequest request; - private final TaskListener listener; - - public DeferredContributorNames(GitHubSCMSourceRequest request, TaskListener listener) { - this.request = request; - this.listener = listener; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - protected Set create() { - if (collaboratorNames != null) { - return collaboratorNames; - } - listener.getLogger().format("Connecting to %s to obtain list of collaborators for %s/%s%n", - apiUri, repoOwner, repository); - StandardCredentials credentials = Connector.lookupScanCredentials( - (Item) getOwner(), apiUri, credentialsId - ); - // Github client and validation - try { - GitHub github = Connector.connect(apiUri, credentials); - try { - Connector.configureLocalRateLimitChecker(listener, github); - - // Input data validation - Connector.checkConnectionValidity(apiUri, listener, credentials, github); - // Input data validation - String credentialsName = - credentials == null - ? "anonymous access" - : CredentialsNameProvider.name(credentials); - if (credentials != null && !isCredentialValid(github)) { - listener.getLogger().format("Invalid scan credentials %s to connect to %s, " - + "assuming no trusted collaborators%n", - credentialsName, apiUri); - collaboratorNames = Collections.singleton(repoOwner); - } else { - if (!github.isAnonymous()) { - listener.getLogger() - .format("Connecting to %s using %s%n", - apiUri, - credentialsName); - } else { - listener.getLogger() - .format("Connecting to %s with no credentials, anonymous access%n", - apiUri); - } - - // Input data validation - if (isBlank(getRepository())) { - collaboratorNames = Collections.singleton(repoOwner); - } else { - String fullName = repoOwner + "/" + repository; - ghRepository = github.getRepository(fullName); - resolvedRepositoryUrl = ghRepository.getHtmlUrl(); - return new LazyContributorNames(request, listener, github, ghRepository, credentials); - } - } - return collaboratorNames; - } finally { - Connector.release(github); - } - } catch (IOException | InterruptedException e) { - throw new WrappedException(e); - } - } - } - - private class DeferredPermissionsSource extends GitHubPermissionsSource implements Closeable { - - private final TaskListener listener; - private GitHub github; - private GHRepository repo; - - public DeferredPermissionsSource(TaskListener listener) { - this.listener = listener; - } - - @Override - public GHPermissionType fetch(String username) throws IOException, InterruptedException { - if (repo == null) { - listener.getLogger().format("Connecting to %s to check permissions of obtain list of %s for %s/%s%n", - apiUri, username, repoOwner, repository); - StandardCredentials credentials = Connector.lookupScanCredentials( - (Item) getOwner(), apiUri, credentialsId - ); - github = Connector.connect(apiUri, credentials); - String fullName = repoOwner + "/" + repository; - repo = github.getRepository(fullName); - } - return repo.getPermission(username); - } - - @Override - public void close() throws IOException { - if (github != null) { - Connector.release(github); - github = null; - repo = null; - } - } - } + public void close() throws IOException { + if (github != null) { + Connector.release(github); + github = null; + repo = null; + } + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceBuilder.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceBuilder.java index 80e845e61..dc42c8a78 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceBuilder.java @@ -32,97 +32,87 @@ * * @since 2.2.0 */ -public class GitHubSCMSourceBuilder extends SCMSourceBuilder { - /** - * The {@link GitHubSCMSource#getId()}. - */ - @CheckForNull - private final String id; - /** - * The {@link GitHubSCMSource#getApiUri()}. - */ - @CheckForNull - private final String apiUri; - /** - * The credentials id or {@code null} to use anonymous scanning. - */ - @CheckForNull - private final String credentialsId; - /** - * The repository owner. - */ - @NonNull - private final String repoOwner; +public class GitHubSCMSourceBuilder + extends SCMSourceBuilder { + /** The {@link GitHubSCMSource#getId()}. */ + @CheckForNull private final String id; + /** The {@link GitHubSCMSource#getApiUri()}. */ + @CheckForNull private final String apiUri; + /** The credentials id or {@code null} to use anonymous scanning. */ + @CheckForNull private final String credentialsId; + /** The repository owner. */ + @NonNull private final String repoOwner; - /** - * Constructor. - * - * @param id the {@link GitHubSCMSource#getId()} - * @param apiUri the {@link GitHubSCMSource#getApiUri()} - * @param credentialsId the credentials id. - * @param repoOwner the repository owner. - * @param repoName the project name. - */ - public GitHubSCMSourceBuilder(@CheckForNull String id, @CheckForNull String apiUri, - @CheckForNull String credentialsId, @NonNull String repoOwner, - @NonNull String repoName) { - super(GitHubSCMSource.class, repoName); - this.id = id; - this.apiUri = apiUri; - this.repoOwner = repoOwner; - this.credentialsId = credentialsId; - } + /** + * Constructor. + * + * @param id the {@link GitHubSCMSource#getId()} + * @param apiUri the {@link GitHubSCMSource#getApiUri()} + * @param credentialsId the credentials id. + * @param repoOwner the repository owner. + * @param repoName the project name. + */ + public GitHubSCMSourceBuilder( + @CheckForNull String id, + @CheckForNull String apiUri, + @CheckForNull String credentialsId, + @NonNull String repoOwner, + @NonNull String repoName) { + super(GitHubSCMSource.class, repoName); + this.id = id; + this.apiUri = apiUri; + this.repoOwner = repoOwner; + this.credentialsId = credentialsId; + } - /** - * The id of the {@link GitHubSCMSource} that is being built. - * - * @return the id of the {@link GitHubSCMSource} that is being built. - */ - public final String id() { - return id; - } + /** + * The id of the {@link GitHubSCMSource} that is being built. + * + * @return the id of the {@link GitHubSCMSource} that is being built. + */ + public final String id() { + return id; + } - /** - * The endpoint of the {@link GitHubSCMSource} that is being built. - * - * @return the endpoint of the {@link GitHubSCMSource} that is being built. - */ - @CheckForNull - public final String apiUri() { - return apiUri; - } + /** + * The endpoint of the {@link GitHubSCMSource} that is being built. + * + * @return the endpoint of the {@link GitHubSCMSource} that is being built. + */ + @CheckForNull + public final String apiUri() { + return apiUri; + } - /** - * The credentials that the {@link GitHubSCMSource} will use. - * - * @return the credentials that the {@link GitHubSCMSource} will use. - */ - @CheckForNull - public final String credentialsId() { - return credentialsId; - } + /** + * The credentials that the {@link GitHubSCMSource} will use. + * + * @return the credentials that the {@link GitHubSCMSource} will use. + */ + @CheckForNull + public final String credentialsId() { + return credentialsId; + } - /** - * The repository owner that the {@link GitHubSCMSource} will be configured to use. - * - * @return the repository owner that the {@link GitHubSCMSource} will be configured to use. - */ - @NonNull - public final String repoOwner() { - return repoOwner; - } + /** + * The repository owner that the {@link GitHubSCMSource} will be configured to use. + * + * @return the repository owner that the {@link GitHubSCMSource} will be configured to use. + */ + @NonNull + public final String repoOwner() { + return repoOwner; + } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public GitHubSCMSource build() { - GitHubSCMSource result = new GitHubSCMSource(repoOwner, projectName()); - result.setId(id()); - result.setApiUri(apiUri()); - result.setCredentialsId(credentialsId()); - result.setTraits(traits()); - return result; - } + /** {@inheritDoc} */ + @NonNull + @Override + public GitHubSCMSource build() { + GitHubSCMSource result = new GitHubSCMSource(repoOwner, projectName()); + result.setId(id()); + result.setApiUri(apiUri()); + result.setCredentialsId(credentialsId()); + result.setTraits(traits()); + return result; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java index 9f251cf35..0ed5599a4 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceContext.java @@ -26,13 +26,11 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.model.TaskListener; - import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; - import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceCriteria; @@ -45,264 +43,277 @@ * @since 2.2.0 */ public class GitHubSCMSourceContext - extends SCMSourceContext { - /** - * {@code true} if the {@link GitHubSCMSourceRequest} will need information about branches. - */ - private boolean wantBranches; - /** - * {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. - */ - private boolean wantTags; - /** - * {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin pull requests. - */ - private boolean wantOriginPRs; - /** - * {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork pull requests. - */ - private boolean wantForkPRs; - /** - * Set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - */ - @NonNull - private Set originPRStrategies = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - /** - * Set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - */ - @NonNull - private Set forkPRStrategies = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - /** - * {@code true} if notifications should be disabled in this context. - */ - private boolean notificationsDisabled; - /** - * Strategies used to notify Github of build status. - * - * @since 2.3.2 - */ - private final List notificationStrategies = new ArrayList<>(); + extends SCMSourceContext { + /** {@code true} if the {@link GitHubSCMSourceRequest} will need information about branches. */ + private boolean wantBranches; + /** {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. */ + private boolean wantTags; + /** + * {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin pull + * requests. + */ + private boolean wantOriginPRs; + /** + * {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork pull + * requests. + */ + private boolean wantForkPRs; + /** Set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. */ + @NonNull + private Set originPRStrategies = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + /** Set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. */ + @NonNull + private Set forkPRStrategies = + EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + /** {@code true} if notifications should be disabled in this context. */ + private boolean notificationsDisabled; + /** + * Strategies used to notify Github of build status. + * + * @since 2.3.2 + */ + private final List notificationStrategies = new ArrayList<>(); - /** - * Constructor. - * - * @param criteria (optional) criteria. - * @param observer the {@link SCMHeadObserver}. - */ - public GitHubSCMSourceContext(@CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer) { - super(criteria, observer); - } - - /** - * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about branches. - * - * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about branches. - */ - public final boolean wantBranches() { - return wantBranches; - } + /** + * Constructor. + * + * @param criteria (optional) criteria. + * @param observer the {@link SCMHeadObserver}. + */ + public GitHubSCMSourceContext( + @CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer) { + super(criteria, observer); + } - /** - * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. - * - * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. - */ - public final boolean wantTags() { - return wantTags; - } + /** + * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about + * branches. + * + * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about + * branches. + */ + public final boolean wantBranches() { + return wantBranches; + } - /** - * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about pull requests. - * - * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about pull requests. - */ - public final boolean wantPRs() { - return wantOriginPRs || wantForkPRs; - } + /** + * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. + * + * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about tags. + */ + public final boolean wantTags() { + return wantTags; + } - /** - * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin pull requests. - * - * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin pull requests. - */ - public final boolean wantOriginPRs() { - return wantOriginPRs; - } + /** + * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about pull + * requests. + * + * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about pull + * requests. + */ + public final boolean wantPRs() { + return wantOriginPRs || wantForkPRs; + } - /** - * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork pull requests. - * - * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork pull requests. - */ - public final boolean wantForkPRs() { - return wantForkPRs; - } + /** + * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin + * pull requests. + * + * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about origin + * pull requests. + */ + public final boolean wantOriginPRs() { + return wantOriginPRs; + } + /** + * Returns {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork + * pull requests. + * + * @return {@code true} if the {@link GitHubSCMSourceRequest} will need information about fork + * pull requests. + */ + public final boolean wantForkPRs() { + return wantForkPRs; + } - /** - * Returns the set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - * - * @return the set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - */ - @NonNull - public final Set originPRStrategies() { - return originPRStrategies; - } + /** + * Returns the set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull + * request. + * + * @return the set of {@link ChangeRequestCheckoutStrategy} to create for each origin pull + * request. + */ + @NonNull + public final Set originPRStrategies() { + return originPRStrategies; + } - /** - * Returns the set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - * - * @return the set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - */ - @NonNull - public final Set forkPRStrategies() { - return forkPRStrategies; - } - /** - * Returns the strategies used to notify Github of build status. - * @return the strategies used to notify Github of build status. - * @since 2.3.2 - */ - public final List notificationStrategies() { - if (notificationStrategies.isEmpty()) { - return Collections.singletonList(new DefaultGitHubNotificationStrategy()); - } - return Collections.unmodifiableList(notificationStrategies); - } - /** - * Returns {@code true} if notifications should be disabled. - * - * @return {@code true} if notifications should be disabled. - */ - public final boolean notificationsDisabled() { - return notificationsDisabled; + /** + * Returns the set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. + * + * @return the set of {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. + */ + @NonNull + public final Set forkPRStrategies() { + return forkPRStrategies; + } + /** + * Returns the strategies used to notify Github of build status. + * + * @return the strategies used to notify Github of build status. + * @since 2.3.2 + */ + public final List notificationStrategies() { + if (notificationStrategies.isEmpty()) { + return Collections.singletonList(new DefaultGitHubNotificationStrategy()); } + return Collections.unmodifiableList(notificationStrategies); + } + /** + * Returns {@code true} if notifications should be disabled. + * + * @return {@code true} if notifications should be disabled. + */ + public final boolean notificationsDisabled() { + return notificationsDisabled; + } - /** - * Adds a requirement for branch details to any {@link GitHubSCMSourceRequest} 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. - */ - @NonNull - public GitHubSCMSourceContext wantBranches(boolean include) { - wantBranches = wantBranches || include; - return this; - } + /** + * Adds a requirement for branch details to any {@link GitHubSCMSourceRequest} 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. + */ + @NonNull + public GitHubSCMSourceContext wantBranches(boolean include) { + wantBranches = wantBranches || include; + return this; + } - /** - * Adds a requirement for tag details to any {@link GitHubSCMSourceRequest} 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. - */ - @NonNull - public GitHubSCMSourceContext wantTags(boolean include) { - wantTags = wantTags || include; - return this; - } + /** + * Adds a requirement for tag details to any {@link GitHubSCMSourceRequest} 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. + */ + @NonNull + public GitHubSCMSourceContext wantTags(boolean include) { + wantTags = wantTags || include; + return this; + } - /** - * Adds a requirement for origin pull request details to any {@link GitHubSCMSourceRequest} 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. - */ - @NonNull - public GitHubSCMSourceContext wantOriginPRs(boolean include) { - wantOriginPRs = wantOriginPRs || include; - return this; - } + /** + * Adds a requirement for origin pull request details to any {@link GitHubSCMSourceRequest} 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. + */ + @NonNull + public GitHubSCMSourceContext wantOriginPRs(boolean include) { + wantOriginPRs = wantOriginPRs || include; + return this; + } - /** - * Adds a requirement for fork pull request details to any {@link GitHubSCMSourceRequest} 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. - */ - @NonNull - public GitHubSCMSourceContext wantForkPRs(boolean include) { - wantForkPRs = wantForkPRs || include; - return this; - } + /** + * Adds a requirement for fork pull request details to any {@link GitHubSCMSourceRequest} 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. + */ + @NonNull + public GitHubSCMSourceContext wantForkPRs(boolean include) { + wantForkPRs = wantForkPRs || include; + return this; + } - /** - * Defines the {@link ChangeRequestCheckoutStrategy} instances to create for each origin pull request. - * - * @param strategies the strategies. - * @return {@code this} for method chaining. - */ - @NonNull - public GitHubSCMSourceContext withOriginPRStrategies(Set strategies) { - originPRStrategies.addAll(strategies); - return this; - } + /** + * Defines the {@link ChangeRequestCheckoutStrategy} instances to create for each origin pull + * request. + * + * @param strategies the strategies. + * @return {@code this} for method chaining. + */ + @NonNull + public GitHubSCMSourceContext withOriginPRStrategies( + Set strategies) { + originPRStrategies.addAll(strategies); + return this; + } - /** - * Defines the {@link ChangeRequestCheckoutStrategy} instances to create for each fork pull request. - * - * @param strategies the strategies. - * @return {@code this} for method chaining. - */ - @NonNull - public GitHubSCMSourceContext withForkPRStrategies(Set strategies) { - forkPRStrategies.addAll(strategies); - return this; - } - /** - * Replaces the list of strategies used to notify Github of build status. - * - * @param strategies the strategies used to notify Github of build status. - * @return {@code this} for method chaining. - * @since 2.3.2 - */ - @NonNull - public final GitHubSCMSourceContext withNotificationStrategies(List strategies) { - notificationStrategies.clear(); - for (AbstractGitHubNotificationStrategy strategy : strategies) { - if (!notificationStrategies.contains(strategy)) { - notificationStrategies.add(strategy); - } - } - return this; + /** + * Defines the {@link ChangeRequestCheckoutStrategy} instances to create for each fork pull + * request. + * + * @param strategies the strategies. + * @return {@code this} for method chaining. + */ + @NonNull + public GitHubSCMSourceContext withForkPRStrategies( + Set strategies) { + forkPRStrategies.addAll(strategies); + return this; + } + /** + * Replaces the list of strategies used to notify Github of build status. + * + * @param strategies the strategies used to notify Github of build status. + * @return {@code this} for method chaining. + * @since 2.3.2 + */ + @NonNull + public final GitHubSCMSourceContext withNotificationStrategies( + List strategies) { + notificationStrategies.clear(); + for (AbstractGitHubNotificationStrategy strategy : strategies) { + if (!notificationStrategies.contains(strategy)) { + notificationStrategies.add(strategy); + } } + return this; + } - /** - * Add a strategy used to notify Github of build status. - * @param strategy a strategy used to notify Github of build status. - * @return {@code this} for method chaining. - * @since 2.3.2 - */ - @NonNull - public final GitHubSCMSourceContext withNotificationStrategy(AbstractGitHubNotificationStrategy strategy) { - if (!notificationStrategies.contains(strategy)) { - notificationStrategies.add(strategy); - } - return this; + /** + * Add a strategy used to notify Github of build status. + * + * @param strategy a strategy used to notify Github of build status. + * @return {@code this} for method chaining. + * @since 2.3.2 + */ + @NonNull + public final GitHubSCMSourceContext withNotificationStrategy( + AbstractGitHubNotificationStrategy strategy) { + if (!notificationStrategies.contains(strategy)) { + notificationStrategies.add(strategy); } + return this; + } - /** - * Defines the notification mode to use in this context. - * - * @param disabled {@code true} to disable automatic notifications. - * @return {@code this} for method chaining. - */ - @NonNull - public final GitHubSCMSourceContext withNotificationsDisabled(boolean disabled) { - notificationsDisabled = disabled; - return this; - } + /** + * Defines the notification mode to use in this context. + * + * @param disabled {@code true} to disable automatic notifications. + * @return {@code this} for method chaining. + */ + @NonNull + public final GitHubSCMSourceContext withNotificationsDisabled(boolean disabled) { + notificationsDisabled = disabled; + return this; + } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public GitHubSCMSourceRequest newRequest(@NonNull SCMSource source, @CheckForNull TaskListener listener) { - return new GitHubSCMSourceRequest(source, this, listener); - } + /** {@inheritDoc} */ + @NonNull + @Override + public GitHubSCMSourceRequest newRequest( + @NonNull SCMSource source, @CheckForNull TaskListener listener) { + return new GitHubSCMSourceRequest(source, this, listener); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRepositoryNameContributor.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRepositoryNameContributor.java index d5f11558d..54a2725a3 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRepositoryNameContributor.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRepositoryNameContributor.java @@ -28,9 +28,8 @@ import com.cloudbees.jenkins.GitHubRepositoryNameContributor; import hudson.Extension; import hudson.model.Item; -import jenkins.scm.api.SCMSourceOwner; - import java.util.Collection; +import jenkins.scm.api.SCMSourceOwner; /** * Finds the repository name(s) associated with a {@link SCMSourceOwner}'s {@link GitHubSCMSource}s. @@ -41,20 +40,20 @@ @Extension public class GitHubSCMSourceRepositoryNameContributor extends GitHubRepositoryNameContributor { - @Override - public void parseAssociatedNames(Item item, Collection result) { - if (item instanceof SCMSourceOwner) { - SCMSourceOwner mp = (SCMSourceOwner) item; - for (Object o : mp.getSCMSources()) { - if (o instanceof GitHubSCMSource) { - GitHubSCMSource gitHubSCMSource = (GitHubSCMSource) o; - result.add(new GitHubRepositoryName( - RepositoryUriResolver.hostnameFromApiUri(gitHubSCMSource.getApiUri()), - gitHubSCMSource.getRepoOwner(), - gitHubSCMSource.getRepository())); - - } - } + @Override + public void parseAssociatedNames(Item item, Collection result) { + if (item instanceof SCMSourceOwner) { + SCMSourceOwner mp = (SCMSourceOwner) item; + for (Object o : mp.getSCMSources()) { + if (o instanceof GitHubSCMSource) { + GitHubSCMSource gitHubSCMSource = (GitHubSCMSource) o; + result.add( + new GitHubRepositoryName( + RepositoryUriResolver.hostnameFromApiUri(gitHubSCMSource.getApiUri()), + gitHubSCMSource.getRepoOwner(), + gitHubSCMSource.getRepository())); } + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java index 79617edf0..7e2b1bae8 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceRequest.java @@ -55,455 +55,429 @@ * @since 2.2.0 */ public class GitHubSCMSourceRequest extends SCMSourceRequest { - /** - * {@code true} if branch details need to be fetched. - */ - private final boolean fetchBranches; - /** - * {@code true} if tag details need to be fetched. - */ - private final boolean fetchTags; - /** - * {@code true} if origin pull requests need to be fetched. - */ - private final boolean fetchOriginPRs; - /** - * {@code true} if fork pull requests need to be fetched. - */ - private final boolean fetchForkPRs; - /** - * The {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - */ - @NonNull - private final Set originPRStrategies; - /** - * The {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - */ - @NonNull - private final Set forkPRStrategies; - /** - * The set of pull request numbers that the request is scoped to or {@code null} if the request is not limited. - */ - @CheckForNull - private final Set requestedPullRequestNumbers; - /** - * The set of origin branch names that the request is scoped to or {@code null} if the request is not limited. - */ - @CheckForNull - private final Set requestedOriginBranchNames; - /** - * The set of tag names that the request is scoped to or {@code null} if the request is not limited. - */ - @CheckForNull - private final Set requestedTagNames; - /** - * The pull request details or {@code null} if not {@link #isFetchPRs()}. - */ - @CheckForNull - private Iterable pullRequests; - /** - * The branch details or {@code null} if not {@link #isFetchBranches()}. - */ - @CheckForNull - private Iterable branches; - /** - * The tag details or {@code null} if not {@link #isFetchTags()}. - */ - @CheckForNull - private Iterable tags; - /** - * The repository collaborator names or {@code null} if not provided. - */ - @CheckForNull - private Set collaboratorNames; - /** - * A connection to the GitHub API or {@code null} if none established yet. - */ - @CheckForNull - private GitHub gitHub; - /** - * The repository. - */ - @CheckForNull - private GHRepository repository; - /** - * The resolved permissions keyed by user. - */ - @NonNull - @GuardedBy("self") - private final Map permissions = new HashMap<>(); - /** - * A deferred lookup of the permissions. - */ - @CheckForNull - private GitHubPermissionsSource permissionsSource; - - /** - * Constructor. - * - * @param source the source. - * @param context the context. - * @param listener the listener. - */ - GitHubSCMSourceRequest(SCMSource source, GitHubSCMSourceContext context, TaskListener listener) { - super(source, context, listener); - fetchBranches = context.wantBranches(); - fetchTags = context.wantTags(); - fetchOriginPRs = context.wantOriginPRs(); - fetchForkPRs = context.wantForkPRs(); - originPRStrategies = fetchOriginPRs && !context.originPRStrategies().isEmpty() - ? Collections.unmodifiableSet(EnumSet.copyOf(context.originPRStrategies())) - : Collections.emptySet(); - forkPRStrategies = fetchForkPRs && !context.forkPRStrategies().isEmpty() - ? Collections.unmodifiableSet(EnumSet.copyOf(context.forkPRStrategies())) - : Collections.emptySet(); - Set includes = context.observer().getIncludes(); - if (includes != null) { - Set pullRequestNumbers = new HashSet<>(includes.size()); - Set branchNames = new HashSet<>(includes.size()); - Set tagNames = new HashSet<>(includes.size()); - for (SCMHead h : includes) { - if (h instanceof BranchSCMHead) { - branchNames.add(h.getName()); - } else if (h instanceof PullRequestSCMHead) { - pullRequestNumbers.add(((PullRequestSCMHead) h).getNumber()); - if (SCMHeadOrigin.DEFAULT.equals(h.getOrigin())) { - branchNames.add(((PullRequestSCMHead) h).getOriginName()); - } - } else if (h instanceof GitHubTagSCMHead) { - tagNames.add(h.getName()); - } - } - this.requestedPullRequestNumbers = Collections.unmodifiableSet(pullRequestNumbers); - this.requestedOriginBranchNames = Collections.unmodifiableSet(branchNames); - this.requestedTagNames = Collections.unmodifiableSet(tagNames); - } else { - requestedPullRequestNumbers = null; - requestedOriginBranchNames = null; - requestedTagNames = null; + /** {@code true} if branch details need to be fetched. */ + private final boolean fetchBranches; + /** {@code true} if tag details need to be fetched. */ + private final boolean fetchTags; + /** {@code true} if origin pull requests need to be fetched. */ + private final boolean fetchOriginPRs; + /** {@code true} if fork pull requests need to be fetched. */ + private final boolean fetchForkPRs; + /** The {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. */ + @NonNull private final Set originPRStrategies; + /** The {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. */ + @NonNull private final Set forkPRStrategies; + /** + * The set of pull request numbers that the request is scoped to or {@code null} if the request is + * not limited. + */ + @CheckForNull private final Set requestedPullRequestNumbers; + /** + * The set of origin branch names that the request is scoped to or {@code null} if the request is + * not limited. + */ + @CheckForNull private final Set requestedOriginBranchNames; + /** + * The set of tag names that the request is scoped to or {@code null} if the request is not + * limited. + */ + @CheckForNull private final Set requestedTagNames; + /** The pull request details or {@code null} if not {@link #isFetchPRs()}. */ + @CheckForNull private Iterable pullRequests; + /** The branch details or {@code null} if not {@link #isFetchBranches()}. */ + @CheckForNull private Iterable branches; + /** The tag details or {@code null} if not {@link #isFetchTags()}. */ + @CheckForNull private Iterable tags; + /** The repository collaborator names or {@code null} if not provided. */ + @CheckForNull private Set collaboratorNames; + /** A connection to the GitHub API or {@code null} if none established yet. */ + @CheckForNull private GitHub gitHub; + /** The repository. */ + @CheckForNull private GHRepository repository; + /** The resolved permissions keyed by user. */ + @NonNull + @GuardedBy("self") + private final Map permissions = new HashMap<>(); + /** A deferred lookup of the permissions. */ + @CheckForNull private GitHubPermissionsSource permissionsSource; + + /** + * Constructor. + * + * @param source the source. + * @param context the context. + * @param listener the listener. + */ + GitHubSCMSourceRequest(SCMSource source, GitHubSCMSourceContext context, TaskListener listener) { + super(source, context, listener); + fetchBranches = context.wantBranches(); + fetchTags = context.wantTags(); + fetchOriginPRs = context.wantOriginPRs(); + fetchForkPRs = context.wantForkPRs(); + originPRStrategies = + fetchOriginPRs && !context.originPRStrategies().isEmpty() + ? Collections.unmodifiableSet(EnumSet.copyOf(context.originPRStrategies())) + : Collections.emptySet(); + forkPRStrategies = + fetchForkPRs && !context.forkPRStrategies().isEmpty() + ? Collections.unmodifiableSet(EnumSet.copyOf(context.forkPRStrategies())) + : Collections.emptySet(); + Set includes = context.observer().getIncludes(); + if (includes != null) { + Set pullRequestNumbers = new HashSet<>(includes.size()); + Set branchNames = new HashSet<>(includes.size()); + Set tagNames = new HashSet<>(includes.size()); + for (SCMHead h : includes) { + if (h instanceof BranchSCMHead) { + branchNames.add(h.getName()); + } else if (h instanceof PullRequestSCMHead) { + pullRequestNumbers.add(((PullRequestSCMHead) h).getNumber()); + if (SCMHeadOrigin.DEFAULT.equals(h.getOrigin())) { + branchNames.add(((PullRequestSCMHead) h).getOriginName()); + } + } else if (h instanceof GitHubTagSCMHead) { + tagNames.add(h.getName()); } + } + this.requestedPullRequestNumbers = Collections.unmodifiableSet(pullRequestNumbers); + this.requestedOriginBranchNames = Collections.unmodifiableSet(branchNames); + this.requestedTagNames = Collections.unmodifiableSet(tagNames); + } else { + requestedPullRequestNumbers = null; + requestedOriginBranchNames = null; + requestedTagNames = null; } - - /** - * Returns {@code true} if branch details need to be fetched. - * - * @return {@code true} if branch details need to be fetched. - */ - public final boolean isFetchBranches() { - return fetchBranches; - } - - /** - * Returns {@code true} if tag details need to be fetched. - * - * @return {@code true} if tag details need to be fetched. - */ - public final boolean isFetchTags() { - return fetchTags; - } - - /** - * Returns {@code true} if pull request details need to be fetched. - * - * @return {@code true} if pull request details need to be fetched. - */ - public final boolean isFetchPRs() { - return isFetchOriginPRs() || isFetchForkPRs(); - } - - /** - * Returns {@code true} if origin pull request details need to be fetched. - * - * @return {@code true} if origin pull request details need to be fetched. - */ - public final boolean isFetchOriginPRs() { - return fetchOriginPRs; - } - - /** - * Returns {@code true} if fork pull request details need to be fetched. - * - * @return {@code true} if fork pull request details need to be fetched. - */ - public final boolean isFetchForkPRs() { - return fetchForkPRs; - } - - /** - * Returns the {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - * - * @return the {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. - */ - @NonNull - public final Set getOriginPRStrategies() { - return originPRStrategies; - } - - /** - * Returns the {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - * - * @return the {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. - */ - @NonNull - public final Set getForkPRStrategies() { - return forkPRStrategies; - } - - /** - * Returns the {@link ChangeRequestCheckoutStrategy} to create for pull requests of the specified type. - * - * @param fork {@code true} to return strategies for the fork pull requests, {@code false} for origin pull requests. - * @return the {@link ChangeRequestCheckoutStrategy} to create for each pull request. - */ - @NonNull - public final Set getPRStrategies(boolean fork) { - if (fork) { - return fetchForkPRs ? getForkPRStrategies() : Collections.emptySet(); - } - return fetchOriginPRs ? getOriginPRStrategies() : Collections.emptySet(); + } + + /** + * Returns {@code true} if branch details need to be fetched. + * + * @return {@code true} if branch details need to be fetched. + */ + public final boolean isFetchBranches() { + return fetchBranches; + } + + /** + * Returns {@code true} if tag details need to be fetched. + * + * @return {@code true} if tag details need to be fetched. + */ + public final boolean isFetchTags() { + return fetchTags; + } + + /** + * Returns {@code true} if pull request details need to be fetched. + * + * @return {@code true} if pull request details need to be fetched. + */ + public final boolean isFetchPRs() { + return isFetchOriginPRs() || isFetchForkPRs(); + } + + /** + * Returns {@code true} if origin pull request details need to be fetched. + * + * @return {@code true} if origin pull request details need to be fetched. + */ + public final boolean isFetchOriginPRs() { + return fetchOriginPRs; + } + + /** + * Returns {@code true} if fork pull request details need to be fetched. + * + * @return {@code true} if fork pull request details need to be fetched. + */ + public final boolean isFetchForkPRs() { + return fetchForkPRs; + } + + /** + * Returns the {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. + * + * @return the {@link ChangeRequestCheckoutStrategy} to create for each origin pull request. + */ + @NonNull + public final Set getOriginPRStrategies() { + return originPRStrategies; + } + + /** + * Returns the {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. + * + * @return the {@link ChangeRequestCheckoutStrategy} to create for each fork pull request. + */ + @NonNull + public final Set getForkPRStrategies() { + return forkPRStrategies; + } + + /** + * Returns the {@link ChangeRequestCheckoutStrategy} to create for pull requests of the specified + * type. + * + * @param fork {@code true} to return strategies for the fork pull requests, {@code false} for + * origin pull requests. + * @return the {@link ChangeRequestCheckoutStrategy} to create for each pull request. + */ + @NonNull + public final Set getPRStrategies(boolean fork) { + if (fork) { + return fetchForkPRs ? getForkPRStrategies() : Collections.emptySet(); } - - /** - * Returns the {@link ChangeRequestCheckoutStrategy} to create for each pull request. - * - * @return a map of the {@link ChangeRequestCheckoutStrategy} to create for each pull request keyed by whether the - * strategy applies to forks or not ({@link Boolean#FALSE} is the key for origin pull requests) - */ - public final Map> getPRStrategies() { - Map> result = new HashMap<>(); - for (Boolean fork : new Boolean[]{Boolean.TRUE, Boolean.FALSE}) { - result.put(fork, getPRStrategies(fork)); - } - return result; + return fetchOriginPRs ? getOriginPRStrategies() : Collections.emptySet(); + } + + /** + * Returns the {@link ChangeRequestCheckoutStrategy} to create for each pull request. + * + * @return a map of the {@link ChangeRequestCheckoutStrategy} to create for each pull request + * keyed by whether the strategy applies to forks or not ({@link Boolean#FALSE} is the key for + * origin pull requests) + */ + public final Map> getPRStrategies() { + Map> result = new HashMap<>(); + for (Boolean fork : new Boolean[] {Boolean.TRUE, Boolean.FALSE}) { + result.put(fork, getPRStrategies(fork)); } - - /** - * Returns requested pull request numbers. - * - * @return the requested pull request numbers or {@code null} if the request was not scoped to a subset of pull - * requests. - */ - @CheckForNull - public final Set getRequestedPullRequestNumbers() { - return requestedPullRequestNumbers; + return result; + } + + /** + * Returns requested pull request numbers. + * + * @return the requested pull request numbers or {@code null} if the request was not scoped to a + * subset of pull requests. + */ + @CheckForNull + public final Set getRequestedPullRequestNumbers() { + return requestedPullRequestNumbers; + } + + /** + * Gets requested origin branch names. + * + * @return the requested origin branch names or {@code null} if the request was not scoped to a + * subset of branches. + */ + @CheckForNull + public final Set getRequestedOriginBranchNames() { + return requestedOriginBranchNames; + } + + /** + * Gets requested tag names. + * + * @return the requested tag names or {@code null} if the request was not scoped to a subset of + * tags. + */ + @CheckForNull + public final Set getRequestedTagNames() { + return requestedTagNames; + } + + /** + * Provides the requests with the pull request details. + * + * @param pullRequests the pull request details. + */ + public void setPullRequests(@CheckForNull Iterable pullRequests) { + this.pullRequests = pullRequests; + } + + /** + * Returns the pull request details or an empty list if either the request did not specify to + * {@link #isFetchPRs()} or if the pull request details have not been provided by {@link + * #setPullRequests(Iterable)} yet. + * + * @return the details of pull requests, may be limited by {@link + * #getRequestedPullRequestNumbers()} or may be empty if not {@link #isFetchPRs()} + */ + @NonNull + public Iterable getPullRequests() { + return Util.fixNull(pullRequests); + } + + /** + * Provides the requests with the branch details. + * + * @param branches the branch details. + */ + public final void setBranches(@CheckForNull Iterable branches) { + this.branches = branches; + } + + /** + * Returns the branch details or an empty list if either the request did not specify to {@link + * #isFetchBranches()} or if the branch details have not been provided by {@link + * #setBranches(Iterable)} yet. + * + * @return the branch details (may be empty) + */ + @NonNull + public final Iterable getBranches() { + return Util.fixNull(branches); + } + + /** + * Provides the requests with the tag details. + * + * @param tags the tag details. + */ + public final void setTags(@CheckForNull Iterable tags) { + this.tags = tags; + } + + /** + * Returns the branch details or an empty list if either the request did not specify to {@link + * #isFetchBranches()} or if the branch details have not been provided by {@link + * #setBranches(Iterable)} yet. + * + * @return the branch details (may be empty) + */ + @NonNull + public final Iterable getTags() { + return Util.fixNull(tags); + } + + // TODO Iterable getTags() and setTags(...) + + /** + * Provides the request with the names of the repository collaborators. + * + * @param collaboratorNames the names of the repository collaborators. + */ + public final void setCollaboratorNames(@CheckForNull Set collaboratorNames) { + this.collaboratorNames = collaboratorNames; + } + + /** + * Returns the names of the repository collaborators or {@code null} if those details have not + * been provided yet. + * + * @return the names of the repository collaborators or {@code null} if those details have not + * been provided yet. + */ + public final Set getCollaboratorNames() { + return collaboratorNames; + } + + /** + * Checks the API rate limit and sleeps if over-used until the remaining limit is on-target for + * expected usage. + * + * @throws IOException if the rate limit could not be obtained. + * @throws InterruptedException if interrupted while waiting. + * @deprecated rate limit checking is done automatically + */ + @Deprecated + public final void checkApiRateLimit() throws IOException, InterruptedException { + if (gitHub != null) { + Connector.configureLocalRateLimitChecker(listener(), Objects.requireNonNull(gitHub)); } - - /** - * Gets requested origin branch names. - * - * @return the requested origin branch names or {@code null} if the request was not scoped to a subset of branches. - */ - @CheckForNull - public final Set getRequestedOriginBranchNames() { - return requestedOriginBranchNames; + } + + /** + * Returns the {@link GitHub} API connector to use for the request. + * + * @return the {@link GitHub} API connector to use for the request or {@code null} if caller + * should establish their own. + */ + @CheckForNull + public GitHub getGitHub() { + return gitHub; + } + + /** + * Provides the {@link GitHub} API connector to use for the request. + * + * @param gitHub {@link GitHub} API connector to use for the request. + */ + public void setGitHub(@CheckForNull GitHub gitHub) { + this.gitHub = gitHub; + } + + /** + * Returns the {@link GHRepository}. + * + * @return the {@link GHRepository}. + */ + public GHRepository getRepository() { + return repository; + } + + /** + * Sets the {@link GHRepository}. + * + * @param repository the {@link GHRepository}. + */ + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + if (pullRequests instanceof Closeable) { + ((Closeable) pullRequests).close(); } - - /** - * Gets requested tag names. - * - * @return the requested tag names or {@code null} if the request was not scoped to a subset of tags. - */ - @CheckForNull - public final Set getRequestedTagNames() { - return requestedTagNames; + if (branches instanceof Closeable) { + ((Closeable) branches).close(); } - - /** - * Provides the requests with the pull request details. - * - * @param pullRequests the pull request details. - */ - public void setPullRequests(@CheckForNull Iterable pullRequests) { - this.pullRequests = pullRequests; + if (permissionsSource instanceof Closeable) { + ((Closeable) permissionsSource).close(); } - - /** - * Returns the pull request details or an empty list if either the request did not specify to {@link #isFetchPRs()} - * or if the pull request details have not been provided by {@link #setPullRequests(Iterable)} yet. - * - * @return the details of pull requests, may be limited by {@link #getRequestedPullRequestNumbers()} or - * may be empty if not {@link #isFetchPRs()} - */ - @NonNull - public Iterable getPullRequests() { - return Util.fixNull(pullRequests); + super.close(); + } + + /** + * Returns the permissions of the supplied user. + * + * @param username the user. + * @return the permissions of the supplied user. + * @throws IOException if the permissions could not be retrieved. + * @throws InterruptedException if interrupted while retrieving the permissions. + */ + public GHPermissionType getPermissions(String username) throws IOException, InterruptedException { + synchronized (permissions) { + if (permissions.containsKey(username)) { + return permissions.get(username); + } } - - /** - * Provides the requests with the branch details. - * - * @param branches the branch details. - */ - public final void setBranches(@CheckForNull Iterable branches) { - this.branches = branches; - } - - /** - * Returns the branch details or an empty list if either the request did not specify to {@link #isFetchBranches()} - * or if the branch details have not been provided by {@link #setBranches(Iterable)} yet. - * - * @return the branch details (may be empty) - */ - @NonNull - public final Iterable getBranches() { - return Util.fixNull(branches); + if (permissionsSource != null) { + GHPermissionType result = permissionsSource.fetch(username); + synchronized (permissions) { + permissions.put(username, result); + } + return result; } - - /** - * Provides the requests with the tag details. - * - * @param tags the tag details. - */ - public final void setTags(@CheckForNull Iterable tags) { - this.tags = tags; + if (repository != null && username.equalsIgnoreCase(repository.getOwnerName())) { + return GHPermissionType.ADMIN; } - - /** - * Returns the branch details or an empty list if either the request did not specify to {@link #isFetchBranches()} - * or if the branch details have not been provided by {@link #setBranches(Iterable)} yet. - * - * @return the branch details (may be empty) - */ - @NonNull - public final Iterable getTags() { - return Util.fixNull(tags); - } - - // TODO Iterable getTags() and setTags(...) - - /** - * Provides the request with the names of the repository collaborators. - * - * @param collaboratorNames the names of the repository collaborators. - */ - public final void setCollaboratorNames(@CheckForNull Set collaboratorNames) { - this.collaboratorNames = collaboratorNames; - } - - /** - * Returns the names of the repository collaborators or {@code null} if those details have not been provided yet. - * - * @return the names of the repository collaborators or {@code null} if those details have not been provided yet. - */ - public final Set getCollaboratorNames() { - return collaboratorNames; - } - - /** - * Checks the API rate limit and sleeps if over-used until the remaining limit is on-target for expected usage. - * - * @throws IOException if the rate limit could not be obtained. - * @throws InterruptedException if interrupted while waiting. - * @deprecated rate limit checking is done automatically - */ - @Deprecated - public final void checkApiRateLimit() throws IOException, InterruptedException { - if (gitHub != null) { - Connector.configureLocalRateLimitChecker(listener(), Objects.requireNonNull(gitHub)); - } - } - - /** - * Returns the {@link GitHub} API connector to use for the request. - * - * @return the {@link GitHub} API connector to use for the request or {@code null} if caller should establish - * their own. - */ - @CheckForNull - public GitHub getGitHub() { - return gitHub; - } - - /** - * Provides the {@link GitHub} API connector to use for the request. - * - * @param gitHub {@link GitHub} API connector to use for the request. - */ - public void setGitHub(@CheckForNull GitHub gitHub) { - this.gitHub = gitHub; - } - - /** - * Returns the {@link GHRepository}. - * - * @return the {@link GHRepository}. - */ - public GHRepository getRepository() { - return repository; - } - - /** - * Sets the {@link GHRepository}. - * - * @param repository the {@link GHRepository}. - */ - public void setRepository(GHRepository repository) { - this.repository = repository; - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - if (pullRequests instanceof Closeable) { - ((Closeable) pullRequests).close(); - } - if (branches instanceof Closeable) { - ((Closeable) branches).close(); - } - if (permissionsSource instanceof Closeable) { - ((Closeable) permissionsSource).close(); - } - super.close(); - } - - /** - * Returns the permissions of the supplied user. - * - * @param username the user. - * @return the permissions of the supplied user. - * @throws IOException if the permissions could not be retrieved. - * @throws InterruptedException if interrupted while retrieving the permissions. - */ - public GHPermissionType getPermissions(String username) throws IOException, InterruptedException { - synchronized (permissions) { - if (permissions.containsKey(username)) { - return permissions.get(username); - } - } - if (permissionsSource != null) { - GHPermissionType result = permissionsSource.fetch(username); - synchronized (permissions) { - permissions.put(username, result); - } - return result; - } - if (repository != null && username.equalsIgnoreCase(repository.getOwnerName())) { - return GHPermissionType.ADMIN; - } - if (collaboratorNames != null && collaboratorNames.contains(username)) { - return GHPermissionType.WRITE; - } - return GHPermissionType.NONE; - } - - /** - * Returns the permission source. - * - * @return the permission source. - */ - @CheckForNull - public GitHubPermissionsSource getPermissionsSource() { - return permissionsSource; - } - - /** - * Sets the permission source. - * - * @param permissionsSource the permission source. - */ - public void setPermissionsSource(@CheckForNull GitHubPermissionsSource permissionsSource) { - this.permissionsSource = permissionsSource; + if (collaboratorNames != null && collaboratorNames.contains(username)) { + return GHPermissionType.WRITE; } + return GHPermissionType.NONE; + } + + /** + * Returns the permission source. + * + * @return the permission source. + */ + @CheckForNull + public GitHubPermissionsSource getPermissionsSource() { + return permissionsSource; + } + + /** + * Sets the permission source. + * + * @param permissionsSource the permission source. + */ + public void setPermissionsSource(@CheckForNull GitHubPermissionsSource permissionsSource) { + this.permissionsSource = permissionsSource; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubTagSCMHead.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubTagSCMHead.java index adffc92fb..23765d424 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubTagSCMHead.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubTagSCMHead.java @@ -6,22 +6,19 @@ public class GitHubTagSCMHead extends GitTagSCMHead implements TagSCMHead { - /** - * Constructor. - * - * @param name the name. - * @param timestamp the tag timestamp; - */ - public GitHubTagSCMHead(@NonNull String name, long timestamp) { - super(name, timestamp); - } - - /** - * {@inheritDoc} - */ - @Override - public String getPronoun() { - return Messages.GitHubTagSCMHead_Pronoun(); - } + /** + * Constructor. + * + * @param name the name. + * @param timestamp the tag timestamp; + */ + public GitHubTagSCMHead(@NonNull String name, long timestamp) { + super(name, timestamp); + } + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.GitHubTagSCMHead_Pronoun(); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/HttpsRepositoryUriResolver.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/HttpsRepositoryUriResolver.java index 2da1bd16b..e43f941f2 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/HttpsRepositoryUriResolver.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/HttpsRepositoryUriResolver.java @@ -24,20 +24,16 @@ package org.jenkinsci.plugins.github_branch_source; -/** - * A {@link RepositoryUriResolver} that resolves HTTP git URLs. - */ +/** A {@link RepositoryUriResolver} that resolves HTTP git URLs. */ public class HttpsRepositoryUriResolver extends RepositoryUriResolver { - /** - * {@inheritDoc} - */ - @Override - public String getRepositoryUri(String apiUri, String owner, String repository) { - if (apiUri == null || apiUri.startsWith("https://")) { - return "https://" + hostnameFromApiUri(apiUri) + "/" + owner + "/" + repository + ".git"; - } else { - return "http://" + hostnameFromApiUri(apiUri) + "/" + owner + "/" + repository + ".git"; - } + /** {@inheritDoc} */ + @Override + public String getRepositoryUri(String apiUri, String owner, String repository) { + if (apiUri == null || apiUri.startsWith("https://")) { + return "https://" + hostnameFromApiUri(apiUri) + "/" + owner + "/" + repository + ".git"; + } else { + return "http://" + hostnameFromApiUri(apiUri) + "/" + owner + "/" + repository + ".git"; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/InvalidPrivateKeyException.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/InvalidPrivateKeyException.java index cc1f8d65f..5a3c8cc69 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/InvalidPrivateKeyException.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/InvalidPrivateKeyException.java @@ -2,7 +2,7 @@ public class InvalidPrivateKeyException extends RuntimeException { - public InvalidPrivateKeyException(String message) { - super(message); - } + public InvalidPrivateKeyException(String message) { + super(message); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/LazyIterable.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/LazyIterable.java index c44bf05df..1af8d5acc 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/LazyIterable.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/LazyIterable.java @@ -34,28 +34,23 @@ * @since 2.2.0 */ abstract class LazyIterable implements Iterable { - /** - * The delegate. - */ - @CheckForNull - private Iterable delegate; + /** The delegate. */ + @CheckForNull private Iterable delegate; - /** - * Instantiates the delegate. - * - * @return the delegate. - */ - @NonNull - protected abstract Iterable create(); + /** + * Instantiates the delegate. + * + * @return the delegate. + */ + @NonNull + protected abstract Iterable create(); - /** - * {@inheritDoc} - */ - @Override - public synchronized Iterator iterator() { - if (delegate == null) { - delegate = create(); - } - return delegate.iterator(); + /** {@inheritDoc} */ + @Override + public synchronized Iterator iterator() { + if (delegate == null) { + delegate = create(); } + return delegate.iterator(); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/LazySet.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/LazySet.java index 9b11dafcb..c91baae94 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/LazySet.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/LazySet.java @@ -36,142 +36,111 @@ * @param the type of elements in the set. */ abstract class LazySet extends AbstractSet { - /** - * The delegate. - */ - @CheckForNull - private Set delegate; - - /** - * Instantiates the delegate. - * - * @return the delegate. - */ - @NonNull - protected abstract Set create(); - - /** - * Gets the delegate. - * - * @return the delegate. - */ - @NonNull - private synchronized Set delegate() { - if (delegate == null) { - delegate = create(); - } - return delegate; - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return delegate().size(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEmpty() { - return delegate().isEmpty(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(Object o) { - return delegate().contains(o); - } - - /** - * {@inheritDoc} - */ - @Override - public Iterator iterator() { - return delegate().iterator(); - } - - /** - * {@inheritDoc} - */ - @Override - public T[] toArray(T[] a) { - return delegate().toArray(a); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(E e) { - return delegate().add(e); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean remove(Object o) { - return delegate().remove(o); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsAll(Collection c) { - return delegate().containsAll(c); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean addAll(Collection c) { - return delegate().addAll(c); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean retainAll(Collection c) { - return delegate().retainAll(c); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removeAll(Collection c) { - return delegate().removeAll(c); - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - delegate().clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - return delegate().equals(o); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return delegate().hashCode(); + /** The delegate. */ + @CheckForNull private Set delegate; + + /** + * Instantiates the delegate. + * + * @return the delegate. + */ + @NonNull + protected abstract Set create(); + + /** + * Gets the delegate. + * + * @return the delegate. + */ + @NonNull + private synchronized Set delegate() { + if (delegate == null) { + delegate = create(); } + return delegate; + } + + /** {@inheritDoc} */ + @Override + public int size() { + return delegate().size(); + } + + /** {@inheritDoc} */ + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + /** {@inheritDoc} */ + @Override + public boolean contains(Object o) { + return delegate().contains(o); + } + + /** {@inheritDoc} */ + @Override + public Iterator iterator() { + return delegate().iterator(); + } + + /** {@inheritDoc} */ + @Override + public T[] toArray(T[] a) { + return delegate().toArray(a); + } + + /** {@inheritDoc} */ + @Override + public boolean add(E e) { + return delegate().add(e); + } + + /** {@inheritDoc} */ + @Override + public boolean remove(Object o) { + return delegate().remove(o); + } + + /** {@inheritDoc} */ + @Override + public boolean containsAll(Collection c) { + return delegate().containsAll(c); + } + + /** {@inheritDoc} */ + @Override + public boolean addAll(Collection c) { + return delegate().addAll(c); + } + + /** {@inheritDoc} */ + @Override + public boolean retainAll(Collection c) { + return delegate().retainAll(c); + } + + /** {@inheritDoc} */ + @Override + public boolean removeAll(Collection c) { + return delegate().removeAll(c); + } + + /** {@inheritDoc} */ + @Override + public void clear() { + delegate().clear(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + return delegate().equals(o); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return delegate().hashCode(); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/MergeWithGitSCMExtension.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/MergeWithGitSCMExtension.java index d7619d264..10e907417 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/MergeWithGitSCMExtension.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/MergeWithGitSCMExtension.java @@ -40,11 +40,11 @@ @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") public class MergeWithGitSCMExtension extends jenkins.plugins.git.MergeWithGitSCMExtension { - MergeWithGitSCMExtension(@NonNull String baseName, @CheckForNull String baseHash) { - super(baseName, baseHash); - } + MergeWithGitSCMExtension(@NonNull String baseName, @CheckForNull String baseHash) { + super(baseName, baseHash); + } - private Object readResolve() throws ObjectStreamException { - return new jenkins.plugins.git.MergeWithGitSCMExtension(getBaseName(), getBaseHash()); - } + private Object readResolve() throws ObjectStreamException { + return new jenkins.plugins.git.MergeWithGitSCMExtension(getBaseName(), getBaseHash()); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTrait.java index b0ecd1978..72d1f42bc 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTrait.java @@ -48,184 +48,158 @@ import org.kohsuke.stapler.DataBoundConstructor; /** - * A {@link Discovery} trait for GitHub that will discover pull requests originating from a branch in the repository - * itself. + * A {@link Discovery} trait for GitHub that will discover pull requests originating from a branch + * in the repository itself. * * @since 2.2.0 */ public class OriginPullRequestDiscoveryTrait extends SCMSourceTrait { - /** - * None strategy. - */ - public static final int NONE = 0; - /** - * Merging the pull request with the current target branch revision. - */ - public static final int MERGE = 1; - /** - * The current pull request revision. - */ - public static final int HEAD = 2; - /** - * Both the current pull request revision and the pull request merged with the current target branch revision. - */ - public static final int HEAD_AND_MERGE = 3; - - /** - * The strategy encoded as a bit-field. - */ - private final int strategyId; - - /** - * Constructor for stapler. - * - * @param strategyId the strategy id. - */ - @DataBoundConstructor - public OriginPullRequestDiscoveryTrait(int strategyId) { - this.strategyId = strategyId; + /** None strategy. */ + public static final int NONE = 0; + /** Merging the pull request with the current target branch revision. */ + public static final int MERGE = 1; + /** The current pull request revision. */ + public static final int HEAD = 2; + /** + * Both the current pull request revision and the pull request merged with the current target + * branch revision. + */ + public static final int HEAD_AND_MERGE = 3; + + /** The strategy encoded as a bit-field. */ + private final int strategyId; + + /** + * Constructor for stapler. + * + * @param strategyId the strategy id. + */ + @DataBoundConstructor + public OriginPullRequestDiscoveryTrait(int strategyId) { + this.strategyId = strategyId; + } + + /** + * Constructor for programmatic instantiation. + * + * @param strategies the {@link ChangeRequestCheckoutStrategy} instances. + */ + public OriginPullRequestDiscoveryTrait(Set strategies) { + this( + (strategies.contains(ChangeRequestCheckoutStrategy.MERGE) ? MERGE : NONE) + + (strategies.contains(ChangeRequestCheckoutStrategy.HEAD) ? HEAD : NONE)); + } + + /** + * Gets the strategy id. + * + * @return the strategy id. + */ + public int getStrategyId() { + return strategyId; + } + + /** + * Returns the strategies. + * + * @return the strategies. + */ + @NonNull + public Set getStrategies() { + switch (strategyId) { + case OriginPullRequestDiscoveryTrait.MERGE: + return EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); + case OriginPullRequestDiscoveryTrait.HEAD: + return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD); + case OriginPullRequestDiscoveryTrait.HEAD_AND_MERGE: + return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD, ChangeRequestCheckoutStrategy.MERGE); + default: + return EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + } + } + + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantOriginPRs(true); + ctx.withAuthority(new OriginChangeRequestSCMHeadAuthority()); + ctx.withOriginPRStrategies(getStrategies()); + } + + /** {@inheritDoc} */ + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category instanceof ChangeRequestSCMHeadCategory; + } + + /** Our descriptor. */ + @Symbol("gitHubPullRequestDiscovery") + @Extension + @Discovery + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.OriginPullRequestDiscoveryTrait_discoverPullRequestsFromOrigin(); } - /** - * Constructor for programmatic instantiation. - * - * @param strategies the {@link ChangeRequestCheckoutStrategy} instances. - */ - public OriginPullRequestDiscoveryTrait(Set strategies) { - this((strategies.contains(ChangeRequestCheckoutStrategy.MERGE) ? MERGE : NONE) - + (strategies.contains(ChangeRequestCheckoutStrategy.HEAD) ? HEAD : NONE)); + /** {@inheritDoc} */ + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; } - /** - * Gets the strategy id. - * - * @return the strategy id. - */ - public int getStrategyId() { - return strategyId; + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; } /** - * Returns the strategies. + * Populates the strategy options. * - * @return the strategies. + * @return the strategy options. */ @NonNull - public Set getStrategies() { - switch (strategyId) { - case OriginPullRequestDiscoveryTrait.MERGE: - return EnumSet.of(ChangeRequestCheckoutStrategy.MERGE); - case OriginPullRequestDiscoveryTrait.HEAD: - return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD); - case OriginPullRequestDiscoveryTrait.HEAD_AND_MERGE: - return EnumSet.of(ChangeRequestCheckoutStrategy.HEAD, ChangeRequestCheckoutStrategy.MERGE); - default: - return EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void decorateContext(SCMSourceContext context) { - GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; - ctx.wantOriginPRs(true); - ctx.withAuthority(new OriginChangeRequestSCMHeadAuthority()); - ctx.withOriginPRStrategies(getStrategies()); + @Restricted(NoExternalUse.class) + @SuppressWarnings("unused") // stapler + public ListBoxModel doFillStrategyIdItems() { + ListBoxModel result = new ListBoxModel(); + result.add(Messages.ForkPullRequestDiscoveryTrait_mergeOnly(), String.valueOf(MERGE)); + result.add(Messages.ForkPullRequestDiscoveryTrait_headOnly(), String.valueOf(HEAD)); + result.add( + Messages.ForkPullRequestDiscoveryTrait_headAndMerge(), String.valueOf(HEAD_AND_MERGE)); + return result; } + } - /** - * {@inheritDoc} - */ + /** A {@link SCMHeadAuthority} that trusts origin pull requests */ + public static class OriginChangeRequestSCMHeadAuthority + extends SCMHeadAuthority { + /** {@inheritDoc} */ @Override - public boolean includeCategory(@NonNull SCMHeadCategory category) { - return category instanceof ChangeRequestSCMHeadCategory; + protected boolean checkTrusted( + @NonNull SCMSourceRequest request, @NonNull ChangeRequestSCMHead2 head) { + return SCMHeadOrigin.DEFAULT.equals(head.getOrigin()); } - /** - * Our descriptor. - */ - @Symbol("gitHubPullRequestDiscovery") + /** Our descriptor. */ @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.OriginPullRequestDiscoveryTrait_discoverPullRequestsFromOrigin(); - } - - /** - * {@inheritDoc} - */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } - - /** - * Populates the strategy options. - * - * @return the strategy options. - */ - @NonNull - @Restricted(NoExternalUse.class) - @SuppressWarnings("unused") // stapler - public ListBoxModel doFillStrategyIdItems() { - ListBoxModel result = new ListBoxModel(); - result.add(Messages.ForkPullRequestDiscoveryTrait_mergeOnly(), String.valueOf(MERGE)); - result.add(Messages.ForkPullRequestDiscoveryTrait_headOnly(), String.valueOf(HEAD)); - result.add(Messages.ForkPullRequestDiscoveryTrait_headAndMerge(), String.valueOf(HEAD_AND_MERGE)); - return result; - } - } - - /** - * A {@link SCMHeadAuthority} that trusts origin pull requests - */ - public static class OriginChangeRequestSCMHeadAuthority - extends SCMHeadAuthority { - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull ChangeRequestSCMHead2 head) { - return SCMHeadOrigin.DEFAULT.equals(head.getOrigin()); - } - - /** - * Our descriptor. - */ - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.OriginPullRequestDiscoveryTrait_authorityDisplayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); - } - } + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.OriginPullRequestDiscoveryTrait_authorityDisplayName(); + } + + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestAction.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestAction.java index e0e283017..ba723fa6e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestAction.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestAction.java @@ -35,56 +35,57 @@ /** * Retained for binary compatibility only + * * @deprecated Retained for binary compatibility only */ @Deprecated @Restricted(NoExternalUse.class) final class PullRequestAction extends InvisibleAction { - private final int number; - private final URL url; - private final String title; - private final String userLogin; - private final String baseRef; + private final int number; + private final URL url; + private final String title; + private final String userLogin; + private final String baseRef; - PullRequestAction(GHPullRequest pr) throws IOException { - number = pr.getNumber(); - url = pr.getHtmlUrl(); - title = pr.getTitle(); - userLogin = pr.getUser().getLogin(); - baseRef = pr.getBase().getRef(); - } + PullRequestAction(GHPullRequest pr) throws IOException { + number = pr.getNumber(); + url = pr.getHtmlUrl(); + title = pr.getTitle(); + userLogin = pr.getUser().getLogin(); + baseRef = pr.getBase().getRef(); + } - PullRequestAction(int number, URL url, String title, String userLogin, String baseRef) { - this.number = number; - this.url = url; - this.title = title; - this.userLogin = userLogin; - this.baseRef = baseRef; - } + PullRequestAction(int number, URL url, String title, String userLogin, String baseRef) { + this.number = number; + this.url = url; + this.title = title; + this.userLogin = userLogin; + this.baseRef = baseRef; + } - @NonNull - public String getId() { - return Integer.toString(number); - } + @NonNull + public String getId() { + return Integer.toString(number); + } - @NonNull - public URL getURL() { - return url; - } + @NonNull + public URL getURL() { + return url; + } - @NonNull - public String getTitle() { - return title; - } + @NonNull + public String getTitle() { + return title; + } - @NonNull - public String getAuthor() { - return userLogin; - } + @NonNull + public String getAuthor() { + return userLogin; + } - @NonNull - public SCMHead getTarget() { - return new BranchSCMHead(baseRef); - } + @NonNull + public SCMHead getTarget() { + return new BranchSCMHead(baseRef); + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java index 6bbe044ff..d543b75f1 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestGHEventSubscriber.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.google.common.collect.Sets.immutableEnumSet; +import static org.kohsuke.github.GHEvent.PULL_REQUEST; + import com.cloudbees.jenkins.GitHubRepositoryName; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -63,298 +66,337 @@ import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; -import static com.google.common.collect.Sets.immutableEnumSet; -import static org.kohsuke.github.GHEvent.PULL_REQUEST; - -/** - * This subscriber manages {@link org.kohsuke.github.GHEvent} PULL_REQUEST. - */ +/** This subscriber manages {@link org.kohsuke.github.GHEvent} PULL_REQUEST. */ @Extension public class PullRequestGHEventSubscriber extends GHEventsSubscriber { - private static final Logger LOGGER = Logger.getLogger(PullRequestGHEventSubscriber.class.getName()); - private static final Pattern REPOSITORY_NAME_PATTERN = Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); + private static final Logger LOGGER = + Logger.getLogger(PullRequestGHEventSubscriber.class.getName()); + private static final Pattern REPOSITORY_NAME_PATTERN = + Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); - @Override - protected boolean isApplicable(@Nullable Item project) { - if (project != null) { - if (project instanceof SCMSourceOwner) { - SCMSourceOwner owner = (SCMSourceOwner) project; - for (SCMSource source : owner.getSCMSources()) { - if (source instanceof GitHubSCMSource) { - return true; - } - } - } - if (project.getParent() instanceof SCMSourceOwner) { - SCMSourceOwner owner = (SCMSourceOwner) project.getParent(); - for (SCMSource source : owner.getSCMSources()) { - if (source instanceof GitHubSCMSource) { - return true; - } - } - } + @Override + protected boolean isApplicable(@Nullable Item project) { + if (project != null) { + if (project instanceof SCMSourceOwner) { + SCMSourceOwner owner = (SCMSourceOwner) project; + for (SCMSource source : owner.getSCMSources()) { + if (source instanceof GitHubSCMSource) { + return true; + } } - return false; - } - - /** - * @return set with only PULL_REQUEST event - */ - @Override - protected Set events() { - return immutableEnumSet(PULL_REQUEST); + } + if (project.getParent() instanceof SCMSourceOwner) { + SCMSourceOwner owner = (SCMSourceOwner) project.getParent(); + for (SCMSource source : owner.getSCMSources()) { + if (source instanceof GitHubSCMSource) { + return true; + } + } + } } + return false; + } - @Override - protected void onEvent(GHSubscriberEvent event) { - try { - final GHEventPayload.PullRequest p = GitHub.offline() - .parseEventPayload(new StringReader(event.getPayload()), GHEventPayload.PullRequest.class); - String action = p.getAction(); - String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); - LOGGER.log(Level.FINE, "Received {0} for {1} from {2}", - new Object[]{event.getGHEvent(), repoUrl, event.getOrigin()} - ); - Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); - if (matcher.matches()) { - final GitHubRepositoryName changedRepository = GitHubRepositoryName.create(repoUrl); - if (changedRepository == null) { - LOGGER.log(Level.WARNING, "Malformed repository URL {0}", repoUrl); - return; - } + /** @return set with only PULL_REQUEST event */ + @Override + protected Set events() { + return immutableEnumSet(PULL_REQUEST); + } - if ("opened".equals(action)) { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.CREATED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } else if ("reopened".equals(action) || "synchronize".equals(action) || "edited".equals(action)) { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.UPDATED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } else if ("closed".equals(action)) { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.REMOVED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } - } + @Override + protected void onEvent(GHSubscriberEvent event) { + try { + final GHEventPayload.PullRequest p = + GitHub.offline() + .parseEventPayload( + new StringReader(event.getPayload()), GHEventPayload.PullRequest.class); + String action = p.getAction(); + String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); + LOGGER.log( + Level.FINE, + "Received {0} for {1} from {2}", + new Object[] {event.getGHEvent(), repoUrl, event.getOrigin()}); + Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); + if (matcher.matches()) { + final GitHubRepositoryName changedRepository = GitHubRepositoryName.create(repoUrl); + if (changedRepository == null) { + LOGGER.log(Level.WARNING, "Malformed repository URL {0}", repoUrl); + return; + } - } catch (IOException e) { - LogRecord lr = new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); - lr.setParameters(new Object[]{event.getGHEvent(), event.getOrigin(), event.getPayload()}); - lr.setThrown(e); - LOGGER.log(lr); + if ("opened".equals(action)) { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.CREATED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); + } else if ("reopened".equals(action) + || "synchronize".equals(action) + || "edited".equals(action)) { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.UPDATED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); + } else if ("closed".equals(action)) { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.REMOVED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); } - } + } - private void fireAfterDelay(final SCMHeadEventImpl e) { - SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + } catch (IOException e) { + LogRecord lr = + new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); + lr.setParameters(new Object[] {event.getGHEvent(), event.getOrigin(), event.getPayload()}); + lr.setThrown(e); + LOGGER.log(lr); } + } - private static class SCMHeadEventImpl extends SCMHeadEvent { - private final String repoHost; - private final String repoOwner; - private final String repository; + private void fireAfterDelay(final SCMHeadEventImpl e) { + SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + } - public SCMHeadEventImpl(Type type, long timestamp, GHEventPayload.PullRequest pullRequest, GitHubRepositoryName repo, - String origin) { - super(type, timestamp, pullRequest, origin); - this.repoHost = repo.getHost(); - this.repoOwner = pullRequest.getRepository().getOwnerName(); - this.repository = pullRequest.getRepository().getName(); - } + private static class SCMHeadEventImpl extends SCMHeadEvent { + private final String repoHost; + private final String repoOwner; + private final String repository; - private boolean isApiMatch(String apiUri) { - return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); - } + public SCMHeadEventImpl( + Type type, + long timestamp, + GHEventPayload.PullRequest pullRequest, + GitHubRepositoryName repo, + String origin) { + super(type, timestamp, pullRequest, origin); + this.repoHost = repo.getHost(); + this.repoOwner = pullRequest.getRepository().getOwnerName(); + this.repository = pullRequest.getRepository().getName(); + } - @Override - public boolean isMatch(@NonNull SCMNavigator navigator) { - return navigator instanceof GitHubSCMNavigator - && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); - } + private boolean isApiMatch(String apiUri) { + return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); + } - @Override - public String descriptionFor(@NonNull SCMNavigator navigator) { - String action = getPayload().getAction(); - if (action != null) { - switch (action) { - case "opened": - return "Pull request #" + getPayload().getNumber() + " opened in repository " + repository; - case "reopened": - return "Pull request #" + getPayload().getNumber() + " reopened in repository " + repository; - case "synchronize": - return "Pull request #" + getPayload().getNumber() + " updated in repository " + repository; - case "closed": - return "Pull request #" + getPayload().getNumber() + " closed in repository " + repository; - } - } - return "Pull request #" + getPayload().getNumber() + " event in repository " + repository; - } + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + return navigator instanceof GitHubSCMNavigator + && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); + } - @Override - public String descriptionFor(SCMSource source) { - String action = getPayload().getAction(); - if (action != null) { - switch (action) { - case "opened": - return "Pull request #" + getPayload().getNumber() + " opened"; - case "reopened": - return "Pull request #" + getPayload().getNumber() + " reopened"; - case "synchronize": - return "Pull request #" + getPayload().getNumber() + " updated"; - case "closed": - return "Pull request #" + getPayload().getNumber() + " closed"; - } - } - return "Pull request #" + getPayload().getNumber() + " event"; + @Override + public String descriptionFor(@NonNull SCMNavigator navigator) { + String action = getPayload().getAction(); + if (action != null) { + switch (action) { + case "opened": + return "Pull request #" + + getPayload().getNumber() + + " opened in repository " + + repository; + case "reopened": + return "Pull request #" + + getPayload().getNumber() + + " reopened in repository " + + repository; + case "synchronize": + return "Pull request #" + + getPayload().getNumber() + + " updated in repository " + + repository; + case "closed": + return "Pull request #" + + getPayload().getNumber() + + " closed in repository " + + repository; } + } + return "Pull request #" + getPayload().getNumber() + " event in repository " + repository; + } - @Override - public String description() { - String action = getPayload().getAction(); - if (action != null) { - switch (action) { - case "opened": - return "Pull request #" + getPayload().getNumber() + " opened in repository " + repoOwner + "/" + repository; - case "reopened": - return "Pull request #" + getPayload().getNumber() + " reopened in repository " + repoOwner - + "/" + repository; - case "synchronize": - return "Pull request #" + getPayload().getNumber() + " updated in repository " + repoOwner + "/" - + repository; - case "closed": - return "Pull request #" + getPayload().getNumber() + " closed in repository " + repoOwner + "/" - + repository; - } - } - return "Pull request #" + getPayload().getNumber() + " event in repository " + repoOwner + "/" + repository; + @Override + public String descriptionFor(SCMSource source) { + String action = getPayload().getAction(); + if (action != null) { + switch (action) { + case "opened": + return "Pull request #" + getPayload().getNumber() + " opened"; + case "reopened": + return "Pull request #" + getPayload().getNumber() + " reopened"; + case "synchronize": + return "Pull request #" + getPayload().getNumber() + " updated"; + case "closed": + return "Pull request #" + getPayload().getNumber() + " closed"; } + } + return "Pull request #" + getPayload().getNumber() + " event"; + } - @NonNull - @Override - public String getSourceName() { - return repository; + @Override + public String description() { + String action = getPayload().getAction(); + if (action != null) { + switch (action) { + case "opened": + return "Pull request #" + + getPayload().getNumber() + + " opened in repository " + + repoOwner + + "/" + + repository; + case "reopened": + return "Pull request #" + + getPayload().getNumber() + + " reopened in repository " + + repoOwner + + "/" + + repository; + case "synchronize": + return "Pull request #" + + getPayload().getNumber() + + " updated in repository " + + repoOwner + + "/" + + repository; + case "closed": + return "Pull request #" + + getPayload().getNumber() + + " closed in repository " + + repoOwner + + "/" + + repository; } + } + return "Pull request #" + + getPayload().getNumber() + + " event in repository " + + repoOwner + + "/" + + repository; + } - @NonNull - @Override - public Map heads(@NonNull SCMSource source) { - if (!(source instanceof GitHubSCMSource - && isApiMatch(((GitHubSCMSource) source).getApiUri()) - && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) - && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()))) { - return Collections.emptyMap(); - } - GitHubSCMSource src = (GitHubSCMSource) source; - GHEventPayload.PullRequest pullRequest = getPayload(); - GHPullRequest ghPullRequest = pullRequest.getPullRequest(); - GHRepository repo = pullRequest.getRepository(); - String prRepoName = repo.getName(); - if (!prRepoName.matches(GitHubSCMSource.VALID_GITHUB_REPO_NAME)) { - // fake repository name - return Collections.emptyMap(); - } - GHUser user; - try { - user = ghPullRequest.getHead().getUser(); - } catch (IOException e) { - // fake owner name - return Collections.emptyMap(); - } - String prOwnerName = user.getLogin(); - if (!prOwnerName.matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)) { - // fake owner name - return Collections.emptyMap(); - } - if (!ghPullRequest.getBase().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) { - // fake base sha1 - return Collections.emptyMap(); - } - if (!ghPullRequest.getHead().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) { - // fake head sha1 - return Collections.emptyMap(); - } + @NonNull + @Override + public String getSourceName() { + return repository; + } - boolean fork = !src.getRepoOwner().equalsIgnoreCase(prOwnerName); + @NonNull + @Override + public Map heads(@NonNull SCMSource source) { + if (!(source instanceof GitHubSCMSource + && isApiMatch(((GitHubSCMSource) source).getApiUri()) + && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) + && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()))) { + return Collections.emptyMap(); + } + GitHubSCMSource src = (GitHubSCMSource) source; + GHEventPayload.PullRequest pullRequest = getPayload(); + GHPullRequest ghPullRequest = pullRequest.getPullRequest(); + GHRepository repo = pullRequest.getRepository(); + String prRepoName = repo.getName(); + if (!prRepoName.matches(GitHubSCMSource.VALID_GITHUB_REPO_NAME)) { + // fake repository name + return Collections.emptyMap(); + } + GHUser user; + try { + user = ghPullRequest.getHead().getUser(); + } catch (IOException e) { + // fake owner name + return Collections.emptyMap(); + } + String prOwnerName = user.getLogin(); + if (!prOwnerName.matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)) { + // fake owner name + return Collections.emptyMap(); + } + if (!ghPullRequest.getBase().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) { + // fake base sha1 + return Collections.emptyMap(); + } + if (!ghPullRequest.getHead().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) { + // fake head sha1 + return Collections.emptyMap(); + } - Map result = new HashMap<>(); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(src.getTraits()); - if (!fork && context.wantBranches()) { - final String branchName = ghPullRequest.getHead().getRef(); - SCMHead head = new BranchSCMHead(branchName); - boolean excluded = false; - for (SCMHeadPrefilter prefilter: context.prefilters()) { - if (prefilter.isExcluded(source, head)) { - excluded = true; - break; - } - } - if (!excluded) { - SCMRevision hash = - new AbstractGitSCMSource.SCMRevisionImpl(head, ghPullRequest.getHead().getSha()); - result.put(head, hash); - } - } - if (context.wantPRs()) { - int number = pullRequest.getNumber(); - Set strategies = fork ? context.forkPRStrategies() : context.originPRStrategies(); - for (ChangeRequestCheckoutStrategy strategy: strategies) { - final String branchName; - if (strategies.size() == 1) { - branchName = "PR-" + number; - } else { - branchName = "PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH); - } - PullRequestSCMHead head; - PullRequestSCMRevision revision; - switch (strategy) { - case MERGE: - // it will take a call to GitHub to get the merge commit, so let the event receiver poll - head = new PullRequestSCMHead(ghPullRequest, branchName, true); - revision = null; - break; - default: - // Give the event receiver the data we have so they can revalidate - head = new PullRequestSCMHead(ghPullRequest, branchName, false); - revision = new PullRequestSCMRevision( - head, - ghPullRequest.getBase().getSha(), - ghPullRequest.getHead().getSha() - ); - break; - } - boolean excluded = false; - for (SCMHeadPrefilter prefilter : context.prefilters()) { - if (prefilter.isExcluded(source, head)) { - excluded = true; - break; - } - } - if (!excluded) { - result.put(head, revision); - } - } + boolean fork = !src.getRepoOwner().equalsIgnoreCase(prOwnerName); + + Map result = new HashMap<>(); + GitHubSCMSourceContext context = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()).withTraits(src.getTraits()); + if (!fork && context.wantBranches()) { + final String branchName = ghPullRequest.getHead().getRef(); + SCMHead head = new BranchSCMHead(branchName); + boolean excluded = false; + for (SCMHeadPrefilter prefilter : context.prefilters()) { + if (prefilter.isExcluded(source, head)) { + excluded = true; + break; + } + } + if (!excluded) { + SCMRevision hash = + new AbstractGitSCMSource.SCMRevisionImpl(head, ghPullRequest.getHead().getSha()); + result.put(head, hash); + } + } + if (context.wantPRs()) { + int number = pullRequest.getNumber(); + Set strategies = + fork ? context.forkPRStrategies() : context.originPRStrategies(); + for (ChangeRequestCheckoutStrategy strategy : strategies) { + final String branchName; + if (strategies.size() == 1) { + branchName = "PR-" + number; + } else { + branchName = "PR-" + number + "-" + strategy.name().toLowerCase(Locale.ENGLISH); + } + PullRequestSCMHead head; + PullRequestSCMRevision revision; + switch (strategy) { + case MERGE: + // it will take a call to GitHub to get the merge commit, so let the event receiver + // poll + head = new PullRequestSCMHead(ghPullRequest, branchName, true); + revision = null; + break; + default: + // Give the event receiver the data we have so they can revalidate + head = new PullRequestSCMHead(ghPullRequest, branchName, false); + revision = + new PullRequestSCMRevision( + head, ghPullRequest.getBase().getSha(), ghPullRequest.getHead().getSha()); + break; + } + boolean excluded = false; + for (SCMHeadPrefilter prefilter : context.prefilters()) { + if (prefilter.isExcluded(source, head)) { + excluded = true; + break; } - return result; + } + if (!excluded) { + result.put(head, revision); + } } + } + return result; + } - @Override - public boolean isMatch(@NonNull SCM scm) { - return false; - } + @Override + public boolean isMatch(@NonNull SCM scm) { + return false; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMHead.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMHead.java index aee442ab8..6726ba0ba 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMHead.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMHead.java @@ -42,314 +42,323 @@ import org.kohsuke.github.GHRepository; /** - * Head corresponding to a pull request. - * Named like {@code PR-123} or {@code PR-123-merge} or {@code PR-123-head}. + * Head corresponding to a pull request. Named like {@code PR-123} or {@code PR-123-merge} or {@code + * PR-123-head}. */ public class PullRequestSCMHead extends SCMHead implements ChangeRequestSCMHead2 { - private static final Logger LOGGER = Logger.getLogger(PullRequestSCMHead.class.getName()); - private static final AtomicBoolean UPGRADE_SKIPPED_2_0_X = new AtomicBoolean(false); - - private static final long serialVersionUID = 1; - - private Boolean merge; - private final int number; - private final BranchSCMHead target; - private final String sourceOwner; - private final String sourceRepo; - private final String sourceBranch; - private final SCMHeadOrigin origin; - /** - * Only populated if de-serializing instances. - */ - private transient Metadata metadata; - - PullRequestSCMHead(PullRequestSCMHead copy) { - super(copy.getName()); - this.merge = copy.merge; - this.number = copy.number; - this.target = copy.target; - this.sourceOwner = copy.sourceOwner; - this.sourceRepo = copy.sourceRepo; - this.sourceBranch = copy.sourceBranch; - this.origin = copy.origin; - this.metadata = copy.metadata; + private static final Logger LOGGER = Logger.getLogger(PullRequestSCMHead.class.getName()); + private static final AtomicBoolean UPGRADE_SKIPPED_2_0_X = new AtomicBoolean(false); + + private static final long serialVersionUID = 1; + + private Boolean merge; + private final int number; + private final BranchSCMHead target; + private final String sourceOwner; + private final String sourceRepo; + private final String sourceBranch; + private final SCMHeadOrigin origin; + /** Only populated if de-serializing instances. */ + private transient Metadata metadata; + + PullRequestSCMHead(PullRequestSCMHead copy) { + super(copy.getName()); + this.merge = copy.merge; + this.number = copy.number; + this.target = copy.target; + this.sourceOwner = copy.sourceOwner; + this.sourceRepo = copy.sourceRepo; + this.sourceBranch = copy.sourceBranch; + this.origin = copy.origin; + this.metadata = copy.metadata; + } + + PullRequestSCMHead(GHPullRequest pr, String name, boolean merge) { + super(name); + // the merge flag is encoded into the name, so safe to store here + this.merge = merge; + this.number = pr.getNumber(); + this.target = new BranchSCMHead(pr.getBase().getRef()); + // the source stuff is immutable for a pull request on github, so safe to store here + GHRepository repository = + pr.getHead().getRepository(); // may be null for deleted forks JENKINS-41246 + this.sourceOwner = repository == null ? null : repository.getOwnerName(); + this.sourceRepo = repository == null ? null : repository.getName(); + this.sourceBranch = pr.getHead().getRef(); + + if (pr.getRepository().getOwnerName().equalsIgnoreCase(sourceOwner)) { + this.origin = SCMHeadOrigin.DEFAULT; + } else { + // if the forked repo name differs from the upstream repo name + this.origin = + pr.getBase().getRepository().getName().equalsIgnoreCase(sourceRepo) + ? new SCMHeadOrigin.Fork(this.sourceOwner) + : new SCMHeadOrigin.Fork( + repository == null ? this.sourceOwner : repository.getFullName()); } - - PullRequestSCMHead(GHPullRequest pr, String name, boolean merge) { - super(name); - // the merge flag is encoded into the name, so safe to store here - this.merge = merge; - this.number = pr.getNumber(); - this.target = new BranchSCMHead(pr.getBase().getRef()); - // the source stuff is immutable for a pull request on github, so safe to store here - GHRepository repository = pr.getHead().getRepository(); // may be null for deleted forks JENKINS-41246 - this.sourceOwner = repository == null ? null : repository.getOwnerName(); - this.sourceRepo = repository == null ? null : repository.getName(); - this.sourceBranch = pr.getHead().getRef(); - - if (pr.getRepository().getOwnerName().equalsIgnoreCase(sourceOwner)) { - this.origin = SCMHeadOrigin.DEFAULT; - } else { - // if the forked repo name differs from the upstream repo name - this.origin = pr.getBase().getRepository().getName().equalsIgnoreCase(sourceRepo) - ? new SCMHeadOrigin.Fork(this.sourceOwner) - : new SCMHeadOrigin.Fork(repository == null ? this.sourceOwner : repository.getFullName()); - } + } + + public PullRequestSCMHead( + @NonNull String name, + String sourceOwner, + String sourceRepo, + String sourceBranch, + int number, + BranchSCMHead target, + SCMHeadOrigin origin, + ChangeRequestCheckoutStrategy strategy) { + super(name); + this.merge = ChangeRequestCheckoutStrategy.MERGE == strategy; + this.number = number; + this.target = target; + this.sourceOwner = sourceOwner; + this.sourceRepo = sourceRepo; + this.sourceBranch = sourceBranch; + this.origin = origin; + } + + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.PullRequestSCMHead_Pronoun(); + } + + public int getNumber() { + return number; + } + + /** + * Default for old settings. + * + * @return the deserialized object. + */ + @SuppressFBWarnings("SE_PRIVATE_READ_RESOLVE_NOT_INHERITED") // because JENKINS-41453 + private Object readResolve() { + if (merge == null) { + merge = true; } - - public PullRequestSCMHead(@NonNull String name, String sourceOwner, String sourceRepo, String sourceBranch, int number, - BranchSCMHead target, SCMHeadOrigin origin, ChangeRequestCheckoutStrategy strategy) { - super(name); - this.merge = ChangeRequestCheckoutStrategy.MERGE == strategy; - this.number = number; - this.target = target; - this.sourceOwner = sourceOwner; - this.sourceRepo = sourceRepo; - this.sourceBranch = sourceBranch; - this.origin = origin; + if (metadata != null) { + // Upgrade from 1.x: + if (UPGRADE_SKIPPED_2_0_X.compareAndSet(false, true)) { + LOGGER.log( + Level.WARNING, + "GitHub Branch Source plugin was directly upgraded from 1.x to 2.2.0 " + + "or newer without completing a full fetch from all repositories. Consequently startup may be " + + "delayed while GitHub is queried for the missing information"); + } + // we need the help of FixMetadataMigration + return new FixMetadata( + getName(), merge, metadata.getNumber(), new BranchSCMHead(metadata.getBaseRef())); } + if (origin == null && !(this instanceof FixOrigin)) { + // Upgrade from 2.0.x - /** - * {@inheritDoc} - */ - @Override - public String getPronoun() { - return Messages.PullRequestSCMHead_Pronoun(); + // we need the help of FixOriginMigration + return new FixOrigin(this); + } + return this; + } + + /** + * Whether we intend to build the merge of the PR head with the base branch. + * + * @return {@code true} if this is a merge PR head. + */ + public boolean isMerge() { + return merge; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public ChangeRequestCheckoutStrategy getCheckoutStrategy() { + return merge ? ChangeRequestCheckoutStrategy.MERGE : ChangeRequestCheckoutStrategy.HEAD; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getId() { + return Integer.toString(number); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public BranchSCMHead getTarget() { + return target; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getOriginName() { + return sourceBranch; + } + + public String getSourceOwner() { + return sourceOwner; + } + + public String getSourceBranch() { + return sourceBranch; + } + + public String getSourceRepo() { + return sourceRepo; + } + + @NonNull + @Override + public SCMHeadOrigin getOrigin() { + return origin == null ? SCMHeadOrigin.DEFAULT : origin; + } + + /** Holds legacy data so we can recover the details. */ + private static class Metadata { + private final int number; + private final String url; + private final String userLogin; + private final String baseRef; + + public Metadata(int number, String url, String userLogin, String baseRef) { + this.number = number; + this.url = url; + this.userLogin = userLogin; + this.baseRef = baseRef; } public int getNumber() { - return number; + return number; } - /** - * Default for old settings. - * - * @return the deserialized object. - */ - @SuppressFBWarnings("SE_PRIVATE_READ_RESOLVE_NOT_INHERITED") // because JENKINS-41453 - private Object readResolve() { - if (merge == null) { - merge = true; - } - if (metadata != null) { - // Upgrade from 1.x: - if (UPGRADE_SKIPPED_2_0_X.compareAndSet(false, true)) { - LOGGER.log(Level.WARNING, "GitHub Branch Source plugin was directly upgraded from 1.x to 2.2.0 " - + "or newer without completing a full fetch from all repositories. Consequently startup may be " - + "delayed while GitHub is queried for the missing information"); - } - // we need the help of FixMetadataMigration - return new FixMetadata( - getName(), - merge, - metadata.getNumber(), - new BranchSCMHead(metadata.getBaseRef()) - ); - } - if (origin == null && !(this instanceof FixOrigin)) { - // Upgrade from 2.0.x - - // we need the help of FixOriginMigration - return new FixOrigin(this); - } - return this; + public String getUrl() { + return url; } - /** - * Whether we intend to build the merge of the PR head with the base branch. - * - * @return {@code true} if this is a merge PR head. - */ - public boolean isMerge() { - return merge; + public String getUserLogin() { + return userLogin; } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public ChangeRequestCheckoutStrategy getCheckoutStrategy() { - return merge ? ChangeRequestCheckoutStrategy.MERGE : ChangeRequestCheckoutStrategy.HEAD; + public String getBaseRef() { + return baseRef; } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getId() { - return Integer.toString(number); + } + + /** + * Used to handle data migration. + * + * @see FixOriginMigration + * @deprecated used for data migration. + */ + @Deprecated + @Restricted(NoExternalUse.class) + public static class FixOrigin extends PullRequestSCMHead { + + FixOrigin(PullRequestSCMHead pullRequestSCMHead) { + super(pullRequestSCMHead); } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public BranchSCMHead getTarget() { - return target; + } + + /** + * Used to handle data migration. + * + * @see FixOriginMigration + * @deprecated used for data migration. + */ + @Restricted(NoExternalUse.class) + @Extension + public static class FixOriginMigration + extends SCMHeadMigration { + public FixOriginMigration() { + super(GitHubSCMSource.class, FixOrigin.class, PullRequestSCMRevision.class); } - /** - * {@inheritDoc} - */ - @NonNull @Override - public String getOriginName() { - return sourceBranch; + public PullRequestSCMHead migrate(@NonNull GitHubSCMSource source, @NonNull FixOrigin head) { + return new PullRequestSCMHead( + head.getName(), + head.getSourceOwner(), + head.getSourceRepo(), + head.getSourceBranch(), + head.getNumber(), + head.getTarget(), + source.getRepoOwner().equalsIgnoreCase(head.getSourceOwner()) + ? SCMHeadOrigin.DEFAULT + : new SCMHeadOrigin.Fork(head.getSourceOwner()), + head.getCheckoutStrategy()); } - public String getSourceOwner() { - return sourceOwner; - } - - public String getSourceBranch() { - return sourceBranch; - } - - public String getSourceRepo() { - return sourceRepo; - } - - @NonNull @Override - public SCMHeadOrigin getOrigin() { - return origin == null ? SCMHeadOrigin.DEFAULT : origin; + public SCMRevision migrate( + @NonNull GitHubSCMSource source, @NonNull PullRequestSCMRevision revision) { + PullRequestSCMHead head = migrate(source, (FixOrigin) revision.getHead()); + return head != null + ? new PullRequestSCMRevision(head, revision.getBaseHash(), revision.getPullHash()) + : null; } - - /** - * Holds legacy data so we can recover the details. - */ - private static class Metadata { - private final int number; - private final String url; - private final String userLogin; - private final String baseRef; - - public Metadata(int number, String url, String userLogin, String baseRef) { - this.number = number; - this.url = url; - this.userLogin = userLogin; - this.baseRef = baseRef; - } - - public int getNumber() { - return number; - } - - public String getUrl() { - return url; - } - - public String getUserLogin() { - return userLogin; - } - - public String getBaseRef() { - return baseRef; - } + } + + /** + * Used to handle data migration. + * + * @see FixMetadataMigration + * @deprecated used for data migration. + */ + @Deprecated + @Restricted(NoExternalUse.class) + public static class FixMetadata extends PullRequestSCMHead { + FixMetadata(String name, Boolean merge, int number, BranchSCMHead branchSCMHead) { + super( + name, + null, + null, + null, + number, + branchSCMHead, + null, + merge ? ChangeRequestCheckoutStrategy.MERGE : ChangeRequestCheckoutStrategy.HEAD); } - - /** - * Used to handle data migration. - * - * @see FixOriginMigration - * @deprecated used for data migration. - */ - @Deprecated - @Restricted(NoExternalUse.class) - public static class FixOrigin extends PullRequestSCMHead { - - FixOrigin(PullRequestSCMHead pullRequestSCMHead) { - super(pullRequestSCMHead); - } + } + + /** + * Used to handle data migration. + * + * @see FixOriginMigration + * @deprecated used for data migration. + */ + @Restricted(NoExternalUse.class) + @Extension + public static class FixMetadataMigration + extends SCMHeadMigration { + public FixMetadataMigration() { + super(GitHubSCMSource.class, FixMetadata.class, PullRequestSCMRevision.class); } - /** - * Used to handle data migration. - * - * @see FixOriginMigration - * @deprecated used for data migration. - */ - @Restricted(NoExternalUse.class) - @Extension - public static class FixOriginMigration extends - SCMHeadMigration { - public FixOriginMigration() { - super(GitHubSCMSource.class, FixOrigin.class, PullRequestSCMRevision.class); - } - - @Override - public PullRequestSCMHead migrate(@NonNull GitHubSCMSource source, @NonNull FixOrigin head) { - return new PullRequestSCMHead(head.getName(), head.getSourceOwner(), head.getSourceRepo(), - head.getSourceBranch(), head.getNumber(), head.getTarget(), source.getRepoOwner().equalsIgnoreCase(head.getSourceOwner()) - ? SCMHeadOrigin.DEFAULT - : new SCMHeadOrigin.Fork(head.getSourceOwner()), head.getCheckoutStrategy()); - } - - @Override - public SCMRevision migrate(@NonNull GitHubSCMSource source, - @NonNull PullRequestSCMRevision revision) { - PullRequestSCMHead head = migrate(source, (FixOrigin) revision.getHead()); - return head != null ? new PullRequestSCMRevision( - head, - revision.getBaseHash(), - revision.getPullHash() - ) : null; - } - } - - /** - * Used to handle data migration. - * - * @see FixMetadataMigration - * @deprecated used for data migration. - */ - @Deprecated - @Restricted(NoExternalUse.class) - public static class FixMetadata extends PullRequestSCMHead { - FixMetadata(String name, Boolean merge, int number, BranchSCMHead branchSCMHead) { - super(name, null, null, null, number, branchSCMHead, null, merge ? ChangeRequestCheckoutStrategy.MERGE : ChangeRequestCheckoutStrategy.HEAD); - } - + @Override + public PullRequestSCMHead migrate(@NonNull GitHubSCMSource source, @NonNull FixMetadata head) { + PullRequestSource src = source.retrievePullRequestSource(head.getNumber()); + return new PullRequestSCMHead( + head.getName(), + src == null ? null : src.getSourceOwner(), + src == null ? null : src.getSourceRepo(), + src == null ? null : src.getSourceBranch(), + head.getNumber(), + head.getTarget(), + src != null && source.getRepoOwner().equalsIgnoreCase(src.getSourceOwner()) + ? SCMHeadOrigin.DEFAULT + : new SCMHeadOrigin.Fork(head.getSourceOwner()), + head.getCheckoutStrategy()); } - /** - * Used to handle data migration. - * - * @see FixOriginMigration - * @deprecated used for data migration. - */ - @Restricted(NoExternalUse.class) - @Extension - public static class FixMetadataMigration extends - SCMHeadMigration { - public FixMetadataMigration() { - super(GitHubSCMSource.class, FixMetadata.class, PullRequestSCMRevision.class); - } - - @Override - public PullRequestSCMHead migrate(@NonNull GitHubSCMSource source, @NonNull FixMetadata head) { - PullRequestSource src = source.retrievePullRequestSource(head.getNumber()); - return new PullRequestSCMHead(head.getName(), src == null ? null : src.getSourceOwner(), - src == null ? null : src.getSourceRepo(), src == null ? null : src.getSourceBranch(), - head.getNumber(), head.getTarget(), src != null && source.getRepoOwner().equalsIgnoreCase(src.getSourceOwner()) - ? SCMHeadOrigin.DEFAULT - : new SCMHeadOrigin.Fork(head.getSourceOwner()), head.getCheckoutStrategy()); - } - - @Override - public SCMRevision migrate(@NonNull GitHubSCMSource source, - @NonNull PullRequestSCMRevision revision) { - PullRequestSCMHead head = migrate(source, (FixMetadata) revision.getHead()); - return head != null ? new PullRequestSCMRevision( - head, - revision.getBaseHash(), - revision.getPullHash() - ) : null; - } + @Override + public SCMRevision migrate( + @NonNull GitHubSCMSource source, @NonNull PullRequestSCMRevision revision) { + PullRequestSCMHead head = migrate(source, (FixMetadata) revision.getHead()); + return head != null + ? new PullRequestSCMRevision(head, revision.getBaseHash(), revision.getPullHash()) + : null; } - + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java index 2ad6c0230..979420e07 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevision.java @@ -30,108 +30,122 @@ import hudson.AbortException; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; -import jenkins.scm.api.mixin.ChangeRequestSCMRevision; import jenkins.scm.api.mixin.ChangeRequestSCMHead2; +import jenkins.scm.api.mixin.ChangeRequestSCMRevision; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.export.Exported; -/** - * Revision of a pull request. - */ +/** Revision of a pull request. */ public class PullRequestSCMRevision extends ChangeRequestSCMRevision { - private static final long serialVersionUID = 1L; - static final String NOT_MERGEABLE_HASH = "NOT_MERGEABLE"; - - private final @NonNull String baseHash; - private final @NonNull String pullHash; - private final String mergeHash; - - public PullRequestSCMRevision(@NonNull PullRequestSCMHead head, @NonNull String baseHash, @NonNull String pullHash) { - this(head, baseHash, pullHash, null); + private static final long serialVersionUID = 1L; + static final String NOT_MERGEABLE_HASH = "NOT_MERGEABLE"; + + private final @NonNull String baseHash; + private final @NonNull String pullHash; + private final String mergeHash; + + public PullRequestSCMRevision( + @NonNull PullRequestSCMHead head, @NonNull String baseHash, @NonNull String pullHash) { + this(head, baseHash, pullHash, null); + } + + PullRequestSCMRevision( + @NonNull PullRequestSCMHead head, + @NonNull String baseHash, + @NonNull String pullHash, + String mergeHash) { + super(head, new AbstractGitSCMSource.SCMRevisionImpl(head.getTarget(), baseHash)); + this.baseHash = baseHash; + this.pullHash = pullHash; + this.mergeHash = mergeHash; + } + + @SuppressFBWarnings({ + "SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", + "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" + }) + private Object readResolve() { + if (getTarget() == null) { + // fix an instance prior to the type migration, thankfully we have all the required info + return new PullRequestSCMRevision((PullRequestSCMHead) getHead(), baseHash, pullHash); } - - PullRequestSCMRevision(@NonNull PullRequestSCMHead head, @NonNull String baseHash, @NonNull String pullHash, String mergeHash) { - super(head, new AbstractGitSCMSource.SCMRevisionImpl(head.getTarget(), baseHash)); - this.baseHash = baseHash; - this.pullHash = pullHash; - this.mergeHash = mergeHash; - } - - @SuppressFBWarnings({"SE_PRIVATE_READ_RESOLVE_NOT_INHERITED", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"}) - private Object readResolve() { - if (getTarget() == null) { - // fix an instance prior to the type migration, thankfully we have all the required info - return new PullRequestSCMRevision((PullRequestSCMHead) getHead(), baseHash, pullHash); - } - return this; - } - - /** - * The commit hash of the base branch we are tracking. - * If {@link ChangeRequestSCMHead2#getCheckoutStrategy()} {@link ChangeRequestCheckoutStrategy#MERGE}, this - * would be the current head of the base branch. - * Otherwise it would be the PR’s {@code .base.sha}, the common ancestor of the PR branch and the base branch. - * - * @return the commit hash of the base branch we are tracking. - */ - @NonNull - public String getBaseHash() { - return baseHash; + return this; + } + + /** + * The commit hash of the base branch we are tracking. If {@link + * ChangeRequestSCMHead2#getCheckoutStrategy()} {@link ChangeRequestCheckoutStrategy#MERGE}, this + * would be the current head of the base branch. Otherwise it would be the PR’s {@code .base.sha}, + * the common ancestor of the PR branch and the base branch. + * + * @return the commit hash of the base branch we are tracking. + */ + @NonNull + public String getBaseHash() { + return baseHash; + } + + /** + * The commit hash of the head of the pull request branch. + * + * @return The commit hash of the head of the pull request branch + */ + @Exported + @NonNull + public String getPullHash() { + return pullHash; + } + + /** + * The commit hash of the head of the pull request branch. + * + * @return The commit hash of the head of the pull request branch + */ + @CheckForNull + public String getMergeHash() { + return mergeHash; + } + + void validateMergeHash() throws AbortException { + if (this.mergeHash == NOT_MERGEABLE_HASH) { + throw new AbortException( + "Pull request " + + ((PullRequestSCMHead) this.getHead()).getNumber() + + " : Not mergeable at " + + this.toString()); } + } - /** - * The commit hash of the head of the pull request branch. - * - * @return The commit hash of the head of the pull request branch - */ - @Exported - @NonNull - public String getPullHash() { - return pullHash; + @Override + public boolean equivalent(ChangeRequestSCMRevision o) { + if (!(o instanceof PullRequestSCMRevision)) { + return false; } - - /** - * The commit hash of the head of the pull request branch. - * - * @return The commit hash of the head of the pull request branch - */ - @CheckForNull - public String getMergeHash() { - return mergeHash; - } - - void validateMergeHash() throws AbortException { - if (this.mergeHash == NOT_MERGEABLE_HASH) { - throw new AbortException("Pull request " + ((PullRequestSCMHead)this.getHead()).getNumber() + " : Not mergeable at " + this.toString()); - } - } - - @Override - public boolean equivalent(ChangeRequestSCMRevision o) { - if (!(o instanceof PullRequestSCMRevision)) { - return false; - } - PullRequestSCMRevision other = (PullRequestSCMRevision) o; - - // JENKINS-57583 - Equivalent is used to make decisions about when to build. - // mergeHash is an implementation detail of github, generated from base and target - // If only mergeHash changes we do not consider it a different revision - return getHead().equals(other.getHead()) && pullHash.equals(other.pullHash); - } - - @Override - public int _hashCode() { - return pullHash.hashCode(); - } - - @Override - public String toString() { - String result = pullHash; - if (getHead() instanceof PullRequestSCMHead && ((PullRequestSCMHead) getHead()).isMerge()) { - result += "+" + baseHash + - " (" + StringUtils.defaultIfBlank(mergeHash, "UNKNOWN_MERGE_STATE") + ")"; - } - return result; + PullRequestSCMRevision other = (PullRequestSCMRevision) o; + + // JENKINS-57583 - Equivalent is used to make decisions about when to build. + // mergeHash is an implementation detail of github, generated from base and target + // If only mergeHash changes we do not consider it a different revision + return getHead().equals(other.getHead()) && pullHash.equals(other.pullHash); + } + + @Override + public int _hashCode() { + return pullHash.hashCode(); + } + + @Override + public String toString() { + String result = pullHash; + if (getHead() instanceof PullRequestSCMHead && ((PullRequestSCMHead) getHead()).isMerge()) { + result += + "+" + + baseHash + + " (" + + StringUtils.defaultIfBlank(mergeHash, "UNKNOWN_MERGE_STATE") + + ")"; } + return result; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSource.java index a3c2a9002..8754c415a 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PullRequestSource.java @@ -25,29 +25,30 @@ /** * Used for data migration for a 1.x upgrade. + * * @since 2.2.0 */ @Deprecated // TODO remove once migration from 1.x is no longer supported class PullRequestSource { - private final String sourceOwner; - private final String sourceRepo; - private final String sourceBranch; + private final String sourceOwner; + private final String sourceRepo; + private final String sourceBranch; - PullRequestSource(String sourceOwner, String sourceRepo, String sourceBranch) { - this.sourceOwner = sourceOwner; - this.sourceRepo = sourceRepo; - this.sourceBranch = sourceBranch; - } + PullRequestSource(String sourceOwner, String sourceRepo, String sourceBranch) { + this.sourceOwner = sourceOwner; + this.sourceRepo = sourceRepo; + this.sourceBranch = sourceBranch; + } - public String getSourceOwner() { - return sourceOwner; - } + public String getSourceOwner() { + return sourceOwner; + } - public String getSourceRepo() { - return sourceRepo; - } + public String getSourceRepo() { + return sourceRepo; + } - public String getSourceBranch() { - return sourceBranch; - } + public String getSourceBranch() { + return sourceBranch; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java index 582bedfdc..dae47510c 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.google.common.collect.Sets.immutableEnumSet; +import static org.kohsuke.github.GHEvent.PUSH; + import com.cloudbees.jenkins.GitHubRepositoryName; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -58,311 +61,300 @@ import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import static com.google.common.collect.Sets.immutableEnumSet; -import static org.kohsuke.github.GHEvent.PUSH; - -/** - * This subscriber manages {@link GHEvent} PUSH. - */ +/** This subscriber manages {@link GHEvent} PUSH. */ @Extension public class PushGHEventSubscriber extends GHEventsSubscriber { - /** - * Our logger. - */ - private static final Logger LOGGER = Logger.getLogger(PushGHEventSubscriber.class.getName()); - /** - * Pattern to parse github repository urls. - */ - private static final Pattern REPOSITORY_NAME_PATTERN = Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); + /** Our logger. */ + private static final Logger LOGGER = Logger.getLogger(PushGHEventSubscriber.class.getName()); + /** Pattern to parse github repository urls. */ + private static final Pattern REPOSITORY_NAME_PATTERN = + Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+)"); - /** - * {@inheritDoc} - */ - @Override - protected boolean isApplicable(@Nullable Item project) { - if (project != null) { - if (project instanceof SCMSourceOwner) { - SCMSourceOwner owner = (SCMSourceOwner) project; - for (SCMSource source : owner.getSCMSources()) { - if (source instanceof GitHubSCMSource) { - return true; - } - } - } - if (project.getParent() instanceof SCMSourceOwner) { - SCMSourceOwner owner = (SCMSourceOwner) project.getParent(); - for (SCMSource source : owner.getSCMSources()) { - if (source instanceof GitHubSCMSource) { - return true; - } - } - } + /** {@inheritDoc} */ + @Override + protected boolean isApplicable(@Nullable Item project) { + if (project != null) { + if (project instanceof SCMSourceOwner) { + SCMSourceOwner owner = (SCMSourceOwner) project; + for (SCMSource source : owner.getSCMSources()) { + if (source instanceof GitHubSCMSource) { + return true; + } } - return false; - } - - /** - * {@inheritDoc} - * - * @return set with only PULL_REQUEST event - */ - @Override - protected Set events() { - return immutableEnumSet(PUSH); + } + if (project.getParent() instanceof SCMSourceOwner) { + SCMSourceOwner owner = (SCMSourceOwner) project.getParent(); + for (SCMSource source : owner.getSCMSources()) { + if (source instanceof GitHubSCMSource) { + return true; + } + } + } } + return false; + } - /** - * {@inheritDoc} - */ - @Override - protected void onEvent(GHSubscriberEvent event) { - try { - final GHEventPayload.Push p = GitHub.offline() - .parseEventPayload(new StringReader(event.getPayload()), GHEventPayload.Push.class); - String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); - LOGGER.log(Level.FINE, "Received {0} for {1} from {2}", - new Object[]{event.getGHEvent(), repoUrl, event.getOrigin()} - ); - Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); - if (matcher.matches()) { - final GitHubRepositoryName changedRepository = GitHubRepositoryName.create(repoUrl); - if (changedRepository == null) { - LOGGER.log(Level.WARNING, "Malformed repository URL {0}", repoUrl); - return; - } + /** + * {@inheritDoc} + * + * @return set with only PULL_REQUEST event + */ + @Override + protected Set events() { + return immutableEnumSet(PUSH); + } - if (p.isCreated()) { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.CREATED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } else if (p.isDeleted()) { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.REMOVED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } else { - fireAfterDelay(new SCMHeadEventImpl( - SCMEvent.Type.UPDATED, - event.getTimestamp(), - p, - changedRepository, - event.getOrigin() - )); - } - } else { - LOGGER.log(Level.WARNING, "{0} does not match expected repository name pattern", repoUrl); - } - } catch (Error e) { - throw e; - } catch (Throwable e) { - LogRecord lr = new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); - lr.setParameters(new Object[]{event.getGHEvent(), event.getOrigin(), event.getPayload()}); - lr.setThrown(e); - LOGGER.log(lr); + /** {@inheritDoc} */ + @Override + protected void onEvent(GHSubscriberEvent event) { + try { + final GHEventPayload.Push p = + GitHub.offline() + .parseEventPayload(new StringReader(event.getPayload()), GHEventPayload.Push.class); + String repoUrl = p.getRepository().getHtmlUrl().toExternalForm(); + LOGGER.log( + Level.FINE, + "Received {0} for {1} from {2}", + new Object[] {event.getGHEvent(), repoUrl, event.getOrigin()}); + Matcher matcher = REPOSITORY_NAME_PATTERN.matcher(repoUrl); + if (matcher.matches()) { + final GitHubRepositoryName changedRepository = GitHubRepositoryName.create(repoUrl); + if (changedRepository == null) { + LOGGER.log(Level.WARNING, "Malformed repository URL {0}", repoUrl); + return; } - } - private void fireAfterDelay(final SCMHeadEventImpl e) { - SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + if (p.isCreated()) { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.CREATED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); + } else if (p.isDeleted()) { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.REMOVED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); + } else { + fireAfterDelay( + new SCMHeadEventImpl( + SCMEvent.Type.UPDATED, + event.getTimestamp(), + p, + changedRepository, + event.getOrigin())); + } + } else { + LOGGER.log(Level.WARNING, "{0} does not match expected repository name pattern", repoUrl); + } + } catch (Error e) { + throw e; + } catch (Throwable e) { + LogRecord lr = + new LogRecord(Level.WARNING, "Could not parse {0} event from {1} with payload: {2}"); + lr.setParameters(new Object[] {event.getGHEvent(), event.getOrigin(), event.getPayload()}); + lr.setThrown(e); + LOGGER.log(lr); } + } - private static class SCMHeadEventImpl extends SCMHeadEvent { - private static final String R_HEADS = "refs/heads/"; - private static final String R_TAGS = "refs/tags/"; - private final String repoHost; - private final String repoOwner; - private final String repository; + private void fireAfterDelay(final SCMHeadEventImpl e) { + SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); + } - public SCMHeadEventImpl(Type type, long timestamp, GHEventPayload.Push pullRequest, GitHubRepositoryName repo, - String origin) { - super(type, timestamp, pullRequest, origin); - this.repoHost = repo.getHost(); - this.repoOwner = pullRequest.getRepository().getOwnerName(); - this.repository = pullRequest.getRepository().getName(); - } + private static class SCMHeadEventImpl extends SCMHeadEvent { + private static final String R_HEADS = "refs/heads/"; + private static final String R_TAGS = "refs/tags/"; + private final String repoHost; + private final String repoOwner; + private final String repository; - private boolean isApiMatch(String apiUri) { - return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); - } + public SCMHeadEventImpl( + Type type, + long timestamp, + GHEventPayload.Push pullRequest, + GitHubRepositoryName repo, + String origin) { + super(type, timestamp, pullRequest, origin); + this.repoHost = repo.getHost(); + this.repoOwner = pullRequest.getRepository().getOwnerName(); + this.repository = pullRequest.getRepository().getName(); + } - /** - * {@inheritDoc} - */ - @Override - public boolean isMatch(@NonNull SCMNavigator navigator) { - return navigator instanceof GitHubSCMNavigator - && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); - } + private boolean isApiMatch(String apiUri) { + return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); + } - /** - * {@inheritDoc} - */ - @Override - public String descriptionFor(@NonNull SCMNavigator navigator) { - String ref = getPayload().getRef(); - if (ref.startsWith(R_TAGS)) { - ref = ref.substring(R_TAGS.length()); - return "Push event for tag " + ref + " in repository " + repository; - } - if (ref.startsWith(R_HEADS)) { - ref = ref.substring(R_HEADS.length()); - } - return "Push event to branch " + ref + " in repository " + repository; - } + /** {@inheritDoc} */ + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + return navigator instanceof GitHubSCMNavigator + && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); + } - /** - * {@inheritDoc} - */ - @Override - public String descriptionFor(SCMSource source) { - String ref = getPayload().getRef(); - if (ref.startsWith(R_TAGS)) { - ref = ref.substring(R_TAGS.length()); - return "Push event for tag " + ref; - } - if (ref.startsWith(R_HEADS)) { - ref = ref.substring(R_HEADS.length()); - } - return "Push event to branch " + ref; - } + /** {@inheritDoc} */ + @Override + public String descriptionFor(@NonNull SCMNavigator navigator) { + String ref = getPayload().getRef(); + if (ref.startsWith(R_TAGS)) { + ref = ref.substring(R_TAGS.length()); + return "Push event for tag " + ref + " in repository " + repository; + } + if (ref.startsWith(R_HEADS)) { + ref = ref.substring(R_HEADS.length()); + } + return "Push event to branch " + ref + " in repository " + repository; + } - /** - * {@inheritDoc} - */ - @Override - public String description() { - String ref = getPayload().getRef(); - if (ref.startsWith(R_TAGS)) { - ref = ref.substring(R_TAGS.length()); - return "Push event for tag " + ref + " in repository " + repoOwner + "/" + repository; - } - if (ref.startsWith(R_HEADS)) { - ref = ref.substring(R_HEADS.length()); - } - return "Push event to branch " + ref + " in repository " + repoOwner + "/" + repository; - } + /** {@inheritDoc} */ + @Override + public String descriptionFor(SCMSource source) { + String ref = getPayload().getRef(); + if (ref.startsWith(R_TAGS)) { + ref = ref.substring(R_TAGS.length()); + return "Push event for tag " + ref; + } + if (ref.startsWith(R_HEADS)) { + ref = ref.substring(R_HEADS.length()); + } + return "Push event to branch " + ref; + } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getSourceName() { - return repository; - } + /** {@inheritDoc} */ + @Override + public String description() { + String ref = getPayload().getRef(); + if (ref.startsWith(R_TAGS)) { + ref = ref.substring(R_TAGS.length()); + return "Push event for tag " + ref + " in repository " + repoOwner + "/" + repository; + } + if (ref.startsWith(R_HEADS)) { + ref = ref.substring(R_HEADS.length()); + } + return "Push event to branch " + ref + " in repository " + repoOwner + "/" + repository; + } - /** - * {@inheritDoc} - */ - @NonNull - @Override - public Map heads(@NonNull SCMSource source) { - if (!(source instanceof GitHubSCMSource - && isApiMatch(((GitHubSCMSource) source).getApiUri()) - && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) - && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()))) { - return Collections.emptyMap(); - } - GitHubSCMSource src = (GitHubSCMSource) source; - GHEventPayload.Push push = getPayload(); - GHRepository repo = push.getRepository(); - String repoName = repo.getName(); - if (!repoName.matches(GitHubSCMSource.VALID_GITHUB_REPO_NAME)) { - // fake repository name - return Collections.emptyMap(); - } - String repoOwner = push.getRepository().getOwnerName(); - if (!repoOwner.matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)) { - // fake owner name - return Collections.emptyMap(); - } - if (!push.getHead().matches(GitHubSCMSource.VALID_GIT_SHA1)) { - // fake head sha1 - return Collections.emptyMap(); - } + /** {@inheritDoc} */ + @NonNull + @Override + public String getSourceName() { + return repository; + } - /* - * What we are looking for is to return the BranchSCMHead for this push - * - * Since anything we provide here is untrusted, we don't have to worry about whether this is also a PR... - * It will be revalidated later when the event is processed - * - * In any case, if it is also a PR then there will be a PullRequest:synchronize event that will handle - * things for us, so we just claim a BranchSCMHead - */ + /** {@inheritDoc} */ + @NonNull + @Override + public Map heads(@NonNull SCMSource source) { + if (!(source instanceof GitHubSCMSource + && isApiMatch(((GitHubSCMSource) source).getApiUri()) + && repoOwner.equalsIgnoreCase(((GitHubSCMSource) source).getRepoOwner()) + && repository.equalsIgnoreCase(((GitHubSCMSource) source).getRepository()))) { + return Collections.emptyMap(); + } + GitHubSCMSource src = (GitHubSCMSource) source; + GHEventPayload.Push push = getPayload(); + GHRepository repo = push.getRepository(); + String repoName = repo.getName(); + if (!repoName.matches(GitHubSCMSource.VALID_GITHUB_REPO_NAME)) { + // fake repository name + return Collections.emptyMap(); + } + String repoOwner = push.getRepository().getOwnerName(); + if (!repoOwner.matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)) { + // fake owner name + return Collections.emptyMap(); + } + if (!push.getHead().matches(GitHubSCMSource.VALID_GIT_SHA1)) { + // fake head sha1 + return Collections.emptyMap(); + } - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, SCMHeadObserver.none()) - .withTraits(src.getTraits()); - String ref = push.getRef(); - if (context.wantBranches() && !ref.startsWith(R_TAGS)) { - // we only want the branch details if the branch is actually built! - BranchSCMHead head; - if (ref.startsWith(R_HEADS)) { - // GitHub is consistent in inconsistency, this ref is the full ref... other refs are not! - head = new BranchSCMHead(ref.substring(R_HEADS.length())); - } else { - head = new BranchSCMHead(ref); - } - boolean excluded = false; - for (SCMHeadPrefilter prefilter : context.prefilters()) { - if (prefilter.isExcluded(source, head)) { - excluded = true; - break; - } - } - if (!excluded) { - return Collections.singletonMap(head, new AbstractGitSCMSource.SCMRevisionImpl(head, push.getHead())); - } - } - if (context.wantTags() && ref.startsWith(R_TAGS)) { - // NOTE: GitHub provides the timestamp of the head commit, but if this is an annotated tag - // then that would be an incorrect timestamp, so we have to assume we are going to have the - // wrong timestamp for everything except lightweight tags. - // - // Now in any case, this actually does not matter. - // - // Event consumers are supposed to *not* trust the details reported by an event, it's just a hint. - // All we really want is that we report enough of a head to provide the head.getName() - // then the event consumer is supposed to turn around and do a fetch(..., event, ...) - // and as GitHubSCMSourceRequest strips out the timestamp in calculating the requested - // tag names, we have a winner. - // - // So let's make the assumption that tags are not pushed a long time after their creation and - // use the event timestamp. This may cause issues if anyone has a pre-filter that filters - // out tags that are less than X seconds old, but as such a filter would be incompatible with events - // discovering tags, no harm... the key part is that a pre-filter that removes tags older than X days - // will not strip the tag *here* (because it will always be only a few seconds "old"), but when - // the fetch call actually has the real tag date the pre-filter will apply at that point in time. + /* + * What we are looking for is to return the BranchSCMHead for this push + * + * Since anything we provide here is untrusted, we don't have to worry about whether this is also a PR... + * It will be revalidated later when the event is processed + * + * In any case, if it is also a PR then there will be a PullRequest:synchronize event that will handle + * things for us, so we just claim a BranchSCMHead + */ - GitHubTagSCMHead head = new GitHubTagSCMHead(ref.substring(R_TAGS.length()), getTimestamp()); - boolean excluded = false; - for (SCMHeadPrefilter prefilter : context.prefilters()) { - if (prefilter.isExcluded(source, head)) { - excluded = true; - break; - } - } - if (!excluded) { - return Collections.singletonMap(head, new GitTagSCMRevision(head, push.getHead())); - } - } - return Collections.emptyMap(); + GitHubSCMSourceContext context = + new GitHubSCMSourceContext(null, SCMHeadObserver.none()).withTraits(src.getTraits()); + String ref = push.getRef(); + if (context.wantBranches() && !ref.startsWith(R_TAGS)) { + // we only want the branch details if the branch is actually built! + BranchSCMHead head; + if (ref.startsWith(R_HEADS)) { + // GitHub is consistent in inconsistency, this ref is the full ref... other refs are not! + head = new BranchSCMHead(ref.substring(R_HEADS.length())); + } else { + head = new BranchSCMHead(ref); + } + boolean excluded = false; + for (SCMHeadPrefilter prefilter : context.prefilters()) { + if (prefilter.isExcluded(source, head)) { + excluded = true; + break; + } } + if (!excluded) { + return Collections.singletonMap( + head, new AbstractGitSCMSource.SCMRevisionImpl(head, push.getHead())); + } + } + if (context.wantTags() && ref.startsWith(R_TAGS)) { + // NOTE: GitHub provides the timestamp of the head commit, but if this is an annotated tag + // then that would be an incorrect timestamp, so we have to assume we are going to have the + // wrong timestamp for everything except lightweight tags. + // + // Now in any case, this actually does not matter. + // + // Event consumers are supposed to *not* trust the details reported by an event, it's just a + // hint. + // All we really want is that we report enough of a head to provide the head.getName() + // then the event consumer is supposed to turn around and do a fetch(..., event, ...) + // and as GitHubSCMSourceRequest strips out the timestamp in calculating the requested + // tag names, we have a winner. + // + // So let's make the assumption that tags are not pushed a long time after their creation + // and + // use the event timestamp. This may cause issues if anyone has a pre-filter that filters + // out tags that are less than X seconds old, but as such a filter would be incompatible + // with events + // discovering tags, no harm... the key part is that a pre-filter that removes tags older + // than X days + // will not strip the tag *here* (because it will always be only a few seconds "old"), but + // when + // the fetch call actually has the real tag date the pre-filter will apply at that point in + // time. - /** - * {@inheritDoc} - */ - @Override - public boolean isMatch(@NonNull SCM scm) { - return false; + GitHubTagSCMHead head = + new GitHubTagSCMHead(ref.substring(R_TAGS.length()), getTimestamp()); + boolean excluded = false; + for (SCMHeadPrefilter prefilter : context.prefilters()) { + if (prefilter.isExcluded(source, head)) { + excluded = true; + break; + } + } + if (!excluded) { + return Collections.singletonMap(head, new GitTagSCMRevision(head, push.getHead())); } + } + return Collections.emptyMap(); + } + + /** {@inheritDoc} */ + @Override + public boolean isMatch(@NonNull SCM scm) { + return false; } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/RateLimitExceededException.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/RateLimitExceededException.java index 023c51e2a..bbcd825a2 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/RateLimitExceededException.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/RateLimitExceededException.java @@ -28,44 +28,43 @@ public class RateLimitExceededException extends IOException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private long limit; + private long limit; - private long remaining; + private long remaining; - private long reset; + private long reset; - public RateLimitExceededException() { - super(); - } + public RateLimitExceededException() { + super(); + } - public RateLimitExceededException(String msg, long limit, long remaining, long reset) { - super(msg); - this.limit = limit; - this.remaining = remaining; - this.reset = reset; - } + public RateLimitExceededException(String msg, long limit, long remaining, long reset) { + super(msg); + this.limit = limit; + this.remaining = remaining; + this.reset = reset; + } - public RateLimitExceededException(Throwable cause) { - initCause(cause); - } + public RateLimitExceededException(Throwable cause) { + initCause(cause); + } - public RateLimitExceededException(String message, Throwable cause) { - super(message); - initCause(cause); - } + public RateLimitExceededException(String message, Throwable cause) { + super(message); + initCause(cause); + } - public long getReset() { - return reset; - } + public long getReset() { + return reset; + } - public long getRemaining() { - return remaining; - } - - public long getLimit() { - return limit; - } + public long getRemaining() { + return remaining; + } + public long getLimit() { + return limit; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/RepositoryUriResolver.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/RepositoryUriResolver.java index b91cf15d3..0e3019c98 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/RepositoryUriResolver.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/RepositoryUriResolver.java @@ -29,40 +29,39 @@ import java.net.MalformedURLException; import java.net.URL; -/** - * Resolves the URI of a GitHub repository from the API URI, owner and repository name. - */ +/** Resolves the URI of a GitHub repository from the API URI, owner and repository name. */ public abstract class RepositoryUriResolver { - /** - * Resolves the URI of a repository. - * - * @param apiUri the API URL of the GitHub server. - * @param owner the owner of the repository. - * @param repository the name of the repository. - * @return the GIT URL of the repository. - */ - @NonNull - public abstract String getRepositoryUri(@NonNull String apiUri, @NonNull String owner, @NonNull String repository); + /** + * Resolves the URI of a repository. + * + * @param apiUri the API URL of the GitHub server. + * @param owner the owner of the repository. + * @param repository the name of the repository. + * @return the GIT URL of the repository. + */ + @NonNull + public abstract String getRepositoryUri( + @NonNull String apiUri, @NonNull String owner, @NonNull String repository); - /** - * Helper method that returns the hostname of a GitHub server from its API URL. - * - * @param apiUri the API URL. - * @return the hostname of a GitHub server - */ - @NonNull - public static String hostnameFromApiUri(@CheckForNull String apiUri) { - if (apiUri != null) { - try { - URL endpoint = new URL(apiUri); - if (!"api.github.com".equals(endpoint.getHost())) { - return endpoint.getHost(); - } - } catch (MalformedURLException e) { - // ignore - } + /** + * Helper method that returns the hostname of a GitHub server from its API URL. + * + * @param apiUri the API URL. + * @return the hostname of a GitHub server + */ + @NonNull + public static String hostnameFromApiUri(@CheckForNull String apiUri) { + if (apiUri != null) { + try { + URL endpoint = new URL(apiUri); + if (!"api.github.com".equals(endpoint.getHost())) { + return endpoint.getHost(); } - return "github.com"; + } catch (MalformedURLException e) { + // ignore + } } + return "github.com"; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java index 2c9bb7cd7..c7a3205f0 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTrait.java @@ -56,172 +56,165 @@ import org.kohsuke.stapler.QueryParameter; /** - * A {@link SCMSourceTrait} for {@link GitHubSCMSource} that causes the {@link GitSCM} checkout to be performed using a - * SSH private key rather than the GitHub username password credentials used for scanning / indexing. + * A {@link SCMSourceTrait} for {@link GitHubSCMSource} that causes the {@link GitSCM} checkout to + * be performed using a SSH private key rather than the GitHub username password credentials used + * for scanning / indexing. * * @since 2.2.0 */ public class SSHCheckoutTrait extends SCMSourceTrait { - /** - * Credentials for actual clone; may be SSH private key. - */ - @CheckForNull - private final String credentialsId; + /** Credentials for actual clone; may be SSH private key. */ + @CheckForNull private final String credentialsId; - /** - * Constructor. - * - * @param credentialsId the {@link SSHUserPrivateKey#getId()} of the credentials to use or - * {@link GitHubSCMSource.DescriptorImpl#ANONYMOUS} to defer to the agent configured - * credentials (typically anonymous but not always) - */ - @DataBoundConstructor - public SSHCheckoutTrait(@CheckForNull String credentialsId) { - if (GitHubSCMSource.DescriptorImpl.ANONYMOUS.equals(credentialsId)) { - // legacy migration of "magic" credential ID. - this.credentialsId = null; - } else { - this.credentialsId = Util.fixEmpty(credentialsId); - } + /** + * Constructor. + * + * @param credentialsId the {@link SSHUserPrivateKey#getId()} of the credentials to use or {@link + * GitHubSCMSource.DescriptorImpl#ANONYMOUS} to defer to the agent configured credentials + * (typically anonymous but not always) + */ + @DataBoundConstructor + public SSHCheckoutTrait(@CheckForNull String credentialsId) { + if (GitHubSCMSource.DescriptorImpl.ANONYMOUS.equals(credentialsId)) { + // legacy migration of "magic" credential ID. + this.credentialsId = null; + } else { + this.credentialsId = Util.fixEmpty(credentialsId); } + } - /** - * Returns the configured credentials id. - * - * @return the configured credentials id or {@code null} to use the build agent's key. - */ - @CheckForNull - public final String getCredentialsId() { - return credentialsId; - } + /** + * Returns the configured credentials id. + * + * @return the configured credentials id or {@code null} to use the build agent's key. + */ + @CheckForNull + public final String getCredentialsId() { + return credentialsId; + } - /** - * {@inheritDoc} - */ - @Override - protected void decorateBuilder(SCMBuilder builder) { - ((GitHubSCMBuilder)builder).withCredentials(credentialsId, GitHubSCMBuilder.SSH); - } + /** {@inheritDoc} */ + @Override + protected void decorateBuilder(SCMBuilder builder) { + ((GitHubSCMBuilder) builder).withCredentials(credentialsId, GitHubSCMBuilder.SSH); + } - /** - * Our descriptor. - */ - @Symbol("gitHubSshCheckout") - @Extension - public static class DescriptorImpl extends SCMSourceTraitDescriptor { + /** Our descriptor. */ + @Symbol("gitHubSshCheckout") + @Extension + public static class DescriptorImpl extends SCMSourceTraitDescriptor { - /** - * {@inheritDoc} - */ - @NonNull - @Override - public String getDisplayName() { - return Messages.SSHCheckoutTrait_displayName(); - } + /** {@inheritDoc} */ + @NonNull + @Override + public String getDisplayName() { + return Messages.SSHCheckoutTrait_displayName(); + } - /** - * {@inheritDoc} - */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } + /** {@inheritDoc} */ + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; + } - /** - * {@inheritDoc} - */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; + } - /** - * {@inheritDoc} - */ - @Override - public Class getBuilderClass() { - return GitSCMBuilder.class; - } + /** {@inheritDoc} */ + @Override + public Class getBuilderClass() { + return GitSCMBuilder.class; + } - /** - * {@inheritDoc} - */ - @Override - public Class getScmClass() { - return GitSCM.class; - } + /** {@inheritDoc} */ + @Override + public Class getScmClass() { + return GitSCM.class; + } - /** - * Form completion. - * - * @param context the context. - * @param apiUri the server url. - * @param credentialsId the current selection. - * @return the form items. - */ - @Restricted(NoExternalUse.class) - @SuppressWarnings("unused") // stapler form binding - public ListBoxModel doFillCredentialsIdItems(@CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String credentialsId) { - if (context == null - ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) - : !context.hasPermission(Item.EXTENDED_READ)) { - return new StandardListBoxModel().includeCurrentValue(credentialsId); - } - StandardListBoxModel result = new StandardListBoxModel(); - result.add(Messages.SSHCheckoutTrait_useAgentKey(), ""); - return result.includeMatchingAs( - context instanceof Queue.Task - ? ((Queue.Task) context).getDefaultAuthentication() - : ACL.SYSTEM, - context, - StandardUsernameCredentials.class, - Connector.githubDomainRequirements(apiUri), - CredentialsMatchers.instanceOf(SSHUserPrivateKey.class) - ); - } + /** + * Form completion. + * + * @param context the context. + * @param apiUri the server url. + * @param credentialsId the current selection. + * @return the form items. + */ + @Restricted(NoExternalUse.class) + @SuppressWarnings("unused") // stapler form binding + public ListBoxModel doFillCredentialsIdItems( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String apiUri, + @QueryParameter String credentialsId) { + if (context == null + ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + : !context.hasPermission(Item.EXTENDED_READ)) { + return new StandardListBoxModel().includeCurrentValue(credentialsId); + } + StandardListBoxModel result = new StandardListBoxModel(); + result.add(Messages.SSHCheckoutTrait_useAgentKey(), ""); + return result.includeMatchingAs( + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + context, + StandardUsernameCredentials.class, + Connector.githubDomainRequirements(apiUri), + CredentialsMatchers.instanceOf(SSHUserPrivateKey.class)); + } - /** - * Validation for checkout credentials. - * - * @param context the context. - * @param serverUrl the server url. - * @param value the current selection. - * @return the validation results - */ - @Restricted(NoExternalUse.class) - @SuppressWarnings("unused") // stapler form binding - public FormValidation doCheckCredentialsId(@CheckForNull @AncestorInPath Item context, - @QueryParameter String serverUrl, - @QueryParameter String value) { - if (context == null - ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) - : !context.hasPermission(Item.EXTENDED_READ)) { - return FormValidation.ok(); - } - if (StringUtils.isBlank(value)) { - // use agent key - return FormValidation.ok(); - } - if (CredentialsMatchers.firstOrNull(CredentialsProvider.lookupCredentials( - SSHUserPrivateKey.class, - context, - context instanceof Queue.Task ? ((Queue.Task) context).getDefaultAuthentication() : ACL.SYSTEM, - URIRequirementBuilder.fromUri(serverUrl).build()), - CredentialsMatchers.withId(value)) != null) { - return FormValidation.ok(); - } - if (CredentialsMatchers.firstOrNull(CredentialsProvider.lookupCredentials( - StandardUsernameCredentials.class, - context, - context instanceof Queue.Task ? ((Queue.Task) context).getDefaultAuthentication() : ACL.SYSTEM, - URIRequirementBuilder.fromUri(serverUrl).build()), - CredentialsMatchers.withId(value)) != null) { - return FormValidation.error(Messages.SSHCheckoutTrait_incompatibleCredentials()); - } - return FormValidation.warning(Messages.SSHCheckoutTrait_missingCredentials()); - } + /** + * Validation for checkout credentials. + * + * @param context the context. + * @param serverUrl the server url. + * @param value the current selection. + * @return the validation results + */ + @Restricted(NoExternalUse.class) + @SuppressWarnings("unused") // stapler form binding + public FormValidation doCheckCredentialsId( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String serverUrl, + @QueryParameter String value) { + if (context == null + ? !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + : !context.hasPermission(Item.EXTENDED_READ)) { + return FormValidation.ok(); + } + if (StringUtils.isBlank(value)) { + // use agent key + return FormValidation.ok(); + } + if (CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + SSHUserPrivateKey.class, + context, + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + URIRequirementBuilder.fromUri(serverUrl).build()), + CredentialsMatchers.withId(value)) + != null) { + return FormValidation.ok(); + } + if (CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + StandardUsernameCredentials.class, + context, + context instanceof Queue.Task + ? ((Queue.Task) context).getDefaultAuthentication() + : ACL.SYSTEM, + URIRequirementBuilder.fromUri(serverUrl).build()), + CredentialsMatchers.withId(value)) + != null) { + return FormValidation.error(Messages.SSHCheckoutTrait_incompatibleCredentials()); + } + return FormValidation.warning(Messages.SSHCheckoutTrait_missingCredentials()); } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/SinglePassIterable.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/SinglePassIterable.java index df634bc4f..51a35d872 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/SinglePassIterable.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/SinglePassIterable.java @@ -10,127 +10,111 @@ import net.jcip.annotations.GuardedBy; /** - * Takes either an {@link Iterable} or an {@link Iterator} and converts it into a {@link Iterable} that will walk - * the backing {@link Iterator} once only but can be walked repeatedly itself. + * Takes either an {@link Iterable} or an {@link Iterator} and converts it into a {@link Iterable} + * that will walk the backing {@link Iterator} once only but can be walked repeatedly itself. * * @param */ class SinglePassIterable implements Iterable { - /** - * The delegate. - */ - @GuardedBy("items") - @CheckForNull - private Iterator delegate; - /** - * The items we have seen so far. - */ - private final List items; + /** The delegate. */ + @GuardedBy("items") + @CheckForNull + private Iterator delegate; + /** The items we have seen so far. */ + private final List items; - /** - * Constructor. - * - * @param delegate the {@link Iterable}. - */ - public SinglePassIterable(@NonNull Iterable delegate) { - this(delegate.iterator()); - } + /** + * Constructor. + * + * @param delegate the {@link Iterable}. + */ + public SinglePassIterable(@NonNull Iterable delegate) { + this(delegate.iterator()); + } + + /** + * Constructor. + * + * @param delegate the {@link Iterator}. + */ + public SinglePassIterable(@NonNull Iterator delegate) { + this.delegate = delegate; + items = new ArrayList<>(); + } - /** - * Constructor. - * - * @param delegate the {@link Iterator}. - */ - public SinglePassIterable(@NonNull Iterator delegate) { - this.delegate = delegate; - items = new ArrayList<>(); + /** {@inheritDoc} */ + @Override + public final Iterator iterator() { + synchronized (items) { + if (delegate == null || !delegate.hasNext()) { + // we have walked the iterator once, so now items is complete + return Collections.unmodifiableList(items).iterator(); + } } + return new Iterator() { + int index = 0; + + /** {@inheritDoc} */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } - /** - * {@inheritDoc} - */ - @Override - public final Iterator iterator() { + /** {@inheritDoc} */ + @Override + public boolean hasNext() { synchronized (items) { - if (delegate == null || !delegate.hasNext()) { - // we have walked the iterator once, so now items is complete - return Collections.unmodifiableList(items).iterator(); + if (index < items.size()) { + return true; + } + if (delegate != null) { + if (delegate.hasNext()) { + return true; } + delegate = null; + completed(); + } + return false; } - return new Iterator() { - int index = 0; + } - /** - * {@inheritDoc} - */ - @Override - public void remove() { - throw new UnsupportedOperationException(); + /** {@inheritDoc} */ + @Override + public V next() { + synchronized (items) { + if (index < items.size()) { + return items.get(index++); + } + try { + if (delegate != null && delegate.hasNext()) { + V element = delegate.next(); + observe(element); + items.add(element); + // Index needs to be incremented + index++; + return element; + } else { + throw new NoSuchElementException(); } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasNext() { - synchronized (items) { - if (index < items.size()) { - return true; - } - if (delegate != null) { - if (delegate.hasNext()) { - return true; - } - delegate = null; - completed(); - } - return false; - } + } catch (NoSuchElementException e) { + if (delegate != null) { + delegate = null; + completed(); } + throw e; + } + } + } + }; + } - /** - * {@inheritDoc} - */ - @Override - public V next() { - synchronized (items) { - if (index < items.size()) { - return items.get(index++); - } - try { - if (delegate != null && delegate.hasNext()) { - V element = delegate.next(); - observe(element); - items.add(element); - //Index needs to be incremented - index++; - return element; - } else { - throw new NoSuchElementException(); - } - } catch (NoSuchElementException e) { - if (delegate != null) { - delegate = null; - completed(); - } - throw e; - } - } - } - }; - } - - /** - * Callback for each element observed from the delegate. - * - * @param v the element. - */ - protected void observe(V v) { - } + /** + * Callback for each element observed from the delegate. + * + * @param v the element. + */ + protected void observe(V v) {} - /** - * Callback for when the delegate has reached the end. - */ - protected void completed() { - } + /** Callback for when the delegate has reached the end. */ + protected void completed() {} } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/SshRepositoryUriResolver.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/SshRepositoryUriResolver.java index 6b773ba0c..88a1b8ad7 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/SshRepositoryUriResolver.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/SshRepositoryUriResolver.java @@ -24,17 +24,12 @@ package org.jenkinsci.plugins.github_branch_source; -/** - * A {@link RepositoryUriResolver} that resolves SSH git URLs. - */ +/** A {@link RepositoryUriResolver} that resolves SSH git URLs. */ public class SshRepositoryUriResolver extends RepositoryUriResolver { - /** - * {@inheritDoc} - */ - @Override - public String getRepositoryUri(String apiUri, String owner, String repository) { - return "git@" + hostnameFromApiUri(apiUri) + ":" + owner + "/" + repository + ".git"; - } - + /** {@inheritDoc} */ + @Override + public String getRepositoryUri(String apiUri, String owner, String repository) { + return "git@" + hostnameFromApiUri(apiUri) + ":" + owner + "/" + repository + ".git"; + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTrait.java index 7b25d818f..b33f8325c 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTrait.java @@ -46,96 +46,73 @@ * @since 2.3.0 */ public class TagDiscoveryTrait extends SCMSourceTrait { - /** - * Constructor for stapler. - */ - @DataBoundConstructor - public TagDiscoveryTrait() { - } + /** Constructor for stapler. */ + @DataBoundConstructor + public TagDiscoveryTrait() {} + + /** {@inheritDoc} */ + @Override + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantTags(true); + ctx.withAuthority(new TagSCMHeadAuthority()); + } + + /** {@inheritDoc} */ + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category instanceof TagSCMHeadCategory; + } - /** - * {@inheritDoc} - */ + /** Our descriptor. */ + @Symbol("gitHubTagDiscovery") + @Extension + @Discovery + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + /** {@inheritDoc} */ @Override - protected void decorateContext(SCMSourceContext context) { - GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; - ctx.wantTags(true); - ctx.withAuthority(new TagSCMHeadAuthority()); + public String getDisplayName() { + return Messages.TagDiscoveryTrait_displayName(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override - public boolean includeCategory(@NonNull SCMHeadCategory category) { - return category instanceof TagSCMHeadCategory; + public Class getContextClass() { + return GitHubSCMSourceContext.class; } - /** - * Our descriptor. - */ - @Symbol("gitHubTagDiscovery") - @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.TagDiscoveryTrait_displayName(); - } - - /** - * {@inheritDoc} - */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; } + } - /** - * Trusts tags from the origin repository. - */ - public static class TagSCMHeadAuthority extends SCMHeadAuthority { - /** - * {@inheritDoc} - */ - @Override - protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull GitHubTagSCMHead head) { - return true; - } + /** Trusts tags from the origin repository. */ + public static class TagSCMHeadAuthority + extends SCMHeadAuthority { + /** {@inheritDoc} */ + @Override + protected boolean checkTrusted( + @NonNull SCMSourceRequest request, @NonNull GitHubTagSCMHead head) { + return true; + } - /** - * Out descriptor. - */ - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - /** - * {@inheritDoc} - */ - @Override - public String getDisplayName() { - return Messages.TagDiscoveryTrait_authorityDisplayName(); - } + /** Out descriptor. */ + @Extension + public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return Messages.TagDiscoveryTrait_authorityDisplayName(); + } - /** - * {@inheritDoc} - */ - @Override - public boolean isApplicableToOrigin(@NonNull Class originClass) { - return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); - } - } + /** {@inheritDoc} */ + @Override + public boolean isApplicableToOrigin(@NonNull Class originClass) { + return SCMHeadOrigin.Default.class.isAssignableFrom(originClass); + } } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/TeamSlugTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/TeamSlugTrait.java index 2dd00aad0..4df37979c 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/TeamSlugTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/TeamSlugTrait.java @@ -2,6 +2,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import javax.annotation.Nonnull; import jenkins.scm.api.trait.SCMNavigatorContext; import jenkins.scm.api.trait.SCMNavigatorTrait; import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; @@ -9,65 +10,57 @@ import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; -import javax.annotation.Nonnull; - /** - * Decorates a {@link SCMNavigatorContext} with a GitHub team slug which will allow restricting the discovery of repositories - * by specific teams - * + * Decorates a {@link SCMNavigatorContext} with a GitHub team slug which will allow restricting the + * discovery of repositories by specific teams */ public class TeamSlugTrait extends SCMNavigatorTrait { - /** - * The team slug. - */ - @NonNull - private final String teamSlug; + /** The team slug. */ + @NonNull private final String teamSlug; - /** - * Stapler constructor. - * - * @param teamSlug the team slug to use when searching for github repos restricted to a specific team only. - */ - @DataBoundConstructor - public TeamSlugTrait(@NonNull String teamSlug) { - this.teamSlug = teamSlug; - } + /** + * Stapler constructor. + * + * @param teamSlug the team slug to use when searching for github repos restricted to a specific + * team only. + */ + @DataBoundConstructor + public TeamSlugTrait(@NonNull String teamSlug) { + this.teamSlug = teamSlug; + } - /** - * Returns the teamSlug. - * - * @return the teamSlug. - */ - @NonNull - public String getTeamSlug() { - return teamSlug; - } + /** + * Returns the teamSlug. + * + * @return the teamSlug. + */ + @NonNull + public String getTeamSlug() { + return teamSlug; + } - @Override - protected void decorateContext(final SCMNavigatorContext context) { - super.decorateContext(context); - ((GitHubSCMNavigatorContext) context).setTeamSlug(teamSlug); - } - - /** - * TeamSlug descriptor. - */ - @Symbol("teamSlugFilter") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + @Override + protected void decorateContext(final SCMNavigatorContext context) { + super.decorateContext(context); + ((GitHubSCMNavigatorContext) context).setTeamSlug(teamSlug); + } - @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; - } + /** TeamSlug descriptor. */ + @Symbol("teamSlugFilter") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { - @Nonnull - @Override - public String getDisplayName() { - return Messages.TeamSlugTrait_displayName(); - } + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; } + @Nonnull + @Override + public String getDisplayName() { + return Messages.TeamSlugTrait_displayName(); + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/TopicsTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/TopicsTrait.java index d25978cf0..5147bda36 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/TopicsTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/TopicsTrait.java @@ -1,8 +1,9 @@ package org.jenkinsci.plugins.github_branch_source; -import java.util.ArrayList; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import java.util.ArrayList; +import javax.annotation.Nonnull; import jenkins.scm.api.trait.SCMNavigatorContext; import jenkins.scm.api.trait.SCMNavigatorTrait; import jenkins.scm.api.trait.SCMNavigatorTraitDescriptor; @@ -10,76 +11,65 @@ import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; -import javax.annotation.Nonnull; - -/** - * Decorates a {@link SCMNavigatorContext} with GitHub topics - * - */ +/** Decorates a {@link SCMNavigatorContext} with GitHub topics */ public class TopicsTrait extends SCMNavigatorTrait { - /** - * The topics - */ - @NonNull - private final ArrayList topics; - private final String topicList; + /** The topics */ + @NonNull private final ArrayList topics; - /** - * Stapler constructor. - * - * @param topicList a comma-separated list of topics - */ - @DataBoundConstructor - public TopicsTrait(@NonNull String topicList) { - this.topicList = topicList; - this.topics = new ArrayList(); + private final String topicList; - for (String topic : topicList.split(",")) { - this.topics.add(topic.trim()); - } + /** + * Stapler constructor. + * + * @param topicList a comma-separated list of topics + */ + @DataBoundConstructor + public TopicsTrait(@NonNull String topicList) { + this.topicList = topicList; + this.topics = new ArrayList(); + for (String topic : topicList.split(",")) { + this.topics.add(topic.trim()); } + } - /** - * Returns the topics - * - * @return the topics - */ - @NonNull - public ArrayList getTopics() { - return topics; - } + /** + * Returns the topics + * + * @return the topics + */ + @NonNull + public ArrayList getTopics() { + return topics; + } - @NonNull - public String getTopicList() { - return topicList; - } + @NonNull + public String getTopicList() { + return topicList; + } - @Override - protected void decorateContext(final SCMNavigatorContext context) { - super.decorateContext(context); - ((GitHubSCMNavigatorContext) context).setTopics(topics); - } - - /** - * Topics descriptor. - */ - @Symbol("gitHubTopicsFilter") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + @Override + protected void decorateContext(final SCMNavigatorContext context) { + super.decorateContext(context); + ((GitHubSCMNavigatorContext) context).setTopics(topics); + } - @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; - } + /** Topics descriptor. */ + @Symbol("gitHubTopicsFilter") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { - @Nonnull - @Override - public String getDisplayName() { - return Messages.TopicsTrait_displayName(); - } + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; } + @Nonnull + @Override + public String getDisplayName() { + return Messages.TopicsTrait_displayName(); + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java index 40da45bc3..c7af274ea 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/UntrustedPullRequestSCMRevision.java @@ -29,28 +29,27 @@ @Deprecated class UntrustedPullRequestSCMRevision extends AbstractGitSCMSource.SCMRevisionImpl { - - private static final long serialVersionUID = -6961458604178249880L; - - final String baseHash; - - private UntrustedPullRequestSCMRevision(SCMHead head, String hash, String baseHash) { - super(head, hash); - this.baseHash = baseHash; - } - - @Override - public boolean equals(Object o) { - return super.equals(o) && baseHash.equals(((UntrustedPullRequestSCMRevision) o).baseHash); - } - - @Override - public int hashCode() { - return super.hashCode(); // good enough - } - - private Object readResolve() { - return new PullRequestSCMRevision((PullRequestSCMHead) getHead(), baseHash, getHash()); - } + private static final long serialVersionUID = -6961458604178249880L; + + final String baseHash; + + private UntrustedPullRequestSCMRevision(SCMHead head, String hash, String baseHash) { + super(head, hash); + this.baseHash = baseHash; + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && baseHash.equals(((UntrustedPullRequestSCMRevision) o).baseHash); + } + + @Override + public int hashCode() { + return super.hashCode(); // good enough + } + + private Object readResolve() { + return new PullRequestSCMRevision((PullRequestSCMHead) getHead(), baseHash, getHash()); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java index 437f68da8..26fef7f41 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java @@ -1,5 +1,8 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; + import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; @@ -8,93 +11,95 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.junit.WireMockRule; +import java.io.File; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.jvnet.hudson.test.JenkinsRule; -import java.io.File; -import java.time.Duration; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; - -/** - * @author Liam Newman - */ +/** @author Liam Newman */ public abstract class AbstractGitHubWireMockTest extends Assert { - // By default the wiremock tests will run without proxy - // The tests will use only the stubbed data and will fail if requests are made for missing data. - // You can use the proxy while writing and debugging tests. - private final static boolean useProxy = !System.getProperty("test.github.useProxy", "false").equals("false"); + // By default the wiremock tests will run without proxy + // The tests will use only the stubbed data and will fail if requests are made for missing data. + // You can use the proxy while writing and debugging tests. + private static final boolean useProxy = + !System.getProperty("test.github.useProxy", "false").equals("false"); - @ClassRule - public static JenkinsRule r = new JenkinsRule(); + @ClassRule public static JenkinsRule r = new JenkinsRule(); - public static WireMockRuleFactory factory = new WireMockRuleFactory(); + public static WireMockRuleFactory factory = new WireMockRuleFactory(); - @Rule - public WireMockRule githubRaw = factory.getRule(WireMockConfiguration.options() - .dynamicPort() - .usingFilesUnderClasspath("raw") - ); - @Rule - public WireMockRule githubApi = factory.getRule(WireMockConfiguration.options() - .dynamicPort() - .usingFilesUnderClasspath("api") - .extensions( - new ResponseTransformer() { - @Override - public Response transform(Request request, Response response, FileSource files, - Parameters parameters) { - if ("application/json" - .equals(response.getHeaders().getContentTypeHeader().mimeTypePart())) { + @Rule + public WireMockRule githubRaw = + factory.getRule( + WireMockConfiguration.options().dynamicPort().usingFilesUnderClasspath("raw")); + + @Rule + public WireMockRule githubApi = + factory.getRule( + WireMockConfiguration.options() + .dynamicPort() + .usingFilesUnderClasspath("api") + .extensions( + new ResponseTransformer() { + @Override + public Response transform( + Request request, + Response response, + FileSource files, + Parameters parameters) { + if ("application/json" + .equals(response.getHeaders().getContentTypeHeader().mimeTypePart())) { return Response.Builder.like(response) .but() - .body(response.getBodyAsString() - .replace("https://api.github.com/", - "http://localhost:" + githubApi.port() + "/") - .replace("https://raw.githubusercontent.com/", - "http://localhost:" + githubRaw.port() + "/") - ) + .body( + response + .getBodyAsString() + .replace( + "https://api.github.com/", + "http://localhost:" + githubApi.port() + "/") + .replace( + "https://raw.githubusercontent.com/", + "http://localhost:" + githubRaw.port() + "/")) .build(); + } + return response; } - return response; - } - - @Override - public String getName() { - return "url-rewrite"; - } - }) - ); + @Override + public String getName() { + return "url-rewrite"; + } + })); - @Before - public void prepareMockGitHub() { - prepareMockGitHubFileMappings(); - - if (useProxy) { - githubApi.stubFor( - get(urlMatching(".*")).atPriority(10) - .willReturn(aResponse().proxiedFrom("https://api.github.com/"))); - githubRaw.stubFor(get(urlMatching(".*")).atPriority(10) - .willReturn(aResponse().proxiedFrom("https://raw.githubusercontent.com/"))); - } - } + @Before + public void prepareMockGitHub() { + prepareMockGitHubFileMappings(); - void prepareMockGitHubFileMappings() { - new File("src/test/resources/api/mappings").mkdirs(); - new File("src/test/resources/api/__files").mkdirs(); - new File("src/test/resources/raw/mappings").mkdirs(); - new File("src/test/resources/raw/__files").mkdirs(); - githubApi.enableRecordMappings(new SingleRootFileSource("src/test/resources/api/mappings"), - new SingleRootFileSource("src/test/resources/api/__files")); - githubRaw.enableRecordMappings(new SingleRootFileSource("src/test/resources/raw/mappings"), - new SingleRootFileSource("src/test/resources/raw/__files")); + if (useProxy) { + githubApi.stubFor( + get(urlMatching(".*")) + .atPriority(10) + .willReturn(aResponse().proxiedFrom("https://api.github.com/"))); + githubRaw.stubFor( + get(urlMatching(".*")) + .atPriority(10) + .willReturn(aResponse().proxiedFrom("https://raw.githubusercontent.com/"))); } + } - + void prepareMockGitHubFileMappings() { + new File("src/test/resources/api/mappings").mkdirs(); + new File("src/test/resources/api/__files").mkdirs(); + new File("src/test/resources/raw/mappings").mkdirs(); + new File("src/test/resources/raw/__files").mkdirs(); + githubApi.enableRecordMappings( + new SingleRootFileSource("src/test/resources/api/mappings"), + new SingleRootFileSource("src/test/resources/api/__files")); + githubRaw.enableRecordMappings( + new SingleRootFileSource("src/test/resources/raw/mappings"), + new SingleRootFileSource("src/test/resources/raw/__files")); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitCheckerTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitCheckerTest.java index 9d6448701..8de3179d4 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitCheckerTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/ApiRateLimitCheckerTest.java @@ -1,18 +1,16 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.resetAllScenarios; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; + import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.ScenarioMappingBuilder; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; import com.github.tomakehurst.wiremock.stubbing.Scenario; import hudson.util.LogTaskListener; import hudson.util.RingBufferLogHandler; -import org.jenkinsci.plugins.github.config.GitHubServerConfig; -import org.junit.Test; -import org.junit.Before; -import org.kohsuke.github.GHRateLimit; -import org.kohsuke.github.GitHub; -import org.mockito.Mockito; - import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; @@ -22,867 +20,894 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.stream.Stream; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.resetAllScenarios; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import org.jenkinsci.plugins.github.config.GitHubServerConfig; +import org.junit.Before; +import org.junit.Test; +import org.kohsuke.github.GHRateLimit; +import org.kohsuke.github.GitHub; +import org.mockito.Mockito; public class ApiRateLimitCheckerTest extends AbstractGitHubWireMockTest { - private RingBufferLogHandler handler; - private LogTaskListener listener; + private RingBufferLogHandler handler; + private LogTaskListener listener; - private final Random entropy = new Random(1000); + private final Random entropy = new Random(1000); - private final Date soon = Date.from(LocalDateTime.now().plusMinutes(60).atZone(ZoneId.systemDefault()).toInstant()); + private final Date soon = + Date.from(LocalDateTime.now().plusMinutes(60).atZone(ZoneId.systemDefault()).toInstant()); - private GitHub github; - private int initialRequestCount; + private GitHub github; + private int initialRequestCount; - private Stream getOutputLines() { - return handler.getView().stream().map(LogRecord::getMessage); - } + private Stream getOutputLines() { + return handler.getView().stream().map(LogRecord::getMessage); + } - private long countOfOutputLines(Predicate predicate) { - return getOutputLines().filter(predicate).count(); - } + private long countOfOutputLines(Predicate predicate) { + return getOutputLines().filter(predicate).count(); + } - private long countOfOutputLinesContaining(String substring) { - return countOfOutputLines(m -> m.contains(substring)); - } + private long countOfOutputLinesContaining(String substring) { + return countOfOutputLines(m -> m.contains(substring)); + } - public static int getRequestCount(WireMockServer server) { - return server.countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); - } + public static int getRequestCount(WireMockServer server) { + return server.countRequestsMatching(RequestPatternBuilder.allRequests().build()).getCount(); + } - private class RateLimit { - final int remaining; - final int limit; - final Date reset; + private class RateLimit { + final int remaining; + final int limit; + final Date reset; - RateLimit(int limit, int remaining, Date reset) { - this.limit = limit; - this.remaining = remaining; - this.reset = reset; - } + RateLimit(int limit, int remaining, Date reset) { + this.limit = limit; + this.remaining = remaining; + this.reset = reset; } - - - @Before - public void setUp() throws Exception { - resetAllScenarios(); - - handler = new RingBufferLogHandler(1000); - final Logger logger = Logger.getLogger(getClass().getName()); - logger.addHandler(handler); - listener = new LogTaskListener(logger, Level.INFO); - - final Logger defaultLogger = Logger.getLogger(ApiRateLimitChecker.class.getName()); - defaultLogger.addHandler(handler); - - // Set the random to a known state for testing - ApiRateLimitChecker.setEntropy(entropy); - - // Default the expiration window to a small but measurable time for testing - ApiRateLimitChecker.setExpirationWaitMillis(20); - - // Default the notification interval to a small but measurable time for testing - ApiRateLimitChecker.setNotificationWaitMillis(60); - - ApiRateLimitChecker.resetLocalChecker(); + } + + @Before + public void setUp() throws Exception { + resetAllScenarios(); + + handler = new RingBufferLogHandler(1000); + final Logger logger = Logger.getLogger(getClass().getName()); + logger.addHandler(handler); + listener = new LogTaskListener(logger, Level.INFO); + + final Logger defaultLogger = Logger.getLogger(ApiRateLimitChecker.class.getName()); + defaultLogger.addHandler(handler); + + // Set the random to a known state for testing + ApiRateLimitChecker.setEntropy(entropy); + + // Default the expiration window to a small but measurable time for testing + ApiRateLimitChecker.setExpirationWaitMillis(20); + + // Default the notification interval to a small but measurable time for testing + ApiRateLimitChecker.setNotificationWaitMillis(60); + + ApiRateLimitChecker.resetLocalChecker(); + } + + private void setupStubs(List scenarios) throws Exception { + + githubApi.stubFor( + get(urlEqualTo("/meta")) + .willReturn( + aResponse() + .withStatus(200) + .withBody("{\"verifiable_password_authentication\": false}"))); + + githubApi.stubFor( + get(urlEqualTo("/")) + .willReturn( + aResponse() + .withStatus(200) + .withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}"))); + + scenarios.add(0, new RateLimit(1000, 1000, new Date(0))); + String scenarioName = UUID.randomUUID().toString(); + for (int i = 0; i < scenarios.size(); i++) { + String state = (i == 0) ? Scenario.STARTED : Integer.toString(i); + String nextState = Integer.toString(i + 1); + RateLimit scenarioResponse = scenarios.get(i); + + String limit = Integer.toString(scenarioResponse.limit); + String remaining = Integer.toString(scenarioResponse.remaining); + String reset = Long.toString(scenarioResponse.reset.toInstant().getEpochSecond()); + String body = + "{" + + String.format( + " \"rate\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", + limit, remaining, reset) + + " \"resources\": {" + + String.format( + " \"core\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", + limit, remaining, reset) + + String.format( + " \"search\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", + limit, remaining, reset) + + String.format( + " \"graphql\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", + limit, remaining, reset) + + String.format( + " \"integration_manifest\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s }", + limit, remaining, reset) + + " } }"; + ScenarioMappingBuilder scenario = + get(urlEqualTo("/rate_limit")) + .inScenario(scenarioName) + .whenScenarioStateIs(state) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withHeader("X-RateLimit-Limit", limit) + .withHeader("X-RateLimit-Remaining", remaining) + .withHeader("X-RateLimit-Reset", reset) + .withBody(body)); + if (i != scenarios.size() - 1) { + scenario = scenario.willSetStateTo(nextState); + } + githubApi.stubFor(scenario); } - private void setupStubs(List scenarios) throws Exception { - - githubApi.stubFor(get(urlEqualTo("/meta")) - .willReturn(aResponse() - .withStatus(200) - .withBody("{\"verifiable_password_authentication\": false}") - )); - - githubApi.stubFor(get(urlEqualTo("/")) - .willReturn(aResponse() - .withStatus(200) - .withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}") - )); - - scenarios.add(0, new RateLimit(1000, 1000, new Date(0))); - String scenarioName = UUID.randomUUID().toString(); - for (int i = 0; i < scenarios.size(); i++) { - String state = (i == 0) ? Scenario.STARTED : Integer.toString(i); - String nextState = Integer.toString(i + 1); - RateLimit scenarioResponse = scenarios.get(i); - - String limit = Integer.toString(scenarioResponse.limit); - String remaining = Integer.toString(scenarioResponse.remaining); - String reset = Long.toString(scenarioResponse.reset.toInstant().getEpochSecond()); - String body = "{" + - String.format(" \"rate\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", limit, remaining, reset) + - " \"resources\": {" + - String.format(" \"core\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", limit, remaining, reset) + - String.format(" \"search\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", limit, remaining, reset) + - String.format(" \"graphql\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s },", limit, remaining, reset) + - String.format(" \"integration_manifest\": { \"limit\": %s, \"remaining\": %s, \"reset\": %s }", limit, remaining, reset) + - " } }"; - ScenarioMappingBuilder scenario = get(urlEqualTo("/rate_limit")) - .inScenario(scenarioName) - .whenScenarioStateIs(state) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withHeader("X-RateLimit-Limit", limit) - .withHeader("X-RateLimit-Remaining", remaining) - .withHeader("X-RateLimit-Reset", reset) - .withBody(body) - ); - if (i != scenarios.size() - 1) { - scenario = scenario.willSetStateTo(nextState); - } - githubApi.stubFor(scenario); - } - - github = Connector.connect("http://localhost:" + githubApi.port(), null); - initialRequestCount = getRequestCount(githubApi); - assertEquals(2, initialRequestCount); + github = Connector.connect("http://localhost:" + githubApi.port(), null); + initialRequestCount = getRequestCount(githubApi); + assertEquals(2, initialRequestCount); + } + + @Test + public void NoCheckerConfigured() throws Exception { + // set up scenarios + List scenarios = new ArrayList<>(); + long now = System.currentTimeMillis(); + int limit = 5000; + scenarios.add(new RateLimit(limit, 30, new Date(now - 10000))); + scenarios.add(new RateLimit(limit, limit, new Date(now - 8000))); + scenarios.add(new RateLimit(limit, 20, new Date(now - 6000))); + scenarios.add(new RateLimit(limit, limit, new Date(now - 4000))); + scenarios.add(new RateLimit(limit, 10, new Date(now - 2000))); + scenarios.add(new RateLimit(limit, limit, new Date(now))); + setupStubs(scenarios); + + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + github.getMeta(); + ApiRateLimitChecker.resetLocalChecker(); + + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + github.getMeta(); + ApiRateLimitChecker.resetLocalChecker(); + + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); + github.getMeta(); + + assertEquals( + 3, + countOfOutputLinesContaining("LocalChecker for rate limit was not set for this thread.")); + assertEquals(3, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + // github rate_limit endpoint should be contacted for ThrottleOnOver + // rateLimit() + // getRateLimit() + // meta endpoint + + // Do not use NoThrottle when apiUrl is not known + assertEquals(1, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); + + assertEquals(initialRequestCount + 9, getRequestCount(githubApi)); + } + + /** + * Verify that the throttle does not happen in OnOver throttle when none of the quota has been + * used + * + * @author Julian V. Modesto + */ + @Test + public void ThrottleOnOverTestWithQuota() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + // Given a full rate limit quota, then we expect no throttling + // Also only 1 call to get rate limit, since rate limit record is valid for a while + for (int i = 0; i < 100; i++) { + github.getMeta(); } - @Test - public void NoCheckerConfigured() throws Exception { - // set up scenarios - List scenarios = new ArrayList<>(); - long now = System.currentTimeMillis(); - int limit = 5000; - scenarios.add(new RateLimit(limit, 30, new Date(now - 10000))); - scenarios.add(new RateLimit(limit, limit, new Date(now - 8000))); - scenarios.add(new RateLimit(limit, 20, new Date(now - 6000))); - scenarios.add(new RateLimit(limit, limit, new Date(now - 4000))); - scenarios.add(new RateLimit(limit, 10, new Date(now - 2000))); - scenarios.add(new RateLimit(limit, limit, new Date(now))); - setupStubs(scenarios); - - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - github.getMeta(); - ApiRateLimitChecker.resetLocalChecker(); - - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - github.getMeta(); - ApiRateLimitChecker.resetLocalChecker(); - - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); - github.getMeta(); - - assertEquals(3, countOfOutputLinesContaining("LocalChecker for rate limit was not set for this thread.")); - assertEquals(3, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - // github rate_limit endpoint should be contacted for ThrottleOnOver - // rateLimit() - // getRateLimit() - // meta endpoint - - // Do not use NoThrottle when apiUrl is not known - assertEquals(1, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); - - - assertEquals(initialRequestCount + 9, getRequestCount(githubApi)); + assertEquals(0, countOfOutputLinesContaining("Sleeping")); + // Rate limit record remains valid so only one rate limit request made + assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); + } + + /** + * Verify when the throttle is not happening in "OnNormalize" throttle when none of the quota has + * been used + * + * @author Julian V. Modesto + */ + @Test + public void ThrottleOnNormalizeTestWithQuota() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + // set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + // Given a full rate limit quota, then we expect no throttling + for (int i = 0; i < 100; i++) { + github.getMeta(); } - /** - * Verify that the throttle does not happen in OnOver throttle - * when none of the quota has been used - * - * @author Julian V. Modesto - */ - @Test - public void ThrottleOnOverTestWithQuota() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - // Given a full rate limit quota, then we expect no throttling - // Also only 1 call to get rate limit, since rate limit record is valid for a while - for (int i = 0; i < 100; i++) { - github.getMeta(); - } - - - - assertEquals(0, countOfOutputLinesContaining("Sleeping")); - // Rate limit record remains valid so only one rate limit request made - assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); + assertEquals(0, countOfOutputLinesContaining("Sleeping")); + // Rate limit record remains valid so only one rate limit request made + assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); + } + + /** + * Verify that "NoThrottle" does not contact the GitHub api nor output any logs + * + * @author Marc Salles Navarro + */ + @Test + public void NoThrottleTestShouldNotThrottle() throws Exception { + // set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + // Have so little quota it should always fire. + scenarios.add(new RateLimit(limit, 10, soon)); + setupStubs(scenarios); + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + for (int i = 0; i < 100; i++) { + github.getMeta(); } - /** - * Verify when the throttle is not happening in "OnNormalize" throttle - * when none of the quota has been used - * - * @author Julian V. Modesto - */ - @Test - public void ThrottleOnNormalizeTestWithQuota() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - // set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - // Given a full rate limit quota, then we expect no throttling - for (int i = 0; i < 100; i++) { - github.getMeta(); - } - - assertEquals(0, countOfOutputLinesContaining("Sleeping")); - // Rate limit record remains valid so only one rate limit request made - assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); + // there should be no output + assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); + assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + // github rate_limit endpoint should be contacted once + assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); + } + + /** + * Verify that "NoThrottle" does not contact the GitHub api nor output any logs + * + * @author Marc Salles Navarro + */ + @Test + public void NoThrottleTestShouldNotThrottle404() throws Exception { + + setupStubs(new ArrayList<>()); + GHRateLimit.Record initial = github.lastRateLimit().getCore(); + assertEquals(2, getRequestCount(githubApi)); + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); + + // Return 404 for /rate_limit + githubApi.stubFor(get(urlEqualTo("/rate_limit")).willReturn(aResponse().withStatus(404))); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + github.getMeta(); + + // The core should be unknown, but different from initial + assertTrue(github.rateLimit().getCore() instanceof GHRateLimit.UnknownLimitRecord); + assertNotEquals(initial, github.rateLimit().getCore()); + + // there should be no output + assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); + assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + // github rate_limit endpoint should be contacted once + meta + assertEquals(initialRequestCount + 2, getRequestCount(githubApi)); + } + + /** + * Verify that "NoThrottle" falls back to "ThrottleOnOver" if using GitHub.com + * + * @author Marc Salles Navarro + */ + @Test + public void NoThrottleTestShouldFallbackToThrottleOnOverForGitHubDotCom() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + int buffer = ApiRateLimitChecker.calculateBuffer(limit); + long now = System.currentTimeMillis(); + scenarios.add(new RateLimit(limit, buffer - 1, new Date(now))); + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); + + GitHub spy = Mockito.spy(github); + Mockito.when(spy.getApiUrl()).thenReturn(GitHubServerConfig.GITHUB_URL); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, spy); + + spy.getMeta(); + + assertEquals(1, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); + assertEquals(1, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + // github rate_limit endpoint should be contacted by ThrottleOnOver + meta + assertEquals(initialRequestCount + 3, getRequestCount(githubApi)); + } + + /** + * Verify exactly when the throttle is occurring in "OnOver" + * + * @author Julian V. Modesto + */ + @Test + public void ThrottleOnOverTest() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // set up scenarios + List scenarios = new ArrayList<>(); + // set remaining quota to over buffer to trigger throttle + int limit = 5000; + int buffer = ApiRateLimitChecker.calculateBuffer(limit); + int expectedNumThrottles = 10; + + // This is going to not throttle for 10 values and then throttle the next 20 + for (int i = -10; i <= expectedNumThrottles; i++) { + scenarios.add(new RateLimit(limit, buffer - i, soon)); } - /** - * Verify that "NoThrottle" does not contact the GitHub api nor output any logs - * - * @author Marc Salles Navarro - */ - @Test - public void NoThrottleTestShouldNotThrottle() throws Exception { - // set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - // Have so little quota it should always fire. - scenarios.add(new RateLimit(limit, 10, soon)); - setupStubs(scenarios); - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - for (int i = 0; i < 100; i++) { - github.getMeta(); - } - - // there should be no output - assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); - assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - // github rate_limit endpoint should be contacted once - assertEquals(initialRequestCount + 101, getRequestCount(githubApi)); - } + // finally, stop throttling by restoring quota + scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); + setupStubs(scenarios); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - /** - * Verify that "NoThrottle" does not contact the GitHub api nor output any logs - * - * @author Marc Salles Navarro - */ - @Test - public void NoThrottleTestShouldNotThrottle404() throws Exception { - - setupStubs(new ArrayList<>()); - GHRateLimit.Record initial = github.lastRateLimit().getCore(); - assertEquals(2, getRequestCount(githubApi)); - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); - - //Return 404 for /rate_limit - githubApi.stubFor(get(urlEqualTo("/rate_limit")) - .willReturn(aResponse() - .withStatus(404) - )); + ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + // check rate limit to hit the first 11 scenarios because the throttle (add more here) + // does not happen until under buffer + for (int i = 0; i < 11; i++) { + assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); + } - github.getMeta(); + // should be no output + assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - // The core should be unknown, but different from initial - assertTrue(github.rateLimit().getCore() instanceof GHRateLimit.UnknownLimitRecord); - assertNotEquals(initial, github.rateLimit().getCore()); + assertEquals(initialRequestCount + 11, getRequestCount(githubApi)); - // there should be no output - assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); - assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - // github rate_limit endpoint should be contacted once + meta - assertEquals(initialRequestCount + 2, getRequestCount(githubApi)); + // check rate limit to hit the next 9 scenarios + for (int i = 0; i < 10; i++) { + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); } - - /** - * Verify that "NoThrottle" falls back to "ThrottleOnOver" if using GitHub.com - * - * @author Marc Salles Navarro - */ - @Test - public void NoThrottleTestShouldFallbackToThrottleOnOverForGitHubDotCom() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - int buffer = ApiRateLimitChecker.calculateBuffer(limit); - long now = System.currentTimeMillis(); - scenarios.add(new RateLimit(limit, buffer -1, new Date(now))); - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); - - GitHub spy = Mockito.spy(github); - Mockito.when(spy.getApiUrl()).thenReturn(GitHubServerConfig.GITHUB_URL); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, spy); - - spy.getMeta(); - - assertEquals(1, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); - assertEquals(1, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - // github rate_limit endpoint should be contacted by ThrottleOnOver + meta - assertEquals(initialRequestCount + 3, getRequestCount(githubApi)); + // This simulates the waiting until refreshed + currentChecker.resetExpiration(); + assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 10)); + + // output for all the throttled scenarios. Sleeps normally on the first and then the `notify` + // hits the next 9 + assertEquals(1, countOfOutputLinesContaining("Sleeping until reset.")); + assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); + // Refresh functionality was removed + assertEquals(0, countOfOutputLinesContaining("refreshed")); + assertEquals(initialRequestCount + 22, getRequestCount(githubApi)); + + // Make sure no new output + github.getMeta(); + assertEquals(1, countOfOutputLinesContaining("Sleeping until reset")); + assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); + // Refresh functionality was removed + assertEquals(0, countOfOutputLinesContaining("refreshed")); + // Only new request should be to meta, the existing rate limit is valid + assertEquals(initialRequestCount + 23, getRequestCount(githubApi)); + } + + /** + * Verify the bounds of the throttle for "Normalize" + * + * @author Julian V. Modesto + */ + @Test + public void ThrottleForNormalizeTestWithinIdeal() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + List scenarios = new ArrayList<>(); + int limit = 5000; + int buffer = ApiRateLimitChecker.calculateBuffer(limit); + + // Approximate the ideal here + int approximateIdeal = 4000; + + // NOTE: The behavior below is no longer interesting. + // All of the value adjustments do not matter. + // The checker no longer rechecks values until after the expiration time, no matter what. + // Changes before then will be ignored. + + // Check that if we're above within our ideal, then we don't throttle + scenarios.add(new RateLimit(limit, approximateIdeal + buffer - 100, soon)); + + // Check that we are under our ideal so we should throttle + scenarios.add(new RateLimit(limit, approximateIdeal - 100, soon)); + + // Check that we are under our ideal so we should throttle again + scenarios.add(new RateLimit(limit, approximateIdeal - 100, soon)); + + // Check that we are further under our ideal so we should throttle again + scenarios.add(new RateLimit(limit, approximateIdeal - 101, soon)); + + // Check that we can back to our original throttle + // ignored as invalid by github-api library + scenarios.add(new RateLimit(limit, approximateIdeal - 100, new Date(soon.getTime() + 2000))); + + // "Less" under the ideal but should recheck and throttle again + scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); + + // Check that we are under our ideal so we should throttle + scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); + + // Reset back to a full limit + scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 3000))); + setupStubs(scenarios); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + // First check will not say under budget (add counts) + github.getMeta(); + + assertEquals(4, getRequestCount(githubApi)); + // Feature removed, no output for under budget + assertEquals(0, countOfOutputLinesContaining("under budget")); + assertFalse(handler.getView().stream().anyMatch(m -> m.getMessage().contains("Sleeping"))); + + ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + + // check rate limit to hit the next 6 scenarios + for (int i = 0; i < 6; i++) { + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); } - - /** - * Verify exactly when the throttle is occurring in "OnOver" - * - * @author Julian V. Modesto - */ - @Test - public void ThrottleOnOverTest() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // set up scenarios - List scenarios = new ArrayList<>(); - // set remaining quota to over buffer to trigger throttle - int limit = 5000; - int buffer = ApiRateLimitChecker.calculateBuffer(limit); - int expectedNumThrottles = 10; - - //This is going to not throttle for 10 values and then throttle the next 20 - for (int i = -10; i <= expectedNumThrottles; i++) { - scenarios.add(new RateLimit(limit, buffer - i, soon)); - } - - // finally, stop throttling by restoring quota - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); - setupStubs(scenarios); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - - // check rate limit to hit the first 11 scenarios because the throttle (add more here) - // does not happen until under buffer - for (int i = 0; i < 11; i++) { - assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); - } - - //should be no output - assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); - - assertEquals(initialRequestCount + 11, getRequestCount(githubApi)); - - // check rate limit to hit the next 9 scenarios - for (int i = 0; i < 10; i++) { - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); - } - // This simulates the waiting until refreshed - currentChecker.resetExpiration(); - assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 10)); - - - //output for all the throttled scenarios. Sleeps normally on the first and then the `notify` hits the next 9 - assertEquals(1, countOfOutputLinesContaining("Sleeping until reset.")); - assertEquals(expectedNumThrottles-1, countOfOutputLinesContaining("Still sleeping")); - // Refresh functionality was removed - assertEquals(0, countOfOutputLinesContaining("refreshed")); - assertEquals(initialRequestCount + 22, getRequestCount(githubApi)); - - //Make sure no new output - github.getMeta(); - assertEquals(1, countOfOutputLinesContaining("Sleeping until reset")); - assertEquals(expectedNumThrottles-1, countOfOutputLinesContaining("Still sleeping")); - // Refresh functionality was removed - assertEquals(0, countOfOutputLinesContaining("refreshed")); - // Only new request should be to meta, the existing rate limit is valid - assertEquals(initialRequestCount + 23, getRequestCount(githubApi)); + // This simulates the waiting until refreshed + currentChecker.resetExpiration(); + assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 9)); + + assertEquals(initialRequestCount + 9, getRequestCount(githubApi)); + // Functionality removed + assertEquals(0, countOfOutputLinesContaining("rechecking")); + assertEquals(5, countOfOutputLinesContaining("Still sleeping")); + assertEquals(1, countOfOutputLinesContaining("Sleeping for")); + // Functionality removed + assertEquals(0, countOfOutputLinesContaining("under budget")); + assertEquals(1, countOfOutputLinesContaining("over budget")); + assertEquals( + 1, + countOfOutputLinesContaining( + "Jenkins is attempting to evenly distribute GitHub API requests")); + + // The last scenario will trigger back to under budget with a full limit but no new messages + assertEquals(initialRequestCount + 6, handler.getView().size()); + } + + /** + * Verify OnNormal throttling when past the buffer + * + * @author Julian V. Modesto + */ + @Test + public void NormalizeThrottleWithBurnedBuffer() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + long now = System.currentTimeMillis(); + + // Set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + // Trigger a throttle but the reset time is past + scenarios.add(new RateLimit(limit, 0, new Date(now))); + // Trigger a throttle but the reset time is past + scenarios.add(new RateLimit(limit, 0, new Date(now))); + // We never want to go under the buffer regardless of time past + scenarios.add(new RateLimit(limit, 0, new Date(now - TimeUnit.SECONDS.toMillis(30)))); + // Trigger a throttle but we have burned our buffer + scenarios.add(new RateLimit(limit, 0, soon)); + // Refresh rate limit + scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); + setupStubs(scenarios); + + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + // Run check against API limit + ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 1)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 2)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 3)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 4)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 5)); + + // Expect a triggered throttle for normalize + // GitHubRateLimitChecker add 1 second to notification loop, this hides the entropy value + assertEquals( + 3, + countOfOutputLinesContaining( + "Current quota for Github API usage has 0 remaining (250 over budget). Next quota of 5000 due now. Sleeping for 1 sec.")); + assertEquals( + 4, + countOfOutputLinesContaining( + "Jenkins is attempting to evenly distribute GitHub API requests. To configure a different rate limiting strategy, such as having Jenkins restrict GitHub API requests only when near or above the GitHub rate limit, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings.")); + assertEquals(4, countOfOutputLinesContaining("Sleeping")); + assertEquals(2, countOfOutputLinesContaining("now only 59 min remaining")); + // Refresh functionality was removed + assertEquals(0, countOfOutputLinesContaining("refreshed")); + assertEquals(initialRequestCount + 6, getRequestCount(githubApi)); + } + + /** + * Verify throttle in "OnOver" and the wait happens for the correct amount of time + * + * @author Alex Taylor + */ + @Test + public void OnOverThrottleTimingRateLimitCheck() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // Longer timings that test defaults for more consistent measurements. + ApiRateLimitChecker.setExpirationWaitMillis(60); + ApiRateLimitChecker.setNotificationWaitMillis(200); + + // set up scenarios + List scenarios = new ArrayList<>(); + // set remaining quota to over buffer to trigger throttle + int limit = 5000; + int buffer = ApiRateLimitChecker.calculateBuffer(limit); + int expectedNumThrottles = 5; + + // This is going to not throttle for 5 values and then throttle the next 5 + for (int i = -5; i <= expectedNumThrottles; i++) { + scenarios.add(new RateLimit(limit, buffer - i, soon)); } - /** - * Verify the bounds of the throttle for "Normalize" - * - * @author Julian V. Modesto - */ - @Test - public void ThrottleForNormalizeTestWithinIdeal() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - List scenarios = new ArrayList<>(); - int limit = 5000; - int buffer = ApiRateLimitChecker.calculateBuffer(limit); - - // Approximate the ideal here - int approximateIdeal = 4000; - - // NOTE: The behavior below is no longer interesting. - // All of the value adjustments do not matter. - // The checker no longer rechecks values until after the expiration time, no matter what. - // Changes before then will be ignored. - - // Check that if we're above within our ideal, then we don't throttle - scenarios.add(new RateLimit(limit, approximateIdeal + buffer - 100, soon)); - - // Check that we are under our ideal so we should throttle - scenarios.add(new RateLimit(limit, approximateIdeal - 100, soon)); - - // Check that we are under our ideal so we should throttle again - scenarios.add(new RateLimit(limit, approximateIdeal - 100, soon)); - - // Check that we are further under our ideal so we should throttle again - scenarios.add(new RateLimit(limit, approximateIdeal - 101, soon)); - - // Check that we can back to our original throttle - // ignored as invalid by github-api library - scenarios.add(new RateLimit(limit, approximateIdeal - 100, new Date(soon.getTime() + 2000))); - - // "Less" under the ideal but should recheck and throttle again - scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); - - // Check that we are under our ideal so we should throttle - scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); - - // Reset back to a full limit - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 3000))); - setupStubs(scenarios); + // finally, stop throttling by restoring quota + scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); + setupStubs(scenarios); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - // First check will not say under budget (add counts) - github.getMeta(); + long start = System.currentTimeMillis(); - assertEquals(4, getRequestCount(githubApi)); - // Feature removed, no output for under budget - assertEquals(0, countOfOutputLinesContaining("under budget")); - assertFalse(handler.getView().stream().anyMatch(m -> m.getMessage().contains("Sleeping"))); + // check rate limit to hit the first 10 scenarios + for (int i = 0; i < 6; i++) { + github.getRateLimit(); + // calls rateLimit() for first loop so we have to getRateLimit() for each loop + github.getMeta(); + } - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + // (rate_limit + meta) x 6 + assertEquals(initialRequestCount + 12, getRequestCount(githubApi)); - // check rate limit to hit the next 6 scenarios - for (int i = 0; i < 6; i++) { - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); - } - // This simulates the waiting until refreshed - currentChecker.resetExpiration(); - assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 9)); + // should be no output + assertEquals(0, countOfOutputLinesContaining("Sleeping")); - assertEquals(initialRequestCount + 9, getRequestCount(githubApi)); - // Functionality removed - assertEquals(0, countOfOutputLinesContaining("rechecking")); - assertEquals(5, countOfOutputLinesContaining("Still sleeping")); - assertEquals(1, countOfOutputLinesContaining("Sleeping for")); - // Functionality removed - assertEquals(0, countOfOutputLinesContaining("under budget")); - assertEquals(1, countOfOutputLinesContaining("over budget")); - assertEquals(1, countOfOutputLinesContaining("Jenkins is attempting to evenly distribute GitHub API requests")); + ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - // The last scenario will trigger back to under budget with a full limit but no new messages - assertEquals(initialRequestCount + 6, handler.getView().size()); + // check rate limit to hit the next 5 scenarios + for (int i = 0; i < 5; i++) { + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); } - - /** - * Verify OnNormal throttling when past the buffer - * - * @author Julian V. Modesto + // This simulates the waiting until refreshed + currentChecker.resetExpiration(); + assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 5)); + + assertEquals(initialRequestCount + 18, getRequestCount(githubApi)); + + // want to make sure that the 5 API checks (the last one is resetting) are taking at least 1000 + // MS + assertTrue((System.currentTimeMillis() - start) > 1000); + + // output for all the throttled scenarios. Again the first will show the remaining and then the + // rest will just sleep + assertEquals(1, countOfOutputLinesContaining("Sleeping")); + assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); + + // no new output + github.getMeta(); + // No new rate_limit request should be made, the existing rate limit is valid + assertEquals(initialRequestCount + 19, getRequestCount(githubApi)); + assertEquals(1, countOfOutputLinesContaining("Sleeping")); + assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); + } + + /** + * Verify the "OnNormalize" throttle and wait is happening for the correct amount of time + * + * @author Alex Taylor + */ + @Test + public void NormalizeThrottleTimingRateLimitCheck() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + ApiRateLimitChecker.setExpirationWaitMillis(60); + ApiRateLimitChecker.setNotificationWaitMillis(200); + + // Set up scenarios + List scenarios = new ArrayList<>(); + int limit = 5000; + // estimate the ideal + int approximateIdeal = 4000; + int burst = ApiRateLimitChecker.calculateNormalizedBurst(limit); + // Warm up server + scenarios.add(new RateLimit(limit, limit, soon)); + // Trigger a throttle for normalize + scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); + // Trigger a wait until rate limit + scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); + // Trigger a wait until rate limit + scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); + // Refresh rate limit + // github-api will ignore ratelimit responses that appear invalid + // Rate limit only goes up when the the reset date is later than previous records. + scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); + setupStubs(scenarios); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + + github.getMeta(); + + // start timing + long start = System.currentTimeMillis(); + + // Run check + ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 1)); + assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 2)); + // This simulates the waiting until refreshed + currentChecker.resetExpiration(); + assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 3)); + + assertEquals(initialRequestCount + 6, getRequestCount(githubApi)); + + // Want to make sure that the 3 API checks are taking at least 600 MS + assertTrue((System.currentTimeMillis() - start) > 600); + // Expect a triggered throttle for normalize + assertEquals(1, countOfOutputLinesContaining("Sleeping")); + // Expect a wait until rate limit + assertEquals(2, countOfOutputLinesContaining("Still sleeping")); + // Refresh functionality was removed + assertEquals(0, countOfOutputLinesContaining("refreshed")); + } + + /** + * Verify the throttle is happening for the "OnNormalize" and proves the ideal "limit" changes + * correctly with time + * + * @author Alex Taylor + */ + @Test + public void NormalizeExpectedIdealOverTime() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + // Set up scenarios + List scenarios = new ArrayList<>(); + long start = System.currentTimeMillis(); + + /* + * With the limit at 1000: the burst will be limit/5 and buffer will be limit/20 */ - @Test - public void NormalizeThrottleWithBurnedBuffer() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - long now = System.currentTimeMillis(); - - // Set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - // Trigger a throttle but the reset time is past - scenarios.add(new RateLimit(limit, 0, new Date(now))); - // Trigger a throttle but the reset time is past - scenarios.add(new RateLimit(limit, 0, new Date(now))); - // We never want to go under the buffer regardless of time past - scenarios.add(new RateLimit(limit, 0, new Date(now - TimeUnit.SECONDS.toMillis(30)))); - // Trigger a throttle but we have burned our buffer - scenarios.add(new RateLimit(limit, 0, soon)); - // Refresh rate limit - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - // Run check against API limit - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 1)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 2)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 3)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 4)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 5)); - - - // Expect a triggered throttle for normalize - // GitHubRateLimitChecker add 1 second to notification loop, this hides the entropy value - assertEquals(3, countOfOutputLinesContaining( - "Current quota for Github API usage has 0 remaining (250 over budget). Next quota of 5000 due now. Sleeping for 1 sec.")); - assertEquals(4, countOfOutputLinesContaining( - "Jenkins is attempting to evenly distribute GitHub API requests. To configure a different rate limiting strategy, such as having Jenkins restrict GitHub API requests only when near or above the GitHub rate limit, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings.")); - assertEquals(4, countOfOutputLinesContaining("Sleeping")); - assertEquals(2, countOfOutputLinesContaining("now only 59 min remaining")); - // Refresh functionality was removed - assertEquals(0, countOfOutputLinesContaining("refreshed")); - assertEquals(initialRequestCount + 6, getRequestCount(githubApi)); + int limit = 1000; + // estimate the ideal + // Formula should be the ((limit - (burst + buffer)) * % of hour left before reset) + buffer + // buffer for this limit will be limit/20 = 250 + // burst for this will be limit/5 = 1000 + // Ideal calculated at 45, 30, 15, and 0 minutes + int[] morePreciseIdeal = {50, 237, 424, 612}; + + // deadline set for those times as well + for (int i = 0; i < 4; i++) { + scenarios.add( + new RateLimit( + limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); } - - /** - * Verify throttle in "OnOver" and the wait happens for the correct amount of time - * - * @author Alex Taylor + /* + * With the limit at 400: the burst will be limit/10 and buffer will be limit/20 */ - @Test - public void OnOverThrottleTimingRateLimitCheck() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // Longer timings that test defaults for more consistent measurements. - ApiRateLimitChecker.setExpirationWaitMillis(60); - ApiRateLimitChecker.setNotificationWaitMillis(200); - - // set up scenarios - List scenarios = new ArrayList<>(); - // set remaining quota to over buffer to trigger throttle - int limit = 5000; - int buffer = ApiRateLimitChecker.calculateBuffer(limit); - int expectedNumThrottles = 5; - - //This is going to not throttle for 5 values and then throttle the next 5 - for (int i = -5; i <= expectedNumThrottles; i++) { - scenarios.add(new RateLimit(limit, buffer - i, soon)); - } - - // finally, stop throttling by restoring quota - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - long start = System.currentTimeMillis(); - - // check rate limit to hit the first 10 scenarios - for (int i = 0; i < 6; i++) { - github.getRateLimit(); - // calls rateLimit() for first loop so we have to getRateLimit() for each loop - github.getMeta(); - } - - // (rate_limit + meta) x 6 - assertEquals(initialRequestCount + 12, getRequestCount(githubApi)); - - //should be no output - assertEquals(0, countOfOutputLinesContaining("Sleeping")); - - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - - // check rate limit to hit the next 5 scenarios - for (int i = 0; i < 5; i++) { - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); - } - // This simulates the waiting until refreshed - currentChecker.resetExpiration(); - assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 5)); - - assertEquals(initialRequestCount + 18, getRequestCount(githubApi)); - - //want to make sure that the 5 API checks (the last one is resetting) are taking at least 1000 MS - assertTrue((System.currentTimeMillis() - start) > 1000); - - //output for all the throttled scenarios. Again the first will show the remaining and then the rest will just sleep - assertEquals(1, countOfOutputLinesContaining("Sleeping")); - assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); - - //no new output - github.getMeta(); - // No new rate_limit request should be made, the existing rate limit is valid - assertEquals(initialRequestCount + 19, getRequestCount(githubApi)); - assertEquals(1, countOfOutputLinesContaining("Sleeping")); - assertEquals(expectedNumThrottles - 1, countOfOutputLinesContaining("Still sleeping")); + limit = 400; + morePreciseIdeal = new int[] {20, 104, 189, 274}; + + // deadline set for those times as well + for (int i = 0; i < 4; i++) { + scenarios.add( + new RateLimit( + limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); } - - /** - * Verify the "OnNormalize" throttle and wait is happening for the correct amount of time - * - * @author Alex Taylor + /* + * With the limit at 1000: the burst will be limit/5 and buffer will be 15 */ - @Test - public void NormalizeThrottleTimingRateLimitCheck() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - ApiRateLimitChecker.setExpirationWaitMillis(60); - ApiRateLimitChecker.setNotificationWaitMillis(200); - - // Set up scenarios - List scenarios = new ArrayList<>(); - int limit = 5000; - // estimate the ideal - int approximateIdeal = 4000; - int burst = ApiRateLimitChecker.calculateNormalizedBurst(limit); - // Warm up server - scenarios.add(new RateLimit(limit, limit, soon)); - // Trigger a throttle for normalize - scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); - // Trigger a wait until rate limit - scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); - // Trigger a wait until rate limit - scenarios.add(new RateLimit(limit, approximateIdeal - burst, soon)); - // Refresh rate limit - // github-api will ignore ratelimit responses that appear invalid - // Rate limit only goes up when the the reset date is later than previous records. - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); - setupStubs(scenarios); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - github.getMeta(); - - //start timing - long start = System.currentTimeMillis(); - - // Run check - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); - - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 0)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 1)); - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 2)); - // This simulates the waiting until refreshed - currentChecker.resetExpiration(); - assertFalse(currentChecker.checkRateLimit(github.getRateLimit().getCore(), 3)); - - assertEquals(initialRequestCount + 6, getRequestCount(githubApi)); - - // Want to make sure that the 3 API checks are taking at least 600 MS - assertTrue((System.currentTimeMillis() - start) > 600); - // Expect a triggered throttle for normalize - assertEquals(1, countOfOutputLinesContaining("Sleeping")); - // Expect a wait until rate limit - assertEquals(2, countOfOutputLinesContaining("Still sleeping")); - // Refresh functionality was removed - assertEquals(0, countOfOutputLinesContaining("refreshed")); + limit = 200; + morePreciseIdeal = new int[] {15, 56, 97, 138}; + + // deadline set for those times as well + for (int i = 0; i < 4; i++) { + scenarios.add( + new RateLimit( + limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); } + setupStubs(scenarios); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - /** - * Verify the throttle is happening for the "OnNormalize" - * and proves the ideal "limit" changes correctly with time - * - * @author Alex Taylor - */ - @Test - public void NormalizeExpectedIdealOverTime() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - // Set up scenarios - List scenarios = new ArrayList<>(); - long start = System.currentTimeMillis(); - - /* - * With the limit at 1000: the burst will be limit/5 and buffer will be limit/20 - */ - int limit = 1000; - // estimate the ideal - // Formula should be the ((limit - (burst + buffer)) * % of hour left before reset) + buffer - // buffer for this limit will be limit/20 = 250 - // burst for this will be limit/5 = 1000 - // Ideal calculated at 45, 30, 15, and 0 minutes - int[] morePreciseIdeal = {50, 237, 424, 612}; - - // deadline set for those times as well - for (int i = 0; i < 4; i++) { - scenarios.add(new RateLimit(limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); - } - /* - * With the limit at 400: the burst will be limit/10 and buffer will be limit/20 - */ - limit = 400; - morePreciseIdeal = new int[]{20, 104, 189, 274}; - - // deadline set for those times as well - for (int i = 0; i < 4; i++) { - scenarios.add(new RateLimit(limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); - } - /* - * With the limit at 1000: the burst will be limit/5 and buffer will be 15 - */ - limit = 200; - morePreciseIdeal = new int[]{15, 56, 97, 138}; - - // deadline set for those times as well - for (int i = 0; i < 4; i++) { - scenarios.add(new RateLimit(limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis(i * 15)))); - } - - setupStubs(scenarios); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - for (int i = 0; i < 12; i++) { - if (i > 1) { - github.getRateLimit(); - } - // calls rateLimit() for first loop so we have to getRateLimit() for each loop - github.getMeta(); - } - - // rate_limit + meta x 12 - assertEquals(initialRequestCount + 24, getRequestCount(githubApi)); - - // Expect a triggered throttle for normalize, feature removed - assertEquals(0, countOfOutputLinesContaining("Current quota")); - // Making sure the budgets are correct, feature removed - assertEquals(0, countOfOutputLinesContaining("0 under budget")); - // no occurrences of sleeping - assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + for (int i = 0; i < 12; i++) { + if (i > 1) { + github.getRateLimit(); + } + // calls rateLimit() for first loop so we have to getRateLimit() for each loop + github.getMeta(); } - /** - * Verify when the throttle is happening for the "OnOver" - * and prove the current "limit" does not change the same way as Normalize - * - * @author Alex Taylor - */ - @Test - public void OnOverExpectedIdealOverTime() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - long start = System.currentTimeMillis(); - // Set up scenarios - List scenarios = new ArrayList<>(); - int limit = 1000; - // estimate the ideal(which does not apply in this scenario) - // Rate limit - int[] morePreciseIdeal = {49, 237, 424, 612}; - - // Rate limit records that expire early than the last returned are ignored as invalid - // Must be the same or greater - // deadline set for those times as well - for (int i = 0; i < 4; i++) { - scenarios.add(new RateLimit(limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis((i) * 15)))); - } - - // Refresh rate limit - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - // Run check a few times to ensure we don't get stuck - for (int i = 0; i < 5; i++) { - if (i > 1) { - github.getRateLimit(); - } - // calls rateLimit() for first loop so we have to getRateLimit() for each loop - github.getMeta(); - } - - assertEquals(12, getRequestCount(githubApi)); - - // Expect this to only get throttled when we are over the buffer limit - assertEquals(1, countOfOutputLinesContaining("Current quota")); - //Making sure the budget messages are correct - assertEquals(1, countOfOutputLinesContaining("1 over budget")); - assertEquals(1, countOfOutputLinesContaining("Jenkins is restricting GitHub API requests only when near or above the rate limit. To configure a different rate limiting strategy, such as having Jenkins attempt to evenly distribute GitHub API requests, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings.")); + // rate_limit + meta x 12 + assertEquals(initialRequestCount + 24, getRequestCount(githubApi)); + + // Expect a triggered throttle for normalize, feature removed + assertEquals(0, countOfOutputLinesContaining("Current quota")); + // Making sure the budgets are correct, feature removed + assertEquals(0, countOfOutputLinesContaining("0 under budget")); + // no occurrences of sleeping + assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + } + + /** + * Verify when the throttle is happening for the "OnOver" and prove the current "limit" does not + * change the same way as Normalize + * + * @author Alex Taylor + */ + @Test + public void OnOverExpectedIdealOverTime() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + long start = System.currentTimeMillis(); + // Set up scenarios + List scenarios = new ArrayList<>(); + int limit = 1000; + // estimate the ideal(which does not apply in this scenario) + // Rate limit + int[] morePreciseIdeal = {49, 237, 424, 612}; + + // Rate limit records that expire early than the last returned are ignored as invalid + // Must be the same or greater + // deadline set for those times as well + for (int i = 0; i < 4; i++) { + scenarios.add( + new RateLimit( + limit, morePreciseIdeal[i], new Date(start + TimeUnit.MINUTES.toMillis((i) * 15)))); } - /** - * Verify the expected reset happens and notifications happen on time in the logs for Normalize - * - * @author Alex Taylor - */ - @Test - public void ExpectedResetTimingNormalize() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); - - // Use a longer notification interval to make the test produce stable output - // The GitHubRateLimitChecker adds a one second sleep to each notification loop - ApiRateLimitChecker.setNotificationWaitMillis(1000); - - // Set up scenarios - List scenarios = new ArrayList<>(); - int limit = 1000; - int buffer = 50; - //Giving a bit of time to make sure the setup happens on time - long start = System.currentTimeMillis() + 7000; - scenarios.add(new RateLimit(limit, limit, new Date(start))); - - for (int i = 0; i <= 3; i++) { - scenarios.add(new RateLimit(limit, buffer - 5, new Date(start))); - } - // Refresh rate limit - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - - // First server warm up - github.getRateLimit(); - github.getRateLimit(); + // Refresh rate limit + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); - while(System.currentTimeMillis() + 6000 < start) - { - Thread.sleep(25); - } + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - github.getMeta(); + // Run check a few times to ensure we don't get stuck + for (int i = 0; i < 5; i++) { + if (i > 1) { + github.getRateLimit(); + } + // calls rateLimit() for first loop so we have to getRateLimit() for each loop + github.getMeta(); + } - // Expect a triggered throttle for normalize - assertEquals(2, countOfOutputLinesContaining("Current quota")); - assertEquals(2, countOfOutputLinesContaining("Still sleeping")); - assertEquals(initialRequestCount + 7, getRequestCount(githubApi)); + assertEquals(12, getRequestCount(githubApi)); + + // Expect this to only get throttled when we are over the buffer limit + assertEquals(1, countOfOutputLinesContaining("Current quota")); + // Making sure the budget messages are correct + assertEquals(1, countOfOutputLinesContaining("1 over budget")); + assertEquals( + 1, + countOfOutputLinesContaining( + "Jenkins is restricting GitHub API requests only when near or above the rate limit. To configure a different rate limiting strategy, such as having Jenkins attempt to evenly distribute GitHub API requests, go to \"GitHub API usage\" under \"Configure System\" in the Jenkins settings.")); + } + + /** + * Verify the expected reset happens and notifications happen on time in the logs for Normalize + * + * @author Alex Taylor + */ + @Test + public void ExpectedResetTimingNormalize() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleForNormalize); + + // Use a longer notification interval to make the test produce stable output + // The GitHubRateLimitChecker adds a one second sleep to each notification loop + ApiRateLimitChecker.setNotificationWaitMillis(1000); + + // Set up scenarios + List scenarios = new ArrayList<>(); + int limit = 1000; + int buffer = 50; + // Giving a bit of time to make sure the setup happens on time + long start = System.currentTimeMillis() + 7000; + scenarios.add(new RateLimit(limit, limit, new Date(start))); + + for (int i = 0; i <= 3; i++) { + scenarios.add(new RateLimit(limit, buffer - 5, new Date(start))); } + // Refresh rate limit + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); - /** - * Verify the expected reset happens and notifications happen on time in the logs for OnOver - * - * @author Alex Taylor - */ - @Test - public void ExpectedResetTimingOnOver() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // Use a longer notification interval to make the test produce stable output - // The GitHubRateLimitChecker adds a one second sleep to each notification loop - ApiRateLimitChecker.setNotificationWaitMillis(1000); - - // Set up scenarios - List scenarios = new ArrayList<>(); - int limit = 1000; - int buffer = 50; - //Giving a bit of time to make sure the setup happens on time - long start = System.currentTimeMillis() + 8000; - scenarios.add(new RateLimit(limit, limit, new Date(start))); - - for (int i = 0; i <= 3; i++) { - scenarios.add(new RateLimit(limit, buffer - 5, new Date(start))); - } - // Refresh rate limit - scenarios.add(new RateLimit(limit, limit, soon)); - setupStubs(scenarios); - // First server warm up - github.getRateLimit(); - github.getRateLimit(); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + // First server warm up + github.getRateLimit(); + github.getRateLimit(); - while(System.currentTimeMillis() + 6000 < start) - { - Thread.sleep(25); - } + while (System.currentTimeMillis() + 6000 < start) { + Thread.sleep(25); + } + + github.getMeta(); + + // Expect a triggered throttle for normalize + assertEquals(2, countOfOutputLinesContaining("Current quota")); + assertEquals(2, countOfOutputLinesContaining("Still sleeping")); + assertEquals(initialRequestCount + 7, getRequestCount(githubApi)); + } + + /** + * Verify the expected reset happens and notifications happen on time in the logs for OnOver + * + * @author Alex Taylor + */ + @Test + public void ExpectedResetTimingOnOver() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // Use a longer notification interval to make the test produce stable output + // The GitHubRateLimitChecker adds a one second sleep to each notification loop + ApiRateLimitChecker.setNotificationWaitMillis(1000); + + // Set up scenarios + List scenarios = new ArrayList<>(); + int limit = 1000; + int buffer = 50; + // Giving a bit of time to make sure the setup happens on time + long start = System.currentTimeMillis() + 8000; + scenarios.add(new RateLimit(limit, limit, new Date(start))); + + for (int i = 0; i <= 3; i++) { + scenarios.add(new RateLimit(limit, buffer - 5, new Date(start))); + } + // Refresh rate limit + scenarios.add(new RateLimit(limit, limit, soon)); + setupStubs(scenarios); + // First server warm up + github.getRateLimit(); + github.getRateLimit(); - github.getMeta(); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - // This test exercises the case where an expired rate limit is returned after the - // time where it should have expired. The checker should continue to wait and notify - // at the same rate not faster - assertEquals(2, countOfOutputLinesContaining("Current quota")); - assertEquals(2, countOfOutputLinesContaining("Still sleeping")); - assertEquals(initialRequestCount + 7, getRequestCount(githubApi)); + while (System.currentTimeMillis() + 6000 < start) { + Thread.sleep(25); } + + github.getMeta(); + + // This test exercises the case where an expired rate limit is returned after the + // time where it should have expired. The checker should continue to wait and notify + // at the same rate not faster + assertEquals(2, countOfOutputLinesContaining("Current quota")); + assertEquals(2, countOfOutputLinesContaining("Still sleeping")); + assertEquals(initialRequestCount + 7, getRequestCount(githubApi)); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTraitTest.java index 7a801917b..dc9764646 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/BranchDiscoveryTraitTest.java @@ -1,5 +1,13 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + import hudson.util.ListBoxModel; import java.util.Collections; import jenkins.scm.api.SCMHeadObserver; @@ -9,89 +17,82 @@ import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - public class BranchDiscoveryTraitTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); + @ClassRule public static JenkinsRule j = new JenkinsRule(); - @Test - public void given__discoverAll__when__appliedToContext__then__noFilter() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - ))); - BranchDiscoveryTrait instance = new BranchDiscoveryTrait(true, true); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(true)); - assertThat(ctx.wantPRs(), is(false)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.authorities(), hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - )); - } + @Test + public void given__discoverAll__when__appliedToContext__then__noFilter() throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class)))); + BranchDiscoveryTrait instance = new BranchDiscoveryTrait(true, true); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(true)); + assertThat(ctx.wantPRs(), is(false)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.authorities(), hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class))); + } - @Test - public void given__excludingPRs__when__appliedToContext__then__filter() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - ))); - BranchDiscoveryTrait instance = new BranchDiscoveryTrait(true, false); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(true)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), - contains(instanceOf(BranchDiscoveryTrait.ExcludeOriginPRBranchesSCMHeadFilter.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - )); - } + @Test + public void given__excludingPRs__when__appliedToContext__then__filter() throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class)))); + BranchDiscoveryTrait instance = new BranchDiscoveryTrait(true, false); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(true)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat( + ctx.filters(), + contains(instanceOf(BranchDiscoveryTrait.ExcludeOriginPRBranchesSCMHeadFilter.class))); + assertThat( + ctx.authorities(), hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class))); + } - @Test - public void given__onlyPRs__when__appliedToContext__then__filter() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - ))); - BranchDiscoveryTrait instance = new BranchDiscoveryTrait(false, true); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(true)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), contains(instanceOf(BranchDiscoveryTrait.OnlyOriginPRBranchesSCMHeadFilter.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class) - )); - } + @Test + public void given__onlyPRs__when__appliedToContext__then__filter() throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class)))); + BranchDiscoveryTrait instance = new BranchDiscoveryTrait(false, true); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(true)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat( + ctx.filters(), + contains(instanceOf(BranchDiscoveryTrait.OnlyOriginPRBranchesSCMHeadFilter.class))); + assertThat( + ctx.authorities(), hasItem(instanceOf(BranchDiscoveryTrait.BranchSCMHeadAuthority.class))); + } - @Test - public void given__descriptor__when__displayingOptions__then__allThreePresent() { - ListBoxModel options = - j.jenkins.getDescriptorByType(BranchDiscoveryTrait.DescriptorImpl.class).doFillStrategyIdItems(); - assertThat(options.size(), is(3)); - assertThat(options.get(0).value, is("1")); - assertThat(options.get(1).value, is("2")); - assertThat(options.get(2).value, is("3")); - } + @Test + public void given__descriptor__when__displayingOptions__then__allThreePresent() { + ListBoxModel options = + j.jenkins + .getDescriptorByType(BranchDiscoveryTrait.DescriptorImpl.class) + .doFillStrategyIdItems(); + assertThat(options.size(), is(3)); + assertThat(options.get(0).value, is("1")); + assertThat(options.get(1).value, is("2")); + assertThat(options.get(2).value, is("3")); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategyTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategyTest.java index afe4f987d..7562b73c4 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategyTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/DefaultGitHubNotificationStrategyTest.java @@ -24,6 +24,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.*; + import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.util.LogTaskListener; @@ -31,83 +34,111 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; - import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.*; - public class DefaultGitHubNotificationStrategyTest { - @Rule - public JenkinsRule j = new JenkinsRule(); + @Rule public JenkinsRule j = new JenkinsRule(); - @Test - public void given_basicJob_then_singleNotification() throws Exception { - List srcs = Arrays.asList( - new GitHubSCMSource("example", "test", null, false), - new GitHubSCMSource("", "", "http://github.com/example/test", true) - ); - for( GitHubSCMSource src: srcs) { - FreeStyleProject job = j.createFreeStyleProject(); - FreeStyleBuild run = j.buildAndAssertSuccess(job); - DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); - List notifications = - instance.notifications(GitHubNotificationContext.build(job, run, src, new BranchSCMHead("master")), - new LogTaskListener( - Logger.getLogger(getClass().getName()), Level.INFO)); - assertThat(notifications, hasSize(1)); - } + @Test + public void given_basicJob_then_singleNotification() throws Exception { + List srcs = + Arrays.asList( + new GitHubSCMSource("example", "test", null, false), + new GitHubSCMSource("", "", "http://github.com/example/test", true)); + for (GitHubSCMSource src : srcs) { + FreeStyleProject job = j.createFreeStyleProject(); + FreeStyleBuild run = j.buildAndAssertSuccess(job); + DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); + List notifications = + instance.notifications( + GitHubNotificationContext.build(job, run, src, new BranchSCMHead("master")), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); + assertThat(notifications, hasSize(1)); } + } - @Test - public void given_differentSCMheads_then_distinctNotifications() throws Exception { - List srcs = Arrays.asList( - new GitHubSCMSource("example", "test", "http://github.com/ignored/ignored", false), - new GitHubSCMSource("", "", "http://github.com/example/test", true) - ); - for( GitHubSCMSource src: srcs) { - FreeStyleProject job = j.createFreeStyleProject(); - FreeStyleBuild run = j.buildAndAssertSuccess(job); - DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); - BranchSCMHead testBranch = new BranchSCMHead("master"); - List notificationsA = - instance.notifications(GitHubNotificationContext.build(job, run, src, testBranch), - new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); - List notificationsB = - instance.notifications(GitHubNotificationContext.build(job, run, src, - new PullRequestSCMHead("test-pr", "owner", "repo", "branch", - 1, testBranch, SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE)), - new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); - List notificationsC = - instance.notifications(GitHubNotificationContext.build(job, run, src, - new PullRequestSCMHead("test-pr", "owner", "repo", "branch", - 1, testBranch, SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.HEAD)), - new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); - assertNotEquals(notificationsA, notificationsB); - assertNotEquals(notificationsB, notificationsC); - assertNotEquals(notificationsA, notificationsC); - } + @Test + public void given_differentSCMheads_then_distinctNotifications() throws Exception { + List srcs = + Arrays.asList( + new GitHubSCMSource("example", "test", "http://github.com/ignored/ignored", false), + new GitHubSCMSource("", "", "http://github.com/example/test", true)); + for (GitHubSCMSource src : srcs) { + FreeStyleProject job = j.createFreeStyleProject(); + FreeStyleBuild run = j.buildAndAssertSuccess(job); + DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); + BranchSCMHead testBranch = new BranchSCMHead("master"); + List notificationsA = + instance.notifications( + GitHubNotificationContext.build(job, run, src, testBranch), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); + List notificationsB = + instance.notifications( + GitHubNotificationContext.build( + job, + run, + src, + new PullRequestSCMHead( + "test-pr", + "owner", + "repo", + "branch", + 1, + testBranch, + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE)), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); + List notificationsC = + instance.notifications( + GitHubNotificationContext.build( + job, + run, + src, + new PullRequestSCMHead( + "test-pr", + "owner", + "repo", + "branch", + 1, + testBranch, + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.HEAD)), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)); + assertNotEquals(notificationsA, notificationsB); + assertNotEquals(notificationsB, notificationsC); + assertNotEquals(notificationsA, notificationsC); } + } - @Test - public void given_jobOrRun_then_differentURLs() throws Exception { - List srcs = Arrays.asList( - new GitHubSCMSource("example", "test", null, false), - new GitHubSCMSource("", "", "http://github.com/example/test", true) - ); - for( GitHubSCMSource src: srcs) { - FreeStyleProject job = j.createFreeStyleProject(); - FreeStyleBuild run = j.buildAndAssertSuccess(job); - DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); - String urlA = instance.notifications(GitHubNotificationContext.build(null, run, src, new BranchSCMHead("master")), - new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)).get(0).getUrl(); - String urlB = instance.notifications(GitHubNotificationContext.build(job, null, src, new BranchSCMHead("master")), - new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)).get(0).getUrl(); - assertNotEquals(urlA, urlB); - } + @Test + public void given_jobOrRun_then_differentURLs() throws Exception { + List srcs = + Arrays.asList( + new GitHubSCMSource("example", "test", null, false), + new GitHubSCMSource("", "", "http://github.com/example/test", true)); + for (GitHubSCMSource src : srcs) { + FreeStyleProject job = j.createFreeStyleProject(); + FreeStyleBuild run = j.buildAndAssertSuccess(job); + DefaultGitHubNotificationStrategy instance = new DefaultGitHubNotificationStrategy(); + String urlA = + instance + .notifications( + GitHubNotificationContext.build(null, run, src, new BranchSCMHead("master")), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)) + .get(0) + .getUrl(); + String urlB = + instance + .notifications( + GitHubNotificationContext.build(job, null, src, new BranchSCMHead("master")), + new LogTaskListener(Logger.getLogger(getClass().getName()), Level.INFO)) + .get(0) + .getUrl(); + assertNotEquals(urlA, urlB); } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java index c442a642d..2d064e476 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/EndpointTest.java @@ -1,5 +1,10 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.Page; @@ -10,6 +15,13 @@ import hudson.Util; import hudson.model.UnprotectedRootAction; import hudson.security.csrf.CrumbExclusion; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import org.junit.Before; import org.junit.Rule; @@ -22,126 +34,129 @@ import org.kohsuke.stapler.StaplerResponse; import org.xml.sax.SAXException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URL; -import java.util.Arrays; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - public class EndpointTest { - @Rule - public final JenkinsRule j = new JenkinsRule(); - private String testUrl; - - @Before - public void setUp() throws Exception { - j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); - MockAuthorizationStrategy auth = new MockAuthorizationStrategy(); - auth.grant(Jenkins.ADMINISTER).everywhere().to("alice"); - auth.grant(Jenkins.READ).everywhere().toEveryone(); - j.jenkins.setAuthorizationStrategy(auth); - testUrl = Util.rawEncode(j.getURL().toString() + "testroot/"); + @Rule public final JenkinsRule j = new JenkinsRule(); + private String testUrl; + + @Before + public void setUp() throws Exception { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy auth = new MockAuthorizationStrategy(); + auth.grant(Jenkins.ADMINISTER).everywhere().to("alice"); + auth.grant(Jenkins.READ).everywhere().toEveryone(); + j.jenkins.setAuthorizationStrategy(auth); + testUrl = Util.rawEncode(j.getURL().toString() + "testroot/"); + } + + @Test + @Issue("SECURITY-806") + public void cantGet_doCheckApiUri() throws IOException, SAXException { + try { + j.createWebClient() + .goTo( + appendCrumb( + "descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + + testUrl)); + fail("Should not be able to do that"); + } catch (FailingHttpStatusCodeException e) { + assertEquals(405, e.getStatusCode()); } - - @Test @Issue("SECURITY-806") - public void cantGet_doCheckApiUri() throws IOException, SAXException { - try { - j.createWebClient().goTo(appendCrumb("descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + testUrl)); - fail("Should not be able to do that"); - } catch (FailingHttpStatusCodeException e) { - assertEquals(405, e.getStatusCode()); - } - assertFalse(TestRoot.get().visited); + assertFalse(TestRoot.get().visited); + } + + @Test + @Issue("SECURITY-806") + public void cantPostAsAnonymous_doCheckApiUri() throws Exception { + try { + post( + "descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + + testUrl, + null); + fail("Should not be able to do that"); + } catch (FailingHttpStatusCodeException e) { + assertEquals(403, e.getStatusCode()); } - - @Test @Issue("SECURITY-806") - public void cantPostAsAnonymous_doCheckApiUri() throws Exception { - try { - post("descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + testUrl, null); - fail("Should not be able to do that"); - } catch (FailingHttpStatusCodeException e) { - assertEquals(403, e.getStatusCode()); - } - assertFalse(TestRoot.get().visited); + assertFalse(TestRoot.get().visited); + } + + @Test + @Issue("SECURITY-806") + public void canPostAsAdmin_doCheckApiUri() throws Exception { + post( + "descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + + testUrl, + "alice"); + assertTrue(TestRoot.get().visited); + } + + private String appendCrumb(String url) { + return url + "&" + getCrumb(); + } + + private String getCrumb() { + return Functions.getCrumbRequestField() + "=" + Functions.getCrumb(null); + } + + private Page post(String relative, String userName) throws Exception { + final JenkinsRule.WebClient client; + if (userName != null) { + client = j.createWebClient().login(userName); + } else { + client = j.createWebClient(); } - @Test @Issue("SECURITY-806") - public void canPostAsAdmin_doCheckApiUri() throws Exception { - post("descriptorByName/org.jenkinsci.plugins.github_branch_source.Endpoint/checkApiUri?apiUri=" + testUrl, "alice"); - assertTrue(TestRoot.get().visited); - } + final WebRequest request = + new WebRequest(new URL(client.getContextPath() + relative), HttpMethod.POST); + request.setAdditionalHeader("Accept", client.getBrowserVersion().getHtmlAcceptHeader()); + request.setRequestParameters( + Arrays.asList( + new NameValuePair(Functions.getCrumbRequestField(), Functions.getCrumb(null)))); + return client.getPage(request); + } - private String appendCrumb(String url) { - return url + "&" + getCrumb(); - } + @TestExtension + public static class TestRoot implements UnprotectedRootAction { - private String getCrumb() { - return Functions.getCrumbRequestField() + "=" + Functions.getCrumb(null); - } + boolean visited = false; - private Page post(String relative, String userName) throws Exception { - final JenkinsRule.WebClient client; - if (userName != null) { - client = j.createWebClient().login(userName); - } else { - client = j.createWebClient(); - } - - final WebRequest request = new WebRequest(new URL(client.getContextPath() + relative), HttpMethod.POST); - request.setAdditionalHeader("Accept", client.getBrowserVersion().getHtmlAcceptHeader()); - request.setRequestParameters(Arrays.asList(new NameValuePair(Functions.getCrumbRequestField(), Functions.getCrumb(null)))); - return client.getPage(request); + @Override + public String getIconFileName() { + return null; } - @TestExtension - public static class TestRoot implements UnprotectedRootAction { - - boolean visited = false; - - @Override - public String getIconFileName() { - return null; - } - - @Override - public String getDisplayName() { - return null; - } - - @Override - public String getUrlName() { - return "testroot"; - } - - public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException { - visited = true; - response.getWriter().println("OK"); - } + @Override + public String getDisplayName() { + return null; + } - static TestRoot get() { - return ExtensionList.lookup(UnprotectedRootAction.class).get(TestRoot.class); - } + @Override + public String getUrlName() { + return "testroot"; } - @TestExtension - public static class CrumbExcluder extends CrumbExclusion { - @Override - public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - final String pathInfo = request.getPathInfo(); - if (pathInfo == null || !pathInfo.contains("testroot")) { - return false; - } - chain.doFilter(request, response); - return true; - } + public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException { + visited = true; + response.getWriter().println("OK"); } -} \ No newline at end of file + static TestRoot get() { + return ExtensionList.lookup(UnprotectedRootAction.class).get(TestRoot.class); + } + } + + @TestExtension + public static class CrumbExcluder extends CrumbExclusion { + @Override + public boolean process( + HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + final String pathInfo = request.getPathInfo(); + if (pathInfo == null || !pathInfo.contains("testroot")) { + return false; + } + chain.doFilter(request, response); + return true; + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java index e22339163..7416da7f4 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/EventsTest.java @@ -25,6 +25,10 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; import jenkins.scm.api.SCMEvent; import jenkins.scm.api.SCMEvents; import jenkins.scm.api.SCMHeadEvent; @@ -39,188 +43,185 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; - public class EventsTest { - /** - * All tests in this class only use Jenkins for the extensions - */ - @ClassRule - public static JenkinsRule r = new JenkinsRule(); + /** All tests in this class only use Jenkins for the extensions */ + @ClassRule public static JenkinsRule r = new JenkinsRule(); + + private static int defaultFireDelayInSeconds = GitHubSCMSource.getEventDelaySeconds(); + + private static SCMEvent.Type firedEventType; + private static GHSubscriberEvent ghEvent; + + @BeforeClass + public static void setupDelay() { + GitHubSCMSource.setEventDelaySeconds(1); + } + + @Before + public void resetFiredEvent() { + firedEventType = null; + ghEvent = null; + TestSCMEventListener.setReceived(false); + } + + @AfterClass + public static void resetDelay() { + GitHubSCMSource.setEventDelaySeconds(defaultFireDelayInSeconds); + } + + @Test + public void given_ghPushEventCreated_then_createdHeadEventFired() throws Exception { + PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + + firedEventType = SCMEvent.Type.CREATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pushEventCreated.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPushEventDeleted_then_removedHeadEventFired() throws Exception { + PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + + firedEventType = SCMEvent.Type.REMOVED; + ghEvent = callOnEvent(subscriber, "EventsTest/pushEventRemoved.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPushEventUpdated_then_updatedHeadEventFired() throws Exception { + PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pushEventUpdated.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPullRequestEventOpened_then_createdHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + + firedEventType = SCMEvent.Type.CREATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventCreated.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPullRequestEventClosed_then_removedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + + firedEventType = SCMEvent.Type.REMOVED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventRemoved.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPullRequestEventReopened_then_updatedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - private static int defaultFireDelayInSeconds = GitHubSCMSource.getEventDelaySeconds(); + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdated.json"); + waitAndAssertReceived(true); + } - private static SCMEvent.Type firedEventType; - private static GHSubscriberEvent ghEvent; + @Test + public void given_ghPullRequestEventSync_then_updatedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - @BeforeClass - public static void setupDelay() { - GitHubSCMSource.setEventDelaySeconds(1); - } + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedSync.json"); + waitAndAssertReceived(true); + } - @Before - public void resetFiredEvent() { - firedEventType = null; - ghEvent = null; - TestSCMEventListener.setReceived(false); - } + @Test + public void given_ghRepositoryEventCreatedFromFork_then_createdSourceEventFired() + throws Exception { + GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); - @AfterClass - public static void resetDelay() { - GitHubSCMSource.setEventDelaySeconds(defaultFireDelayInSeconds); - } + firedEventType = SCMEvent.Type.CREATED; + ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventCreated.json"); + waitAndAssertReceived(true); + } - @Test - public void given_ghPushEventCreated_then_createdHeadEventFired() throws Exception { - PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + @Test + public void given_ghRepositoryEventCreatedNotFork_then_noSourceEventFired() throws Exception { + GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); - firedEventType = SCMEvent.Type.CREATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pushEventCreated.json"); - waitAndAssertReceived(true); - } - - @Test - public void given_ghPushEventDeleted_then_removedHeadEventFired() throws Exception { - PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventNotFiredNotFork.json"); + waitAndAssertReceived(false); + } + + @Test + public void given_ghRepositoryEventWrongAction_then_noSourceEventFired() throws Exception { + GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); + + ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventNotFiredWrongAction.json"); + waitAndAssertReceived(false); + } + + private GHSubscriberEvent callOnEvent(PushGHEventSubscriber subscriber, String eventPayloadFile) + throws IOException { + GHSubscriberEvent event = createEvent(eventPayloadFile); + subscriber.onEvent(event); + return event; + } + + private GHSubscriberEvent callOnEvent( + PullRequestGHEventSubscriber subscriber, String eventPayloadFile) throws IOException { + GHSubscriberEvent event = createEvent(eventPayloadFile); + subscriber.onEvent(event); + return event; + } - firedEventType = SCMEvent.Type.REMOVED; - ghEvent = callOnEvent(subscriber, "EventsTest/pushEventRemoved.json"); - waitAndAssertReceived(true); - } + private GHSubscriberEvent callOnEvent( + GitHubRepositoryEventSubscriber subscriber, String eventPayloadFile) throws IOException { + GHSubscriberEvent event = createEvent(eventPayloadFile); + subscriber.onEvent(event); + return event; + } - @Test - public void given_ghPushEventUpdated_then_updatedHeadEventFired() throws Exception { - PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); + private GHSubscriberEvent createEvent(String eventPayloadFile) throws IOException { + String payload = IOUtils.toString(getClass().getResourceAsStream(eventPayloadFile)); + return new GHSubscriberEvent("myOrigin", null, payload); + } - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pushEventUpdated.json"); - waitAndAssertReceived(true); - } + private void waitAndAssertReceived(boolean received) throws InterruptedException { + long watermark = SCMEvents.getWatermark(); + // event will be fired by subscriber at some point + SCMEvents.awaitOne(watermark, 1200, TimeUnit.MILLISECONDS); - @Test - public void given_ghPullRequestEventOpened_then_createdHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + assertEquals( + "Event should have " + ((!received) ? "not " : "") + "been received", + received, + TestSCMEventListener.didReceive()); + } - firedEventType = SCMEvent.Type.CREATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventCreated.json"); - waitAndAssertReceived(true); - } + @TestExtension + public static class TestSCMEventListener extends jenkins.scm.api.SCMEventListener { - @Test - public void given_ghPullRequestEventClosed_then_removedHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + private static boolean eventReceived = false; - firedEventType = SCMEvent.Type.REMOVED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventRemoved.json"); - waitAndAssertReceived(true); + public void onSCMHeadEvent(SCMHeadEvent event) { + receiveEvent(event.getType(), event.getOrigin()); } - @Test - public void given_ghPullRequestEventReopened_then_updatedHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdated.json"); - waitAndAssertReceived(true); + public void onSCMSourceEvent(SCMSourceEvent event) { + receiveEvent(event.getType(), event.getOrigin()); } - @Test - public void given_ghPullRequestEventSync_then_updatedHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + private void receiveEvent(SCMEvent.Type type, String origin) { + eventReceived = true; - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedSync.json"); - waitAndAssertReceived(true); + assertEquals("Event type should be the same", type, firedEventType); + assertEquals("Event origin should be the same", origin, ghEvent.getOrigin()); } - @Test - public void given_ghRepositoryEventCreatedFromFork_then_createdSourceEventFired() throws Exception { - GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); - - firedEventType = SCMEvent.Type.CREATED; - ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventCreated.json"); - waitAndAssertReceived(true); + public static boolean didReceive() { + return eventReceived; } - @Test - public void given_ghRepositoryEventCreatedNotFork_then_noSourceEventFired() throws Exception { - GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); - - ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventNotFiredNotFork.json"); - waitAndAssertReceived(false); - } - - @Test - public void given_ghRepositoryEventWrongAction_then_noSourceEventFired() throws Exception { - GitHubRepositoryEventSubscriber subscriber = new GitHubRepositoryEventSubscriber(); - - ghEvent = callOnEvent(subscriber, "EventsTest/repositoryEventNotFiredWrongAction.json"); - waitAndAssertReceived(false); + public static void setReceived(boolean received) { + eventReceived = received; } - - private GHSubscriberEvent callOnEvent(PushGHEventSubscriber subscriber, String eventPayloadFile) throws IOException { - GHSubscriberEvent event = createEvent(eventPayloadFile); - subscriber.onEvent(event); - return event; - } - - private GHSubscriberEvent callOnEvent(PullRequestGHEventSubscriber subscriber, String eventPayloadFile) throws IOException { - GHSubscriberEvent event = createEvent(eventPayloadFile); - subscriber.onEvent(event); - return event; - } - - private GHSubscriberEvent callOnEvent(GitHubRepositoryEventSubscriber subscriber, String eventPayloadFile) throws IOException { - GHSubscriberEvent event = createEvent(eventPayloadFile); - subscriber.onEvent(event); - return event; - } - - private GHSubscriberEvent createEvent(String eventPayloadFile) throws IOException { - String payload = IOUtils.toString(getClass().getResourceAsStream(eventPayloadFile)); - return new GHSubscriberEvent("myOrigin", null, payload); - } - - private void waitAndAssertReceived(boolean received) throws InterruptedException { - long watermark = SCMEvents.getWatermark(); - // event will be fired by subscriber at some point - SCMEvents.awaitOne(watermark, 1200, TimeUnit.MILLISECONDS); - - assertEquals("Event should have " + ((!received) ? "not " : "") + "been received", received, TestSCMEventListener.didReceive()); - } - - @TestExtension - public static class TestSCMEventListener extends jenkins.scm.api.SCMEventListener { - - private static boolean eventReceived = false; - - public void onSCMHeadEvent(SCMHeadEvent event) { - receiveEvent(event.getType(), event.getOrigin()); - } - - public void onSCMSourceEvent(SCMSourceEvent event) { - receiveEvent(event.getType(), event.getOrigin()); - } - - private void receiveEvent(SCMEvent.Type type, String origin) { - eventReceived = true; - - assertEquals("Event type should be the same", type, firedEventType); - assertEquals("Event origin should be the same", origin, ghEvent.getOrigin()); - } - - public static boolean didReceive() { - return eventReceived; - } - - public static void setReceived(boolean received) { - eventReceived = received; - } - - } - -} \ No newline at end of file + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait2Test.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait2Test.java index 991a322f1..1fd2ba058 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait2Test.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTrait2Test.java @@ -23,6 +23,8 @@ */ package org.jenkinsci.plugins.github_branch_source; +import static org.junit.Assert.*; + import java.util.Collections; import java.util.List; import jenkins.branch.BranchSource; @@ -32,53 +34,59 @@ import jenkins.scm.api.trait.SCMSourceTrait; import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject; import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.*; import org.junit.Rule; +import org.junit.Test; import org.jvnet.hudson.test.For; import org.jvnet.hudson.test.JenkinsRule; @For(ForkPullRequestDiscoveryTrait.class) public class ForkPullRequestDiscoveryTrait2Test { - @Rule - public JenkinsRule r = new JenkinsRule(); - - @Ignore("These tests fail because users get automatically migrated to URL-based configuration if they re-save the GitHubSCMSource") - @Test - public void configRoundtrip() throws Exception { - WorkflowMultiBranchProject p = r.createProject(WorkflowMultiBranchProject.class); + @Rule public JenkinsRule r = new JenkinsRule(); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustNobody(), false); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), false); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), false); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), false); - } + @Ignore( + "These tests fail because users get automatically migrated to URL-based configuration if they re-save the GitHubSCMSource") + @Test + public void configRoundtrip() throws Exception { + WorkflowMultiBranchProject p = r.createProject(WorkflowMultiBranchProject.class); - @Test - public void configRoundtripWithRawUrl() throws Exception { - WorkflowMultiBranchProject p = r.createProject(WorkflowMultiBranchProject.class); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustNobody(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), false); + } - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustNobody(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), true); - } + @Test + public void configRoundtripWithRawUrl() throws Exception { + WorkflowMultiBranchProject p = r.createProject(WorkflowMultiBranchProject.class); - private void assertRoundTrip(WorkflowMultiBranchProject p, SCMHeadAuthority trust, boolean configuredByUrl) throws Exception { + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustNobody(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), true); + } - GitHubSCMSource s; - if (configuredByUrl) - s = new GitHubSCMSource("", "", "https://github.com/nobody/nowhere", true); - else - s = new GitHubSCMSource("nobody", "nowhere", null, false); + private void assertRoundTrip( + WorkflowMultiBranchProject p, + SCMHeadAuthority< + ? super GitHubSCMSourceRequest, + ? extends ChangeRequestSCMHead2, + ? extends SCMRevision> + trust, + boolean configuredByUrl) + throws Exception { - p.setSourcesList(Collections.singletonList(new BranchSource(s))); - s.setTraits(Collections.singletonList(new ForkPullRequestDiscoveryTrait(0, trust))); - r.configRoundtrip(p); - List traits = ((GitHubSCMSource) p.getSourcesList().get(0).getSource()).getTraits(); - assertEquals(1, traits.size()); - assertEquals(trust.getClass(), ((ForkPullRequestDiscoveryTrait) traits.get(0)).getTrust().getClass()); - } + GitHubSCMSource s; + if (configuredByUrl) s = new GitHubSCMSource("", "", "https://github.com/nobody/nowhere", true); + else s = new GitHubSCMSource("nobody", "nowhere", null, false); + p.setSourcesList(Collections.singletonList(new BranchSource(s))); + s.setTraits(Collections.singletonList(new ForkPullRequestDiscoveryTrait(0, trust))); + r.configRoundtrip(p); + List traits = + ((GitHubSCMSource) p.getSourcesList().get(0).getSource()).getTraits(); + assertEquals(1, traits.size()); + assertEquals( + trust.getClass(), ((ForkPullRequestDiscoveryTrait) traits.get(0)).getTrust().getClass()); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTraitTest.java index dfd225056..86f30bb98 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/ForkPullRequestDiscoveryTraitTest.java @@ -1,5 +1,12 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + import hudson.util.XStream2; import java.util.Collections; import java.util.EnumSet; @@ -10,116 +17,119 @@ import org.hamcrest.Matchers; import org.junit.Test; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - public class ForkPullRequestDiscoveryTraitTest { - @Test - public void xstream() throws Exception { - System.out.println(new XStream2().toXML(new ForkPullRequestDiscoveryTrait(3, new ForkPullRequestDiscoveryTrait.TrustContributors()))); - } + @Test + public void xstream() throws Exception { + System.out.println( + new XStream2() + .toXML( + new ForkPullRequestDiscoveryTrait( + 3, new ForkPullRequestDiscoveryTrait.TrustContributors()))); + } - @Test - public void given__discoverHeadMerge__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - ))); - ForkPullRequestDiscoveryTrait instance = new ForkPullRequestDiscoveryTrait( - EnumSet.allOf(ChangeRequestCheckoutStrategy.class), - new ForkPullRequestDiscoveryTrait.TrustContributors() - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.forkPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - )); - } + @Test + public void given__discoverHeadMerge__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class)))); + ForkPullRequestDiscoveryTrait instance = + new ForkPullRequestDiscoveryTrait( + EnumSet.allOf(ChangeRequestCheckoutStrategy.class), + new ForkPullRequestDiscoveryTrait.TrustContributors()); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.forkPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); + assertThat( + ctx.authorities(), + hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class))); + } - @Test - public void given__discoverHeadOnly__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - ))); - ForkPullRequestDiscoveryTrait instance = new ForkPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), - new ForkPullRequestDiscoveryTrait.TrustContributors() - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.forkPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); - assertThat(ctx.authorities(), hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - )); - } + @Test + public void given__discoverHeadOnly__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class)))); + ForkPullRequestDiscoveryTrait instance = + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), + new ForkPullRequestDiscoveryTrait.TrustContributors()); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat(ctx.forkPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); + assertThat( + ctx.authorities(), + hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class))); + } - @Test - public void given__discoverMergeOnly__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - ))); - ForkPullRequestDiscoveryTrait instance = new ForkPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), - new ForkPullRequestDiscoveryTrait.TrustContributors() - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.forkPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); - assertThat(ctx.authorities(), hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - )); - } + @Test + public void given__discoverMergeOnly__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class)))); + ForkPullRequestDiscoveryTrait instance = + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.forkPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); + assertThat( + ctx.authorities(), + hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class))); + } - @Test - public void given__nonDefaultTrust__when__appliedToContext__then__authoritiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class) - ))); - ForkPullRequestDiscoveryTrait instance = new ForkPullRequestDiscoveryTrait( - EnumSet.allOf(ChangeRequestCheckoutStrategy.class), - new ForkPullRequestDiscoveryTrait.TrustEveryone() - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.forkPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(ForkPullRequestDiscoveryTrait.TrustEveryone.class) - )); - } + @Test + public void given__nonDefaultTrust__when__appliedToContext__then__authoritiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not(hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustContributors.class)))); + ForkPullRequestDiscoveryTrait instance = + new ForkPullRequestDiscoveryTrait( + EnumSet.allOf(ChangeRequestCheckoutStrategy.class), + new ForkPullRequestDiscoveryTrait.TrustEveryone()); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.forkPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); + assertThat( + ctx.authorities(), hasItem(instanceOf(ForkPullRequestDiscoveryTrait.TrustEveryone.class))); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsJCasCCompatibilityTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsJCasCCompatibilityTest.java index 008f95e5e..6204e87d6 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsJCasCCompatibilityTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsJCasCCompatibilityTest.java @@ -1,14 +1,18 @@ package org.jenkinsci.plugins.github_branch_source; +import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile; +import static io.jenkins.plugins.casc.misc.Util.toYamlString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.jvnet.hudson.test.JenkinsMatchers.hasPlainText; + import com.cloudbees.plugins.credentials.Credentials; -import com.cloudbees.plugins.credentials.GlobalCredentialsConfiguration; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.casc.CredentialsRootConfigurator; import com.cloudbees.plugins.credentials.domains.DomainCredentials; -import hudson.ExtensionList; import io.jenkins.plugins.casc.ConfigurationContext; import io.jenkins.plugins.casc.ConfiguratorRegistry; -import io.jenkins.plugins.casc.impl.configurators.GlobalConfigurationCategoryConfigurator; import io.jenkins.plugins.casc.misc.ConfiguredWithCode; import io.jenkins.plugins.casc.misc.EnvVarsRule; import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; @@ -22,70 +26,68 @@ import org.junit.Test; import org.junit.rules.RuleChain; -import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile; -import static io.jenkins.plugins.casc.misc.Util.toYamlString; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.jvnet.hudson.test.JenkinsMatchers.hasPlainText; - public class GitHubAppCredentialsJCasCCompatibilityTest { - @ConfiguredWithCode("github-app-jcasc-minimal.yaml") - public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); - - private static final String GITHUB_APP_KEY = "SomeString"; - - @ClassRule - public static RuleChain chain = RuleChain - .outerRule(new EnvVarsRule().set("GITHUB_APP_KEY", GITHUB_APP_KEY)) - .around(j); - - @Test - public void should_support_configuration_as_code() { - List domainCredentials = SystemCredentialsProvider.getInstance() - .getDomainCredentials(); - - assertThat(domainCredentials.size(), is(1)); - List credentials = domainCredentials.get(0).getCredentials(); - assertThat(credentials.size(), is(1)); - - Credentials credential = credentials.get(0); - assertThat(credential, instanceOf(GitHubAppCredentials.class)); - GitHubAppCredentials gitHubAppCredentials = (GitHubAppCredentials) credential; - - assertThat(gitHubAppCredentials.getAppID(), is("1111")); - assertThat(gitHubAppCredentials.getDescription(), is("GitHub app 1111")); - assertThat(gitHubAppCredentials.getId(), is("github-app")); - assertThat(gitHubAppCredentials.getPrivateKey(), hasPlainText(GITHUB_APP_KEY)); - } - - @Test - public void should_support_configuration_export() throws Exception { - Sequence credentials = getCredentials(); - CNode githubApp = credentials.get(0).asMapping().get("gitHubApp"); - - String exported = toYamlString(githubApp) - // replace secret with a constant value - .replaceAll("privateKey: .*", "privateKey: \"some-secret-value\""); - - String expected = toStringFromYamlFile(this, "github-app-jcasc-minimal-expected-export.yaml"); - - assertThat(exported, is(expected)); - } - - private Sequence getCredentials() throws Exception { - CredentialsRootConfigurator root = Jenkins.get() - .getExtensionList(CredentialsRootConfigurator.class).get(0); - - ConfiguratorRegistry registry = ConfiguratorRegistry.get(); - ConfigurationContext context = new ConfigurationContext(registry); - Mapping configNode = Objects - .requireNonNull(root.describe(root.getTargetComponent(context), context)).asMapping(); - Mapping domainCredentials = configNode - .get("system").asMapping().get("domainCredentials") - .asSequence() - .get(0).asMapping(); - return domainCredentials.get("credentials").asSequence(); - } + @ConfiguredWithCode("github-app-jcasc-minimal.yaml") + public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); + + private static final String GITHUB_APP_KEY = "SomeString"; + + @ClassRule + public static RuleChain chain = + RuleChain.outerRule(new EnvVarsRule().set("GITHUB_APP_KEY", GITHUB_APP_KEY)).around(j); + + @Test + public void should_support_configuration_as_code() { + List domainCredentials = + SystemCredentialsProvider.getInstance().getDomainCredentials(); + + assertThat(domainCredentials.size(), is(1)); + List credentials = domainCredentials.get(0).getCredentials(); + assertThat(credentials.size(), is(1)); + + Credentials credential = credentials.get(0); + assertThat(credential, instanceOf(GitHubAppCredentials.class)); + GitHubAppCredentials gitHubAppCredentials = (GitHubAppCredentials) credential; + + assertThat(gitHubAppCredentials.getAppID(), is("1111")); + assertThat(gitHubAppCredentials.getDescription(), is("GitHub app 1111")); + assertThat(gitHubAppCredentials.getId(), is("github-app")); + assertThat(gitHubAppCredentials.getPrivateKey(), hasPlainText(GITHUB_APP_KEY)); + } + + @Test + public void should_support_configuration_export() throws Exception { + Sequence credentials = getCredentials(); + CNode githubApp = credentials.get(0).asMapping().get("gitHubApp"); + + String exported = + toYamlString(githubApp) + // replace secret with a constant value + .replaceAll("privateKey: .*", "privateKey: \"some-secret-value\""); + + String expected = toStringFromYamlFile(this, "github-app-jcasc-minimal-expected-export.yaml"); + + assertThat(exported, is(expected)); + } + + private Sequence getCredentials() throws Exception { + CredentialsRootConfigurator root = + Jenkins.get().getExtensionList(CredentialsRootConfigurator.class).get(0); + + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + Mapping configNode = + Objects.requireNonNull(root.describe(root.getTargetComponent(context), context)) + .asMapping(); + Mapping domainCredentials = + configNode + .get("system") + .asMapping() + .get("domainCredentials") + .asSequence() + .get(0) + .asMapping(); + return domainCredentials.get("credentials").asSequence(); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchSourcesJCasCCompatibilityTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchSourcesJCasCCompatibilityTest.java index 2dab52862..8840d3a63 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchSourcesJCasCCompatibilityTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubBranchSourcesJCasCCompatibilityTest.java @@ -1,5 +1,8 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import io.jenkins.plugins.casc.misc.RoundTripAbstractTest; import org.jenkinsci.plugins.workflow.libs.GlobalLibraries; @@ -8,35 +11,35 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.RestartableJenkinsRule; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - public class GitHubBranchSourcesJCasCCompatibilityTest extends RoundTripAbstractTest { - @Issue("JENKINS-57557") - @Override - protected void assertConfiguredAsExpected(RestartableJenkinsRule restartableJenkinsRule, String s) { - assertEquals(1, GlobalLibraries.get().getLibraries().size()); - final LibraryConfiguration library = GlobalLibraries.get().getLibraries().get(0); - assertEquals("jenkins-pipeline-lib", library.getName()); - final SCMSourceRetriever retriever = (SCMSourceRetriever) library.getRetriever(); - final GitHubSCMSource scm = (GitHubSCMSource) retriever.getScm(); - assertEquals("e43d6600-ba0e-46c5-8eae-3989bf654055", scm.getId()); - assertEquals("jenkins-infra", scm.getRepoOwner()); - assertEquals("pipeline-library", scm.getRepository()); - assertEquals(3, scm.getTraits().size()); - final BranchDiscoveryTrait branchDiscovery = (BranchDiscoveryTrait) scm.getTraits().get(0); - assertEquals(1, branchDiscovery.getStrategyId()); - final OriginPullRequestDiscoveryTrait prDiscovery = (OriginPullRequestDiscoveryTrait) scm.getTraits().get(1); - assertEquals(2, prDiscovery.getStrategyId()); - final ForkPullRequestDiscoveryTrait forkDiscovery = (ForkPullRequestDiscoveryTrait) scm.getTraits().get(2); - assertEquals(3, forkDiscovery.getStrategyId()); - assertThat(forkDiscovery.getTrust(), instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)); - } + @Issue("JENKINS-57557") + @Override + protected void assertConfiguredAsExpected( + RestartableJenkinsRule restartableJenkinsRule, String s) { + assertEquals(1, GlobalLibraries.get().getLibraries().size()); + final LibraryConfiguration library = GlobalLibraries.get().getLibraries().get(0); + assertEquals("jenkins-pipeline-lib", library.getName()); + final SCMSourceRetriever retriever = (SCMSourceRetriever) library.getRetriever(); + final GitHubSCMSource scm = (GitHubSCMSource) retriever.getScm(); + assertEquals("e43d6600-ba0e-46c5-8eae-3989bf654055", scm.getId()); + assertEquals("jenkins-infra", scm.getRepoOwner()); + assertEquals("pipeline-library", scm.getRepository()); + assertEquals(3, scm.getTraits().size()); + final BranchDiscoveryTrait branchDiscovery = (BranchDiscoveryTrait) scm.getTraits().get(0); + assertEquals(1, branchDiscovery.getStrategyId()); + final OriginPullRequestDiscoveryTrait prDiscovery = + (OriginPullRequestDiscoveryTrait) scm.getTraits().get(1); + assertEquals(2, prDiscovery.getStrategyId()); + final ForkPullRequestDiscoveryTrait forkDiscovery = + (ForkPullRequestDiscoveryTrait) scm.getTraits().get(2); + assertEquals(3, forkDiscovery.getStrategyId()); + assertThat( + forkDiscovery.getTrust(), instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)); + } - @Override - protected String stringInLogExpected() { - return "Setting class org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.repoOwner = jenkins-infra"; - } + @Override + protected String stringInLogExpected() { + return "Setting class org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.repoOwner = jenkins-infra"; + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationTest.java index 03f9c023c..0a353d76f 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubNotificationTest.java @@ -24,91 +24,98 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + import hudson.model.TaskListener; +import java.util.Collections; +import java.util.List; import jenkins.scm.api.SCMHeadObserver; import org.hamcrest.Matchers; import org.junit.Test; -import java.util.Collections; -import java.util.List; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; public class GitHubNotificationTest { - @Test - public void given__notificationsDisabled__when__appliedToContext__then__notificationsDisabled() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationsDisabled(), is(false)); - ctx.withNotificationsDisabled(true); - assertThat(ctx.notificationsDisabled(), is(true)); - ctx.withNotificationsDisabled(false); - assertThat(ctx.notificationsDisabled(), is(false)); - } + @Test + public void given__notificationsDisabled__when__appliedToContext__then__notificationsDisabled() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationsDisabled(), is(false)); + ctx.withNotificationsDisabled(true); + assertThat(ctx.notificationsDisabled(), is(true)); + ctx.withNotificationsDisabled(false); + assertThat(ctx.notificationsDisabled(), is(false)); + } - @Test - public void given__defaultNotificationStrategy__when__appliedToContext__then__duplicatesRemoved() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategy(new DefaultGitHubNotificationStrategy()); - assertThat(ctx.notificationStrategies().size(), is(1)); - } - - @Test - public void given__emptyStrategiesList__when__appliedToContext__then__defaultApplied() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategies(Collections.emptyList()); - assertThat(ctx.notificationStrategies().size(), is(1)); - } + @Test + public void given__defaultNotificationStrategy__when__appliedToContext__then__duplicatesRemoved() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategy(new DefaultGitHubNotificationStrategy()); + assertThat(ctx.notificationStrategies().size(), is(1)); + } - @Test - public void given__defaultStrategy__when__emptyStrategyList__then__strategyAdded() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategies(Collections.emptyList()); - assertThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategy(new DefaultGitHubNotificationStrategy()); - assertThat(ctx.notificationStrategies().size(), is(1)); - } + @Test + public void given__emptyStrategiesList__when__appliedToContext__then__defaultApplied() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategies(Collections.emptyList()); + assertThat(ctx.notificationStrategies().size(), is(1)); + } - @Test - public void given__defaultStrategyList__when__emptyStrategyList__then__strategyAdded() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategies(Collections.emptyList()); - assertThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategies(Collections.singletonList(new DefaultGitHubNotificationStrategy())); - assertThat(ctx.notificationStrategies().size(), is(1)); - } + @Test + public void given__defaultStrategy__when__emptyStrategyList__then__strategyAdded() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategies(Collections.emptyList()); + assertThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategy(new DefaultGitHubNotificationStrategy()); + assertThat(ctx.notificationStrategies().size(), is(1)); + } - @Test - public void given__customStrategy__when__emptyStrategyList__then__noDefaultStrategy() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.notificationStrategies().size(), is(1)); - ctx.withNotificationStrategy(new TestNotificationStrategy()); - List strategies = ctx.notificationStrategies(); - assertThat(strategies.size(), is(1)); - assertThat(strategies.get(0), Matchers.instanceOf(TestNotificationStrategy.class)); - } + @Test + public void given__defaultStrategyList__when__emptyStrategyList__then__strategyAdded() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategies(Collections.emptyList()); + assertThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategies( + Collections.singletonList(new DefaultGitHubNotificationStrategy())); + assertThat(ctx.notificationStrategies().size(), is(1)); + } - private final class TestNotificationStrategy extends AbstractGitHubNotificationStrategy { + @Test + public void given__customStrategy__when__emptyStrategyList__then__noDefaultStrategy() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.notificationStrategies().size(), is(1)); + ctx.withNotificationStrategy(new TestNotificationStrategy()); + List strategies = ctx.notificationStrategies(); + assertThat(strategies.size(), is(1)); + assertThat(strategies.get(0), Matchers.instanceOf(TestNotificationStrategy.class)); + } - @Override - public List notifications(GitHubNotificationContext notificationContext, TaskListener listener) { - return null; - } + private final class TestNotificationStrategy extends AbstractGitHubNotificationStrategy { - @Override - public boolean equals(Object o) { - return false; - } + @Override + public List notifications( + GitHubNotificationContext notificationContext, TaskListener listener) { + return null; + } - @Override - public int hashCode() { - return 0; - } + @Override + public boolean equals(Object o) { + return false; } + @Override + public int hashCode() { + return 0; + } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHookTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHookTest.java index bc82113eb..fcf548fad 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHookTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubOrgWebHookTest.java @@ -26,6 +26,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; + import com.github.tomakehurst.wiremock.junit.WireMockRule; import org.junit.Rule; import org.junit.Test; @@ -35,35 +36,44 @@ public class GitHubOrgWebHookTest { - @Rule public JenkinsRule r = new JenkinsRule(); - @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); - - @Issue("JENKINS-58942") - @Test public void registerCustom() throws Exception { - System.setProperty("jenkins.hook.url", "https://mycorp/hook-proxy/"); - //Return 404 for /rate_limit - wireMockRule.stubFor(get(urlEqualTo("/api/rate_limit")) - .willReturn(aResponse() - .withStatus(404) - )); + @Rule public JenkinsRule r = new JenkinsRule(); + @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); - // validate api url - wireMockRule.stubFor(get(urlEqualTo("/api/")) - .willReturn(aResponse() - .withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}") - )); + @Issue("JENKINS-58942") + @Test + public void registerCustom() throws Exception { + System.setProperty("jenkins.hook.url", "https://mycorp/hook-proxy/"); + // Return 404 for /rate_limit + wireMockRule.stubFor( + get(urlEqualTo("/api/rate_limit")).willReturn(aResponse().withStatus(404))); + // validate api url + wireMockRule.stubFor( + get(urlEqualTo("/api/")) + .willReturn( + aResponse().withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}"))); - wireMockRule.stubFor(get(urlEqualTo("/api/users/myorg")).willReturn(aResponse().withBody("{\"login\":\"myorg\"}"))); - wireMockRule.stubFor(get(urlEqualTo("/api/orgs/myorg")).willReturn(aResponse().withBody("{\"login\":\"myorg\",\"html_url\":\"https://github.com/myorg\"}"))); - wireMockRule.stubFor(get(urlEqualTo("/api/orgs/myorg/hooks")).willReturn(aResponse().withBody("[]"))); - wireMockRule.stubFor(post(urlEqualTo("/api/orgs/myorg/hooks")).withRequestBody(matchingJsonPath("$.config.url", equalTo("https://mycorp/hook-proxy/github-webhook/"))).willReturn(aResponse().withBody("{}"))); - GitHub hub = Connector.connect("http://localhost:" + wireMockRule.port() + "/api/", null); - try { - GitHubOrgWebHook.register(hub, "myorg"); - } finally { - Connector.release(hub); - } + wireMockRule.stubFor( + get(urlEqualTo("/api/users/myorg")) + .willReturn(aResponse().withBody("{\"login\":\"myorg\"}"))); + wireMockRule.stubFor( + get(urlEqualTo("/api/orgs/myorg")) + .willReturn( + aResponse() + .withBody("{\"login\":\"myorg\",\"html_url\":\"https://github.com/myorg\"}"))); + wireMockRule.stubFor( + get(urlEqualTo("/api/orgs/myorg/hooks")).willReturn(aResponse().withBody("[]"))); + wireMockRule.stubFor( + post(urlEqualTo("/api/orgs/myorg/hooks")) + .withRequestBody( + matchingJsonPath( + "$.config.url", equalTo("https://mycorp/hook-proxy/github-webhook/"))) + .willReturn(aResponse().withBody("{}"))); + GitHub hub = Connector.connect("http://localhost:" + wireMockRule.port() + "/api/", null); + try { + GitHubOrgWebHook.register(hub, "myorg"); + } finally { + Connector.release(hub); } - + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java index 2d7d2f6b6..5111ef3a9 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubRepoSizeEstimatorTest.java @@ -1,10 +1,17 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + import com.cloudbees.plugins.credentials.*; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import hudson.model.Item; +import java.util.Collections; import jenkins.model.Jenkins; import jenkins.plugins.git.GitToolChooser; import org.junit.Before; @@ -15,65 +22,60 @@ import org.jvnet.hudson.test.JenkinsRule; import org.mockito.Mockito; -import java.util.Collections; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - @RunWith(Parameterized.class) public class GitHubRepoSizeEstimatorTest extends GitSCMSourceBase { - public GitHubRepoSizeEstimatorTest(GitHubSCMSource source) { - this.source = source; - } + public GitHubRepoSizeEstimatorTest(GitHubSCMSource source) { + this.source = source; + } - @Parameterized.Parameters(name = "{index}: revision={0}") - public static GitHubSCMSource[] revisions() { - return new GitHubSCMSource[]{ - new GitHubSCMSource("cloudbeers", "yolo", null, false), - new GitHubSCMSource("", "", "https://github.com/cloudbeers/yolo", true) - }; - } + @Parameterized.Parameters(name = "{index}: revision={0}") + public static GitHubSCMSource[] revisions() { + return new GitHubSCMSource[] { + new GitHubSCMSource("cloudbeers", "yolo", null, false), + new GitHubSCMSource("", "", "https://github.com/cloudbeers/yolo", true) + }; + } - @Rule - public final JenkinsRule j = new JenkinsRule(); + @Rule public final JenkinsRule j = new JenkinsRule(); - private CredentialsStore store = null; + private CredentialsStore store = null; - @Before - public void enableSystemCredentialsProvider() { - SystemCredentialsProvider.getInstance().setDomainCredentialsMap( - Collections.singletonMap(Domain.global(), Collections.emptyList())); - for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.get())) { - if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) { - store = s; - break; - } - } - assertThat("The system credentials provider is enabled", store, notNullValue()); + @Before + public void enableSystemCredentialsProvider() { + SystemCredentialsProvider.getInstance() + .setDomainCredentialsMap( + Collections.singletonMap(Domain.global(), Collections.emptyList())); + for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.get())) { + if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) { + store = s; + break; + } } + assertThat("The system credentials provider is enabled", store, notNullValue()); + } - @Test - public void isApplicableToTest() throws Exception { - GitHubRepoSizeEstimator.RepositorySizeGithubAPI api = j.jenkins.getExtensionList(GitToolChooser.RepositorySizeAPI.class).get(GitHubRepoSizeEstimator.RepositorySizeGithubAPI.class); - Item context = Mockito.mock(Item.class); - store.addCredentials(Domain.global(), createCredential(CredentialsScope.GLOBAL, "github")); - store.save(); + @Test + public void isApplicableToTest() throws Exception { + GitHubRepoSizeEstimator.RepositorySizeGithubAPI api = + j.jenkins + .getExtensionList(GitToolChooser.RepositorySizeAPI.class) + .get(GitHubRepoSizeEstimator.RepositorySizeGithubAPI.class); + Item context = Mockito.mock(Item.class); + store.addCredentials(Domain.global(), createCredential(CredentialsScope.GLOBAL, "github")); + store.save(); - githubApi.stubFor( - get(urlEqualTo("/")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../__files/body-(root)-XwEI7.json"))); + githubApi.stubFor( + get(urlEqualTo("/")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../__files/body-(root)-XwEI7.json"))); - assertThat(api.isApplicableTo(githubApi.baseUrl(), context, "github"), is(false)); - } + assertThat(api.isApplicableTo(githubApi.baseUrl(), context, "github"), is(false)); + } - private StandardCredentials createCredential(CredentialsScope scope, String id) { - return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, "username", "password"); - } + private StandardCredentials createCredential(CredentialsScope scope, String id) { + return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, "username", "password"); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java index 05a9537ba..0c7d572aa 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMBuilderTest.java @@ -1,5 +1,19 @@ package org.jenkinsci.plugins.github_branch_source; +import static java.util.logging.Level.FINEST; +import static java.util.logging.Logger.getAnonymousLogger; +import static jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; +import static jenkins.plugins.git.AbstractGitSCMSource.SpecificRevisionBuildChooser; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsScope; @@ -36,2347 +50,2907 @@ import org.jvnet.hudson.test.JenkinsRule; import org.mockito.Mockito; -import static java.util.logging.Level.FINEST; -import static java.util.logging.Logger.getAnonymousLogger; -import static jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; -import static jenkins.plugins.git.AbstractGitSCMSource.SpecificRevisionBuildChooser; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - @RunWith(Parameterized.class) public class GitHubSCMBuilderTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); - private GitHubSCMSource source; - private WorkflowMultiBranchProject owner; - private boolean configuredByUrl; - - @Parameterized.Parameters - public static Collection generateParams() { - return Arrays.asList(new Object[]{true}, new Object[]{false}); - } - - public GitHubSCMBuilderTest(boolean configuredByUrl){ - this.configuredByUrl = configuredByUrl; - } - - public void createGitHubSCMSourceForTest(boolean configuredByUrl, String repoUrlToConfigure) throws Exception { - if (configuredByUrl) { - // Throw an exception if we don't supply a URL - if (repoUrlToConfigure.isEmpty()) { - throw new Exception("Must supply a URL when testing single-URL configured jobs"); - } - source = new GitHubSCMSource("", "", repoUrlToConfigure, true); - } else { - source = new GitHubSCMSource("tester", "test-repo", null, false); - } - source.setOwner(owner); - } - - @Before - public void setUp() throws IOException { - owner = j.createProject(WorkflowMultiBranchProject.class); - Credentials userPasswordCredential = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "user-pass", null, "git-user", "git-secret"); - Credentials sshPrivateKeyCredential = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "user-key", "git", - new BasicSSHUserPrivateKey.UsersPrivateKeySource(), null, null); - SystemCredentialsProvider.getInstance().setDomainCredentialsMap(Collections.singletonMap(Domain.global(), - Arrays.asList(userPasswordCredential, sshPrivateKeyCredential))); - } - - @After - public void tearDown() throws IOException, InterruptedException { - SystemCredentialsProvider.getInstance().setDomainCredentialsMap(Collections.emptyMap()); - owner.delete(); - } - - @Test - public void given__cloud_branch_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_anon_sshtrait_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userpass_sshtrait_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userkey_sshtrait_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_anon_sshtrait_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userpass_sshtrait_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_rev_userkey_sshtrait_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - SCMRevisionImpl revision = - new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); - sshTrait.decorateBuilder(instance); - - GitSCM actual = instance.build(); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); - SpecificRevisionBuildChooser revChooser = - (SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", mock(GitClient.class), new LogTaskListener( - getAnonymousLogger(), FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_branch_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_branch_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_branch_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_branch_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - AbstractGitSCMSource.SCMRevisionImpl revision = - new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + @ClassRule public static JenkinsRule j = new JenkinsRule(); + private GitHubSCMSource source; + private WorkflowMultiBranchProject owner; + private boolean configuredByUrl; + + @Parameterized.Parameters + public static Collection generateParams() { + return Arrays.asList(new Object[] {true}, new Object[] {false}); + } + + public GitHubSCMBuilderTest(boolean configuredByUrl) { + this.configuredByUrl = configuredByUrl; + } + + public void createGitHubSCMSourceForTest(boolean configuredByUrl, String repoUrlToConfigure) + throws Exception { + if (configuredByUrl) { + // Throw an exception if we don't supply a URL + if (repoUrlToConfigure.isEmpty()) { + throw new Exception("Must supply a URL when testing single-URL configured jobs"); + } + source = new GitHubSCMSource("", "", repoUrlToConfigure, true); + } else { + source = new GitHubSCMSource("tester", "test-repo", null, false); } - - - @Test - public void given__server_branch_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - AbstractGitSCMSource.SCMRevisionImpl revision = - new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__server_branch_rev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - AbstractGitSCMSource.SCMRevisionImpl revision = - new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__server_branch_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_branch_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_branch_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - BranchSCMHead head = new BranchSCMHead("test-branch"); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_pullHead_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_pullHead_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_pullHead_rev_userkey__when__build__then__scmBuilt() throws Exception { - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - createGitHubSCMSourceForTest(false,null); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__cloud_pullHead_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_pullHead_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_pullHead_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_pullHead_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - - @Test - public void given__server_pullHead_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__server_pullHead_rev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "qa-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - } - - @Test - public void given__server_pullHead_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_pullHead_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__server_pullHead_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.HEAD); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(1)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); - } - - @Test - public void given__cloud_pullMerge_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + source.setOwner(owner); + } + + @Before + public void setUp() throws IOException { + owner = j.createProject(WorkflowMultiBranchProject.class); + Credentials userPasswordCredential = + new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, "user-pass", null, "git-user", "git-secret"); + Credentials sshPrivateKeyCredential = + new BasicSSHUserPrivateKey( + CredentialsScope.GLOBAL, + "user-key", + "git", + new BasicSSHUserPrivateKey.UsersPrivateKeySource(), + null, + null); + SystemCredentialsProvider.getInstance() + .setDomainCredentialsMap( + Collections.singletonMap( + Domain.global(), Arrays.asList(userPasswordCredential, sshPrivateKeyCredential))); + } + + @After + public void tearDown() throws IOException, InterruptedException { + SystemCredentialsProvider.getInstance().setDomainCredentialsMap(Collections.emptyMap()); + owner.delete(); + } + + @Test + public void given__cloud_branch_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_anon_sshtrait_anon__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userpass_sshtrait_anon__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userkey_sshtrait_anon__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait(null); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_anon_sshtrait_userkey__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userpass_sshtrait_userkey__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_rev_userkey_sshtrait_userkey__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + SCMRevisionImpl revision = + new SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + SSHCheckoutTrait sshTrait = new SSHCheckoutTrait("user-key"); + sshTrait.decorateBuilder(instance); + + GitSCM actual = instance.build(); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser.getBuildChooser(), instanceOf(SpecificRevisionBuildChooser.class)); + SpecificRevisionBuildChooser revChooser = + (SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + mock(GitClient.class), + new LogTaskListener(getAnonymousLogger(), FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_branch_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_branch_norev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_branch_norev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_branch_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + AbstractGitSCMSource.SCMRevisionImpl revision = + new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(BuildChooserSetting.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_branch_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + AbstractGitSCMSource.SCMRevisionImpl revision = + new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(BuildChooserSetting.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_branch_rev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + AbstractGitSCMSource.SCMRevisionImpl revision = + new AbstractGitSCMSource.SCMRevisionImpl(head, "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(BuildChooserSetting.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_branch_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_branch_norev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_branch_norev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + BranchSCMHead head = new BranchSCMHead("test-branch"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat( + instance.refSpecs(), + contains("+refs/heads/test-branch:refs/remotes/@{remote}/test-branch")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/tester/test-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/tester/test-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/heads/test-branch:refs/remotes/origin/test-branch")); + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_pullHead_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_pullHead_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_pullHead_rev_userkey__when__build__then__scmBuilt() throws Exception { + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + createGitHubSCMSourceForTest(false, null); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__cloud_pullHead_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_pullHead_norev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_pullHead_norev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_pullHead_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_pullHead_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_pullHead_rev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(BuildChooserSetting.class), instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "qa-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + } + + @Test + public void given__server_pullHead_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_pullHead_norev_userpass__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__server_pullHead_norev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.HEAD); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1")); + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(1)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(actual.getExtensions(), contains(instanceOf(GitSCMSourceDefaults.class))); + } + + @Test + public void given__cloud_pullMerge_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - @Test - public void given__cloud_pullMerge_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), + instanceOf(BuildChooserSetting.class), + instanceOf(MergeWithGitSCMExtension.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__cloud_pullMerge_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - @Test - public void given__cloud_pullMerge_rev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), + instanceOf(BuildChooserSetting.class), + instanceOf(MergeWithGitSCMExtension.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__cloud_pullMerge_rev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(BuildChooserSetting.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - @Test - public void given__cloud_pullMerge_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), + instanceOf(BuildChooserSetting.class), + instanceOf(MergeWithGitSCMExtension.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__cloud_pullMerge_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - @Test - public void given__cloud_pullMerge_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(MergeWithGitSCMExtension.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + @Test + public void given__cloud_pullMerge_norev_userpass__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.com/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - @Test - public void given__cloud_pullMerge_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(false, null); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.com/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.com/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.com/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(MergeWithGitSCMExtension.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + @Test + public void given__cloud_pullMerge_norev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(false, null); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.com/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.com:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.com/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(GitSCMSourceDefaults.class), - instanceOf(MergeWithGitSCMExtension.class) - )); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - @Test - public void given__server_pullMerge_rev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("git@github.com:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.com:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(GitSCMSourceDefaults.class), instanceOf(MergeWithGitSCMExtension.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + @Test + public void given__server_pullMerge_rev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - - @Test - public void given__server_pullMerge_rev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), + instanceOf(BuildChooserSetting.class), + instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__server_pullMerge_rev_userpass__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - @Test - public void given__server_pullMerge_rev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - PullRequestSCMRevision revision = new PullRequestSCMRevision( - head, - "deadbeefcafebabedeadbeefcafebabedeadbeef", - "cafebabedeadbeefcafebabedeadbeefcafebabe" - ); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(revision)); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), + instanceOf(BuildChooserSetting.class), + instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__server_pullMerge_rev_userkey__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + PullRequestSCMRevision revision = + new PullRequestSCMRevision( + head, + "deadbeefcafebabedeadbeefcafebabedeadbeef", + "cafebabedeadbeefcafebabedeadbeefcafebabe"); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, revision); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(revision)); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(BuildChooserSetting.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); - assertThat(chooser, notNullValue()); - assertThat(chooser.getBuildChooser(), instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); - AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = - (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); - Collection revisions = revChooser - .getCandidateRevisions(false, "test-branch", Mockito.mock(GitClient.class), new LogTaskListener( - Logger.getAnonymousLogger(), Level.FINEST), null, null); - assertThat(revisions, hasSize(1)); - assertThat(revisions.iterator().next().getSha1String(), is("cafebabedeadbeefcafebabedeadbeefcafebabe")); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); - } - - @Test - public void given__server_pullMerge_norev_anon__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId(null); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is(nullValue())); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), + instanceOf(BuildChooserSetting.class), + instanceOf(GitSCMSourceDefaults.class))); + BuildChooserSetting chooser = getExtension(actual, BuildChooserSetting.class); + assertThat(chooser, notNullValue()); + assertThat( + chooser.getBuildChooser(), + instanceOf(AbstractGitSCMSource.SpecificRevisionBuildChooser.class)); + AbstractGitSCMSource.SpecificRevisionBuildChooser revChooser = + (AbstractGitSCMSource.SpecificRevisionBuildChooser) chooser.getBuildChooser(); + Collection revisions = + revChooser.getCandidateRevisions( + false, + "test-branch", + Mockito.mock(GitClient.class), + new LogTaskListener(Logger.getAnonymousLogger(), Level.FINEST), + null, + null); + assertThat(revisions, hasSize(1)); + assertThat( + revisions.iterator().next().getSha1String(), + is("cafebabedeadbeefcafebabedeadbeefcafebabe")); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is("deadbeefcafebabedeadbeefcafebabedeadbeef")); + } + + @Test + public void given__server_pullMerge_norev_anon__when__build__then__scmBuilt() throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId(null); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is(nullValue())); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is(nullValue())); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - @Test - public void given__server_pullMerge_norev_userpass__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId("user-pass"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-pass")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is(nullValue())); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), instanceOf(GitSCMSourceDefaults.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + @Test + public void given__server_pullMerge_norev_userpass__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId("user-pass"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-pass")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("https://github.test/tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-pass")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - @Test - public void given__server_pullMerge_norev_userkey__when__build__then__scmBuilt() throws Exception { - createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); - PullRequestSCMHead head = new PullRequestSCMHead("PR-1", "qa", "qa-repo", "qa-branch", 1, - new BranchSCMHead("test-branch"), new SCMHeadOrigin.Fork("qa/qa-repo"), - ChangeRequestCheckoutStrategy.MERGE); - source.setCredentialsId("user-key"); - GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); - assertThat(instance.credentialsId(), is("user-key")); - assertThat(instance.head(), is(head)); - assertThat(instance.revision(), is(nullValue())); - assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); - assertThat("expecting guess value until withGitHubRemote called", - instance.remote(), is("https://github.test/tester/test-repo.git")); - assertThat(instance.browser(), instanceOf(GithubWeb.class)); - assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - - instance.withGitHubRemote(); - assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); - - GitSCM actual = instance.build(); - assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); - assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); - assertThat(actual.getGitTool(), nullValue()); - assertThat(actual.getUserRemoteConfigs(), hasSize(1)); - UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); - assertThat(config.getName(), is("origin")); - assertThat(config.getRefspec(), is("+refs/pull/1/head:refs/remotes/origin/PR-1 " + assertThat(config.getUrl(), is("https://github.test/tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-pass")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("https://github.test/tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), instanceOf(GitSCMSourceDefaults.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + @Test + public void given__server_pullMerge_norev_userkey__when__build__then__scmBuilt() + throws Exception { + createGitHubSCMSourceForTest(true, "https://github.test/tester/test-repo.git"); + PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-1", + "qa", + "qa-repo", + "qa-branch", + 1, + new BranchSCMHead("test-branch"), + new SCMHeadOrigin.Fork("qa/qa-repo"), + ChangeRequestCheckoutStrategy.MERGE); + source.setCredentialsId("user-key"); + GitHubSCMBuilder instance = new GitHubSCMBuilder(source, head, null); + assertThat(instance.credentialsId(), is("user-key")); + assertThat(instance.head(), is(head)); + assertThat(instance.revision(), is(nullValue())); + assertThat(instance.refSpecs(), contains("+refs/pull/1/head:refs/remotes/@{remote}/PR-1")); + assertThat( + "expecting guess value until withGitHubRemote called", + instance.remote(), + is("https://github.test/tester/test-repo.git")); + assertThat(instance.browser(), instanceOf(GithubWeb.class)); + assertThat(instance.browser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + + instance.withGitHubRemote(); + assertThat(instance.remote(), is("git@github.test:tester/test-repo.git")); + + GitSCM actual = instance.build(); + assertThat(actual.getBrowser(), instanceOf(GithubWeb.class)); + assertThat(actual.getBrowser().getRepoUrl(), is("https://github.test/qa/qa-repo")); + assertThat(actual.getGitTool(), nullValue()); + assertThat(actual.getUserRemoteConfigs(), hasSize(1)); + UserRemoteConfig config = actual.getUserRemoteConfigs().get(0); + assertThat(config.getName(), is("origin")); + assertThat( + config.getRefspec(), + is( + "+refs/pull/1/head:refs/remotes/origin/PR-1 " + "+refs/heads/test-branch:refs/remotes/origin/test-branch")); - assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); - assertThat(config.getCredentialsId(), is("user-key")); - RemoteConfig origin = actual.getRepositoryByName("origin"); - assertThat(origin, notNullValue()); - assertThat(origin.getURIs(), hasSize(1)); - assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); - assertThat(origin.getFetchRefSpecs(), hasSize(2)); - assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); - assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); - assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); - assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); - assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); - assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); - assertThat(actual.getExtensions(), containsInAnyOrder( - instanceOf(MergeWithGitSCMExtension.class), - instanceOf(GitSCMSourceDefaults.class)) - ); - MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); - assertThat(merge, notNullValue()); - assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); - assertThat(merge.getBaseHash(), is(nullValue())); - } - - private static T getExtension(GitSCM scm, Class type) { - for (GitSCMExtension e : scm.getExtensions()) { - if (type.isInstance(e)) { - return type.cast(e); - } - } - return null; + assertThat(config.getUrl(), is("git@github.test:tester/test-repo.git")); + assertThat(config.getCredentialsId(), is("user-key")); + RemoteConfig origin = actual.getRepositoryByName("origin"); + assertThat(origin, notNullValue()); + assertThat(origin.getURIs(), hasSize(1)); + assertThat(origin.getURIs().get(0).toString(), is("git@github.test:tester/test-repo.git")); + assertThat(origin.getFetchRefSpecs(), hasSize(2)); + assertThat(origin.getFetchRefSpecs().get(0).getSource(), is("refs/pull/1/head")); + assertThat(origin.getFetchRefSpecs().get(0).getDestination(), is("refs/remotes/origin/PR-1")); + assertThat(origin.getFetchRefSpecs().get(0).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(0).isWildcard(), is(false)); + assertThat(origin.getFetchRefSpecs().get(1).getSource(), is("refs/heads/test-branch")); + assertThat( + origin.getFetchRefSpecs().get(1).getDestination(), is("refs/remotes/origin/test-branch")); + assertThat(origin.getFetchRefSpecs().get(1).isForceUpdate(), is(true)); + assertThat(origin.getFetchRefSpecs().get(1).isWildcard(), is(false)); + assertThat( + actual.getExtensions(), + containsInAnyOrder( + instanceOf(MergeWithGitSCMExtension.class), instanceOf(GitSCMSourceDefaults.class))); + MergeWithGitSCMExtension merge = getExtension(actual, MergeWithGitSCMExtension.class); + assertThat(merge, notNullValue()); + assertThat(merge.getBaseName(), is("remotes/origin/test-branch")); + assertThat(merge.getBaseHash(), is(nullValue())); + } + + private static T getExtension(GitSCM scm, Class type) { + for (GitSCMExtension e : scm.getExtensions()) { + if (type.isInstance(e)) { + return type.cast(e); + } } + return null; + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystemTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystemTest.java index 745a37a91..d7bfcd5b2 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystemTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystemTest.java @@ -25,6 +25,17 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assume.assumeThat; + import hudson.AbortException; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.scm.api.SCMFile; @@ -39,211 +50,231 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.core.IsNull.nullValue; -import static org.junit.Assume.assumeThat; - @RunWith(Parameterized.class) public class GitHubSCMFileSystemTest extends AbstractGitHubWireMockTest { - public static SCMHead master = new BranchSCMHead("master"); - private final SCMRevision revision; - - public static PullRequestSCMHead prHead = new PullRequestSCMHead("PR-2", "stephenc", "yolo", "master", 2, (BranchSCMHead) master, - SCMHeadOrigin.Fork.DEFAULT, ChangeRequestCheckoutStrategy.HEAD); - public static PullRequestSCMRevision prHeadRevision = new PullRequestSCMRevision( - prHead, - "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1"); - - - public static PullRequestSCMHead prMerge = new PullRequestSCMHead("PR-2", "stephenc", "yolo", "master", 2, (BranchSCMHead) master, - SCMHeadOrigin.Fork.DEFAULT, ChangeRequestCheckoutStrategy.MERGE); - public static PullRequestSCMRevision prMergeRevision = new PullRequestSCMRevision( - prMerge, - "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1", - "38814ca33833ff5583624c29f305be9133f27a40"); - - public static PullRequestSCMRevision prMergeInvalidRevision = new PullRequestSCMRevision( - prMerge, - "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1", - null); - - public static PullRequestSCMRevision prMergeNotMergeableRevision = new PullRequestSCMRevision( - prMerge, - "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH); - - private GitHubSCMSource source; - - public GitHubSCMFileSystemTest(String revision) { - this.revision = revision == null ? null : new AbstractGitSCMSource.SCMRevisionImpl(master, revision); - } - - @Parameterized.Parameters(name = "{index}: revision={0}") - public static String[] revisions() { - return new String[]{ - "c0e024f89969b976da165eecaa71e09dc60c3da1", // Pull Request #2, unmerged but exposed on target repo - "e301dc6d5bb7e6e18d80e85f19caa92c74e15e96", - null - }; - } - - @Before - @Override - public void prepareMockGitHub() { - super.prepareMockGitHub(); - source = new GitHubSCMSource(null, "http://localhost:" + githubApi.port(), GitHubSCMSource.DescriptorImpl.SAME, null, "cloudbeers", "yolo"); - } - - @Override - void prepareMockGitHubFileMappings() { - super.prepareMockGitHubFileMappings(); - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-mergeable-true.json"))); - - } - - @Test - public void haveFilesystem() throws Exception { - assertThat(SCMFileSystem.of(source, master, revision), notNullValue()); - } - - @Test - public void rootIsADirectory() throws Exception { - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().getType(), is(SCMFile.Type.DIRECTORY)); - - } - - @Test - public void listFilesInRoot() throws Exception { - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().children(), hasItem(Matchers.hasProperty("name", is("README.md")))); - } - - @Test - public void readmeIsAFile() throws Exception { - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().child("README.md").getType(), is(SCMFile.Type.REGULAR_FILE)); - } - - @Test - public void readmeContents() throws Exception { - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().child("README.md").contentAsString(), containsString("yolo")); - } - - @Test - public void readFileFromDir() throws Exception { - assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); - assumeThat(((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), - is("c0e024f89969b976da165eecaa71e09dc60c3da1")); - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - - String expected = "Some text\n"; - // In previous versions of github-api, GHContent.read() (called by contentAsString()) - // would pull from the "raw" url of the GHContent instance. - // Thus on windows, if somebody did not configure Git correctly, - // the checkout may have "fixed" line endings that we needed to handle. - // The problem with the raw url data is that it can get out of sync when from the actual content. - // The GitHub API info stays sync'd and correct, so now GHContent.read() pulls from mime encoded data - // in the GHContent record itself. Keeping this for reference in case it changes again. -// try (InputStream inputStream = getClass().getResourceAsStream("/raw/__files/body-fu-bar.txt-b4k4I.txt")) { -// if (inputStream != null) { -// expected = IOUtils.toString(inputStream, StandardCharsets.US_ASCII); -// } -// } catch (IOException e) { -// // ignore -// } - assertThat(fs.getRoot().child("fu/bar.txt").contentAsString(), is(expected)); - } - - @Test - public void resolveDir() throws Exception { - assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); - assumeThat(((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), - is("c0e024f89969b976da165eecaa71e09dc60c3da1")); - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); - } - - @Test - public void listDir() throws Exception { - assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); - assumeThat(((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), - is("c0e024f89969b976da165eecaa71e09dc60c3da1")); - SCMFileSystem fs = SCMFileSystem.of(source, master, revision); - assertThat(fs.getRoot().child("fu").children(), - hasItem(Matchers.hasProperty("name", is("manchu.txt")))); - } - - @Test - public void resolveDirPRHead() throws Exception { - assumeThat(revision, nullValue()); - - assertThat(prHeadRevision.isMerge(), is(false)); - - SCMFileSystem fs = SCMFileSystem.of(source, prHead, prHeadRevision); - assertThat(fs, instanceOf(GitHubSCMFileSystem.class)); - - // We can't check the sha, but we can check last modified - // which are different for head or merge - assertThat(((GitHubSCMFileSystem)fs).lastModified(), - is(1480691047000L)); - - assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); - } - - @Test - public void resolveDirPRMerge() throws Exception { - assumeThat(revision, nullValue()); - - assertThat(prMergeRevision.isMerge(), is(true)); - - SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeRevision); - assertThat(fs, instanceOf(GitHubSCMFileSystem.class)); - - // We can't check the sha, but we can check last modified - // which are different for head or merge - assertThat(((GitHubSCMFileSystem)fs).lastModified(), - is(1480777447000L)); - - assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); - } - - @Test - public void resolveDirPRInvalidMerge() throws Exception { - assumeThat(revision, nullValue()); - - assertThat(prMergeInvalidRevision.isMerge(), is(true)); - - SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeInvalidRevision); - assertThat(fs, nullValue()); - } - - @Test(expected = AbortException.class) - public void resolveDirPRNotMergeable() throws Exception { - assumeThat(revision, nullValue()); - - assertThat(prMergeNotMergeableRevision.isMerge(), is(true)); - - SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeNotMergeableRevision); - fail(); - } - + public static SCMHead master = new BranchSCMHead("master"); + private final SCMRevision revision; + + public static PullRequestSCMHead prHead = + new PullRequestSCMHead( + "PR-2", + "stephenc", + "yolo", + "master", + 2, + (BranchSCMHead) master, + SCMHeadOrigin.Fork.DEFAULT, + ChangeRequestCheckoutStrategy.HEAD); + public static PullRequestSCMRevision prHeadRevision = + new PullRequestSCMRevision( + prHead, + "8f1314fc3c8284d8c6d5886d473db98f2126071c", + "c0e024f89969b976da165eecaa71e09dc60c3da1"); + + public static PullRequestSCMHead prMerge = + new PullRequestSCMHead( + "PR-2", + "stephenc", + "yolo", + "master", + 2, + (BranchSCMHead) master, + SCMHeadOrigin.Fork.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE); + public static PullRequestSCMRevision prMergeRevision = + new PullRequestSCMRevision( + prMerge, + "8f1314fc3c8284d8c6d5886d473db98f2126071c", + "c0e024f89969b976da165eecaa71e09dc60c3da1", + "38814ca33833ff5583624c29f305be9133f27a40"); + + public static PullRequestSCMRevision prMergeInvalidRevision = + new PullRequestSCMRevision( + prMerge, + "8f1314fc3c8284d8c6d5886d473db98f2126071c", + "c0e024f89969b976da165eecaa71e09dc60c3da1", + null); + + public static PullRequestSCMRevision prMergeNotMergeableRevision = + new PullRequestSCMRevision( + prMerge, + "8f1314fc3c8284d8c6d5886d473db98f2126071c", + "c0e024f89969b976da165eecaa71e09dc60c3da1", + PullRequestSCMRevision.NOT_MERGEABLE_HASH); + + private GitHubSCMSource source; + + public GitHubSCMFileSystemTest(String revision) { + this.revision = + revision == null ? null : new AbstractGitSCMSource.SCMRevisionImpl(master, revision); + } + + @Parameterized.Parameters(name = "{index}: revision={0}") + public static String[] revisions() { + return new String[] { + "c0e024f89969b976da165eecaa71e09dc60c3da1", // Pull Request #2, unmerged but exposed on target + // repo + "e301dc6d5bb7e6e18d80e85f19caa92c74e15e96", + null + }; + } + + @Before + @Override + public void prepareMockGitHub() { + super.prepareMockGitHub(); + source = + new GitHubSCMSource( + null, + "http://localhost:" + githubApi.port(), + GitHubSCMSource.DescriptorImpl.SAME, + null, + "cloudbeers", + "yolo"); + } + + @Override + void prepareMockGitHubFileMappings() { + super.prepareMockGitHubFileMappings(); + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-mergeable-true.json"))); + } + + @Test + public void haveFilesystem() throws Exception { + assertThat(SCMFileSystem.of(source, master, revision), notNullValue()); + } + + @Test + public void rootIsADirectory() throws Exception { + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat(fs.getRoot().getType(), is(SCMFile.Type.DIRECTORY)); + } + + @Test + public void listFilesInRoot() throws Exception { + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat( + fs.getRoot().children(), hasItem(Matchers.hasProperty("name", is("README.md")))); + } + + @Test + public void readmeIsAFile() throws Exception { + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat(fs.getRoot().child("README.md").getType(), is(SCMFile.Type.REGULAR_FILE)); + } + + @Test + public void readmeContents() throws Exception { + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat(fs.getRoot().child("README.md").contentAsString(), containsString("yolo")); + } + + @Test + public void readFileFromDir() throws Exception { + assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); + assumeThat( + ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), + is("c0e024f89969b976da165eecaa71e09dc60c3da1")); + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + + String expected = "Some text\n"; + // In previous versions of github-api, GHContent.read() (called by contentAsString()) + // would pull from the "raw" url of the GHContent instance. + // Thus on windows, if somebody did not configure Git correctly, + // the checkout may have "fixed" line endings that we needed to handle. + // The problem with the raw url data is that it can get out of sync when from the actual + // content. + // The GitHub API info stays sync'd and correct, so now GHContent.read() pulls from mime encoded + // data + // in the GHContent record itself. Keeping this for reference in case it changes again. + // try (InputStream inputStream = + // getClass().getResourceAsStream("/raw/__files/body-fu-bar.txt-b4k4I.txt")) { + // if (inputStream != null) { + // expected = IOUtils.toString(inputStream, StandardCharsets.US_ASCII); + // } + // } catch (IOException e) { + // // ignore + // } + assertThat(fs.getRoot().child("fu/bar.txt").contentAsString(), is(expected)); + } + + @Test + public void resolveDir() throws Exception { + assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); + assumeThat( + ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), + is("c0e024f89969b976da165eecaa71e09dc60c3da1")); + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); + } + + @Test + public void listDir() throws Exception { + assumeThat(revision, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class)); + assumeThat( + ((AbstractGitSCMSource.SCMRevisionImpl) revision).getHash(), + is("c0e024f89969b976da165eecaa71e09dc60c3da1")); + SCMFileSystem fs = SCMFileSystem.of(source, master, revision); + assertThat( + fs.getRoot().child("fu").children(), + hasItem(Matchers.hasProperty("name", is("manchu.txt")))); + } + + @Test + public void resolveDirPRHead() throws Exception { + assumeThat(revision, nullValue()); + + assertThat(prHeadRevision.isMerge(), is(false)); + + SCMFileSystem fs = SCMFileSystem.of(source, prHead, prHeadRevision); + assertThat(fs, instanceOf(GitHubSCMFileSystem.class)); + + // We can't check the sha, but we can check last modified + // which are different for head or merge + assertThat(((GitHubSCMFileSystem) fs).lastModified(), is(1480691047000L)); + + assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); + } + + @Test + public void resolveDirPRMerge() throws Exception { + assumeThat(revision, nullValue()); + + assertThat(prMergeRevision.isMerge(), is(true)); + + SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeRevision); + assertThat(fs, instanceOf(GitHubSCMFileSystem.class)); + + // We can't check the sha, but we can check last modified + // which are different for head or merge + assertThat(((GitHubSCMFileSystem) fs).lastModified(), is(1480777447000L)); + + assertThat(fs.getRoot().child("fu").getType(), is(SCMFile.Type.DIRECTORY)); + } + + @Test + public void resolveDirPRInvalidMerge() throws Exception { + assumeThat(revision, nullValue()); + + assertThat(prMergeInvalidRevision.isMerge(), is(true)); + + SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeInvalidRevision); + assertThat(fs, nullValue()); + } + + @Test(expected = AbortException.class) + public void resolveDirPRNotMergeable() throws Exception { + assumeThat(revision, nullValue()); + + assertThat(prMergeNotMergeableRevision.isMerge(), is(true)); + + SCMFileSystem fs = SCMFileSystem.of(source, prMerge, prMergeNotMergeableRevision); + fail(); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java index 5787ffdd8..a0347c4e7 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTest.java @@ -25,6 +25,8 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.*; + import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; @@ -65,398 +67,426 @@ import org.mockito.Mock; import org.mockito.Mockito; -import static org.hamcrest.Matchers.*; - public class GitHubSCMNavigatorTest extends AbstractGitHubWireMockTest { - @Mock - private SCMSourceOwner scmSourceOwner; - - private BaseStandardCredentials credentials = new UsernamePasswordCredentialsImpl( - CredentialsScope.GLOBAL, "authenticated-user", null, "git-user", "git-secret"); - - private GitHubSCMNavigator navigator; - - @Before - @Override - public void prepareMockGitHub() { - super.prepareMockGitHub(); - setCredentials(Collections.emptyList()); - navigator = navigatorForRepoOwner("cloudbeers", null); - } - - private GitHubSCMNavigator navigatorForRepoOwner(String repoOwner, @Nullable String credentialsId) { - GitHubSCMNavigator navigator = new GitHubSCMNavigator(repoOwner); - navigator.setApiUri("http://localhost:" + githubApi.port()); - navigator.setCredentialsId(credentialsId); - return navigator; - } - - private void setCredentials(List credentials) { - SystemCredentialsProvider.getInstance().setDomainCredentialsMap( - Collections.singletonMap(Domain.global(), credentials)); - } - - @Test - public void fetchSmokes() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); - - assertThat(projectNames, contains("yolo")); - } - - @Test - public void fetchReposWithoutTeamSlug() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "Hello-World", "github-branch-source-plugin", "unknown", "basic", "yolo", "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("basic", "yolo", "yolo-archived")); - } - - @Test - public void fetchReposFromTeamSlug() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - List>> traits = new ArrayList<>(navigator.getTraits()); - traits.add(new TeamSlugTrait("justice-league")); - navigator.setTraits(traits); - navigator.visitSources(SCMSourceObserver.filter(observer, "Hello-World", "github-branch-source-plugin", "unknown", "basic", "yolo", "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("Hello-World", "github-branch-source-plugin", "basic", "yolo-archived")); - } - - @Test - public void fetchOneRepoWithTeamSlug_InTeam() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - List>> traits = new ArrayList<>(navigator.getTraits()); - traits.add(new TeamSlugTrait("justice-league")); - navigator.setTraits(traits); - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + @Mock private SCMSourceOwner scmSourceOwner; + + private BaseStandardCredentials credentials = + new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, "authenticated-user", null, "git-user", "git-secret"); + + private GitHubSCMNavigator navigator; + + @Before + @Override + public void prepareMockGitHub() { + super.prepareMockGitHub(); + setCredentials(Collections.emptyList()); + navigator = navigatorForRepoOwner("cloudbeers", null); + } + + private GitHubSCMNavigator navigatorForRepoOwner( + String repoOwner, @Nullable String credentialsId) { + GitHubSCMNavigator navigator = new GitHubSCMNavigator(repoOwner); + navigator.setApiUri("http://localhost:" + githubApi.port()); + navigator.setCredentialsId(credentialsId); + return navigator; + } + + private void setCredentials(List credentials) { + SystemCredentialsProvider.getInstance() + .setDomainCredentialsMap(Collections.singletonMap(Domain.global(), credentials)); + } + + @Test + public void fetchSmokes() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); + + assertThat(projectNames, contains("yolo")); + } + + @Test + public void fetchReposWithoutTeamSlug() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources( + SCMSourceObserver.filter( + observer, + "Hello-World", + "github-branch-source-plugin", + "unknown", + "basic", + "yolo", + "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("basic", "yolo", "yolo-archived")); + } + + @Test + public void fetchReposFromTeamSlug() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + List>> traits = new ArrayList<>(navigator.getTraits()); + traits.add(new TeamSlugTrait("justice-league")); + navigator.setTraits(traits); + navigator.visitSources( + SCMSourceObserver.filter( + observer, + "Hello-World", + "github-branch-source-plugin", + "unknown", + "basic", + "yolo", + "yolo-archived")); + + assertThat( + projectNames, + containsInAnyOrder("Hello-World", "github-branch-source-plugin", "basic", "yolo-archived")); + } + + @Test + public void fetchOneRepoWithTeamSlug_InTeam() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + List>> traits = new ArrayList<>(navigator.getTraits()); + traits.add(new TeamSlugTrait("justice-league")); + navigator.setTraits(traits); + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("yolo-archived")); + } + + @Test + public void fetchOneRepoWithTeamSlug_NotInTeam() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + List>> traits = new ArrayList<>(navigator.getTraits()); + traits.add(new TeamSlugTrait("justice-league")); + navigator.setTraits(traits); + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); + + assertThat(projectNames, empty()); + } + + @Test + public void fetchOneRepo_BelongingToAuthenticatedUser() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("yolo-archived")); + } + + @Test + public void fetchRepos_BelongingToAuthenticatedUser_FilteredByTopic() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new TopicsTrait("awesome"))); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertEquals(projectNames, Collections.singleton("yolo")); + } + + @Test + public void fetchRepos_BelongingToAuthenticatedUser_FilteredByTopic_RemovesAll() + throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new TopicsTrait("nope"))); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertEquals(projectNames, Collections.emptySet()); + } + + @Test + public void fetchRepos_BelongingToAuthenticatedUser_FilteredByMultipleTopics() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new TopicsTrait("cool, great,was-awesome"))); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertEquals(projectNames, Collections.singleton("yolo-archived")); + } + + @Test + public void fetchOneRepo_BelongingToAuthenticatedUser_ExcludingArchived() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + + assertThat(projectNames, empty()); + } + + @Test + public void fetchOneRepo_ExcludingPublic() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new ExcludePublicRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-private")); + + assertThat(projectNames, containsInAnyOrder("yolo-private")); + } + + @Test + public void fetchOneRepo_BelongingToOrg() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - assertThat(projectNames, containsInAnyOrder( "yolo-archived")); - } - - @Test - public void fetchOneRepoWithTeamSlug_NotInTeam() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - List>> traits = new ArrayList<>(navigator.getTraits()); - traits.add(new TeamSlugTrait("justice-league")); - navigator.setTraits(traits); - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); - - assertThat(projectNames, empty()); - } - - - @Test - public void fetchOneRepo_BelongingToAuthenticatedUser() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("yolo-archived")); - } - - @Test - public void fetchRepos_BelongingToAuthenticatedUser_FilteredByTopic() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new TopicsTrait("awesome"))); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertEquals(projectNames, Collections.singleton("yolo")); - } - - @Test - public void fetchRepos_BelongingToAuthenticatedUser_FilteredByTopic_RemovesAll() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new TopicsTrait("nope"))); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("yolo-archived")); + } + + @Test + public void fetchOneRepo_BelongingToOrg_ExcludingArchived() throws Exception { + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - navigator.visitSources(observer); - - assertEquals(projectNames, Collections.emptySet()); - } + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - @Test - public void fetchRepos_BelongingToAuthenticatedUser_FilteredByMultipleTopics() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new TopicsTrait("cool, great,was-awesome"))); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertEquals(projectNames, Collections.singleton("yolo-archived")); - } + assertThat(projectNames, empty()); + } + + @Test + public void fetchOneRepo_BelongingToUser() throws Exception { + navigator = navigatorForRepoOwner("stephenc", null); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - @Test - public void fetchOneRepo_BelongingToAuthenticatedUser_ExcludingArchived() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - - assertThat(projectNames, empty()); - } - - @Test - public void fetchOneRepo_ExcludingPublic() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new ExcludePublicRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-private")); - - assertThat(projectNames, containsInAnyOrder("yolo-private")); - } + assertThat(projectNames, containsInAnyOrder("yolo-archived")); + } + + @Test + public void fetchOneRepo_BelongingToUser_ExcludingArchived() throws Exception { + navigator = navigatorForRepoOwner("stephenc", null); + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - @Test - public void fetchOneRepo_BelongingToOrg() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + assertThat(projectNames, empty()); + } - assertThat(projectNames, containsInAnyOrder("yolo-archived")); + @Test + public void fetchRepos_BelongingToAuthenticatedUser() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertThat(projectNames, containsInAnyOrder("yolo", "yolo-archived")); + } + + @Test + public void fetchRepos_BelongingToAuthenticatedUser_ExcludingArchived() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertThat(projectNames, containsInAnyOrder("yolo")); + } + + @Test + public void fetchRepos_BelongingToOrg() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources( + SCMSourceObserver.filter(observer, "unknown", "basic", "yolo", "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("basic", "yolo", "yolo-archived")); + } + + @Test + public void fetchRepos_BelongingToOrg_ExcludingArchived() throws Exception { + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources( + SCMSourceObserver.filter(observer, "unknown", "basic", "yolo", "yolo-archived")); + + assertThat(projectNames, containsInAnyOrder("basic", "yolo")); + } + + @Test + public void fetchRepos_BelongingToOrg_ExcludingPublic() throws Exception { + navigator.setTraits(Collections.singletonList(new ExcludePublicRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources( + SCMSourceObserver.filter( + observer, "Hello-World", "github-branch-source-plugin", "yolo-private")); + + assertThat(projectNames, containsInAnyOrder("yolo-private")); + } + + @Test + public void fetchRepos_BelongingToUser() throws Exception { + navigator = navigatorForRepoOwner("stephenc", null); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertThat(projectNames, containsInAnyOrder("yolo", "yolo-archived", "yolo-private")); + } + + @Test + public void fetchRepos_BelongingToUser_ExcludingArchived() throws Exception { + navigator = navigatorForRepoOwner("stephenc", null); + navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertThat(projectNames, containsInAnyOrder("yolo")); + } + + @Test + public void appliesFilters() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo", "rando-unknown")); + + assertEquals(projectNames, Collections.singleton("yolo")); + } + + @Test + public void fetchActions() throws Exception { + assertThat( + navigator.fetchActions(Mockito.mock(SCMNavigatorOwner.class), null, null), + Matchers.containsInAnyOrder( + Matchers.is( + new ObjectMetadataAction( + "CloudBeers, Inc.", null, "https://github.com/cloudbeers")), + Matchers.is( + new GitHubOrgMetadataAction("https://avatars.githubusercontent.com/u/4181899?v=3")), + Matchers.is(new GitHubLink("icon-github-logo", "https://github.com/cloudbeers")))); + } + + @Test + public void doFillScanCredentials() throws Exception { + final GitHubSCMNavigator.DescriptorImpl d = + r.jenkins.getDescriptorByType(GitHubSCMNavigator.DescriptorImpl.class); + final MockFolder dummy = r.createFolder("dummy"); + SecurityRealm realm = r.jenkins.getSecurityRealm(); + AuthorizationStrategy strategy = r.jenkins.getAuthorizationStrategy(); + try { + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); + mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); + mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); + mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); + r.jenkins.setAuthorizationStrategy(mockStrategy); + try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + } + try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + } + try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + } + try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + } + } finally { + r.jenkins.setSecurityRealm(realm); + r.jenkins.setAuthorizationStrategy(strategy); + r.jenkins.remove(dummy); } - - @Test - public void fetchOneRepo_BelongingToOrg_ExcludingArchived() throws Exception { - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - - assertThat(projectNames, empty()); - } - - @Test - public void fetchOneRepo_BelongingToUser() throws Exception { - navigator = navigatorForRepoOwner("stephenc", null); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("yolo-archived")); - } - - @Test - public void fetchOneRepo_BelongingToUser_ExcludingArchived() throws Exception { - navigator = navigatorForRepoOwner("stephenc", null); - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); - - assertThat(projectNames, empty()); - } - - @Test - public void fetchRepos_BelongingToAuthenticatedUser() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertThat(projectNames, containsInAnyOrder("yolo", "yolo-archived")); - } - - @Test - public void fetchRepos_BelongingToAuthenticatedUser_ExcludingArchived() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertThat(projectNames, containsInAnyOrder("yolo")); - } - - @Test - public void fetchRepos_BelongingToOrg() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources( - SCMSourceObserver.filter(observer, "unknown", "basic", "yolo", "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("basic", "yolo", "yolo-archived")); - } - - @Test - public void fetchRepos_BelongingToOrg_ExcludingArchived() throws Exception { - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources( - SCMSourceObserver.filter(observer, "unknown", "basic", "yolo", "yolo-archived")); - - assertThat(projectNames, containsInAnyOrder("basic", "yolo")); - } - - @Test - public void fetchRepos_BelongingToOrg_ExcludingPublic() throws Exception { - navigator.setTraits(Collections.singletonList(new ExcludePublicRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources( - SCMSourceObserver.filter(observer, "Hello-World", "github-branch-source-plugin", "yolo-private")); - - assertThat(projectNames, containsInAnyOrder("yolo-private")); - } - - @Test - public void fetchRepos_BelongingToUser() throws Exception { - navigator = navigatorForRepoOwner("stephenc", null); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertThat(projectNames, containsInAnyOrder("yolo", "yolo-archived", "yolo-private")); - } - - @Test - public void fetchRepos_BelongingToUser_ExcludingArchived() throws Exception { - navigator = navigatorForRepoOwner("stephenc", null); - navigator.setTraits(Collections.singletonList(new ExcludeArchivedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertThat(projectNames, containsInAnyOrder("yolo")); - } - - @Test - public void appliesFilters() throws Exception { - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo", "rando-unknown")); - - assertEquals(projectNames, Collections.singleton("yolo")); - } - - @Test - public void fetchActions() throws Exception { - assertThat(navigator.fetchActions(Mockito.mock(SCMNavigatorOwner.class), null, null), Matchers.containsInAnyOrder( - Matchers.is( - new ObjectMetadataAction("CloudBeers, Inc.", null, "https://github.com/cloudbeers") - ), - Matchers.is(new GitHubOrgMetadataAction("https://avatars.githubusercontent.com/u/4181899?v=3")), - Matchers.is(new GitHubLink("icon-github-logo", "https://github.com/cloudbeers")))); - } - - @Test - public void doFillScanCredentials() throws Exception { - final GitHubSCMNavigator.DescriptorImpl d = - r.jenkins.getDescriptorByType(GitHubSCMNavigator.DescriptorImpl.class); - final MockFolder dummy = r.createFolder("dummy"); - SecurityRealm realm = r.jenkins.getSecurityRealm(); - AuthorizationStrategy strategy = r.jenkins.getAuthorizationStrategy(); - try { - r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); - mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); - mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); - mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); - r.jenkins.setAuthorizationStrategy(mockStrategy); - try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - is("does-not-exist")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - } - try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - is("does-not-exist")); - } - try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - } - try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, is("does-not-exist")); - } - } finally { - r.jenkins.setSecurityRealm(realm); - r.jenkins.setAuthorizationStrategy(strategy); - r.jenkins.remove(dummy); - } - } - - private SCMSourceObserver getObserver(Collection names){ - return new SCMSourceObserver() { - @NonNull - @Override - public SCMSourceOwner getContext() { - return scmSourceOwner; - } - - @NonNull - @Override - public TaskListener getListener() { - return new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO); - } - - @NonNull - @Override - public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException { - names.add(projectName); - return new NoOpProjectObserver(); - } - - @Override - public void addAttribute(@NonNull String key, @Nullable Object value) - throws IllegalArgumentException, ClassCastException { - - } - }; - } - + } + + private SCMSourceObserver getObserver(Collection names) { + return new SCMSourceObserver() { + @NonNull + @Override + public SCMSourceOwner getContext() { + return scmSourceOwner; + } + + @NonNull + @Override + public TaskListener getListener() { + return new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO); + } + + @NonNull + @Override + public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException { + names.add(projectName); + return new NoOpProjectObserver(); + } + + @Override + public void addAttribute(@NonNull String key, @Nullable Object value) + throws IllegalArgumentException, ClassCastException {} + }; + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTraitsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTraitsTest.java index e99a1ed94..ed7945adc 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTraitsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigatorTraitsTest.java @@ -1,5 +1,17 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; @@ -18,1102 +30,1275 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.WithoutJenkins; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; - public class GitHubSCMNavigatorTraitsTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); - @Rule - public TestName currentTestName = new TestName(); - - private GitHubSCMNavigator load() { - return load(currentTestName.getMethodName()); - } - - private GitHubSCMNavigator load(String dataSet) { - return (GitHubSCMNavigator) Jenkins.XSTREAM2 - .fromXML(getClass().getResource(getClass().getSimpleName() + "/" + dataSet + ".xml")); - } - - private static Matcher>> branchDiscoveryTraitItem( - boolean buildBranch, - boolean buildBranchesWithPR) { - return allOf(instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(buildBranch)), - hasProperty("buildBranchesWithPR", is(buildBranchesWithPR))); - } - - private static Matcher> sshCheckoutTraitItem(Matcher credentialsMatcher) { - return Matchers.>allOf(Matchers.instanceOf(SSHCheckoutTrait.class), - hasProperty("credentialsId", credentialsMatcher)); - } - - private static Matcher> sshCheckoutTraitItem(String credentialsId) { - return sshCheckoutTraitItem(is(credentialsId)); - } - - private static Matcher>> forkPullRequestDiscoveryTraitItem(int strategyId) { - return allOf(instanceOf(ForkPullRequestDiscoveryTrait.class), hasProperty("strategyId", is(strategyId))); - } - - private static Matcher>> forkPullRequestDiscoveryTraitItem( - int strategyId, - Matcher trust) { - return allOf(instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(strategyId)), - hasProperty("trust", trust)); - } - - private static Matcher>> originPullRequestDiscoveryTraitItem(int strategyId) { - return allOf(instanceOf(OriginPullRequestDiscoveryTrait.class), hasProperty("strategyId", is(strategyId))); - } - - private static Matcher regexSCMSourceFilterTraitItem(String pattern) { - return allOf(instanceOf(RegexSCMSourceFilterTrait.class), hasProperty("regex", is(pattern))); - } - - private static Matcher wildcardSCMHeadFilterTraitItem(String includes, String excludes) { - return allOf(instanceOf(WildcardSCMHeadFilterTrait.class), - hasProperty("includes", is(includes)), - hasProperty("excludes", is(excludes))); - } - - @Test - public void modern() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://api.github.com::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is(nullValue())); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), is(Collections.>emptyList())); - } - - @Test - public void basic_cloud() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://api.github.com::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is(nullValue())); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat("SAME checkout credentials should mean no checkout trait", - instance.getTraits(), - not(hasItem(instanceOf(SSHCheckoutTrait.class)))); - assertThat(".* as a pattern should mean no RegexSCMSourceFilterTrait", - instance.getTraits(), - not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("SAME")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void basic_server() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat("checkout credentials should mean checkout trait", - instance.getTraits(), - hasItem(sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); - assertThat(".* as a pattern should mean no RegexSCMSourceFilterTrait", - instance.getTraits(), - not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), - sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void use_agent_checkout() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat("checkout credentials should mean checkout trait", - instance.getTraits(), - hasItem(sshCheckoutTraitItem(nullValue()))); - assertThat(".* as a pattern should mean no RegexSCMSourceFilterTrait", - instance.getTraits(), - not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), - sshCheckoutTraitItem(nullValue()))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Issue("JENKINS-45467") - @Test - public void same_checkout_credentials() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat("checkout credentials equal to scan should mean no checkout trait", - instance.getTraits(), - not(hasItem(sshCheckoutTraitItem(nullValue())))); - assertThat(".* as a pattern should mean no RegexSCMSourceFilterTrait", - instance.getTraits(), - not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void limit_repositories() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat("checkout credentials should mean checkout trait", - instance.getTraits(), - hasItem(sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), - sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"), - allOf(instanceOf(RegexSCMSourceFilterTrait.class), hasProperty("regex", is("limited.*"))))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); - assertThat(instance.getPattern(), is("limited.*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void exclude_branches() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://api.github.com::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is(nullValue())); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), - wildcardSCMHeadFilterTraitItem("*", "master"))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("SAME")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("master")); - } - - @Test - public void limit_branches() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.id(), is("https://api.github.com::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is(nullValue())); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2, - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), - wildcardSCMHeadFilterTraitItem("feature/*", ""))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("SAME")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void build_000000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), empty()); - } - - @Test - public void build_000001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_000010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_000011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_000100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_000101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_000110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_000111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_001000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_001001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_001010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_001011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_001100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_001101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_001110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_001111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_010000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(false, true))); - } - - @Test - public void build_010001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_010010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_010011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_010100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_010101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_010110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_010111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_011000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_011001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_011010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_011011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_011100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_011101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_011110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_011111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(false, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_100000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(true, false))); - } - - @Test - public void build_100001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_100010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_100011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_100100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_100101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_100110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_100111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_101000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_101001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_101010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_101011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_101100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_101101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_101110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_101111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_110000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(true, true))); - } - - @Test - public void build_110001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_110010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_110011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_110100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_110101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_110110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_110111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(2), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_111000() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_111001() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_111010() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_111011() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(1), - forkPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_111100() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(3))); - } - - @Test - public void build_111101() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(2))); - } - - @Test - public void build_111110() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(1))); - } - - @Test - public void build_111111() throws Exception { - GitHubSCMNavigator instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - originPullRequestDiscoveryTraitItem(3), - forkPullRequestDiscoveryTraitItem(3))); - } - - @WithoutJenkins - @Test - public void given__legacyCode__when__constructor_cloud__then__discoveryTraitDefaults() throws Exception { - GitHubSCMNavigator instance = new GitHubSCMNavigator(null, - "cloudbeers", - "bcaef157-f105-407f-b150-df7722eab6c1", - "SAME"); - assertThat(instance.id(), is("https://api.github.com::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://api.github.com")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - allOf(instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), - hasProperty("trust", - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("SAME")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void given__legacyCode__when__constructor_server__then__discoveryTraitDefaults() throws Exception { - GitHubSCMNavigator instance = new GitHubSCMNavigator("https://github.test/api/v3", - "cloudbeers", - "bcaef157-f105-407f-b150-df7722eab6c1", - "8b2e4f77-39c5-41a9-b63b-8d367350bfdf"); - assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, true), - sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"), - allOf(instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), - hasProperty("trust", - instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); - // legacy API - assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - } - - @Test - public void given__instance__when__setTraits_empty__then__traitsEmpty() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(new SCMTrait[0]); - assertThat(instance.getTraits(), is(Collections.>emptyList())); - } - - @Test - public void given__instance__when__setTraits__then__traitsSet() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits( - Arrays.asList(new BranchDiscoveryTrait(BranchDiscoveryTrait.EXCLUDE_PRS), new SSHCheckoutTrait(null))); - assertThat(instance.getTraits(), - containsInAnyOrder(branchDiscoveryTraitItem(true, false), sshCheckoutTraitItem(nullValue()))); - } - - @Test - public void given__instance__when__setCredentials_empty__then__credentials_null() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); - } - - @Test - public void given__instance__when__setCredentials_null__then__credentials_null() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); - } - - @Test - public void given__instance__when__setCredentials__then__credentials_set() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setCredentialsId("test"); - assertThat(instance.getCredentialsId(), is("test")); - } - - @WithoutJenkins - @Test - public void given__instance__when__setApiUri_null__then__set__to_api_github_com() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setApiUri(null); - assertThat(instance.getApiUri(), is("https://api.github.com")); - } - - @Test - public void given__instance__when__setApiUri_value__then__valueApplied() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setApiUri("https://github.test"); - assertThat(instance.getApiUri(), is("https://github.test")); - } - - @Test - public void given__instance__when__setApiUri_cloudUrl__then__valueApplied() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setApiUri("https://github.com"); - assertThat(instance.getApiUri(), is("https://github.com")); - } - - @Test - public void given__legacyCode__when__setPattern_default__then__patternSetAndTraitRemoved() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new SSHCheckoutTrait("dummy"))); - assertThat(instance.getPattern(), is("job.*")); - assertThat(instance.getTraits(), hasItem(instanceOf(RegexSCMSourceFilterTrait.class))); - instance.setPattern(".*"); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - - } - - @Test - public void given__legacyCode__when__setPattern_custom__then__patternSetAndTraitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), new SSHCheckoutTrait("dummy"))); - assertThat(instance.getPattern(), is(".*")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); - instance.setPattern("job.*"); - assertThat(instance.getPattern(), is("job.*")); - assertThat(instance.getTraits(), hasItem(regexSCMSourceFilterTraitItem("job.*"))); - - } - - @Test - public void given__legacyCode__when__setPattern_custom__then__patternSetAndTraitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(new SCMTrait[]{ new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new SSHCheckoutTrait("dummy") }); - assertThat(instance.getPattern(), is("job.*")); - assertThat(instance.getTraits(), hasItem(instanceOf(RegexSCMSourceFilterTrait.class))); - instance.setPattern("project.*"); - assertThat(instance.getPattern(), is("project.*")); - assertThat(instance.getTraits(), not(hasItem(regexSCMSourceFilterTraitItem("job.*")))); - assertThat(instance.getTraits(), hasItem(regexSCMSourceFilterTraitItem("project.*"))); - - } - - @Test - public void given__legacyCode__when__checkoutCredentials_SAME__then__noTraitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator(null, "test", "scan", GitHubSCMSource.DescriptorImpl.SAME); - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMNavigator.DescriptorImpl.SAME)); - assertThat(instance.getTraits(), not(hasItem(instanceOf(SSHCheckoutTrait.class)))); - } - - @Test - public void given__legacyCode__when__checkoutCredentials_null__then__traitAdded_ANONYMOUS() { - GitHubSCMNavigator instance = new GitHubSCMNavigator(null, "test", "scan", null); - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); - assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem(nullValue()))); - } - - @Test - public void given__legacyCode__when__checkoutCredentials_value__then__traitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator(null, "test", "scan", "value"); - assertThat(instance.getCheckoutCredentialsId(), is("value")); - assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem("value"))); - } - - @Test - public void given__legacyCode__when__checkoutCredentials_ANONYMOUS__then__traitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator(null, - "test", - "scan", - GitHubSCMSource.DescriptorImpl.ANONYMOUS); - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); - assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem(nullValue()))); - } - - @Test - public void given__legacyCode_withoutExcludes__when__setIncludes_default__then__traitRemoved() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", ""))); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); - instance.setIncludes("*"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); - } - - @Test - public void given__legacyCode_withoutExcludes__when__setIncludes_value__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(new SCMTrait[]{ new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", "") }); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); - instance.setIncludes("bug/*"); - assertThat(instance.getIncludes(), is("bug/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("bug/*", ""))); - } - - @Test - public void given__legacyCode_withoutTrait__when__setIncludes_value__then__traitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits( - Arrays.asList(new BranchDiscoveryTrait(true, false), new RegexSCMSourceFilterTrait("job.*"))); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); - instance.setIncludes("feature/*"); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); - } - - @Test - public void given__legacyCode_withExcludes__when__setIncludes_default__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"))); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); - instance.setIncludes("*"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); - } - - @Test - public void given__legacyCode_withExcludes__when__setIncludes_value__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(new SCMTrait[]{ new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore") }); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); - instance.setIncludes("bug/*"); - assertThat(instance.getIncludes(), is("bug/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("bug/*", "feature/ignore"))); - } - - @Test - public void given__legacyCode_withoutIncludes__when__setExcludes_default__then__traitRemoved() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("*", "feature/ignore"))); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); - instance.setExcludes(""); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); - } - - @Test - public void given__legacyCode_withoutIncludes__when__setExcludes_value__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("*", "feature/ignore"))); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); - instance.setExcludes("bug/ignore"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("bug/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "bug/ignore"))); - } - - @Test - public void given__legacyCode_withoutTrait__when__setExcludes_value__then__traitAdded() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits( - Arrays.asList(new BranchDiscoveryTrait(true, false), new RegexSCMSourceFilterTrait("job.*"))); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); - instance.setExcludes("feature/ignore"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); - } - - @Test - public void given__legacyCode_withIncludes__when__setExcludes_default__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"))); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); - instance.setExcludes(""); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); - } - - @Test - public void given__legacyCode_withIncludes__when__setExcludes_value__then__traitUpdated() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, false), - new RegexSCMSourceFilterTrait("job.*"), - new WildcardSCMHeadFilterTrait("feature/*", ""))); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); - instance.setExcludes("feature/ignore"); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); - } - - @Test - public void given__legacyCode__when__setBuildOriginBranch__then__traitsMaintained() { - GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); - instance.setTraits(Collections.emptyList()); - assertThat(instance.getTraits(), is(empty())); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), is(empty())); - - instance.setBuildOriginBranchWithPR(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), is(empty())); - - instance.setBuildOriginBranchWithPR(true); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), is(empty())); - - instance.setBuildOriginBranchWithPR(true); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), is(empty())); - } - + @ClassRule public static JenkinsRule j = new JenkinsRule(); + @Rule public TestName currentTestName = new TestName(); + + private GitHubSCMNavigator load() { + return load(currentTestName.getMethodName()); + } + + private GitHubSCMNavigator load(String dataSet) { + return (GitHubSCMNavigator) + Jenkins.XSTREAM2.fromXML( + getClass().getResource(getClass().getSimpleName() + "/" + dataSet + ".xml")); + } + + private static Matcher>> branchDiscoveryTraitItem( + boolean buildBranch, boolean buildBranchesWithPR) { + return allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(buildBranch)), + hasProperty("buildBranchesWithPR", is(buildBranchesWithPR))); + } + + private static Matcher> sshCheckoutTraitItem(Matcher credentialsMatcher) { + return Matchers.>allOf( + Matchers.instanceOf(SSHCheckoutTrait.class), + hasProperty("credentialsId", credentialsMatcher)); + } + + private static Matcher> sshCheckoutTraitItem(String credentialsId) { + return sshCheckoutTraitItem(is(credentialsId)); + } + + private static Matcher>> forkPullRequestDiscoveryTraitItem( + int strategyId) { + return allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), hasProperty("strategyId", is(strategyId))); + } + + private static Matcher>> forkPullRequestDiscoveryTraitItem( + int strategyId, Matcher trust) { + return allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(strategyId)), + hasProperty("trust", trust)); + } + + private static Matcher>> originPullRequestDiscoveryTraitItem( + int strategyId) { + return allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(strategyId))); + } + + private static Matcher regexSCMSourceFilterTraitItem(String pattern) { + return allOf(instanceOf(RegexSCMSourceFilterTrait.class), hasProperty("regex", is(pattern))); + } + + private static Matcher wildcardSCMHeadFilterTraitItem(String includes, String excludes) { + return allOf( + instanceOf(WildcardSCMHeadFilterTrait.class), + hasProperty("includes", is(includes)), + hasProperty("excludes", is(excludes))); + } + + @Test + public void modern() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://api.github.com::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is(nullValue())); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat(instance.getTraits(), is(Collections.>emptyList())); + } + + @Test + public void basic_cloud() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://api.github.com::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is(nullValue())); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + "SAME checkout credentials should mean no checkout trait", + instance.getTraits(), + not(hasItem(instanceOf(SSHCheckoutTrait.class)))); + assertThat( + ".* as a pattern should mean no RegexSCMSourceFilterTrait", + instance.getTraits(), + not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("SAME")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void basic_server() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + "checkout credentials should mean checkout trait", + instance.getTraits(), + hasItem(sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); + assertThat( + ".* as a pattern should mean no RegexSCMSourceFilterTrait", + instance.getTraits(), + not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), + sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void use_agent_checkout() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + "checkout credentials should mean checkout trait", + instance.getTraits(), + hasItem(sshCheckoutTraitItem(nullValue()))); + assertThat( + ".* as a pattern should mean no RegexSCMSourceFilterTrait", + instance.getTraits(), + not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), + sshCheckoutTraitItem(nullValue()))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Issue("JENKINS-45467") + @Test + public void same_checkout_credentials() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + "checkout credentials equal to scan should mean no checkout trait", + instance.getTraits(), + not(hasItem(sshCheckoutTraitItem(nullValue())))); + assertThat( + ".* as a pattern should mean no RegexSCMSourceFilterTrait", + instance.getTraits(), + not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void limit_repositories() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + "checkout credentials should mean checkout trait", + instance.getTraits(), + hasItem(sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), + sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"), + allOf( + instanceOf(RegexSCMSourceFilterTrait.class), + hasProperty("regex", is("limited.*"))))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); + assertThat(instance.getPattern(), is("limited.*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void exclude_branches() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://api.github.com::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is(nullValue())); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), + wildcardSCMHeadFilterTraitItem("*", "master"))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("SAME")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("master")); + } + + @Test + public void limit_branches() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.id(), is("https://api.github.com::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is(nullValue())); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem( + 2, instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)), + wildcardSCMHeadFilterTraitItem("feature/*", ""))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("SAME")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void build_000000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), empty()); + } + + @Test + public void build_000001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_000010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_000011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_000100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_000101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_000110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_000111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(2), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_001000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_001001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_001010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_001011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(1), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_001100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(originPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_001101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_001110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_001111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + originPullRequestDiscoveryTraitItem(3), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_010000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(false, true))); + } + + @Test + public void build_010001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_010010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_010011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_010100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_010101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_010110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_010111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_011000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_011001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_011010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_011011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_011100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), originPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_011101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_011110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_011111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(false, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_100000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(true, false))); + } + + @Test + public void build_100001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_100010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_100011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_100100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_100101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_100110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_100111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_101000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_101001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_101010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_101011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_101100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), originPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_101101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_101110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_101111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_110000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat(instance.getTraits(), contains(branchDiscoveryTraitItem(true, true))); + } + + @Test + public void build_110001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_110010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_110011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_110100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_110101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_110110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_110111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(2), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_111000() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_111001() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_111010() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_111011() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(1), + forkPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_111100() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), originPullRequestDiscoveryTraitItem(3))); + } + + @Test + public void build_111101() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(2))); + } + + @Test + public void build_111110() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(1))); + } + + @Test + public void build_111111() throws Exception { + GitHubSCMNavigator instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + originPullRequestDiscoveryTraitItem(3), + forkPullRequestDiscoveryTraitItem(3))); + } + + @WithoutJenkins + @Test + public void given__legacyCode__when__constructor_cloud__then__discoveryTraitDefaults() + throws Exception { + GitHubSCMNavigator instance = + new GitHubSCMNavigator(null, "cloudbeers", "bcaef157-f105-407f-b150-df7722eab6c1", "SAME"); + assertThat(instance.id(), is("https://api.github.com::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://api.github.com")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("SAME")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void given__legacyCode__when__constructor_server__then__discoveryTraitDefaults() + throws Exception { + GitHubSCMNavigator instance = + new GitHubSCMNavigator( + "https://github.test/api/v3", + "cloudbeers", + "bcaef157-f105-407f-b150-df7722eab6c1", + "8b2e4f77-39c5-41a9-b63b-8d367350bfdf"); + assertThat(instance.id(), is("https://github.test/api/v3::cloudbeers")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getCredentialsId(), is("bcaef157-f105-407f-b150-df7722eab6c1")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, true), + sshCheckoutTraitItem("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"), + allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // legacy API + assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + } + + @Test + public void given__instance__when__setTraits_empty__then__traitsEmpty() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits(new SCMTrait[0]); + assertThat(instance.getTraits(), is(Collections.>emptyList())); + } + + @Test + public void given__instance__when__setTraits__then__traitsSet() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(BranchDiscoveryTrait.EXCLUDE_PRS), + new SSHCheckoutTrait(null))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + branchDiscoveryTraitItem(true, false), sshCheckoutTraitItem(nullValue()))); + } + + @Test + public void given__instance__when__setCredentials_empty__then__credentials_null() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setCredentialsId(""); + assertThat(instance.getCredentialsId(), is(nullValue())); + } + + @Test + public void given__instance__when__setCredentials_null__then__credentials_null() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setCredentialsId(""); + assertThat(instance.getCredentialsId(), is(nullValue())); + } + + @Test + public void given__instance__when__setCredentials__then__credentials_set() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setCredentialsId("test"); + assertThat(instance.getCredentialsId(), is("test")); + } + + @WithoutJenkins + @Test + public void given__instance__when__setApiUri_null__then__set__to_api_github_com() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setApiUri(null); + assertThat(instance.getApiUri(), is("https://api.github.com")); + } + + @Test + public void given__instance__when__setApiUri_value__then__valueApplied() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setApiUri("https://github.test"); + assertThat(instance.getApiUri(), is("https://github.test")); + } + + @Test + public void given__instance__when__setApiUri_cloudUrl__then__valueApplied() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setApiUri("https://github.com"); + assertThat(instance.getApiUri(), is("https://github.com")); + } + + @Test + public void given__legacyCode__when__setPattern_default__then__patternSetAndTraitRemoved() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new SSHCheckoutTrait("dummy"))); + assertThat(instance.getPattern(), is("job.*")); + assertThat(instance.getTraits(), hasItem(instanceOf(RegexSCMSourceFilterTrait.class))); + instance.setPattern(".*"); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + } + + @Test + public void given__legacyCode__when__setPattern_custom__then__patternSetAndTraitAdded() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList(new BranchDiscoveryTrait(true, false), new SSHCheckoutTrait("dummy"))); + assertThat(instance.getPattern(), is(".*")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(RegexSCMSourceFilterTrait.class)))); + instance.setPattern("job.*"); + assertThat(instance.getPattern(), is("job.*")); + assertThat(instance.getTraits(), hasItem(regexSCMSourceFilterTraitItem("job.*"))); + } + + @Test + public void given__legacyCode__when__setPattern_custom__then__patternSetAndTraitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + new SCMTrait[] { + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new SSHCheckoutTrait("dummy") + }); + assertThat(instance.getPattern(), is("job.*")); + assertThat(instance.getTraits(), hasItem(instanceOf(RegexSCMSourceFilterTrait.class))); + instance.setPattern("project.*"); + assertThat(instance.getPattern(), is("project.*")); + assertThat(instance.getTraits(), not(hasItem(regexSCMSourceFilterTraitItem("job.*")))); + assertThat(instance.getTraits(), hasItem(regexSCMSourceFilterTraitItem("project.*"))); + } + + @Test + public void given__legacyCode__when__checkoutCredentials_SAME__then__noTraitAdded() { + GitHubSCMNavigator instance = + new GitHubSCMNavigator(null, "test", "scan", GitHubSCMSource.DescriptorImpl.SAME); + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMNavigator.DescriptorImpl.SAME)); + assertThat(instance.getTraits(), not(hasItem(instanceOf(SSHCheckoutTrait.class)))); + } + + @Test + public void given__legacyCode__when__checkoutCredentials_null__then__traitAdded_ANONYMOUS() { + GitHubSCMNavigator instance = new GitHubSCMNavigator(null, "test", "scan", null); + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); + assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem(nullValue()))); + } + + @Test + public void given__legacyCode__when__checkoutCredentials_value__then__traitAdded() { + GitHubSCMNavigator instance = new GitHubSCMNavigator(null, "test", "scan", "value"); + assertThat(instance.getCheckoutCredentialsId(), is("value")); + assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem("value"))); + } + + @Test + public void given__legacyCode__when__checkoutCredentials_ANONYMOUS__then__traitAdded() { + GitHubSCMNavigator instance = + new GitHubSCMNavigator(null, "test", "scan", GitHubSCMSource.DescriptorImpl.ANONYMOUS); + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); + assertThat(instance.getTraits(), hasItem(sshCheckoutTraitItem(nullValue()))); + } + + @Test + public void given__legacyCode_withoutExcludes__when__setIncludes_default__then__traitRemoved() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", ""))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); + instance.setIncludes("*"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + } + + @Test + public void given__legacyCode_withoutExcludes__when__setIncludes_value__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + new SCMTrait[] { + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", "") + }); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); + instance.setIncludes("bug/*"); + assertThat(instance.getIncludes(), is("bug/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("bug/*", ""))); + } + + @Test + public void given__legacyCode_withoutTrait__when__setIncludes_value__then__traitAdded() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), new RegexSCMSourceFilterTrait("job.*"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + instance.setIncludes("feature/*"); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); + } + + @Test + public void given__legacyCode_withExcludes__when__setIncludes_default__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); + instance.setIncludes("*"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); + } + + @Test + public void given__legacyCode_withExcludes__when__setIncludes_value__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + new SCMTrait[] { + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore") + }); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); + instance.setIncludes("bug/*"); + assertThat(instance.getIncludes(), is("bug/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("bug/*", "feature/ignore"))); + } + + @Test + public void given__legacyCode_withoutIncludes__when__setExcludes_default__then__traitRemoved() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("*", "feature/ignore"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); + instance.setExcludes(""); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + } + + @Test + public void given__legacyCode_withoutIncludes__when__setExcludes_value__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("*", "feature/ignore"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); + instance.setExcludes("bug/ignore"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("bug/ignore")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "bug/ignore"))); + } + + @Test + public void given__legacyCode_withoutTrait__when__setExcludes_value__then__traitAdded() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), new RegexSCMSourceFilterTrait("job.*"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), not(hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + instance.setExcludes("feature/ignore"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("*", "feature/ignore"))); + } + + @Test + public void given__legacyCode_withIncludes__when__setExcludes_default__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); + instance.setExcludes(""); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); + } + + @Test + public void given__legacyCode_withIncludes__when__setExcludes_value__then__traitUpdated() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new RegexSCMSourceFilterTrait("job.*"), + new WildcardSCMHeadFilterTrait("feature/*", ""))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getTraits(), hasItem(wildcardSCMHeadFilterTraitItem("feature/*", ""))); + instance.setExcludes("feature/ignore"); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + hasItem(wildcardSCMHeadFilterTraitItem("feature/*", "feature/ignore"))); + } + + @Test + public void given__legacyCode__when__setBuildOriginBranch__then__traitsMaintained() { + GitHubSCMNavigator instance = new GitHubSCMNavigator("test"); + instance.setTraits(Collections.emptyList()); + assertThat(instance.getTraits(), is(empty())); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), is(empty())); + + instance.setBuildOriginBranchWithPR(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), is(empty())); + + instance.setBuildOriginBranchWithPR(true); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), is(empty())); + + instance.setBuildOriginBranchWithPR(true); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), is(empty())); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java index a9a3552a2..fecc483be 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMProbeTest.java @@ -1,10 +1,15 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.*; + import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; +import java.io.File; +import java.io.IOException; import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import org.apache.commons.io.FileUtils; @@ -16,161 +21,185 @@ import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import java.io.File; -import java.io.IOException; -import java.time.Duration; - -import static org.junit.Assert.*; -import static com.github.tomakehurst.wiremock.client.WireMock.*; - public class GitHubSCMProbeTest { - @Rule - public JenkinsRule j = new JenkinsRule(); - public static WireMockRuleFactory factory = new WireMockRuleFactory(); - @Rule - public WireMockRule githubApi = factory.getRule(WireMockConfiguration.options() - .dynamicPort().usingFilesUnderClasspath("cache_failure") - .extensions(ResponseTemplateTransformer.builder().global(true).maxCacheEntries(0L).build())); - private GitHubSCMProbe probe; - - @Before - public void setUp() throws Exception { - // Clear all caches before each test - File cacheBaseDir = new File(j.jenkins.getRootDir(), - GitHubSCMProbe.class.getName() + ".cache"); - if (cacheBaseDir.exists()) { - FileUtils.cleanDirectory(cacheBaseDir); - } - - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBodyFile("body-cloudbeers-yolo-PucD6.json")) - ); - - //Return 404 for /rate_limit - githubApi.stubFor(get(urlEqualTo("/rate_limit")) - .willReturn(aResponse() - .withStatus(404) - )); - - // validate api url - githubApi.stubFor(get(urlEqualTo("/")) - .willReturn(aResponse() - .withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}") - )); + @Rule public JenkinsRule j = new JenkinsRule(); + public static WireMockRuleFactory factory = new WireMockRuleFactory(); + + @Rule + public WireMockRule githubApi = + factory.getRule( + WireMockConfiguration.options() + .dynamicPort() + .usingFilesUnderClasspath("cache_failure") + .extensions( + ResponseTemplateTransformer.builder().global(true).maxCacheEntries(0L).build())); + + private GitHubSCMProbe probe; + + @Before + public void setUp() throws Exception { + // Clear all caches before each test + File cacheBaseDir = new File(j.jenkins.getRootDir(), GitHubSCMProbe.class.getName() + ".cache"); + if (cacheBaseDir.exists()) { + FileUtils.cleanDirectory(cacheBaseDir); } - void createProbeForPR(int number) throws IOException { - final GitHub github = Connector.connect("http://localhost:" + githubApi.port(), null); - - final GHRepository repo = github.getRepository("cloudbeers/yolo"); - final PullRequestSCMHead head = new PullRequestSCMHead("PR-" + number, "cloudbeers", "yolo", "b", number, new BranchSCMHead("master"), new SCMHeadOrigin.Fork("rsandell"), ChangeRequestCheckoutStrategy.MERGE); - probe = new GitHubSCMProbe("http://localhost:" + githubApi.port(), null, + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("body-cloudbeers-yolo-PucD6.json"))); + + // Return 404 for /rate_limit + githubApi.stubFor(get(urlEqualTo("/rate_limit")).willReturn(aResponse().withStatus(404))); + + // validate api url + githubApi.stubFor( + get(urlEqualTo("/")) + .willReturn( + aResponse().withBody("{\"rate_limit_url\": \"https://localhost/placeholder/\"}"))); + } + + void createProbeForPR(int number) throws IOException { + final GitHub github = Connector.connect("http://localhost:" + githubApi.port(), null); + + final GHRepository repo = github.getRepository("cloudbeers/yolo"); + final PullRequestSCMHead head = + new PullRequestSCMHead( + "PR-" + number, + "cloudbeers", + "yolo", + "b", + number, + new BranchSCMHead("master"), + new SCMHeadOrigin.Fork("rsandell"), + ChangeRequestCheckoutStrategy.MERGE); + probe = + new GitHubSCMProbe( + "http://localhost:" + githubApi.port(), + null, repo, - head, new PullRequestSCMRevision(head, "a", "b")); + head, + new PullRequestSCMRevision(head, "a", "b")); + } + + @Issue("JENKINS-54126") + @Test + public void statWhenRootIs404() throws Exception { + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F1%2Fmerge")) + .willReturn(aResponse().withStatus(404)) + .atPriority(0)); + + createProbeForPR(1); + + assertFalse(probe.stat("Jenkinsfile").exists()); + } + + @Issue("JENKINS-54126") + @Test + public void statWhenDirIs404() throws Exception { + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/contents/subdir?ref=refs%2Fpull%2F1%2Fmerge")) + .willReturn(aResponse().withStatus(404)) + .atPriority(0)); + + createProbeForPR(1); + + assertTrue(probe.stat("README.md").exists()); + assertFalse(probe.stat("subdir").exists()); + assertFalse(probe.stat("subdir/Jenkinsfile").exists()); + } + + @Issue("JENKINS-54126") + @Test + public void statWhenRoot404andThenIncorrectCached() throws Exception { + GitHubSCMSource.setCacheSize(10); + + createProbeForPR(9); + + // JENKINS-54126 happens when: + // 1. client asks for a resource "Z" that doesn't exist + // ---> client receives a 404 response from github and caches it. + // ---> Important: GitHub does not send ETag header for 404 responses. + // 2. Resource "Z" gets created on GitHub but some means. + // 3. client (eventually) asks for the resource "Z" again. + // ---> Since the the client has a cached response without ETag, it sends "If-Modified-Since" + // header + // ---> Resource has changed (it was created). + // + // ---> EXPECTED: GitHub should respond with 200 and data. + // ---> ACTUAL: GitHub server lies, responds with incorrect 304 response, telling client that + // the cached data is still valid. + // ---> THE BAD: Client cache believes GitHub - uses the previously cached 404 (and even adds + // the ETag). + // ---> THE UGLY: Client is now stuck with a bad cached 404, and can't get rid of it until the + // resource is _updated_ again or the cache is cleared manually. + // + // This is the cause of JENKINS-54126. This is a pervasive GitHub server problem. + // We see it mostly in this one scenario, but it will happen anywhere the server returns a 404. + // It cannot be reliably detected or mitigated at the level of this plugin. + // + // WORKAROUND (implemented in the github-api library): + // 4. the github-api library recognizes any 404 with ETag as invalid. Does not return it to the + // client. + // ---> The github-api library automatically retries the request with "no-cache" to force + // refresh with valid data. + + // 1. + assertFalse(probe.stat("README.md").exists()); + + // 3. + // Without 4. this would return false and would stay false. + assertTrue(probe.stat("README.md").exists()); + + // 5. Verify caching is working + assertTrue(probe.stat("README.md").exists()); + + // Verify the expected requests were made + if (hudson.Functions.isWindows()) { + // On windows caching is disabled by default, so the work around doesn't happen + githubApi.verify( + 3, + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) + .withHeader("Cache-Control", equalTo("max-age=0")) + .withHeader("If-Modified-Since", absent()) + .withHeader("If-None-Match", absent())); + } else { + // 1. + githubApi.verify( + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) + .withHeader("Cache-Control", equalTo("max-age=0")) + .withHeader("If-None-Match", absent()) + .withHeader("If-Modified-Since", absent())); + + // 3. + githubApi.verify( + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) + .withHeader("Cache-Control", containing("max-age")) + .withHeader("If-None-Match", absent()) + .withHeader("If-Modified-Since", containing("GMT"))); + + // 4. + githubApi.verify( + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) + .withHeader("Cache-Control", equalTo("no-cache")) + .withHeader("If-Modified-Since", absent()) + .withHeader("If-None-Match", absent())); + + // 5. + githubApi.verify( + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) + .withHeader("Cache-Control", equalTo("max-age=0")) + .withHeader("If-None-Match", equalTo("\"d3be5b35b8d84ef7ac03c0cc9c94ed81\""))); } - - @Issue("JENKINS-54126") - @Test - public void statWhenRootIs404() throws Exception { - githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F1%2Fmerge")).willReturn(aResponse().withStatus(404)).atPriority(0)); - - createProbeForPR(1); - - assertFalse(probe.stat("Jenkinsfile").exists()); - } - - @Issue("JENKINS-54126") - @Test - public void statWhenDirIs404() throws Exception { - githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/yolo/contents/subdir?ref=refs%2Fpull%2F1%2Fmerge")).willReturn(aResponse().withStatus(404)).atPriority(0)); - - createProbeForPR(1); - - assertTrue(probe.stat("README.md").exists()); - assertFalse(probe.stat("subdir").exists()); - assertFalse(probe.stat("subdir/Jenkinsfile").exists()); - } - - @Issue("JENKINS-54126") - @Test - public void statWhenRoot404andThenIncorrectCached() throws Exception { - GitHubSCMSource.setCacheSize(10); - - createProbeForPR(9); - - // JENKINS-54126 happens when: - // 1. client asks for a resource "Z" that doesn't exist - // ---> client receives a 404 response from github and caches it. - // ---> Important: GitHub does not send ETag header for 404 responses. - // 2. Resource "Z" gets created on GitHub but some means. - // 3. client (eventually) asks for the resource "Z" again. - // ---> Since the the client has a cached response without ETag, it sends "If-Modified-Since" header - // ---> Resource has changed (it was created). - // - // ---> EXPECTED: GitHub should respond with 200 and data. - // ---> ACTUAL: GitHub server lies, responds with incorrect 304 response, telling client that the cached data is still valid. - // ---> THE BAD: Client cache believes GitHub - uses the previously cached 404 (and even adds the ETag). - // ---> THE UGLY: Client is now stuck with a bad cached 404, and can't get rid of it until the resource is _updated_ again or the cache is cleared manually. - // - // This is the cause of JENKINS-54126. This is a pervasive GitHub server problem. - // We see it mostly in this one scenario, but it will happen anywhere the server returns a 404. - // It cannot be reliably detected or mitigated at the level of this plugin. - // - // WORKAROUND (implemented in the github-api library): - // 4. the github-api library recognizes any 404 with ETag as invalid. Does not return it to the client. - // ---> The github-api library automatically retries the request with "no-cache" to force refresh with valid data. - - // 1. - assertFalse(probe.stat("README.md").exists()); - - // 3. - // Without 4. this would return false and would stay false. - assertTrue(probe.stat("README.md").exists()); - - // 5. Verify caching is working - assertTrue(probe.stat("README.md").exists()); - - // Verify the expected requests were made - if(hudson.Functions.isWindows()) { - // On windows caching is disabled by default, so the work around doesn't happen - githubApi.verify(3, RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) - .withHeader("Cache-Control", equalTo("max-age=0")) - .withHeader("If-Modified-Since", absent()) - .withHeader("If-None-Match", absent()) - ); - } else { - // 1. - githubApi.verify(RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) - .withHeader("Cache-Control", equalTo("max-age=0")) - .withHeader("If-None-Match", absent()) - .withHeader("If-Modified-Since", absent()) - ); - - // 3. - githubApi.verify(RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) - .withHeader("Cache-Control", containing("max-age")) - .withHeader("If-None-Match", absent()) - .withHeader("If-Modified-Since", containing("GMT")) - ); - - // 4. - githubApi.verify(RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) - .withHeader("Cache-Control", equalTo("no-cache")) - .withHeader("If-Modified-Since", absent()) - .withHeader("If-None-Match", absent()) - ); - - // 5. - githubApi.verify(RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/repos/cloudbeers/yolo/contents/")) - .withHeader("Cache-Control", equalTo("max-age=0")) - .withHeader("If-None-Match", equalTo("\"d3be5b35b8d84ef7ac03c0cc9c94ed81\"")) - ); - } - } - -} \ No newline at end of file + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceHelperTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceHelperTest.java index ebe197538..669eca15f 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceHelperTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceHelperTest.java @@ -1,26 +1,27 @@ package org.jenkinsci.plugins.github_branch_source; -import org.junit.Test; - import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; -public class GitHubSCMSourceHelperTest { +import org.junit.Test; - @Test - public void httpsUrl_non_github() { - GitHubRepositoryInfo sut = GitHubRepositoryInfo.forRepositoryUrl("https://mygithub.com/jenkinsci/jenkins"); - assertThat(sut.getRepoOwner(), is("jenkinsci")); - assertThat(sut.getRepository(), is("jenkins")); - assertThat(sut.getRepositoryUrl(), is("https://mygithub.com/jenkinsci/jenkins")); - } +public class GitHubSCMSourceHelperTest { - @Test - public void httpsUrl_github() { - GitHubRepositoryInfo sut = GitHubRepositoryInfo.forRepositoryUrl("https://github.com/jenkinsci/jenkins"); - assertThat(sut.getRepoOwner(), is("jenkinsci")); - assertThat(sut.getRepository(), is("jenkins")); - assertThat(sut.getRepositoryUrl(), is("https://github.com/jenkinsci/jenkins")); - } + @Test + public void httpsUrl_non_github() { + GitHubRepositoryInfo sut = + GitHubRepositoryInfo.forRepositoryUrl("https://mygithub.com/jenkinsci/jenkins"); + assertThat(sut.getRepoOwner(), is("jenkinsci")); + assertThat(sut.getRepository(), is("jenkins")); + assertThat(sut.getRepositoryUrl(), is("https://mygithub.com/jenkinsci/jenkins")); + } + @Test + public void httpsUrl_github() { + GitHubRepositoryInfo sut = + GitHubRepositoryInfo.forRepositoryUrl("https://github.com/jenkinsci/jenkins"); + assertThat(sut.getRepoOwner(), is("jenkinsci")); + assertThat(sut.getRepository(), is("jenkins")); + assertThat(sut.getRepositoryUrl(), is("https://github.com/jenkinsci/jenkins")); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java index b3136aed6..9823936db 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java @@ -25,11 +25,28 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + import com.cloudbees.jenkins.GitHubRepositoryName; import com.cloudbees.jenkins.GitHubRepositoryNameContributor; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.stubbing.Scenario; - import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.ExtensionList; @@ -46,13 +63,11 @@ import java.io.File; import java.io.IOException; import java.util.*; - +import java.util.logging.Level; +import java.util.logging.Logger; import jenkins.branch.BranchSource; import jenkins.model.Jenkins; import jenkins.plugins.git.GitSCMSource; -import java.util.logging.Level; -import java.util.logging.Logger; - import jenkins.scm.api.*; import jenkins.scm.api.metadata.ObjectMetadataAction; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; @@ -65,645 +80,742 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.MockAuthorizationStrategy; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.sameInstance; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.*; - @RunWith(Parameterized.class) -public class GitHubSCMSourceTest extends GitSCMSourceBase{ - - public GitHubSCMSourceTest(GitHubSCMSource source) { - this.source = source; - } - - @Parameterized.Parameters(name = "{index}: revision={0}") - public static GitHubSCMSource[] revisions() { - return new GitHubSCMSource[]{ - new GitHubSCMSource("cloudbeers", "yolo", null, false), - new GitHubSCMSource("", "", "https://github.com/cloudbeers/yolo", true) - }; - } - - - @Before - public void prepareMockGitHubStubs() throws Exception { - new File("src/test/resources/api/mappings").mkdirs(); - new File("src/test/resources/api/__files").mkdirs(); - githubApi.enableRecordMappings(new SingleRootFileSource("src/test/resources/api/mappings"), - new SingleRootFileSource("src/test/resources/api/__files")); - - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) +public class GitHubSCMSourceTest extends GitSCMSourceBase { + + public GitHubSCMSourceTest(GitHubSCMSource source) { + this.source = source; + } + + @Parameterized.Parameters(name = "{index}: revision={0}") + public static GitHubSCMSource[] revisions() { + return new GitHubSCMSource[] { + new GitHubSCMSource("cloudbeers", "yolo", null, false), + new GitHubSCMSource("", "", "https://github.com/cloudbeers/yolo", true) + }; + } + + @Before + public void prepareMockGitHubStubs() throws Exception { + new File("src/test/resources/api/mappings").mkdirs(); + new File("src/test/resources/api/__files").mkdirs(); + githubApi.enableRecordMappings( + new SingleRootFileSource("src/test/resources/api/mappings"), + new SingleRootFileSource("src/test/resources/api/__files")); + + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-mergeable-null.json")) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-mergeable-null.json")) .willSetStateTo("Pull Request Merge Hash - retry 1")); - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs("Pull Request Merge Hash - retry 1") .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-mergeable-null.json")) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-mergeable-null.json")) .willSetStateTo("Pull Request Merge Hash - retry 2")); - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs("Pull Request Merge Hash - retry 2") .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-mergeable-true.json")) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-mergeable-true.json")) .willSetStateTo("Pull Request Merge Hash - retry 2")); - } - - @Test - @Issue("JENKINS-48035") - public void testGitHubRepositoryNameContributor() throws IOException { - WorkflowMultiBranchProject job = r.createProject(WorkflowMultiBranchProject.class); - job.setSourcesList(Arrays.asList(new BranchSource(source))); - Collection names = GitHubRepositoryNameContributor.parseAssociatedNames(job); - assertThat(names, contains(allOf( + } + + @Test + @Issue("JENKINS-48035") + public void testGitHubRepositoryNameContributor() throws IOException { + WorkflowMultiBranchProject job = r.createProject(WorkflowMultiBranchProject.class); + job.setSourcesList(Arrays.asList(new BranchSource(source))); + Collection names = + GitHubRepositoryNameContributor.parseAssociatedNames(job); + assertThat( + names, + contains( + allOf( hasProperty("userName", equalTo("cloudbeers")), - hasProperty("repositoryName", equalTo("yolo")) - ))); - //And specifically... - names = new ArrayList<>(); - ExtensionList.lookup(GitHubRepositoryNameContributor.class).get(GitHubSCMSourceRepositoryNameContributor.class).parseAssociatedNames(job, names); - assertThat(names, contains(allOf( + hasProperty("repositoryName", equalTo("yolo"))))); + // And specifically... + names = new ArrayList<>(); + ExtensionList.lookup(GitHubRepositoryNameContributor.class) + .get(GitHubSCMSourceRepositoryNameContributor.class) + .parseAssociatedNames(job, names); + assertThat( + names, + contains( + allOf( hasProperty("userName", equalTo("cloudbeers")), - hasProperty("repositoryName", equalTo("yolo")) - ))); - } - - @Test - @Issue("JENKINS-48035") - public void testGitHubRepositoryNameContributor_When_not_GitHub() throws IOException { - WorkflowMultiBranchProject job = r.createProject(WorkflowMultiBranchProject.class); - job.setSourcesList(Arrays.asList(new BranchSource(new GitSCMSource("file://tmp/something")))); - Collection names = GitHubRepositoryNameContributor.parseAssociatedNames(job); - assertThat(names, Matchers.empty()); - //And specifically... - names = new ArrayList<>(); - ExtensionList.lookup(GitHubRepositoryNameContributor.class).get(GitHubSCMSourceRepositoryNameContributor.class).parseAssociatedNames(job, names); - assertThat(names, Matchers.empty()); - } - - @Test - @Issue("JENKINS-48035") - public void testGitHubRepositoryNameContributor_When_not_MultiBranch() throws IOException { - FreeStyleProject job = r.createProject(FreeStyleProject.class); - Collection names = GitHubRepositoryNameContributor.parseAssociatedNames((Item) job); - assertThat(names, Matchers.empty()); - //And specifically... - names = new ArrayList<>(); - ExtensionList.lookup(GitHubRepositoryNameContributor.class).get(GitHubSCMSourceRepositoryNameContributor.class).parseAssociatedNames((Item) job, names); - assertThat(names, Matchers.empty()); + hasProperty("repositoryName", equalTo("yolo"))))); + } + + @Test + @Issue("JENKINS-48035") + public void testGitHubRepositoryNameContributor_When_not_GitHub() throws IOException { + WorkflowMultiBranchProject job = r.createProject(WorkflowMultiBranchProject.class); + job.setSourcesList(Arrays.asList(new BranchSource(new GitSCMSource("file://tmp/something")))); + Collection names = + GitHubRepositoryNameContributor.parseAssociatedNames(job); + assertThat(names, Matchers.empty()); + // And specifically... + names = new ArrayList<>(); + ExtensionList.lookup(GitHubRepositoryNameContributor.class) + .get(GitHubSCMSourceRepositoryNameContributor.class) + .parseAssociatedNames(job, names); + assertThat(names, Matchers.empty()); + } + + @Test + @Issue("JENKINS-48035") + public void testGitHubRepositoryNameContributor_When_not_MultiBranch() throws IOException { + FreeStyleProject job = r.createProject(FreeStyleProject.class); + Collection names = + GitHubRepositoryNameContributor.parseAssociatedNames((Item) job); + assertThat(names, Matchers.empty()); + // And specifically... + names = new ArrayList<>(); + ExtensionList.lookup(GitHubRepositoryNameContributor.class) + .get(GitHubSCMSourceRepositoryNameContributor.class) + .parseAssociatedNames((Item) job, names); + assertThat(names, Matchers.empty()); + } + + @Test + public void fetchSmokes() throws Exception { + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); } - - @Test - public void fetchSmokes() throws Exception { - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); - - assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); - assertThat(((PullRequestSCMHead)byName.get("PR-2")).isMerge(), is(true)); - assertThat(revByName.get("PR-2"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-2")), + assertThat( + byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); + + assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); + assertThat(((PullRequestSCMHead) byName.get("PR-2")).isMerge(), is(true)); + assertThat( + revByName.get("PR-2"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-2")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - "38814ca33833ff5583624c29f305be9133f27a40" - ))); - ((PullRequestSCMRevision)revByName.get("PR-2")).validateMergeHash(); - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(true)); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + "38814ca33833ff5583624c29f305be9133f27a40"))); + ((PullRequestSCMRevision) revByName.get("PR-2")).validateMergeHash(); + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(true)); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH - ))); - - // validation should fail for this PR. - Exception abort = null; - try { - ((PullRequestSCMRevision)revByName.get("PR-3")).validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); + PullRequestSCMRevision.NOT_MERGEABLE_HASH))); + + // validation should fail for this PR. + Exception abort = null; + try { + ((PullRequestSCMRevision) revByName.get("PR-3")).validateMergeHash(); + } catch (Exception e) { + abort = e; } - - @Test - public void fetchSmokes_badMergeCommit() throws Exception { - // make it so the merge commit is not found returns 404 - // Causes PR 2 to fall back to null merge_commit_sha - - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/commits/38814ca33833ff5583624c29f305be9133f27a40")) + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchSmokes_badMergeCommit() throws Exception { + // make it so the merge commit is not found returns 404 + // Causes PR 2 to fall back to null merge_commit_sha + + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/commits/38814ca33833ff5583624c29f305be9133f27a40")) .inScenario("PR 2 Merge 404") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-heads-master-notfound.json")) + .withStatus(404) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-heads-master-notfound.json")) .willSetStateTo(Scenario.STARTED)); - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); - - assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); - assertThat(((PullRequestSCMHead)byName.get("PR-2")).isMerge(), is(true)); - assertThat(revByName.get("PR-2"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-2")), + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat( + byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); + + assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); + assertThat(((PullRequestSCMHead) byName.get("PR-2")).isMerge(), is(true)); + assertThat( + revByName.get("PR-2"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-2")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - null - ))); - ((PullRequestSCMRevision)revByName.get("PR-2")).validateMergeHash(); - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(true)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + null))); + ((PullRequestSCMRevision) revByName.get("PR-2")).validateMergeHash(); + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(true)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH - ))); - - // validation should fail for this PR. - Exception abort = null; - try { - ((PullRequestSCMRevision)revByName.get("PR-3")).validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); + PullRequestSCMRevision.NOT_MERGEABLE_HASH))); + + // validation should fail for this PR. + Exception abort = null; + try { + ((PullRequestSCMRevision) revByName.get("PR-3")).validateMergeHash(); + } catch (Exception e) { + abort = e; } - - @Test - public void fetchSmokes_badUser() throws Exception { - // make it so PR-2 returns a file not found for user - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls/2")) + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchSmokes_badUser() throws Exception { + // make it so PR-2 returns a file not found for user + githubApi.stubFor( + get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-bad-user.json"))); - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls\\?state=open")) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-bad-user.json"))); + githubApi.stubFor( + get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls\\?state=open")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-bad-user.json"))); - - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - assertThat(byName.keySet(), containsInAnyOrder("PR-3", "PR-4", "master", "stephenc-patch-1")); - - // PR-2 fails to find user and throws file not found for user. - // Caught and handled by removing PR-2 but scan continues. - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(true)); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-bad-user.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + assertThat(byName.keySet(), containsInAnyOrder("PR-3", "PR-4", "master", "stephenc-patch-1")); + + // PR-2 fails to find user and throws file not found for user. + // Caught and handled by removing PR-2 but scan continues. + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(true)); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH - ))); - - // validation should fail for this PR. - Exception abort = null; - try { - ((PullRequestSCMRevision)revByName.get("PR-3")).validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); + PullRequestSCMRevision.NOT_MERGEABLE_HASH))); + + // validation should fail for this PR. + Exception abort = null; + try { + ((PullRequestSCMRevision) revByName.get("PR-3")).validateMergeHash(); + } catch (Exception e) { + abort = e; } - - @Test - public void fetchSmokes_badTarget() throws Exception { - // make it so the merge commit is not found returns 404 - // Causes PR 2 to fall back to null merge_commit_sha - // Then make it so refs/heads/master returns 404 for first call - // Causes PR 2 to fail because it cannot determine base commit. - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/commits/38814ca33833ff5583624c29f305be9133f27a40")) + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchSmokes_badTarget() throws Exception { + // make it so the merge commit is not found returns 404 + // Causes PR 2 to fall back to null merge_commit_sha + // Then make it so refs/heads/master returns 404 for first call + // Causes PR 2 to fail because it cannot determine base commit. + githubApi.stubFor( + get(urlMatching( + "(/api/v3)?/repos/cloudbeers/yolo/commits/38814ca33833ff5583624c29f305be9133f27a40")) .inScenario("PR 2 Merge 404") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-heads-master-notfound.json")) + .withStatus(404) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-heads-master-notfound.json")) .willSetStateTo(Scenario.STARTED)); - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/git/refs/heads/master")) + githubApi.stubFor( + get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/git/refs/heads/master")) .inScenario("PR 2 Master 404") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-heads-master-notfound.json")) + .withStatus(404) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-heads-master-notfound.json")) .willSetStateTo("Master 200")); - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/git/refs/heads/master")) + githubApi.stubFor( + get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/git/refs/heads/master")) .inScenario("PR 2 Master 404") .whenScenarioStateIs("Master 200") .willReturn( aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-heads-master.json")) + .withStatus(200) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-heads-master.json")) .willSetStateTo("Master 200")); - - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - assertThat(byName.keySet(), containsInAnyOrder("PR-3", "PR-4", "master", "stephenc-patch-1")); - - // PR-2 fails to find master and throws file not found for master. - // Caught and handled by removing PR-2 but scan continues. - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(true)); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + assertThat(byName.keySet(), containsInAnyOrder("PR-3", "PR-4", "master", "stephenc-patch-1")); + + // PR-2 fails to find master and throws file not found for master. + // Caught and handled by removing PR-2 but scan continues. + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(true)); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH - ))); - - // validation should fail for this PR. - Exception abort = null; - try { - ((PullRequestSCMRevision)revByName.get("PR-3")).validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); + PullRequestSCMRevision.NOT_MERGEABLE_HASH))); + + // validation should fail for this PR. + Exception abort = null; + try { + ((PullRequestSCMRevision) revByName.get("PR-3")).validateMergeHash(); + } catch (Exception e) { + abort = e; } - - @Test - public void fetchSmokesUnknownMergeable() throws Exception { - // make it so PR-2 always returns mergeable = null - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchSmokesUnknownMergeable() throws Exception { + // make it so PR-2 always returns mergeable = null + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/2")) .inScenario("Pull Request Merge Hash") .whenScenarioStateIs(Scenario.STARTED) .willReturn( aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-2-mergeable-null.json"))); - - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); - - assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); - assertThat(((PullRequestSCMHead)byName.get("PR-2")).isMerge(), is(true)); - assertThat(revByName.get("PR-2"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-2")), + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-2-mergeable-null.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat( + byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); + + assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); + assertThat(((PullRequestSCMHead) byName.get("PR-2")).isMerge(), is(true)); + assertThat( + revByName.get("PR-2"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-2")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - null - ))); - ((PullRequestSCMRevision)revByName.get("PR-2")).validateMergeHash(); - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(true)); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + null))); + ((PullRequestSCMRevision) revByName.get("PR-2")).validateMergeHash(); + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(true)); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", "c0e024f89969b976da165eecaa71e09dc60c3da1", - PullRequestSCMRevision.NOT_MERGEABLE_HASH - ))); - - // validation should fail for this PR. - Exception abort = null; - try { - ((PullRequestSCMRevision)revByName.get("PR-3")).validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); - + PullRequestSCMRevision.NOT_MERGEABLE_HASH))); + + // validation should fail for this PR. + Exception abort = null; + try { + ((PullRequestSCMRevision) revByName.get("PR-3")).validateMergeHash(); + } catch (Exception e) { + abort = e; + } + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchSmokesUnknownFork() throws Exception { + // make it so PR-2 always returns mergeable = null + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/4")) + .inScenario("Pull Request from Deleted Fork") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-4-deleted-fork.json"))); + githubApi.stubFor( + get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls\\?state=open")) + .inScenario("Pull Request from Deleted Fork") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-yolo-pulls-deleted-fork.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); } - @Test - public void fetchSmokesUnknownFork() throws Exception { - // make it so PR-2 always returns mergeable = null - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/4")) - .inScenario("Pull Request from Deleted Fork") - .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-4-deleted-fork.json"))); - githubApi.stubFor( - get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/pulls\\?state=open")) - .inScenario("Pull Request from Deleted Fork") - .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("body-yolo-pulls-deleted-fork.json"))); - - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - - assertThat(byName.keySet(), hasItem("PR-4")); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), nullValue()); + assertThat(byName.keySet(), hasItem("PR-4")); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), nullValue()); + } + + @Test + public void fetchAltConfig() throws Exception { + source.setBuildForkPRMerge(false); + source.setBuildForkPRHead(true); + source.setBuildOriginPRMerge(false); + source.setBuildOriginPRHead(false); + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + new SCMSourceCriteria() { + @Override + public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) + throws IOException { + return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; + } + }, + collector, + null, + null); + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); } - - @Test - public void fetchAltConfig() throws Exception { - source.setBuildForkPRMerge(false); - source.setBuildForkPRHead(true); - source.setBuildOriginPRMerge(false); - source.setBuildOriginPRHead(false); - SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); - source.fetch(new SCMSourceCriteria() { - @Override - public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) throws IOException { - return probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE; - } - }, collector, null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h: collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); - assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); - assertThat(((PullRequestSCMHead)byName.get("PR-2")).isMerge(), is(false)); - assertThat(revByName.get("PR-2"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-2")), + assertThat( + byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4", "master", "stephenc-patch-1")); + assertThat(byName.get("PR-2"), instanceOf(PullRequestSCMHead.class)); + assertThat(((PullRequestSCMHead) byName.get("PR-2")).isMerge(), is(false)); + assertThat( + revByName.get("PR-2"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-2")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1" - ))); - ((PullRequestSCMRevision)revByName.get("PR-2")).validateMergeHash(); - - assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); - assertThat(((PullRequestSCMHead)byName.get("PR-3")).isMerge(), is(false)); - assertThat(revByName.get("PR-3"), is(new PullRequestSCMRevision((PullRequestSCMHead)(byName.get("PR-3")), + "c0e024f89969b976da165eecaa71e09dc60c3da1"))); + ((PullRequestSCMRevision) revByName.get("PR-2")).validateMergeHash(); + + assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); + assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); + assertThat(((PullRequestSCMHead) byName.get("PR-3")).isMerge(), is(false)); + assertThat( + revByName.get("PR-3"), + is( + new PullRequestSCMRevision( + (PullRequestSCMHead) (byName.get("PR-3")), "8f1314fc3c8284d8c6d5886d473db98f2126071c", - "c0e024f89969b976da165eecaa71e09dc60c3da1" - ))); - - assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); - assertThat(((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), is("stephenc/jenkins-58450")); - - assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("master"), - hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c") - )); - assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); - assertThat(revByName.get("stephenc-patch-1"), - hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263") - )); - } - - @Test - public void fetchActions() throws Exception { - assertThat(source.fetchActions(null, null), Matchers.containsInAnyOrder( - Matchers.is( - new ObjectMetadataAction(null, "You only live once", "http://yolo.example.com") - ), - Matchers.is( - new GitHubDefaultBranch("cloudbeers", "yolo", "master") - ), - instanceOf(GitHubRepoMetadataAction.class), - Matchers.is(new GitHubLink("icon-github-repo", "https://github.com/cloudbeers/yolo")))); - } - - @Test - public void getTrustedRevisionReturnsRevisionIfRepoOwnerAndPullRequestBranchOwnerAreSameWithDifferentCase() throws Exception { - source.setBuildOriginPRHead(true); - PullRequestSCMRevision revision = createRevision("CloudBeers"); - assertThat(source.getTrustedRevision(revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), sameInstance(revision)); - } - - private PullRequestSCMRevision createRevision(String sourceOwner) { - PullRequestSCMHead head = new PullRequestSCMHead("", sourceOwner, "yolo", "", 0, new BranchSCMHead("non-null"), - SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.HEAD); - return new PullRequestSCMRevision(head, "non-null", null); - } - - @Test - public void doFillCredentials() throws Exception { - final GitHubSCMSource.DescriptorImpl d = - r.jenkins.getDescriptorByType(GitHubSCMSource.DescriptorImpl.class); - final WorkflowMultiBranchProject dummy = r.jenkins.add(new WorkflowMultiBranchProject(r.jenkins, "dummy"), "dummy"); - SecurityRealm realm = r.jenkins.getSecurityRealm(); - AuthorizationStrategy strategy = r.jenkins.getAuthorizationStrategy(); - try { - r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); - mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); - mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); - mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); - r.jenkins.setAuthorizationStrategy(mockStrategy); - try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - Matchers.is("does-not-exist")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); - } - try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - Matchers.is("does-not-exist")); - } - try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); - } - try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - Matchers.is("does-not-exist")); - } - } finally { - r.jenkins.setSecurityRealm(realm); - r.jenkins.setAuthorizationStrategy(strategy); - r.jenkins.remove(dummy); - } + "c0e024f89969b976da165eecaa71e09dc60c3da1"))); + + assertThat(byName.get("PR-4"), instanceOf(PullRequestSCMHead.class)); + assertThat( + ((SCMHeadOrigin.Fork) byName.get("PR-4").getOrigin()).getName(), + is("stephenc/jenkins-58450")); + + assertThat(byName.get("master"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("master"), + hasProperty("hash", is("8f1314fc3c8284d8c6d5886d473db98f2126071c"))); + assertThat(byName.get("stephenc-patch-1"), instanceOf(BranchSCMHead.class)); + assertThat( + revByName.get("stephenc-patch-1"), + hasProperty("hash", is("095e69602bb95a278505e937e41d505ac3cdd263"))); + } + + @Test + public void fetchActions() throws Exception { + assertThat( + source.fetchActions(null, null), + Matchers.containsInAnyOrder( + Matchers.is( + new ObjectMetadataAction(null, "You only live once", "http://yolo.example.com")), + Matchers.is(new GitHubDefaultBranch("cloudbeers", "yolo", "master")), + instanceOf(GitHubRepoMetadataAction.class), + Matchers.is(new GitHubLink("icon-github-repo", "https://github.com/cloudbeers/yolo")))); + } + + @Test + public void + getTrustedRevisionReturnsRevisionIfRepoOwnerAndPullRequestBranchOwnerAreSameWithDifferentCase() + throws Exception { + source.setBuildOriginPRHead(true); + PullRequestSCMRevision revision = createRevision("CloudBeers"); + assertThat( + source.getTrustedRevision( + revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + sameInstance(revision)); + } + + private PullRequestSCMRevision createRevision(String sourceOwner) { + PullRequestSCMHead head = + new PullRequestSCMHead( + "", + sourceOwner, + "yolo", + "", + 0, + new BranchSCMHead("non-null"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.HEAD); + return new PullRequestSCMRevision(head, "non-null", null); + } + + @Test + public void doFillCredentials() throws Exception { + final GitHubSCMSource.DescriptorImpl d = + r.jenkins.getDescriptorByType(GitHubSCMSource.DescriptorImpl.class); + final WorkflowMultiBranchProject dummy = + r.jenkins.add(new WorkflowMultiBranchProject(r.jenkins, "dummy"), "dummy"); + SecurityRealm realm = r.jenkins.getSecurityRealm(); + AuthorizationStrategy strategy = r.jenkins.getAuthorizationStrategy(); + try { + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); + mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); + mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); + mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); + r.jenkins.setAuthorizationStrategy(mockStrategy); + try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + Matchers.is("does-not-exist")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); + } + try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + Matchers.is("does-not-exist")); + } + try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, Matchers.is("")); + } + try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + Matchers.is("does-not-exist")); + } + } finally { + r.jenkins.setSecurityRealm(realm); + r.jenkins.setAuthorizationStrategy(strategy); + r.jenkins.remove(dummy); } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTraitsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTraitsTest.java index ddd0e531d..9c6f9ab25 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTraitsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTraitsTest.java @@ -1,10 +1,21 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.Map; - import jenkins.model.Jenkins; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import jenkins.scm.api.trait.SCMSourceTrait; @@ -19,2009 +30,1817 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - public class GitHubSCMSourceTraitsTest { - /** - * All tests in this class only use Jenkins for the extensions - */ - @ClassRule - public static JenkinsRule r = new JenkinsRule(); - - @Rule - public TestName currentTestName = new TestName(); - - private GitHubSCMSource load() { - return load(currentTestName.getMethodName()); - } - - private GitHubSCMSource load(String dataSet) { - return (GitHubSCMSource) Jenkins.XSTREAM2.fromXML( - getClass().getResource(getClass().getSimpleName() + "/" + dataSet + ".xml")); - } - - @Test - public void given__configuredInstance__when__uninstantiating__then__deprecatedFieldsIgnored() throws Exception { - GitHubSCMSource instance = new GitHubSCMSource("repo-owner", "repo", null, false); - instance.setId("test"); - - DescribableModel model = DescribableModel.of(GitHubSCMSource.class); - UninstantiatedDescribable ud = model.uninstantiate2(instance); - Map udMap = ud.toMap(); - GitHubSCMSource recreated = (GitHubSCMSource) model.instantiate(udMap); - - assertThat(DescribableModel.uninstantiate2_(recreated).toString(), - is("@github(id=test,repoOwner=repo-owner,repository=repo)") - ); - recreated.setBuildOriginBranch(true); - recreated.setBuildOriginBranchWithPR(false); - recreated.setBuildOriginPRHead(false); - recreated.setBuildOriginPRMerge(true); - recreated.setBuildForkPRHead(true); - recreated.setBuildForkPRMerge(false); - recreated.setIncludes("i*"); - recreated.setExcludes("production"); - recreated.setScanCredentialsId("foo"); - assertThat(DescribableModel.uninstantiate2_(recreated).toString(), - is("@github(" - + "credentialsId=foo," - + "id=test," - + "repoOwner=repo-owner," - + "repository=repo," - + "traits=[" - + "@gitHubBranchDiscovery$org.jenkinsci.plugins.github_branch_source.BranchDiscoveryTrait(strategyId=1), " - + "@gitHubPullRequestDiscovery$OriginPullRequestDiscoveryTrait(strategyId=1), " - + "@gitHubForkDiscovery$ForkPullRequestDiscoveryTrait(" - + "strategyId=2," - + "trust=@gitHubTrustPermissions$TrustPermission()), " - + "@headWildcardFilter$WildcardSCMHeadFilterTrait(excludes=production,includes=i*)])") - ); - } - - @Test - public void repositoryUrl() throws Exception { - GitHubSCMSource instance = load(); - - assertThat(instance.getId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("joseblas")); - assertThat(instance.getRepository(), is("jx")); - assertThat(instance.getCredentialsId(), is("abcd")); - assertThat(instance.getRepositoryUrl(), is("https://github.com/joseblas/jx")); - assertThat(instance.getTraits(), is(Collections.emptyList())); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(false)); - assertThat(instance.getBuildOriginBranchWithPR(), is(false)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(false)); - } - - @Test - public void modern() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is(nullValue())); - assertThat(instance.getRepositoryUrl(), is("https://github.com/cloudbeers/stunning-adventure")); - assertThat(instance.getTraits(), is(Collections.emptyList())); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(false)); - assertThat(instance.getBuildOriginBranchWithPR(), is(false)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(false)); - } - - @Test - public void basic_cloud() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + /** All tests in this class only use Jenkins for the extensions */ + @ClassRule public static JenkinsRule r = new JenkinsRule(); + + @Rule public TestName currentTestName = new TestName(); + + private GitHubSCMSource load() { + return load(currentTestName.getMethodName()); + } + + private GitHubSCMSource load(String dataSet) { + return (GitHubSCMSource) + Jenkins.XSTREAM2.fromXML( + getClass().getResource(getClass().getSimpleName() + "/" + dataSet + ".xml")); + } + + @Test + public void given__configuredInstance__when__uninstantiating__then__deprecatedFieldsIgnored() + throws Exception { + GitHubSCMSource instance = new GitHubSCMSource("repo-owner", "repo", null, false); + instance.setId("test"); + + DescribableModel model = DescribableModel.of(GitHubSCMSource.class); + UninstantiatedDescribable ud = model.uninstantiate2(instance); + Map udMap = ud.toMap(); + GitHubSCMSource recreated = (GitHubSCMSource) model.instantiate(udMap); + + assertThat( + DescribableModel.uninstantiate2_(recreated).toString(), + is("@github(id=test,repoOwner=repo-owner,repository=repo)")); + recreated.setBuildOriginBranch(true); + recreated.setBuildOriginBranchWithPR(false); + recreated.setBuildOriginPRHead(false); + recreated.setBuildOriginPRMerge(true); + recreated.setBuildForkPRHead(true); + recreated.setBuildForkPRMerge(false); + recreated.setIncludes("i*"); + recreated.setExcludes("production"); + recreated.setScanCredentialsId("foo"); + assertThat( + DescribableModel.uninstantiate2_(recreated).toString(), + is( + "@github(" + + "credentialsId=foo," + + "id=test," + + "repoOwner=repo-owner," + + "repository=repo," + + "traits=[" + + "@gitHubBranchDiscovery$org.jenkinsci.plugins.github_branch_source.BranchDiscoveryTrait(strategyId=1), " + + "@gitHubPullRequestDiscovery$OriginPullRequestDiscoveryTrait(strategyId=1), " + + "@gitHubForkDiscovery$ForkPullRequestDiscoveryTrait(" + + "strategyId=2," + + "trust=@gitHubTrustPermissions$TrustPermission()), " + + "@headWildcardFilter$WildcardSCMHeadFilterTrait(excludes=production,includes=i*)])")); + } + + @Test + public void repositoryUrl() throws Exception { + GitHubSCMSource instance = load(); + + assertThat(instance.getId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("joseblas")); + assertThat(instance.getRepository(), is("jx")); + assertThat(instance.getCredentialsId(), is("abcd")); + assertThat(instance.getRepositoryUrl(), is("https://github.com/joseblas/jx")); + assertThat(instance.getTraits(), is(Collections.emptyList())); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(false)); + assertThat(instance.getBuildOriginBranchWithPR(), is(false)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(false)); + } + + @Test + public void modern() throws Exception { + GitHubSCMSource instance = load(); + assertThat(instance.getId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is(nullValue())); + assertThat(instance.getRepositoryUrl(), is("https://github.com/cloudbeers/stunning-adventure")); + assertThat(instance.getTraits(), is(Collections.emptyList())); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(false)); + assertThat(instance.getBuildOriginBranchWithPR(), is(false)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(false)); + } + + @Test + public void basic_cloud() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://api.github.com" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void basic_server() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void basic_server() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://github.test/api/v3" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(false)); - assertThat(instance.getBuildOriginPRHead(), is(true)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(true)); - assertThat(instance.getBuildForkPRMerge(), is(false)); - } - - @Test - public void custom_checkout_credentials() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(false)); + assertThat(instance.getBuildOriginPRHead(), is(true)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(true)); + assertThat(instance.getBuildForkPRMerge(), is(false)); + } + + @Test + public void custom_checkout_credentials() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://github.test/api/v3" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ), - Matchers.allOf( - Matchers.instanceOf(SSHCheckoutTrait.class), - hasProperty("credentialsId", is("other-credentials")) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is("other-credentials")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(false)); - assertThat(instance.getBuildOriginPRHead(), is(true)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(true)); - assertThat(instance.getBuildForkPRMerge(), is(false)); - } - - @Issue("JENKINS-45467") - @Test - public void same_checkout_credentials() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))), + Matchers.allOf( + Matchers.instanceOf(SSHCheckoutTrait.class), + hasProperty("credentialsId", is("other-credentials"))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is("other-credentials")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(false)); + assertThat(instance.getBuildOriginPRHead(), is(true)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(true)); + assertThat(instance.getBuildForkPRMerge(), is(false)); + } + + @Issue("JENKINS-45467") + @Test + public void same_checkout_credentials() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://github.test/api/v3" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(false)); - assertThat(instance.getBuildOriginPRHead(), is(true)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(true)); - assertThat(instance.getBuildForkPRMerge(), is(false)); - } - - @Test - public void exclude_branches() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(false)); + assertThat(instance.getBuildOriginPRHead(), is(true)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(true)); + assertThat(instance.getBuildForkPRMerge(), is(false)); + } + + @Test + public void exclude_branches() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://api.github.com" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ), - Matchers.allOf( - instanceOf(WildcardSCMHeadFilterTrait.class), - hasProperty("includes", is("*")), - hasProperty("excludes", is("master")) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("master")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void limit_branches() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))), + Matchers.allOf( + instanceOf(WildcardSCMHeadFilterTrait.class), + hasProperty("includes", is("*")), + hasProperty("excludes", is("master"))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("master")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void limit_branches() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://api.github.com" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ), - Matchers.allOf( - instanceOf(WildcardSCMHeadFilterTrait.class), - hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void use_agent_checkout() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getId(), is("org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))), + Matchers.allOf( + instanceOf(WildcardSCMHeadFilterTrait.class), + hasProperty("includes", is("feature/*")), + hasProperty("excludes", is(""))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void use_agent_checkout() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getId(), + is( + "org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator" + "::https://api.github.com" + "::cloudbeers" + "::stunning-adventure")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ), - Matchers.allOf( - Matchers.instanceOf(SSHCheckoutTrait.class), - hasProperty("credentialsId", is(nullValue())) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void given__legacyCode__when__constructor_cloud__then__discoveryTraitDefaults() throws Exception { - GitHubSCMSource instance = new GitHubSCMSource("preserve-id", null, "SAME", - "e4d8c11a-0d24-472f-b86b-4b017c160e9a", "cloudbeers", "stunning-adventure"); - assertThat(instance.getId(), is("preserve-id")); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void given__legacyCode__when__constructor_server__then__discoveryTraitDefaults() throws Exception { - GitHubSCMSource instance = new GitHubSCMSource(null, "https://github.test/api/v3", - "8b2e4f77-39c5-41a9-b63b-8d367350bfdf", "e4d8c11a-0d24-472f-b86b-4b017c160e9a", - "cloudbeers", "stunning-adventure"); - assertThat(instance.getId(), is(notNullValue())); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - assertThat(instance.getRepoOwner(), is("cloudbeers")); - assertThat(instance.getRepository(), is("stunning-adventure")); - assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), - hasProperty("trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class)) - ), - Matchers.allOf( - Matchers.instanceOf(SSHCheckoutTrait.class), - hasProperty("credentialsId", is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")) - ) - ) - ); - // Legacy API - assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getBuildOriginBranch(), is(true)); - assertThat(instance.getBuildOriginBranchWithPR(), is(true)); - assertThat(instance.getBuildOriginPRHead(), is(false)); - assertThat(instance.getBuildOriginPRMerge(), is(false)); - assertThat(instance.getBuildForkPRHead(), is(false)); - assertThat(instance.getBuildForkPRMerge(), is(true)); - } - - @Test - public void given__instance__when__setTraits_empty__then__traitsEmpty() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Collections.emptyList()); - assertThat(instance.getTraits(), is(Collections.emptyList())); - } - - @Test - public void given__legacyCode__when__setBuildOriginBranch__then__traitsMaintained() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Collections.emptyList()); - assertThat(instance.getTraits(), is(Collections.emptyList())); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), is(Collections.emptyList())); - - instance.setBuildOriginBranchWithPR(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), is(Collections.emptyList())); - - instance.setBuildOriginBranchWithPR(true); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), is(Collections.emptyList())); - - instance.setBuildOriginBranchWithPR(true); - instance.setBuildOriginBranch(true); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranch(false); - assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); - instance.setBuildOriginBranchWithPR(false); - assertThat(instance.getTraits(), is(Collections.emptyList())); - } - - @Test - public void given__instance__when__setTraits__then__traitsSet() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList(new BranchDiscoveryTrait(BranchDiscoveryTrait.EXCLUDE_PRS), - new SSHCheckoutTrait("value"))); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(SSHCheckoutTrait.class), - hasProperty("credentialsId", is("value")) - ) - ) - ); - } - - @Test - public void given__instance__when__setApiUri__then__valueSet() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - assertThat("initial default", instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - instance.setApiUri("https://github.test/api/v3"); - assertThat(instance.getApiUri(), is("https://github.test/api/v3")); - instance.setApiUri(null); - assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); - } - - @Test - public void given__instance__when__setCredentials_empty__then__credentials_null() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); - } - - @Test - public void given__instance__when__setCredentials_null__then__credentials_null() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setCredentialsId(""); - assertThat(instance.getCredentialsId(), is(nullValue())); - } - - @Test - public void given__instance__when__setCredentials__then__credentials_set() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setCredentialsId("test"); - assertThat(instance.getCredentialsId(), is("test")); - } - - @Test - public void given__legacyCode_withoutExcludes__when__setIncludes_default__then__traitRemoved() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", "") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1)), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))), + Matchers.allOf( + Matchers.instanceOf(SSHCheckoutTrait.class), + hasProperty("credentialsId", is(nullValue()))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.ANONYMOUS)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void given__legacyCode__when__constructor_cloud__then__discoveryTraitDefaults() + throws Exception { + GitHubSCMSource instance = + new GitHubSCMSource( + "preserve-id", + null, + "SAME", + "e4d8c11a-0d24-472f-b86b-4b017c160e9a", + "cloudbeers", + "stunning-adventure"); + assertThat(instance.getId(), is("preserve-id")); + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is(GitHubSCMSource.DescriptorImpl.SAME)); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void given__legacyCode__when__constructor_server__then__discoveryTraitDefaults() + throws Exception { + GitHubSCMSource instance = + new GitHubSCMSource( + null, + "https://github.test/api/v3", + "8b2e4f77-39c5-41a9-b63b-8d367350bfdf", + "e4d8c11a-0d24-472f-b86b-4b017c160e9a", + "cloudbeers", + "stunning-adventure"); + assertThat(instance.getId(), is(notNullValue())); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + assertThat(instance.getRepoOwner(), is("cloudbeers")); + assertThat(instance.getRepository(), is("stunning-adventure")); + assertThat(instance.getCredentialsId(), is("e4d8c11a-0d24-472f-b86b-4b017c160e9a")); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategies", is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))), + hasProperty( + "trust", instanceOf(ForkPullRequestDiscoveryTrait.TrustPermission.class))), + Matchers.allOf( + Matchers.instanceOf(SSHCheckoutTrait.class), + hasProperty("credentialsId", is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf"))))); + // Legacy API + assertThat(instance.getCheckoutCredentialsId(), is("8b2e4f77-39c5-41a9-b63b-8d367350bfdf")); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat(instance.getBuildOriginBranch(), is(true)); + assertThat(instance.getBuildOriginBranchWithPR(), is(true)); + assertThat(instance.getBuildOriginPRHead(), is(false)); + assertThat(instance.getBuildOriginPRMerge(), is(false)); + assertThat(instance.getBuildForkPRHead(), is(false)); + assertThat(instance.getBuildForkPRMerge(), is(true)); + } + + @Test + public void given__instance__when__setTraits_empty__then__traitsEmpty() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits(Collections.emptyList()); + assertThat(instance.getTraits(), is(Collections.emptyList())); + } + + @Test + public void given__legacyCode__when__setBuildOriginBranch__then__traitsMaintained() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits(Collections.emptyList()); + assertThat(instance.getTraits(), is(Collections.emptyList())); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), is(Collections.emptyList())); + + instance.setBuildOriginBranchWithPR(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), is(Collections.emptyList())); + + instance.setBuildOriginBranchWithPR(true); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), is(Collections.emptyList())); + + instance.setBuildOriginBranchWithPR(true); + instance.setBuildOriginBranch(true); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranch(false); + assertThat(instance.getTraits(), contains(instanceOf(BranchDiscoveryTrait.class))); + instance.setBuildOriginBranchWithPR(false); + assertThat(instance.getTraits(), is(Collections.emptyList())); + } + + @Test + public void given__instance__when__setTraits__then__traitsSet() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(BranchDiscoveryTrait.EXCLUDE_PRS), + new SSHCheckoutTrait("value"))); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(SSHCheckoutTrait.class), hasProperty("credentialsId", is("value"))))); + } + + @Test + public void given__instance__when__setApiUri__then__valueSet() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + assertThat("initial default", instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + instance.setApiUri("https://github.test/api/v3"); + assertThat(instance.getApiUri(), is("https://github.test/api/v3")); + instance.setApiUri(null); + assertThat(instance.getApiUri(), is(GitHubSCMSource.GITHUB_URL)); + } + + @Test + public void given__instance__when__setCredentials_empty__then__credentials_null() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setCredentialsId(""); + assertThat(instance.getCredentialsId(), is(nullValue())); + } + + @Test + public void given__instance__when__setCredentials_null__then__credentials_null() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setCredentialsId(""); + assertThat(instance.getCredentialsId(), is(nullValue())); + } + + @Test + public void given__instance__when__setCredentials__then__credentials_set() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setCredentialsId("test"); + assertThat(instance.getCredentialsId(), is("test")); + } + + @Test + public void given__legacyCode_withoutExcludes__when__setIncludes_default__then__traitRemoved() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", ""))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ))); - instance.setIncludes("*"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(Matchers.hasItem( - instanceOf(WildcardSCMHeadFilterTrait.class) - ))); - } - - @Test - public void given__legacyCode_withoutExcludes__when__setIncludes_value__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", "") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + instance.setIncludes("*"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), not(Matchers.hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + } + + @Test + public void given__legacyCode_withoutExcludes__when__setIncludes_value__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", ""))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ))); - instance.setIncludes("bug/*"); - assertThat(instance.getIncludes(), is("bug/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + instance.setIncludes("bug/*"); + assertThat(instance.getIncludes(), is("bug/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("bug/*")), - hasProperty("excludes", is("")) - ))); - } - - @Test - public void given__legacyCode_withoutTrait__when__setIncludes_value__then__traitAdded() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(Matchers.hasItem( - instanceOf(WildcardSCMHeadFilterTrait.class) - ))); - instance.setIncludes("feature/*"); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + } + + @Test + public void given__legacyCode_withoutTrait__when__setIncludes_value__then__traitAdded() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList(new BranchDiscoveryTrait(true, false), new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), not(Matchers.hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + instance.setIncludes("feature/*"); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ))); - } - - @Test - public void given__legacyCode_withExcludes__when__setIncludes_default__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + } + + @Test + public void given__legacyCode_withExcludes__when__setIncludes_default__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("feature/ignore")) - ))); - instance.setIncludes("*"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + instance.setIncludes("*"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("*")), - hasProperty("excludes", is("feature/ignore")) - ))); - } - - @Test - public void given__legacyCode_withExcludes__when__setIncludes_value__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + } + + @Test + public void given__legacyCode_withExcludes__when__setIncludes_value__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("feature/ignore")) - ))); - instance.setIncludes("bug/*"); - assertThat(instance.getIncludes(), is("bug/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + instance.setIncludes("bug/*"); + assertThat(instance.getIncludes(), is("bug/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("bug/*")), - hasProperty("excludes", is("feature/ignore")) - ))); - } - - @Test - public void given__legacyCode_withoutIncludes__when__setExcludes_default__then__traitRemoved() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("*", "feature/ignore"), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + } + + @Test + public void given__legacyCode_withoutIncludes__when__setExcludes_default__then__traitRemoved() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("*", "feature/ignore"), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("*")), - hasProperty("excludes", is("feature/ignore")) - ))); - instance.setExcludes(""); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(Matchers.hasItem( - instanceOf(WildcardSCMHeadFilterTrait.class) - ))); - } - - @Test - public void given__legacyCode_withoutIncludes__when__setExcludes_value__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("*", "feature/ignore"), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + instance.setExcludes(""); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), not(Matchers.hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + } + + @Test + public void given__legacyCode_withoutIncludes__when__setExcludes_value__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("*", "feature/ignore"), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("*")), - hasProperty("excludes", is("feature/ignore")) - ))); - instance.setExcludes("bug/ignore"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("bug/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + instance.setExcludes("bug/ignore"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("bug/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("*")), - hasProperty("excludes", is("bug/ignore")) - ))); - } - - @Test - public void given__legacyCode_withoutTrait__when__setExcludes_value__then__traitAdded() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), not(Matchers.hasItem( - instanceOf(WildcardSCMHeadFilterTrait.class) - ))); - instance.setExcludes("feature/ignore"); - assertThat(instance.getIncludes(), is("*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("bug/ignore"))))); + } + + @Test + public void given__legacyCode_withoutTrait__when__setExcludes_value__then__traitAdded() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList(new BranchDiscoveryTrait(true, false), new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), not(Matchers.hasItem(instanceOf(WildcardSCMHeadFilterTrait.class)))); + instance.setExcludes("feature/ignore"); + assertThat(instance.getIncludes(), is("*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("*")), - hasProperty("excludes", is("feature/ignore")) - ))); - } - - @Test - public void given__legacyCode_withIncludes__when__setExcludes_default__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + } + + @Test + public void given__legacyCode_withIncludes__when__setExcludes_default__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", "feature/ignore"), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("feature/ignore")) - ))); - instance.setExcludes(""); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is("feature/ignore"))))); + instance.setExcludes(""); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ))); - } - - @Test - public void given__legacyCode_withIncludes__when__setExcludes_value__then__traitUpdated() { - GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); - instance.setTraits(Arrays.asList( - new BranchDiscoveryTrait(true, false), - new WildcardSCMHeadFilterTrait("feature/*", ""), - new SSHCheckoutTrait("someValue") - )); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + } + + @Test + public void given__legacyCode_withIncludes__when__setExcludes_value__then__traitUpdated() { + GitHubSCMSource instance = new GitHubSCMSource("testing", "test-repo"); + instance.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, false), + new WildcardSCMHeadFilterTrait("feature/*", ""), + new SSHCheckoutTrait("someValue"))); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("")) - ))); - instance.setExcludes("feature/ignore"); - assertThat(instance.getIncludes(), is("feature/*")); - assertThat(instance.getExcludes(), is("feature/ignore")); - assertThat(instance.getTraits(), Matchers.hasItem(allOf( + hasProperty("excludes", is(""))))); + instance.setExcludes("feature/ignore"); + assertThat(instance.getIncludes(), is("feature/*")); + assertThat(instance.getExcludes(), is("feature/ignore")); + assertThat( + instance.getTraits(), + Matchers.hasItem( + allOf( instanceOf(WildcardSCMHeadFilterTrait.class), hasProperty("includes", is("feature/*")), - hasProperty("excludes", is("feature/ignore")) - ))); - } - - @Test - public void build_000000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - empty() - ); - } - - @Test - public void build_000001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_000010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_000011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_000100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_000101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_000110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_000111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_001000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_001001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_001010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_001011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_001100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_001101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_001110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_001111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_010000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ) - ) - ); - } - - @Test - public void build_010001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_010010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_010011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_010100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_010101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_010110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_010111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_011000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_011001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_011010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_011011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_011100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_011101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_011110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_011111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(false)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_100000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ) - ) - ); - } - - @Test - public void build_100001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_100010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_100011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_100100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_100101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_100110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_100111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_101000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_101001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_101010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_101011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_101100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_101101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_101110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_101111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(false)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_110000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - contains( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ) - ) - ); - } - - @Test - public void build_110001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_110010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_110011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_110100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_110101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_110110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_110111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_111000() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_111001() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_111010() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_111011() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_111100() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } - - @Test - public void build_111101() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(2)) - ) - ) - ); - } - - @Test - public void build_111110() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(1)) - ) - ) - ); - } - - @Test - public void build_111111() throws Exception { - GitHubSCMSource instance = load(); - assertThat(instance.getTraits(), - containsInAnyOrder( - Matchers.allOf( - instanceOf(BranchDiscoveryTrait.class), - hasProperty("buildBranch", is(true)), - hasProperty("buildBranchesWithPR", is(true)) - ), - Matchers.allOf( - instanceOf(OriginPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ), - Matchers.allOf( - instanceOf(ForkPullRequestDiscoveryTrait.class), - hasProperty("strategyId", is(3)) - ) - ) - ); - } + hasProperty("excludes", is("feature/ignore"))))); + } + + @Test + public void build_000000() throws Exception { + GitHubSCMSource instance = load(); + assertThat(instance.getTraits(), empty()); + } + + @Test + public void build_000001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_000010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_000011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_000100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_000101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_000110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_000111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_001000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_001001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_001010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_001011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_001100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_001101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_001110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_001111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_010000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))))); + } + + @Test + public void build_010001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_010010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_010011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_010100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_010101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_010110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_010111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_011000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_011001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_011010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_011011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_011100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_011101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_011110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_011111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(false)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_100000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))))); + } + + @Test + public void build_100001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_100010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_100011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_100100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_100101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_100110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_100111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_101000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_101001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_101010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_101011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_101100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_101101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_101110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_101111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(false))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_110000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + contains( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))))); + } + + @Test + public void build_110001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_110010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_110011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_110100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_110101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_110110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_110111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_111000() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_111001() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_111010() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_111011() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_111100() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } + + @Test + public void build_111101() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(2))))); + } + + @Test + public void build_111110() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(1))))); + } + + @Test + public void build_111111() throws Exception { + GitHubSCMSource instance = load(); + assertThat( + instance.getTraits(), + containsInAnyOrder( + Matchers.allOf( + instanceOf(BranchDiscoveryTrait.class), + hasProperty("buildBranch", is(true)), + hasProperty("buildBranchesWithPR", is(true))), + Matchers.allOf( + instanceOf(OriginPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))), + Matchers.allOf( + instanceOf(ForkPullRequestDiscoveryTrait.class), + hasProperty("strategyId", is(3))))); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitSCMSourceBase.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitSCMSourceBase.java index 9595427ab..b576fb005 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitSCMSourceBase.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitSCMSourceBase.java @@ -1,27 +1,30 @@ package org.jenkinsci.plugins.github_branch_source; +import java.util.Arrays; +import java.util.EnumSet; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import org.junit.Before; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import java.util.Arrays; -import java.util.EnumSet; - - public class GitSCMSourceBase extends AbstractGitHubWireMockTest { - GitHubSCMSource source; - GitHub github; - GHRepository repo; + GitHubSCMSource source; + GitHub github; + GHRepository repo; - @Before - public void setupSourceTests() throws Exception { - super.prepareMockGitHub(); - // force apiUri to point to test server - source.forceApiUri("http://localhost:" + githubApi.port()); - source.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, true), new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustContributors()))); - github = Connector.connect("http://localhost:" + githubApi.port(), null); - repo = github.getRepository("cloudbeers/yolo"); - } + @Before + public void setupSourceTests() throws Exception { + super.prepareMockGitHub(); + // force apiUri to point to test server + source.forceApiUri("http://localhost:" + githubApi.port()); + source.setTraits( + Arrays.asList( + new BranchDiscoveryTrait(true, true), + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()))); + github = Connector.connect("http://localhost:" + githubApi.port(), null); + repo = github.getRepository("cloudbeers/yolo"); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsAppInstallationTokenTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsAppInstallationTokenTest.java index 46504c548..814b738dc 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsAppInstallationTokenTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsAppInstallationTokenTest.java @@ -1,71 +1,83 @@ package org.jenkinsci.plugins.github_branch_source; -import hudson.util.Secret; -import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import hudson.util.Secret; import java.time.Duration; import java.time.Instant; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import org.junit.Test; public class GithubAppCredentialsAppInstallationTokenTest { - @Test - public void testAppInstallationTokenStale() throws Exception { + @Test + public void testAppInstallationTokenStale() throws Exception { - GitHubAppCredentials.AppInstallationToken token; - long now; + GitHubAppCredentials.AppInstallationToken token; + long now; - now = Instant.now().getEpochSecond(); - Secret secret = Secret.fromString("secret-token"); - token = new GitHubAppCredentials.AppInstallationToken(secret, now); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); + now = Instant.now().getEpochSecond(); + Secret secret = Secret.fromString("secret-token"); + token = new GitHubAppCredentials.AppInstallationToken(secret, now); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); - now = Instant.now().getEpochSecond(); - token = new GitHubAppCredentials.AppInstallationToken(secret, - now + Duration.ofMinutes(15).getSeconds()); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); + now = Instant.now().getEpochSecond(); + token = + new GitHubAppCredentials.AppInstallationToken( + secret, now + Duration.ofMinutes(15).getSeconds()); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); - now = Instant.now().getEpochSecond(); - token = new GitHubAppCredentials.AppInstallationToken(secret, + now = Instant.now().getEpochSecond(); + token = + new GitHubAppCredentials.AppInstallationToken( + secret, now + GitHubAppCredentials.AppInstallationToken.STALE_BEFORE_EXPIRATION_SECONDS + 2); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + equalTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS)); - now = Instant.now().getEpochSecond(); - token = new GitHubAppCredentials.AppInstallationToken(secret, - now + GitHubAppCredentials.AppInstallationToken.STALE_BEFORE_EXPIRATION_SECONDS + Duration - .ofMinutes(7) - .getSeconds()); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), - equalTo(now + Duration.ofMinutes(7).getSeconds())); + now = Instant.now().getEpochSecond(); + token = + new GitHubAppCredentials.AppInstallationToken( + secret, + now + + GitHubAppCredentials.AppInstallationToken.STALE_BEFORE_EXPIRATION_SECONDS + + Duration.ofMinutes(7).getSeconds()); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), equalTo(now + Duration.ofMinutes(7).getSeconds())); - now = Instant.now().getEpochSecond(); - token = new GitHubAppCredentials.AppInstallationToken(secret, - now + Duration.ofMinutes(90).getSeconds()); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), - equalTo(now + GitHubAppCredentials.AppInstallationToken.STALE_AFTER_SECONDS + 1)); + now = Instant.now().getEpochSecond(); + token = + new GitHubAppCredentials.AppInstallationToken( + secret, now + Duration.ofMinutes(90).getSeconds()); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + equalTo(now + GitHubAppCredentials.AppInstallationToken.STALE_AFTER_SECONDS + 1)); - long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; - try { - // Should revert to 1 second minimum - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = -10; + long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; + try { + // Should revert to 1 second minimum + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = -10; - now = Instant.now().getEpochSecond(); - token = new GitHubAppCredentials.AppInstallationToken(secret, now); - assertThat(token.isStale(), is(false)); - assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + 1)); + now = Instant.now().getEpochSecond(); + token = new GitHubAppCredentials.AppInstallationToken(secret, now); + assertThat(token.isStale(), is(false)); + assertThat(token.getTokenStaleEpochSeconds(), equalTo(now + 1)); - // Verify goes stale - Thread.sleep(1000); - assertThat(token.isStale(), is(true)); - } finally { - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; - } + // Verify goes stale + Thread.sleep(1000); + assertThat(token.isStale(), is(true)); + } finally { + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; } -} \ No newline at end of file + } +} diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java index 660ad49ed..d8a0cb3b5 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubAppCredentialsTest.java @@ -1,5 +1,9 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.hamcrest.Matchers.*; +import static org.jenkinsci.plugins.github_branch_source.Connector.createGitHubBuilder; + import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; @@ -13,18 +17,6 @@ import hudson.model.Slave; import hudson.model.StringParameterDefinition; import hudson.util.Secret; -import jenkins.plugins.git.GitSampleRepoRule; -import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; -import org.jenkinsci.plugins.workflow.job.WorkflowJob; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.authorization.AuthorizationProvider; - import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeFormatter; @@ -38,434 +30,513 @@ import java.util.logging.LogRecord; import java.util.logging.SimpleFormatter; import java.util.stream.Collectors; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.hamcrest.Matchers.*; -import static org.jenkinsci.plugins.github_branch_source.Connector.createGitHubBuilder; +import jenkins.plugins.git.GitSampleRepoRule; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.authorization.AuthorizationProvider; public class GithubAppCredentialsTest extends AbstractGitHubWireMockTest { - private static Slave agent; - private static final String myAppCredentialsId = "myAppCredentialsId"; - private static CredentialsStore store; - private static GitHubAppCredentials appCredentials; - private static LogRecorder logRecorder; - - // https://stackoverflow.com/a/22176759/4951015 - public static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" + - // Windows line ending - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n" + - // This should also work - "5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r" + - "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n" + - "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n" + - "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n" + - "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n" + - "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n" + - "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n" + - "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n" + - "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n" + - "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n" + - "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n" + - "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n" + - "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n" + - "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n" + - "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n" + - "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n" + - "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n" + - "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n" + - "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n" + - "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n" + - "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n" + - "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n" + - "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n" + - "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n" + - "Nw9bewRvqjySBlDJ9/aNSeEY\n" + - "-----END PRIVATE KEY-----"; - - @Rule - public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); - - @BeforeClass - public static void setUpJenkins() throws Exception { - //Add credential (Must have valid private key for Jwt to work, but App doesn't have to actually exist) - store = CredentialsProvider.lookupStores(r.jenkins).iterator().next(); - appCredentials = new GitHubAppCredentials( - CredentialsScope.GLOBAL, myAppCredentialsId, "sample", "54321", Secret.fromString(PKCS8_PRIVATE_KEY)); - appCredentials.setOwner("cloudbeers"); - store.addCredentials(Domain.global(), appCredentials); - - // Add agent - agent = r.createOnlineSlave(); - agent.setLabelString("my-agent"); - - // Would use LoggerRule, but need to get agent logs as well - LogRecorderManager mgr = r.jenkins.getLog(); - logRecorder = new LogRecorder(GitHubAppCredentials.class.getName()); - mgr.logRecorders.put(GitHubAppCredentials.class.getName(), logRecorder); - LogRecorder.Target t = new LogRecorder.Target(GitHubAppCredentials.class.getName(), Level.FINE); - logRecorder.targets.add(t); - logRecorder.save(); - t.enable(); - } - - @Before - public void setUpWireMock() throws Exception { - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); - - // During credential refreshes we should never check rate_limit - githubApi.stubFor( - get(urlEqualTo("/rate_limit")) - .willReturn(aResponse() - .withStatus(500) - )); - - //Add wiremock responses for App, App Installation, and App Installation Token - githubApi.stubFor( - get(urlEqualTo("/app")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../AppCredentials/files/body-mapping-githubapp-app.json"))); - githubApi.stubFor( - get(urlEqualTo("/app/installations")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../AppCredentials/files/body-mapping-githubapp-installations.json"))); - - final String scenarioName = "credentials-accesstoken"; - - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("Started") - .willSetStateTo("1") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\n" + - " \"token\": \"super-secret-token\",\n" + - // This token will go stale at the soonest allowed time but will not expire for the duration of the test - " \"expires_at\": \"" + printDate(new Date(System.currentTimeMillis() + Duration.ofMinutes(10).toMillis())) + "\"" + // 2019-08-10T05:54:58Z + private static Slave agent; + private static final String myAppCredentialsId = "myAppCredentialsId"; + private static CredentialsStore store; + private static GitHubAppCredentials appCredentials; + private static LogRecorder logRecorder; + + // https://stackoverflow.com/a/22176759/4951015 + public static final String PKCS8_PRIVATE_KEY = + "-----BEGIN PRIVATE KEY-----\n" + + + // Windows line ending + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n" + + + // This should also work + "5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r" + + "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n" + + "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n" + + "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n" + + "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n" + + "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n" + + "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n" + + "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n" + + "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n" + + "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n" + + "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n" + + "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n" + + "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n" + + "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n" + + "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n" + + "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n" + + "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n" + + "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n" + + "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n" + + "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n" + + "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n" + + "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n" + + "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n" + + "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n" + + "Nw9bewRvqjySBlDJ9/aNSeEY\n" + + "-----END PRIVATE KEY-----"; + + @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); + + @BeforeClass + public static void setUpJenkins() throws Exception { + // Add credential (Must have valid private key for Jwt to work, but App doesn't have to actually + // exist) + store = CredentialsProvider.lookupStores(r.jenkins).iterator().next(); + appCredentials = + new GitHubAppCredentials( + CredentialsScope.GLOBAL, + myAppCredentialsId, + "sample", + "54321", + Secret.fromString(PKCS8_PRIVATE_KEY)); + appCredentials.setOwner("cloudbeers"); + store.addCredentials(Domain.global(), appCredentials); + + // Add agent + agent = r.createOnlineSlave(); + agent.setLabelString("my-agent"); + + // Would use LoggerRule, but need to get agent logs as well + LogRecorderManager mgr = r.jenkins.getLog(); + logRecorder = new LogRecorder(GitHubAppCredentials.class.getName()); + mgr.logRecorders.put(GitHubAppCredentials.class.getName(), logRecorder); + LogRecorder.Target t = new LogRecorder.Target(GitHubAppCredentials.class.getName(), Level.FINE); + logRecorder.targets.add(t); + logRecorder.save(); + t.enable(); + } + + @Before + public void setUpWireMock() throws Exception { + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver); + + // During credential refreshes we should never check rate_limit + githubApi.stubFor(get(urlEqualTo("/rate_limit")).willReturn(aResponse().withStatus(500))); + + // Add wiremock responses for App, App Installation, and App Installation Token + githubApi.stubFor( + get(urlEqualTo("/app")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../AppCredentials/files/body-mapping-githubapp-app.json"))); + githubApi.stubFor( + get(urlEqualTo("/app/installations")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../AppCredentials/files/body-mapping-githubapp-installations.json"))); + + final String scenarioName = "credentials-accesstoken"; + + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("Started") + .willSetStateTo("1") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody( + "{\n" + + " \"token\": \"super-secret-token\",\n" + + + // This token will go stale at the soonest allowed time but will not + // expire for the duration of the test + " \"expires_at\": \"" + + printDate( + new Date( + System.currentTimeMillis() + Duration.ofMinutes(10).toMillis())) + + "\"" + + // 2019-08-10T05:54:58Z "}"))); - // Force an error to test fallback refreshing from agent - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("1") - .willSetStateTo("2") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withStatus(404) - .withStatusMessage("404 Not Found") - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\"message\": \"File not found\"}"))); - - // Force an error to test fallback to returning unexpired token on agent - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("2") - .willSetStateTo("3") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withStatus(404) - .withStatusMessage("404 Not Found") - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\"message\": \"File not found\"}"))); - - // return an expired token on controller - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("3") - .willSetStateTo("4") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\n" + - " \"token\": \"super-secret-token\",\n" + - // token is already expired, but will not go stale for at least the minimum time - // This is a valid scenario - clocks are not always properly synchronized. - " \"expires_at\": \"" + printDate(new Date()) + "\"" + // 2019-08-10T05:54:58Z + // Force an error to test fallback refreshing from agent + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("1") + .willSetStateTo("2") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withStatus(404) + .withStatusMessage("404 Not Found") + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody("{\"message\": \"File not found\"}"))); + + // Force an error to test fallback to returning unexpired token on agent + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("2") + .willSetStateTo("3") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withStatus(404) + .withStatusMessage("404 Not Found") + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody("{\"message\": \"File not found\"}"))); + + // return an expired token on controller + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("3") + .willSetStateTo("4") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody( + "{\n" + + " \"token\": \"super-secret-token\",\n" + + + // token is already expired, but will not go stale for at least the + // minimum time + // This is a valid scenario - clocks are not always properly + // synchronized. + " \"expires_at\": \"" + + printDate(new Date()) + + "\"" + + // 2019-08-10T05:54:58Z "}"))); - // Force an error to test non-fallback scenario and refreshing on agent - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("4") - .willSetStateTo("5") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withStatus(404) - .withStatusMessage("404 Not Found") - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\"message\": \"File not found\"}"))); - - // Valid token retirieved on agent - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("5") - .willSetStateTo("6") - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\n" + - " \"token\": \"super-secret-token\",\n" + - " \"expires_at\": \"" + printDate(new Date()) + "\"" + // 2019-08-10T05:54:58Z + // Force an error to test non-fallback scenario and refreshing on agent + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("4") + .willSetStateTo("5") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withStatus(404) + .withStatusMessage("404 Not Found") + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody("{\"message\": \"File not found\"}"))); + + // Valid token retirieved on agent + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("5") + .willSetStateTo("6") + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody( + "{\n" + + " \"token\": \"super-secret-token\",\n" + + " \"expires_at\": \"" + + printDate(new Date()) + + "\"" + + // 2019-08-10T05:54:58Z "}"))); - // Valid token retirieved on controller - githubApi.stubFor( - post(urlEqualTo("/app/installations/654321/access_tokens")) - .inScenario(scenarioName) - .whenScenarioStateIs("6") - .willSetStateTo("7") // setting this to non-existant state means any extra requests will fail - .withRequestBody( - equalToJson( - "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", true, false)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("{\n" + - " \"token\": \"super-secret-token\",\n" + - " \"expires_at\": \"" + printDate(new Date()) + "\"" + // 2019-08-10T05:54:58Z + // Valid token retirieved on controller + githubApi.stubFor( + post(urlEqualTo("/app/installations/654321/access_tokens")) + .inScenario(scenarioName) + .whenScenarioStateIs("6") + .willSetStateTo( + "7") // setting this to non-existant state means any extra requests will fail + .withRequestBody( + equalToJson( + "{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}", + true, + false)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBody( + "{\n" + + " \"token\": \"super-secret-token\",\n" + + " \"expires_at\": \"" + + printDate(new Date()) + + "\"" + + // 2019-08-10T05:54:58Z "}"))); + } + + @Test + public void testProviderRefresh() throws Exception { + long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; + try { + appCredentials.setApiUri(githubApi.baseUrl()); + + // We want to demonstrate successful caching without waiting for the default 1 minute + // Must set this to a large enough number to avoid flaky test + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; + + // Ensure we are working from sufficiently clean cache state + Thread.sleep( + Duration.ofSeconds( + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) + .toMillis()); + + AuthorizationProvider provider = appCredentials.getAuthorizationProvider(); + GitHub githubInstance = + createGitHubBuilder(githubApi.baseUrl()).withAuthorizationProvider(provider).build(); + + // First Checkout on controller should use cached + provider.getEncodedAuthorization(); + // Multiple checkouts in quick succession should use cached token + provider.getEncodedAuthorization(); + Thread.sleep( + Duration.ofSeconds( + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) + .toMillis()); + // Checkout after token is stale refreshes - fallback due to unexpired token + provider.getEncodedAuthorization(); + // Checkout after error will refresh again on controller - new token expired but not stale + provider.getEncodedAuthorization(); + Thread.sleep( + Duration.ofSeconds( + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) + .toMillis()); + // Checkout after token is stale refreshes - error on controller is not catastrophic + provider.getEncodedAuthorization(); + // Checkout after error will refresh again on controller - new token expired but not stale + provider.getEncodedAuthorization(); + // Multiple checkouts in quick succession should use cached token + provider.getEncodedAuthorization(); + + List credentialsLog = getOutputLines(); + + // Verify correct messages from GitHubAppCredential logger indicating token was retrieved on + // agent + assertThat( + "Creds should cache on master", + credentialsLog, + contains( + // refresh on controller + "Generating App Installation Token for app ID 54321", + // next call uses cached token + // sleep and then refresh stale token + "Generating App Installation Token for app ID 54321", + // next call (error forced by wiremock) + "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", + // next call refreshes the still stale token + "Generating App Installation Token for app ID 54321", + // sleep and then refresh stale token hits another error forced by wiremock + "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", + // next call refreshes the still stale token + "Generating App Installation Token for app ID 54321" + // next call uses cached token + )); + + // Getting the token for via AuthorizationProvider on controller should not check rate_limit + githubApi.verify( + 0, + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/rate_limit"))); + + } finally { + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; + logRecorder.doClear(); } - - @Test - public void testProviderRefresh() throws Exception { - long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; - try { - appCredentials.setApiUri(githubApi.baseUrl()); - - // We want to demonstrate successful caching without waiting for the default 1 minute - // Must set this to a large enough number to avoid flaky test - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; - - // Ensure we are working from sufficiently clean cache state - Thread.sleep(Duration.ofSeconds(GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2).toMillis()); - - AuthorizationProvider provider = appCredentials.getAuthorizationProvider(); - GitHub githubInstance = createGitHubBuilder(githubApi.baseUrl()) - .withAuthorizationProvider(provider).build(); - - // First Checkout on controller should use cached - provider.getEncodedAuthorization(); - // Multiple checkouts in quick succession should use cached token - provider.getEncodedAuthorization(); - Thread.sleep(Duration.ofSeconds(GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2).toMillis()); - // Checkout after token is stale refreshes - fallback due to unexpired token - provider.getEncodedAuthorization(); - // Checkout after error will refresh again on controller - new token expired but not stale - provider.getEncodedAuthorization(); - Thread.sleep(Duration.ofSeconds(GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2).toMillis()); - // Checkout after token is stale refreshes - error on controller is not catastrophic - provider.getEncodedAuthorization(); - // Checkout after error will refresh again on controller - new token expired but not stale - provider.getEncodedAuthorization(); - // Multiple checkouts in quick succession should use cached token - provider.getEncodedAuthorization(); - - List credentialsLog = getOutputLines(); - - //Verify correct messages from GitHubAppCredential logger indicating token was retrieved on agent - assertThat("Creds should cache on master", - credentialsLog, contains( - // refresh on controller - "Generating App Installation Token for app ID 54321", - // next call uses cached token - // sleep and then refresh stale token - "Generating App Installation Token for app ID 54321", - // next call (error forced by wiremock) - "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", - // next call refreshes the still stale token - "Generating App Installation Token for app ID 54321", - // sleep and then refresh stale token hits another error forced by wiremock - "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", - // next call refreshes the still stale token - "Generating App Installation Token for app ID 54321" - // next call uses cached token - )); - - // Getting the token for via AuthorizationProvider on controller should not check rate_limit - githubApi.verify(0, RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/rate_limit"))); - - } finally { - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; - logRecorder.doClear(); - } + } + + @Test + public void testAgentRefresh() throws Exception { + long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; + try { + appCredentials.setApiUri(githubApi.baseUrl()); + + // We want to demonstrate successful caching without waiting for a the default 1 minute + // Must set this to a large enough number to avoid flaky test + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; + + // Ensure we are working from sufficiently clean cache state + Thread.sleep( + Duration.ofSeconds( + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) + .toMillis()); + + final String gitCheckoutStep = + String.format(" git url: REPO, credentialsId: '%s'", myAppCredentialsId); + + final String jenkinsfile = + String.join( + "\n", + "// run checkout several times", + "node ('my-agent') {", + " echo 'First Checkout on agent should use cached token passed via remoting'", + gitCheckoutStep, + " echo 'Multiple checkouts in quick succession should use cached token'", + gitCheckoutStep, + " sleep " + + (GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2), + " echo 'Checkout after token is stale refreshes via remoting - fallback due to unexpired token'", + gitCheckoutStep, + " echo 'Checkout after error will refresh again on controller - new token expired but not stale'", + gitCheckoutStep, + " sleep " + + (GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2), + " echo 'Checkout after token is stale refreshes via remoting - error on controller is not catastrophic'", + gitCheckoutStep, + " echo 'Checkout after error will refresh again on controller - new token expired but not stale'", + gitCheckoutStep, + " echo 'Multiple checkouts in quick succession should use cached token'", + gitCheckoutStep, + "}"); + + // Create a repo with the above Jenkinsfile + sampleRepo.init(); + sampleRepo.write("Jenkinsfile", jenkinsfile); + sampleRepo.git("add", "Jenkinsfile"); + sampleRepo.git("commit", "--message=init"); + + // Create a pipeline job that points the above repo + WorkflowJob job = r.createProject(WorkflowJob.class, "test-creds"); + job.setDefinition(new CpsFlowDefinition(jenkinsfile, false)); + job.addProperty( + new ParametersDefinitionProperty( + new StringParameterDefinition("REPO", sampleRepo.toString()))); + WorkflowRun run = job.scheduleBuild2(0).waitForStart(); + r.waitUntilNoActivity(); + + System.out.println(JenkinsRule.getLog(run)); + + List credentialsLog = getOutputLines(); + + // Verify correct messages from GitHubAppCredential logger indicating token was retrieved on + // agent + assertThat( + "Creds should cache on master, pass to agent, and refresh agent from master once", + credentialsLog, + contains( + // (agent log added out of order, see below) + "Generating App Installation Token for app ID 54321 on agent", // 1 + "Failed to generate new GitHub App Installation Token for app ID 54321 on agent: cached token is stale but has not expired", // 2 + "Generating App Installation Token for app ID 54321 on agent", // 3 + // node ('my-agent') { + // checkout scm + "Generating App Installation Token for app ID 54321", + // checkout scm + // (No token generation) + // sleep + // checkout scm + "Generating App Installation Token for app ID 54321", + // (error forced by wiremock) + "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", + // (error forced by wiremock - failed refresh on the agent) + // "Generating App Installation Token for app ID 54321 on agent", // 1 + "Generating App Installation Token for app ID 54321 for agent", + // (agent log added out of order) "Keeping cached GitHub App Installation Token for + // app ID 54321 on agent: token is stale but has not expired", // 2 + // checkout scm - refresh on controller + "Generating App Installation Token for app ID 54321", + // sleep + // checkout scm + "Generating App Installation Token for app ID 54321", + // (error forced by wiremock) + "Failed to update stale GitHub App installation token for app ID 54321 before sending to agent", + // "Generating App Installation Token for app ID 54321 on agent", // 3 + "Generating App Installation Token for app ID 54321 for agent", + // checkout scm - refresh on controller + "Generating App Installation Token for app ID 54321" + // checkout scm + // (No token generation) + )); + + // Check success after output. Output will be more informative if something goes wrong. + assertThat(run.getResult(), equalTo(Result.SUCCESS)); + + // Getting the token for via AuthorizationProvider on controller should not check rate_limit + // Getting the token for agents via remoting to the controller should not check rate_limit + githubApi.verify( + 0, + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/rate_limit"))); + } finally { + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; + logRecorder.doClear(); } - - @Test - public void testAgentRefresh() throws Exception { - long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; - try { - appCredentials.setApiUri(githubApi.baseUrl()); - - // We want to demonstrate successful caching without waiting for a the default 1 minute - // Must set this to a large enough number to avoid flaky test - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; - - // Ensure we are working from sufficiently clean cache state - Thread.sleep(Duration.ofSeconds(GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2).toMillis()); - - final String gitCheckoutStep = String.format( - " git url: REPO, credentialsId: '%s'", - myAppCredentialsId); - - final String jenkinsfile = String.join( - "\n", - "// run checkout several times", - "node ('my-agent') {", - " echo 'First Checkout on agent should use cached token passed via remoting'", - gitCheckoutStep, - " echo 'Multiple checkouts in quick succession should use cached token'", - gitCheckoutStep, - " sleep " + (GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2), - " echo 'Checkout after token is stale refreshes via remoting - fallback due to unexpired token'", - gitCheckoutStep, - " echo 'Checkout after error will refresh again on controller - new token expired but not stale'", - gitCheckoutStep, - " sleep " + (GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2), - " echo 'Checkout after token is stale refreshes via remoting - error on controller is not catastrophic'", - gitCheckoutStep, - " echo 'Checkout after error will refresh again on controller - new token expired but not stale'", - gitCheckoutStep, - " echo 'Multiple checkouts in quick succession should use cached token'", - gitCheckoutStep, - "}"); - - - // Create a repo with the above Jenkinsfile - sampleRepo.init(); - sampleRepo.write("Jenkinsfile", jenkinsfile); - sampleRepo.git("add", "Jenkinsfile"); - sampleRepo.git("commit", "--message=init"); - - // Create a pipeline job that points the above repo - WorkflowJob job = r.createProject(WorkflowJob.class, "test-creds"); - job.setDefinition(new CpsFlowDefinition(jenkinsfile, false)); - job.addProperty(new ParametersDefinitionProperty( - new StringParameterDefinition("REPO", sampleRepo.toString()))); - WorkflowRun run = job.scheduleBuild2(0).waitForStart(); - r.waitUntilNoActivity(); - - System.out.println(JenkinsRule.getLog(run)); - - List credentialsLog = getOutputLines(); - - //Verify correct messages from GitHubAppCredential logger indicating token was retrieved on agent - assertThat("Creds should cache on master, pass to agent, and refresh agent from master once", - credentialsLog, contains( - // (agent log added out of order, see below) - "Generating App Installation Token for app ID 54321 on agent", // 1 - "Failed to generate new GitHub App Installation Token for app ID 54321 on agent: cached token is stale but has not expired", // 2 - "Generating App Installation Token for app ID 54321 on agent", // 3 - // node ('my-agent') { - // checkout scm - "Generating App Installation Token for app ID 54321", - // checkout scm - // (No token generation) - // sleep - // checkout scm - "Generating App Installation Token for app ID 54321", - // (error forced by wiremock) - "Failed to generate new GitHub App Installation Token for app ID 54321: cached token is stale but has not expired", - // (error forced by wiremock - failed refresh on the agent) - // "Generating App Installation Token for app ID 54321 on agent", // 1 - "Generating App Installation Token for app ID 54321 for agent", - // (agent log added out of order) "Keeping cached GitHub App Installation Token for app ID 54321 on agent: token is stale but has not expired", // 2 - // checkout scm - refresh on controller - "Generating App Installation Token for app ID 54321", - // sleep - // checkout scm - "Generating App Installation Token for app ID 54321", - // (error forced by wiremock) - "Failed to update stale GitHub App installation token for app ID 54321 before sending to agent", - // "Generating App Installation Token for app ID 54321 on agent", // 3 - "Generating App Installation Token for app ID 54321 for agent", - // checkout scm - refresh on controller - "Generating App Installation Token for app ID 54321" - // checkout scm - // (No token generation) - )); - - // Check success after output. Output will be more informative if something goes wrong. - assertThat(run.getResult(), equalTo(Result.SUCCESS)); - - // Getting the token for via AuthorizationProvider on controller should not check rate_limit - // Getting the token for agents via remoting to the controller should not check rate_limit - githubApi.verify(0, RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/rate_limit"))); - } finally { - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; - logRecorder.doClear(); - } + } + + @Test + public void testPassword() throws Exception { + long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; + try { + appCredentials.setApiUri(githubApi.baseUrl()); + + // We want to demonstrate successful caching without waiting for the default 1 minute + // Must set this to a large enough number to avoid flaky test + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; + + // Ensure we are working from sufficiently clean cache state + Thread.sleep( + Duration.ofSeconds( + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) + .toMillis()); + + appCredentials.getPassword(); + + // Getting the token for a credential via getPassword() not check rate_limit + githubApi.verify( + 0, + RequestPatternBuilder.newRequestPattern( + RequestMethod.GET, urlPathEqualTo("/rate_limit"))); + + } finally { + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; + logRecorder.doClear(); } - - @Test - public void testPassword() throws Exception { - long notStaleSeconds = GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS; - try { - appCredentials.setApiUri(githubApi.baseUrl()); - - // We want to demonstrate successful caching without waiting for the default 1 minute - // Must set this to a large enough number to avoid flaky test - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = 5; - - // Ensure we are working from sufficiently clean cache state - Thread.sleep(Duration.ofSeconds(GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS + 2) - .toMillis()); - - appCredentials.getPassword(); - - // Getting the token for a credential via getPassword() not check rate_limit - githubApi.verify(0, RequestPatternBuilder.newRequestPattern(RequestMethod.GET, urlPathEqualTo("/rate_limit"))); - - } finally { - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; - logRecorder.doClear(); - } + } + + private List getOutputLines() { + final Formatter formatter = new SimpleFormatter(); + List result = new ArrayList<>(logRecorder.getLogRecords()); + List agentLogs = logRecorder.getSlaveLogRecords().get(agent.toComputer()); + if (agentLogs != null) { + result.addAll(agentLogs); } - - private List getOutputLines() { - final Formatter formatter = new SimpleFormatter(); - List result = new ArrayList<>(logRecorder.getLogRecords()); - List agentLogs = logRecorder.getSlaveLogRecords().get(agent.toComputer()); - if (agentLogs != null) { - result.addAll(agentLogs); - } - Collections.reverse(result); - return result.stream() - .map(formatter::formatMessage) - .collect(Collectors.toList()); - } - - static String printDate(Date dt) { - return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(dt.getTime()).truncatedTo( - ChronoUnit.SECONDS)); - } - + Collections.reverse(result); + return result.stream().map(formatter::formatMessage).collect(Collectors.toList()); + } + + static String printDate(Date dt) { + return DateTimeFormatter.ISO_INSTANT.format( + Instant.ofEpochMilli(dt.getTime()).truncatedTo(ChronoUnit.SECONDS)); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceBranchesTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceBranchesTest.java index 4915878fe..88e9a3aa9 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceBranchesTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceBranchesTest.java @@ -1,190 +1,203 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.util.Collections; import java.util.Iterator; import jenkins.scm.api.SCMHeadObserver; import org.junit.Test; +import org.kohsuke.github.GHBranch; import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHBranch; import org.mockito.Mockito; -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - public class GithubSCMSourceBranchesTest extends GitSCMSourceBase { - public GithubSCMSourceBranchesTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); - } - - @Test - public void testMissingSingleBranch () throws IOException { - // Situation: Hitting the Github API for a branch and getting a 404 - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches/non-existent-branch")) - .willReturn( - aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-non-existent-branch.json"))); - //stubFor($TYPE(branch/PR/tag), $STATUS, $SCENARIO_NAME) - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new BranchSCMHead("non-existent-branch"))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantBranches(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); - // Expected: In the iterator will be empty - assertFalse(branches.hasNext()); - } + public GithubSCMSourceBranchesTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + } - @Test - public void testExistentSingleBranch () throws IOException { - // Situation: Hitting the Github API for a branch and getting an existing branch - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches/existent-branch")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-branch.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new BranchSCMHead("existent-branch"))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantBranches(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); - // Expected: In the iterator will be a single branch named existent-branch - assertTrue(branches.hasNext()); - assertEquals("existent-branch", branches.next().getName()); - assertFalse(branches.hasNext()); - } + @Test + public void testMissingSingleBranch() throws IOException { + // Situation: Hitting the Github API for a branch and getting a 404 + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches/non-existent-branch")) + .willReturn( + aResponse() + .withStatus(404) + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../branches/_files/body-yolo-branches-non-existent-branch.json"))); + // stubFor($TYPE(branch/PR/tag), $STATUS, $SCENARIO_NAME) + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new BranchSCMHead("non-existent-branch"))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantBranches(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); + // Expected: In the iterator will be empty + assertFalse(branches.hasNext()); + } - @Test - public void testThrownErrorSingleBranchException() throws IOException { - // Situation: When sending a request for a branch which exists, throw a GHNotFoundException - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches/existent-branch")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-branch.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Error e = new Error("Bad Branch Request", new GHFileNotFoundException()); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new BranchSCMHead("existent-branch"))); - GHRepository repoSpy = Mockito.spy(repo); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantBranches(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Mockito.doThrow(e).when(repoSpy).getRef("branches/existent-branch"); - // Expected: This will throw an error when requesting a branch - try{ - Iterator branches = new GitHubSCMSource.LazyBranches(request, repoSpy).iterator(); - fail("This should throw an exception"); - } - catch(Error error){ - //Error is expected here so this is "success" - assertEquals("Bad Branch Request", e.getMessage()); - } + @Test + public void testExistentSingleBranch() throws IOException { + // Situation: Hitting the Github API for a branch and getting an existing branch + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches/existent-branch")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../branches/_files/body-yolo-branches-existent-branch.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantBranches(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); + // Expected: In the iterator will be a single branch named existent-branch + assertTrue(branches.hasNext()); + assertEquals("existent-branch", branches.next().getName()); + assertFalse(branches.hasNext()); + } + @Test + public void testThrownErrorSingleBranchException() throws IOException { + // Situation: When sending a request for a branch which exists, throw a GHNotFoundException + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches/existent-branch")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../branches/_files/body-yolo-branches-existent-branch.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Error e = new Error("Bad Branch Request", new GHFileNotFoundException()); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); + GHRepository repoSpy = Mockito.spy(repo); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantBranches(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Mockito.doThrow(e).when(repoSpy).getRef("branches/existent-branch"); + // Expected: This will throw an error when requesting a branch + try { + Iterator branches = new GitHubSCMSource.LazyBranches(request, repoSpy).iterator(); + fail("This should throw an exception"); + } catch (Error error) { + // Error is expected here so this is "success" + assertEquals("Bad Branch Request", e.getMessage()); } + } - @Test - public void testExistingMultipleBranchesWithDefaultInPosition1() throws IOException { - // Situation: Hitting github and getting back multiple branches where master is first in the lst position - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-multiple-branches-master1.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantBranches(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); - // Expected: In the iterator will be a multiple branches named nexistent-branch1(because it is alphabetically sorted first) - // and master - assertTrue(branches.hasNext()); - assertEquals("master", branches.next().getName()); - assertTrue(branches.hasNext()); - assertEquals("nexistent-branch1", branches.next().getName()); - assertFalse(branches.hasNext()); - } + @Test + public void testExistingMultipleBranchesWithDefaultInPosition1() throws IOException { + // Situation: Hitting github and getting back multiple branches where master is first in the lst + // position + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../branches/_files/body-yolo-branches-existent-multiple-branches-master1.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantBranches(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); + // Expected: In the iterator will be a multiple branches named nexistent-branch1(because it is + // alphabetically sorted first) + // and master + assertTrue(branches.hasNext()); + assertEquals("master", branches.next().getName()); + assertTrue(branches.hasNext()); + assertEquals("nexistent-branch1", branches.next().getName()); + assertFalse(branches.hasNext()); + } - @Test - public void testExistingMultipleBranchesWithDefaultInPosition2() throws IOException { - // Situation: Hitting github and getting back multiple branches where master is first in the 2nd position - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-multiple-branches-master2.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); - // Expected: In the iterator will be a multiple branches named existent-branch2 and master - assertTrue(branches.hasNext()); - assertEquals("master", branches.next().getName()); - assertTrue(branches.hasNext()); - assertEquals("existent-branch2", branches.next().getName()); - } + @Test + public void testExistingMultipleBranchesWithDefaultInPosition2() throws IOException { + // Situation: Hitting github and getting back multiple branches where master is first in the 2nd + // position + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../branches/_files/body-yolo-branches-existent-multiple-branches-master2.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); + // Expected: In the iterator will be a multiple branches named existent-branch2 and master + assertTrue(branches.hasNext()); + assertEquals("master", branches.next().getName()); + assertTrue(branches.hasNext()); + assertEquals("existent-branch2", branches.next().getName()); + } - @Test - public void testExistingMultipleBranchesWithNoDefault() throws IOException { - // Situation: Hitting github and getting back multiple branches where master is not in the list - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-multiple-branches-no-master.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); - // Expected: In the iterator will be a multiple branches named existent-branch2 and existent-branch1 - assertTrue(branches.hasNext()); - assertEquals("existent-branch1", branches.next().getName()); - assertTrue(branches.hasNext()); - assertEquals("existent-branch2", branches.next().getName()); - } + @Test + public void testExistingMultipleBranchesWithNoDefault() throws IOException { + // Situation: Hitting github and getting back multiple branches where master is not in the list + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../branches/_files/body-yolo-branches-existent-multiple-branches-no-master.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator branches = new GitHubSCMSource.LazyBranches(request, repo).iterator(); + // Expected: In the iterator will be a multiple branches named existent-branch2 and + // existent-branch1 + assertTrue(branches.hasNext()); + assertEquals("existent-branch1", branches.next().getName()); + assertTrue(branches.hasNext()); + assertEquals("existent-branch2", branches.next().getName()); + } - @Test - public void testExistingMultipleBranchesWithThrownError() throws IOException { - // Situation: Hitting github and getting back multiple branches but throws an I/O error - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/branches")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../branches/_files/body-yolo-branches-existent-multiple-branches-no-master.json"))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - IOException error = new IOException("Thrown Branch Error"); - Mockito.when(repoSpy.getBranches()).thenThrow(error); - // Expected: In the iterator will throw an error when calling getBranches - try{ - Iterator branches = new GitHubSCMSource.LazyBranches(request, repoSpy).iterator(); - fail("This should throw an exception"); - } - catch(Exception e){ - //We swallow the new GetRef error and then throw the original one for some reason... - assertEquals("java.io.IOException: Thrown Branch Error", e.getMessage()); - } + @Test + public void testExistingMultipleBranchesWithThrownError() throws IOException { + // Situation: Hitting github and getting back multiple branches but throws an I/O error + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/branches")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile( + "../branches/_files/body-yolo-branches-existent-multiple-branches-no-master.json"))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + IOException error = new IOException("Thrown Branch Error"); + Mockito.when(repoSpy.getBranches()).thenThrow(error); + // Expected: In the iterator will throw an error when calling getBranches + try { + Iterator branches = new GitHubSCMSource.LazyBranches(request, repoSpy).iterator(); + fail("This should throw an exception"); + } catch (Exception e) { + // We swallow the new GetRef error and then throw the original one for some reason... + assertEquals("java.io.IOException: Thrown Branch Error", e.getMessage()); } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourcePRsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourcePRsTest.java index de83dff48..73793df15 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourcePRsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourcePRsTest.java @@ -1,5 +1,11 @@ package org.jenkinsci.plugins.github_branch_source; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.*; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.*; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMHeadOrigin; @@ -9,226 +15,281 @@ import org.kohsuke.github.GHRepository; import org.mockito.Mockito; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.*; +public class GithubSCMSourcePRsTest extends GitSCMSourceBase { -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.Assert.*; + public GithubSCMSourcePRsTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + } -public class GithubSCMSourcePRsTest extends GitSCMSourceBase { + @Test + public void testClosedSinglePR() throws IOException { + // Situation: Hitting the Github API for a PR and getting a closed PR + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-closed-pr.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new PullRequestSCMHead( + "PR-1", + "*", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantPRs(); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequest = + new GitHubSCMSource("cloudbeers", "yolo", null, false).new LazyPullRequests(request, repo) + .iterator(); + // Expected: In the iterator will be empty + assertFalse(pullRequest.hasNext()); + } - public GithubSCMSourcePRsTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); - } + // Single PR that is open: returns singleton + @Test + public void testOpenSinglePR() throws IOException { + // Situation: Hitting the Github API for a PR and getting a open PR + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new PullRequestSCMHead( + "PR-1", + "ataylor", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantPRs(); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequest = + new GitHubSCMSource("cloudbeers", "yolo", null, false).new LazyPullRequests(request, repo) + .iterator(); + // Expected: In the iterator will have one item in it + assertTrue(pullRequest.hasNext()); + assertEquals(1, pullRequest.next().getId()); - @Test - public void testClosedSinglePR() throws IOException { - // Situation: Hitting the Github API for a PR and getting a closed PR - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-closed-pr.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new PullRequestSCMHead("PR-1", "*", - "http://localhost:" + githubApi.port(), "master", 1, - new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantPRs(); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequest = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repo).iterator(); - // Expected: In the iterator will be empty - assertFalse(pullRequest.hasNext()); - } + assertFalse(pullRequest.hasNext()); + } - //Single PR that is open: returns singleton - @Test - public void testOpenSinglePR() throws IOException { - // Situation: Hitting the Github API for a PR and getting a open PR - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new PullRequestSCMHead("PR-1", "ataylor", - "http://localhost:" + githubApi.port(), "master", 1, - new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantPRs(); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequest = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repo).iterator(); - // Expected: In the iterator will have one item in it - assertTrue(pullRequest.hasNext()); - assertEquals(1, pullRequest.next().getId()); - - assertFalse(pullRequest.hasNext()); - } + @Test + public void testSinglePRThrowingExceptionOnGettingNumbers() throws Exception { + // Situation: Hitting the Github API for a PR and an IO exception during the building of the + // iterator + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new PullRequestSCMHead( + "PR-1", + "ataylor", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantPRs(); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - @Test - public void testSinglePRThrowingExceptionOnGettingNumbers() throws Exception { - // Situation: Hitting the Github API for a PR and an IO exception during the building of the iterator - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new PullRequestSCMHead("PR-1", "ataylor", - "http://localhost:" + githubApi.port(), "master", 1, - new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantPRs(); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - - GHRepository mockRequest = Mockito.spy(repo); - Mockito.when(mockRequest.getPullRequest(1)).thenThrow(new IOException("Number does not exist")); - - // Expected: This will fail when trying to generate the iterator - try{ - Iterator pullRequest = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, mockRequest).iterator(); - fail(); - } catch (Exception e){ - assertEquals("java.io.IOException: Number does not exist" , e.getMessage()); - } + GHRepository mockRequest = Mockito.spy(repo); + Mockito.when(mockRequest.getPullRequest(1)).thenThrow(new IOException("Number does not exist")); + // Expected: This will fail when trying to generate the iterator + try { + Iterator pullRequest = + new GitHubSCMSource("cloudbeers", "yolo", null, false) + .new LazyPullRequests(request, mockRequest) + .iterator(); + fail(); + } catch (Exception e) { + assertEquals("java.io.IOException: Number does not exist", e.getMessage()); } + } - @Test - public void testOpenSinglePRThrowsFileNotFoundOnObserve() throws Exception { - // Situation: Hitting the Github API for a PR and an FileNotFound exception during the getPullRequest - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new PullRequestSCMHead("PR-1", "ataylor", - "http://localhost:" + githubApi.port(), "master", 1, - new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantPRs(); - - //Spy on repo - GHRepository repoSpy = Mockito.spy(repo); - GHPullRequest pullRequestSpy = Mockito.spy(repoSpy.getPullRequest(1)); - Mockito.when(repoSpy.getPullRequest(1)).thenReturn(pullRequestSpy); - //then throw on the PR during observe - Mockito.when(pullRequestSpy.getUser()).thenThrow(new FileNotFoundException("User not found")); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequestIterator = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repoSpy).iterator(); - - // Expected: In the iterator will have one item in it but when getting that item you receive an FileNotFound exception - assertTrue(pullRequestIterator.hasNext()); - try{ - pullRequestIterator.next(); - fail(); - } catch (Exception e) { - assertEquals("java.io.FileNotFoundException: User not found", e.getMessage()); - } - } + @Test + public void testOpenSinglePRThrowsFileNotFoundOnObserve() throws Exception { + // Situation: Hitting the Github API for a PR and an FileNotFound exception during the + // getPullRequest + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new PullRequestSCMHead( + "PR-1", + "ataylor", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantPRs(); - @Test - public void testOpenSinglePRThrowsIOOnObserve() throws Exception { - // Situation: Hitting the Github API for a PR and an IO exception during the getPullRequest - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new PullRequestSCMHead("PR-1", "ataylor", - "http://localhost:" + githubApi.port(), "master", 1, - new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantPRs(); - - //Spy on repo - GHRepository repoSpy = Mockito.spy(repo); - GHPullRequest pullRequestSpy = Mockito.spy(repoSpy.getPullRequest(1)); - Mockito.when(repoSpy.getPullRequest(1)).thenReturn(pullRequestSpy); - //then throw on the PR during observe - Mockito.when(pullRequestSpy.getUser()).thenThrow(new IOException("Failed to get user")); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequestIterator = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repoSpy).iterator(); - - // Expected: In the iterator will have one item in it but when getting that item you receive an IO exception - assertTrue(pullRequestIterator.hasNext()); - try{ - pullRequestIterator.next(); - fail(); - } catch (Exception e) { - assertEquals("java.io.IOException: Failed to get user", e.getMessage()); - } - } + // Spy on repo + GHRepository repoSpy = Mockito.spy(repo); + GHPullRequest pullRequestSpy = Mockito.spy(repoSpy.getPullRequest(1)); + Mockito.when(repoSpy.getPullRequest(1)).thenReturn(pullRequestSpy); + // then throw on the PR during observe + Mockito.when(pullRequestSpy.getUser()).thenThrow(new FileNotFoundException("User not found")); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequestIterator = + new GitHubSCMSource("cloudbeers", "yolo", null, false) + .new LazyPullRequests(request, repoSpy) + .iterator(); - //Multiple PRs - @Test - public void testOpenMultiplePRs() throws IOException { - // Situation: Hitting the Github API all the PRs and they are all Open. Then we close the request at the end - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls?state=open")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-multiple-PRs.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantOriginPRs(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequest = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repo).iterator(); - // Expected: In the iterator will have 2 items in it - assertTrue(pullRequest.hasNext()); - assertEquals(1, pullRequest.next().getId()); - assertTrue(pullRequest.hasNext()); - assertEquals(2, pullRequest.next().getId()); - assertFalse(pullRequest.hasNext()); - request.close(); + // Expected: In the iterator will have one item in it but when getting that item you receive an + // FileNotFound exception + assertTrue(pullRequestIterator.hasNext()); + try { + pullRequestIterator.next(); + fail(); + } catch (Exception e) { + assertEquals("java.io.FileNotFoundException: User not found", e.getMessage()); } + } + + @Test + public void testOpenSinglePRThrowsIOOnObserve() throws Exception { + // Situation: Hitting the Github API for a PR and an IO exception during the getPullRequest + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls/1")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-pr.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new PullRequestSCMHead( + "PR-1", + "ataylor", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantPRs(); + + // Spy on repo + GHRepository repoSpy = Mockito.spy(repo); + GHPullRequest pullRequestSpy = Mockito.spy(repoSpy.getPullRequest(1)); + Mockito.when(repoSpy.getPullRequest(1)).thenReturn(pullRequestSpy); + // then throw on the PR during observe + Mockito.when(pullRequestSpy.getUser()).thenThrow(new IOException("Failed to get user")); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequestIterator = + new GitHubSCMSource("cloudbeers", "yolo", null, false) + .new LazyPullRequests(request, repoSpy) + .iterator(); - //Multiple PRs - @Test - public void testOpenMultiplePRsWithMasterAsOrigin() throws IOException { - // Situation: Hitting the Github API all the PRs and they are all Open but the master is the head branch - githubApi.stubFor( - get(urlEqualTo("/repos/cloudbeers/yolo/pulls?state=open&head=cloudbeers%3Amaster")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBodyFile("../PRs/_files/body-yolo-pulls-open-multiple-PRs.json"))); - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantOriginPRs(true); - Set masterSet = new HashSet<>(); - SCMHead masterHead = new BranchSCMHead("master"); - masterSet.add(masterHead); - GitHubSCMSourceContext contextSpy = Mockito.spy(context); - Mockito.when(contextSpy.observer().getIncludes()).thenReturn(masterSet); - GitHubSCMSourceRequest request = contextSpy.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator pullRequest = new GitHubSCMSource("cloudbeers", "yolo", null, false) - .new LazyPullRequests(request, repo).iterator(); - // Expected: In the iterator will have 2 items in it - assertTrue(pullRequest.hasNext()); - assertEquals(1, pullRequest.next().getId()); - assertTrue(pullRequest.hasNext()); - assertEquals(2, pullRequest.next().getId()); - assertFalse(pullRequest.hasNext()); + // Expected: In the iterator will have one item in it but when getting that item you receive an + // IO exception + assertTrue(pullRequestIterator.hasNext()); + try { + pullRequestIterator.next(); + fail(); + } catch (Exception e) { + assertEquals("java.io.IOException: Failed to get user", e.getMessage()); } + } + + // Multiple PRs + @Test + public void testOpenMultiplePRs() throws IOException { + // Situation: Hitting the Github API all the PRs and they are all Open. Then we close the + // request at the end + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls?state=open")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-multiple-PRs.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantOriginPRs(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequest = + new GitHubSCMSource("cloudbeers", "yolo", null, false).new LazyPullRequests(request, repo) + .iterator(); + // Expected: In the iterator will have 2 items in it + assertTrue(pullRequest.hasNext()); + assertEquals(1, pullRequest.next().getId()); + assertTrue(pullRequest.hasNext()); + assertEquals(2, pullRequest.next().getId()); + assertFalse(pullRequest.hasNext()); + request.close(); + } + + // Multiple PRs + @Test + public void testOpenMultiplePRsWithMasterAsOrigin() throws IOException { + // Situation: Hitting the Github API all the PRs and they are all Open but the master is the + // head branch + githubApi.stubFor( + get(urlEqualTo("/repos/cloudbeers/yolo/pulls?state=open&head=cloudbeers%3Amaster")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("../PRs/_files/body-yolo-pulls-open-multiple-PRs.json"))); + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantOriginPRs(true); + Set masterSet = new HashSet<>(); + SCMHead masterHead = new BranchSCMHead("master"); + masterSet.add(masterHead); + GitHubSCMSourceContext contextSpy = Mockito.spy(context); + Mockito.when(contextSpy.observer().getIncludes()).thenReturn(masterSet); + GitHubSCMSourceRequest request = + contextSpy.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator pullRequest = + new GitHubSCMSource("cloudbeers", "yolo", null, false).new LazyPullRequests(request, repo) + .iterator(); + // Expected: In the iterator will have 2 items in it + assertTrue(pullRequest.hasNext()); + assertEquals(1, pullRequest.next().getId()); + assertTrue(pullRequest.hasNext()); + assertEquals(2, pullRequest.next().getId()); + assertFalse(pullRequest.hasNext()); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceTagsTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceTagsTest.java index 32e040f76..d6585a1f1 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceTagsTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GithubSCMSourceTagsTest.java @@ -1,6 +1,11 @@ package org.jenkinsci.plugins.github_branch_source; -import jenkins.scm.api.SCMHeadObserver; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -8,339 +13,367 @@ import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; +import jenkins.scm.api.SCMHeadObserver; import org.junit.Test; import org.jvnet.hudson.test.Issue; -import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHException; +import org.kohsuke.github.GHFileNotFoundException; import org.kohsuke.github.GHRef; import org.kohsuke.github.GHRepository; -import org.kohsuke.github.PagedIterator; import org.kohsuke.github.PagedIterable; +import org.kohsuke.github.PagedIterator; import org.mockito.Mockito; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.Assert.assertNotEquals; - - public class GithubSCMSourceTagsTest extends GitSCMSourceBase { - public GithubSCMSourceTagsTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + public GithubSCMSourceTagsTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + } + + @Test + @Issue("JENKINS-54403") + public void testMissingSingleTag() throws IOException { + // Scenario: a single tag which does not exist + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); + + // Expected: No tag is found so an empty list + assertFalse(tags.hasNext()); + } + + @Test + @Issue("JENKINS-54403") + public void testExistentSingleTag() throws IOException { + // Scenario: A single tag which does exist + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); + + // Expected: single tag is found and is named existent-tag + assertTrue(tags.hasNext()); + assertEquals("refs/tags/existent-tag", tags.next().getRef()); + assertFalse(tags.hasNext()); + } + + @Test + public void testThrownErrorSingleTagGHFileNotFound() throws IOException { + // Scenario: A single tag but getting back a FileNotFound when calling getRef + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Error e = new Error("Bad Tag Request", new GHFileNotFoundException()); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); + GHRepository repoSpy = Mockito.spy(repo); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Mockito.doThrow(e).when(repoSpy).getRef("tags/existent-tag"); + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + + // Expected: No tag is found so an empty list + assertFalse(tags.hasNext()); + } + + @Test + public void testThrownErrorSingleTagOtherException() throws IOException { + // Scenario: A single tag but getting back another Error when calling getRef + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Error expectedError = new Error("Bad Tag Request", new RuntimeException()); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton( + new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); + GHRepository repoSpy = Mockito.spy(repo); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Mockito.doThrow(expectedError).when(repoSpy).getRef("tags/existent-tag"); + + // Expected: When getting the tag, an error is thrown so we have to catch it + try { + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + fail("This should throw an exception"); + } catch (Error e) { + // Error is expected here so this is "success" + assertEquals("Bad Tag Request", e.getMessage()); } - - @Test - @Issue("JENKINS-54403") - public void testMissingSingleTag() throws IOException { - // Scenario: a single tag which does not exist - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); - - //Expected: No tag is found so an empty list - assertFalse(tags.hasNext()); + } + + @Test + public void testExistingMultipleTags() throws IOException { + // Scenario: Requesting multiple tags + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); + + // Expected: When getting the tags, we should get both tags and then we are failing on trying to + // get a 3rd tag or remove the iterator + assertTrue(tags.hasNext()); + assertEquals("refs/tags/existent-multiple-tags1", tags.next().getRef()); + assertTrue(tags.hasNext()); + assertEquals("refs/tags/existent-multiple-tags2", tags.next().getRef()); + assertFalse(tags.hasNext()); + try { + tags.next(); + fail("This should throw an exception"); + } catch (NoSuchElementException e) { + // Error is expected here so this is "success" + assertNotEquals("This should throw an exception", e.getMessage()); } - - @Test - @Issue("JENKINS-54403") - public void testExistentSingleTag() throws IOException { - // Scenario: A single tag which does exist - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); - - //Expected: single tag is found and is named existent-tag - assertTrue(tags.hasNext()); - assertEquals("refs/tags/existent-tag", tags.next().getRef()); - assertFalse(tags.hasNext()); + try { + tags.remove(); + fail("This should throw an exception"); + } catch (UnsupportedOperationException e) { + // Error is expected here so this is "success" + assertNotEquals("This should throw an exception", e.getMessage()); } - - @Test - public void testThrownErrorSingleTagGHFileNotFound() throws IOException { - // Scenario: A single tag but getting back a FileNotFound when calling getRef - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Error e = new Error("Bad Tag Request", new GHFileNotFoundException()); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); - GHRepository repoSpy = Mockito.spy(repo); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Mockito.doThrow(e).when(repoSpy).getRef("tags/existent-tag"); - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - - //Expected: No tag is found so an empty list - assertFalse(tags.hasNext()); + } + + @Test + public void testExistingMultipleTagsGHFileNotFoundExceptionIterable() throws IOException { + // Scenario: Requesting multiple tags but a FileNotFound is thrown + // on the first returning the iterator and then an IO error is thrown on the iterator creation + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Error expectedError = new Error("Bad Tag Request", new GHFileNotFoundException()); + Error expectedError2 = new Error("Bad Tag Request IOError", new IOException()); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); + Mockito.when(iterableSpy.iterator()).thenThrow(expectedError).thenThrow(expectedError2); + + // Expected: When initially getting multiple tags, there will then be a thrown filenotfound + // which returns an empty list + // Then for the second tag iterator created it returns an IO error + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + assertEquals(Collections.emptyIterator(), tags); + try { + Iterator tags2 = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + fail("This should throw an exception"); + } catch (Error e) { + assertEquals("Bad Tag Request IOError", e.getMessage()); } - - @Test - public void testThrownErrorSingleTagOtherException() throws IOException { - // Scenario: A single tag but getting back another Error when calling getRef - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Error expectedError = new Error("Bad Tag Request", new RuntimeException()); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections - .singleton(new GitHubTagSCMHead("existent-tag", System.currentTimeMillis()))); - GHRepository repoSpy = Mockito.spy(repo); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Mockito.doThrow(expectedError).when(repoSpy).getRef("tags/existent-tag"); - - //Expected: When getting the tag, an error is thrown so we have to catch it - try{ - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - fail("This should throw an exception"); - } - catch(Error e){ - //Error is expected here so this is "success" - assertEquals("Bad Tag Request", e.getMessage()); - } - + } + + @Test + public void testExistingMultipleTagsIteratorGHFileNotFoundExceptionOnHasNext() + throws IOException { + // Scenario: multiple tags but returns a filenotfound on the first hasNext + // and returns a IO error on the second hasNext + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Error expectedError = new Error("Bad Tag Request", new GHFileNotFoundException()); + Error expectedError2 = new Error("Bad Tag Request IOError", new IOException()); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); + + PagedIterator iteratorSpy = (PagedIterator) Mockito.mock(PagedIterator.class); + Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); + Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError).thenThrow(expectedError2); + + // Expected: When initially getting multiple tags, return a filenotfound on hasNext which means + // it will get an empty list + // Then return a IO error on the second hasNext + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + Iterator tags2 = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + assertFalse(tags.hasNext()); + try { + tags.hasNext(); + fail("This should throw an exception"); + } catch (Error e) { + assertEquals("Bad Tag Request IOError", e.getMessage()); } - - @Test - public void testExistingMultipleTags() throws IOException { - // Scenario: Requesting multiple tags - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - Iterator tags = new GitHubSCMSource.LazyTags(request, repo).iterator(); - - //Expected: When getting the tags, we should get both tags and then we are failing on trying to get a 3rd tag or remove the iterator - assertTrue(tags.hasNext()); - assertEquals("refs/tags/existent-multiple-tags1", tags.next().getRef()); - assertTrue(tags.hasNext()); - assertEquals("refs/tags/existent-multiple-tags2", tags.next().getRef()); - assertFalse(tags.hasNext()); - try{ - tags.next(); - fail("This should throw an exception"); - } - catch(NoSuchElementException e){ - //Error is expected here so this is "success" - assertNotEquals("This should throw an exception", e.getMessage()); - } - try{ - tags.remove(); - fail("This should throw an exception"); - } - catch(UnsupportedOperationException e){ - //Error is expected here so this is "success" - assertNotEquals("This should throw an exception", e.getMessage()); - } + } + + @Test + public void testExistingMultipleTagsIteratorGHExceptionOnHasNextAndHasAtLeastOne() + throws IOException { + // Scenario: multiple tags but returns a GHException and found at least one tag + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Exception expectedError = new GHException("Bad Tag Request"); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); + + PagedIterator iteratorSpy = (PagedIterator) Mockito.mock(PagedIterator.class); + Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + Mockito.when(iteratorSpy.hasNext()).thenReturn(true).thenThrow(expectedError); + + // Expected: First call to hasNext should work true and then will throw an error + try { + // First Call is fine + tags.hasNext(); + // Second Call fails + tags.hasNext(); + fail("This should throw an exception"); + } catch (GHException e) { + assertEquals("Bad Tag Request", e.getMessage()); } - - @Test - public void testExistingMultipleTagsGHFileNotFoundExceptionIterable() throws IOException { - // Scenario: Requesting multiple tags but a FileNotFound is thrown - // on the first returning the iterator and then an IO error is thrown on the iterator creation - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Error expectedError = new Error("Bad Tag Request", new GHFileNotFoundException()); - Error expectedError2 = new Error("Bad Tag Request IOError", new IOException()); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); - Mockito.when(iterableSpy.iterator()).thenThrow(expectedError).thenThrow(expectedError2); - - //Expected: When initially getting multiple tags, there will then be a thrown filenotfound which returns an empty list - //Then for the second tag iterator created it returns an IO error - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - assertEquals(Collections.emptyIterator(), tags); - try{ - Iterator tags2 = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - fail("This should throw an exception"); - } - catch(Error e){ - assertEquals("Bad Tag Request IOError", e.getMessage()); - } + } + + @Test + public void testExistingMultipleTagsIteratorGHExceptionOnHasNextAndDoesNotHaveOne() + throws IOException { + // Scenario: multiple tags but returns a GHException on the first tag + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Exception expectedError = new GHException("Bad Tag Request"); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy).thenCallRealMethod(); + + PagedIterator iteratorSpy = (PagedIterator) Mockito.mock(PagedIterator.class); + Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); + + // Expected: First call to hasNext throws the GHException + try { + tags.hasNext(); + fail("This should throw an exception"); + } catch (GHException e) { + assertEquals("Bad Tag Request", e.getMessage()); } - - @Test - public void testExistingMultipleTagsIteratorGHFileNotFoundExceptionOnHasNext() throws IOException { - // Scenario: multiple tags but returns a filenotfound on the first hasNext - //and returns a IO error on the second hasNext - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Error expectedError = new Error("Bad Tag Request", new GHFileNotFoundException()); - Error expectedError2 = new Error("Bad Tag Request IOError", new IOException()); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); - - PagedIterator iteratorSpy = (PagedIterator)Mockito.mock(PagedIterator.class); - Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); - Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError).thenThrow(expectedError2); - - //Expected: When initially getting multiple tags, return a filenotfound on hasNext which means it will get an empty list - //Then return a IO error on the second hasNext - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - Iterator tags2 = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - assertFalse(tags.hasNext()); - try{ - tags.hasNext(); - fail("This should throw an exception"); - } - catch(Error e){ - assertEquals("Bad Tag Request IOError", e.getMessage()); - } - } - - @Test - public void testExistingMultipleTagsIteratorGHExceptionOnHasNextAndHasAtLeastOne() throws IOException { - // Scenario: multiple tags but returns a GHException and found at least one tag - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Exception expectedError = new GHException("Bad Tag Request"); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); - - PagedIterator iteratorSpy = (PagedIterator)Mockito.mock(PagedIterator.class); - Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - Mockito.when(iteratorSpy.hasNext()).thenReturn(true).thenThrow(expectedError); - - //Expected: First call to hasNext should work true and then will throw an error - try{ - //First Call is fine - tags.hasNext(); - //Second Call fails - tags.hasNext(); - fail("This should throw an exception"); - } - catch(GHException e){ - assertEquals("Bad Tag Request", e.getMessage()); - } - } - - @Test - public void testExistingMultipleTagsIteratorGHExceptionOnHasNextAndDoesNotHaveOne() throws IOException { - // Scenario: multiple tags but returns a GHException on the first tag - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Exception expectedError = new GHException("Bad Tag Request"); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy).thenCallRealMethod(); - - PagedIterator iteratorSpy = (PagedIterator)Mockito.mock(PagedIterator.class); - Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); - - //Expected: First call to hasNext throws the GHException - try{ - tags.hasNext(); - fail("This should throw an exception"); - } - catch(GHException e){ - assertEquals("Bad Tag Request", e.getMessage()); - } - } - - @Test - public void testExistingMultipleTagsIteratorGHExceptionOnHasNextButThrowsFileNotFoundOnGetRefs() throws IOException { - // Scenario: multiple tags but catches a GH exception on hasNext and then - // FilenotFound on getRefs - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Exception expectedError = new GHException("Bad Tag Request"); - Exception expectedGetRefError = new FileNotFoundException("Bad Tag Ref Request"); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); - - PagedIterator iteratorSpy = (PagedIterator)Mockito.mock(PagedIterator.class); - Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); - Mockito.when(repoSpy.getRefs("tags")).thenThrow(expectedGetRefError); - - //Expected: First call to hasNext throws a GHException and then returns a FileNotFound on getRefs so it returns an empty list - assertFalse(tags.hasNext()); - } - - @Test - public void testExistingMultipleTagsIteratorGHExceptionOnHasNextButThrowsIOErrorOnGetRefs() throws IOException { - // Scenario: making a request for a multiple tags but catches a GH exception on hasNext - // and then throws on IO error getRefs - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Exception expectedError = new GHException("Bad Tag Request"); - Exception expectedGetRefError = new IOException("Bad Tag Ref Request"); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn( - new HashSet<>(Arrays.asList( - new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), - new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); - GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); - context.wantTags(true); - GitHubSCMSourceRequest request = context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); - GHRepository repoSpy = Mockito.spy(repo); - PagedIterable iterableSpy = (PagedIterable)Mockito.mock(PagedIterable.class); - Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); - - PagedIterator iteratorSpy = (PagedIterator)Mockito.mock(PagedIterator.class); - Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); - Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); - Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); - Mockito.when(repoSpy.getRefs("tags")).thenThrow(expectedGetRefError); - - //Expected: First call to hasNext throws a GHException and then returns a IO exception on getRefs so it returns an error - try{ - tags.hasNext(); - fail("This should throw an exception"); - } - catch(GHException e){ - //We suppress the new GetRef error and then throw the original one - assertEquals("Bad Tag Request", e.getMessage()); - } + } + + @Test + public void testExistingMultipleTagsIteratorGHExceptionOnHasNextButThrowsFileNotFoundOnGetRefs() + throws IOException { + // Scenario: multiple tags but catches a GH exception on hasNext and then + // FilenotFound on getRefs + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Exception expectedError = new GHException("Bad Tag Request"); + Exception expectedGetRefError = new FileNotFoundException("Bad Tag Ref Request"); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); + + PagedIterator iteratorSpy = (PagedIterator) Mockito.mock(PagedIterator.class); + Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); + Mockito.when(repoSpy.getRefs("tags")).thenThrow(expectedGetRefError); + + // Expected: First call to hasNext throws a GHException and then returns a FileNotFound on + // getRefs so it returns an empty list + assertFalse(tags.hasNext()); + } + + @Test + public void testExistingMultipleTagsIteratorGHExceptionOnHasNextButThrowsIOErrorOnGetRefs() + throws IOException { + // Scenario: making a request for a multiple tags but catches a GH exception on hasNext + // and then throws on IO error getRefs + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Exception expectedError = new GHException("Bad Tag Request"); + Exception expectedGetRefError = new IOException("Bad Tag Ref Request"); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new GitHubTagSCMHead("existent-multiple-tags1", System.currentTimeMillis()), + new GitHubTagSCMHead("existent-multiple-tags2", System.currentTimeMillis())))); + GitHubSCMSourceContext context = new GitHubSCMSourceContext(null, mockSCMHeadObserver); + context.wantTags(true); + GitHubSCMSourceRequest request = + context.newRequest(new GitHubSCMSource("cloudbeers", "yolo", null, false), null); + GHRepository repoSpy = Mockito.spy(repo); + PagedIterable iterableSpy = (PagedIterable) Mockito.mock(PagedIterable.class); + Mockito.when(repoSpy.listRefs("tags")).thenReturn(iterableSpy); + + PagedIterator iteratorSpy = (PagedIterator) Mockito.mock(PagedIterator.class); + Mockito.when(iterableSpy.iterator()).thenReturn(iteratorSpy); + Iterator tags = new GitHubSCMSource.LazyTags(request, repoSpy).iterator(); + Mockito.when(iteratorSpy.hasNext()).thenThrow(expectedError); + Mockito.when(repoSpy.getRefs("tags")).thenThrow(expectedGetRefError); + + // Expected: First call to hasNext throws a GHException and then returns a IO exception on + // getRefs so it returns an error + try { + tags.hasNext(); + fail("This should throw an exception"); + } catch (GHException e) { + // We suppress the new GetRef error and then throw the original one + assertEquals("Bad Tag Request", e.getMessage()); } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTraitTest.java index f8413e3c4..0ae939045 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/OriginPullRequestDiscoveryTraitTest.java @@ -1,5 +1,12 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + import java.util.Collections; import java.util.EnumSet; import jenkins.scm.api.SCMHeadObserver; @@ -9,108 +16,120 @@ import org.hamcrest.Matchers; import org.junit.Test; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - public class OriginPullRequestDiscoveryTraitTest { - @Test - public void given__discoverHeadMerge__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - ))); - OriginPullRequestDiscoveryTrait instance = new OriginPullRequestDiscoveryTrait( - EnumSet.allOf(ChangeRequestCheckoutStrategy.class) - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.originPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - )); - } + @Test + public void given__discoverHeadMerge__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not( + hasItem( + instanceOf( + OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class)))); + OriginPullRequestDiscoveryTrait instance = + new OriginPullRequestDiscoveryTrait(EnumSet.allOf(ChangeRequestCheckoutStrategy.class)); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.originPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); + assertThat( + ctx.authorities(), + hasItem( + instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class))); + } - @Test - public void given__discoverHeadOnly__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - ))); - OriginPullRequestDiscoveryTrait instance = new OriginPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.HEAD) - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.originPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); - assertThat(ctx.authorities(), hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - )); - } + @Test + public void given__discoverHeadOnly__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not( + hasItem( + instanceOf( + OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class)))); + OriginPullRequestDiscoveryTrait instance = + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD)); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.originPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); + assertThat( + ctx.authorities(), + hasItem( + instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class))); + } - @Test - public void given__discoverMergeOnly__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - ))); - OriginPullRequestDiscoveryTrait instance = new OriginPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.MERGE) - ); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.originPRStrategies(), - Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); - assertThat(ctx.authorities(), hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - )); - } + @Test + public void given__discoverMergeOnly__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not( + hasItem( + instanceOf( + OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class)))); + OriginPullRequestDiscoveryTrait instance = + new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE)); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.originPRStrategies(), Matchers.is(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); + assertThat( + ctx.authorities(), + hasItem( + instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class))); + } - @Test - public void given__programmaticConstructor__when__appliedToContext__then__strategiesCorrect() throws Exception { - GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); - assumeThat(ctx.wantBranches(), is(false)); - assumeThat(ctx.wantPRs(), is(false)); - assumeThat(ctx.prefilters(), is(Collections.emptyList())); - assumeThat(ctx.filters(), is(Collections.emptyList())); - assumeThat(ctx.authorities(), not(hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - ))); - OriginPullRequestDiscoveryTrait instance = - new OriginPullRequestDiscoveryTrait(EnumSet.allOf(ChangeRequestCheckoutStrategy.class)); - instance.decorateContext(ctx); - assertThat(ctx.wantBranches(), is(false)); - assertThat(ctx.wantPRs(), is(true)); - assertThat(ctx.prefilters(), is(Collections.emptyList())); - assertThat(ctx.filters(), is(Collections.emptyList())); - assertThat(ctx.originPRStrategies(), - Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); - assertThat(ctx.authorities(), hasItem( - instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class) - )); - } + @Test + public void given__programmaticConstructor__when__appliedToContext__then__strategiesCorrect() + throws Exception { + GitHubSCMSourceContext ctx = new GitHubSCMSourceContext(null, SCMHeadObserver.none()); + assumeThat(ctx.wantBranches(), is(false)); + assumeThat(ctx.wantPRs(), is(false)); + assumeThat(ctx.prefilters(), is(Collections.emptyList())); + assumeThat(ctx.filters(), is(Collections.emptyList())); + assumeThat( + ctx.authorities(), + not( + hasItem( + instanceOf( + OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class)))); + OriginPullRequestDiscoveryTrait instance = + new OriginPullRequestDiscoveryTrait(EnumSet.allOf(ChangeRequestCheckoutStrategy.class)); + instance.decorateContext(ctx); + assertThat(ctx.wantBranches(), is(false)); + assertThat(ctx.wantPRs(), is(true)); + assertThat(ctx.prefilters(), is(Collections.emptyList())); + assertThat(ctx.filters(), is(Collections.emptyList())); + assertThat( + ctx.originPRStrategies(), Matchers.is(EnumSet.allOf(ChangeRequestCheckoutStrategy.class))); + assertThat( + ctx.authorities(), + hasItem( + instanceOf(OriginPullRequestDiscoveryTrait.OriginChangeRequestSCMHeadAuthority.class))); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevisionTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevisionTest.java index 8ab6117dc..774b23148 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevisionTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/PullRequestSCMRevisionTest.java @@ -25,209 +25,355 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import hudson.AbortException; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; - import org.junit.Before; import org.junit.Test; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; -import hudson.AbortException; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - public class PullRequestSCMRevisionTest extends AbstractGitHubWireMockTest { - private GitHub github; - private GHRepository repo; - - @Before - public void setupRevisionTests() throws Exception { - github = Connector.connect("http://localhost:" + githubApi.port(), null); - repo = github.getRepository("cloudbeers/yolo"); + private GitHub github; + private GHRepository repo; + + @Before + public void setupRevisionTests() throws Exception { + github = Connector.connect("http://localhost:" + githubApi.port(), null); + repo = github.getRepository("cloudbeers/yolo"); + } + + public static SCMHead master = new BranchSCMHead("master"); + public static PullRequestSCMHead prHead = + new PullRequestSCMHead( + "", + "stephenc", + "yolo", + "master", + 1, + (BranchSCMHead) master, + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.HEAD); + public static PullRequestSCMHead prMerge = + new PullRequestSCMHead( + "", + "stephenc", + "yolo", + "master", + 1, + (BranchSCMHead) master, + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE); + + @Test + public void createHeadwithNullMergeRevision() throws Exception { + PullRequestSCMHead currentHead = prHead; + PullRequestSCMHead otherHead = prMerge; + + PullRequestSCMRevision currentRevision = + new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision"); + assertThat(currentRevision.toString(), is("pr-branch-revision")); + + try { + currentRevision.validateMergeHash(); + } catch (AbortException e) { + fail("Validation should succeed, but: " + e.getMessage()); } - public static SCMHead master = new BranchSCMHead("master"); - public static PullRequestSCMHead prHead = new PullRequestSCMHead("", "stephenc", "yolo", "master", 1, (BranchSCMHead) master, - SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.HEAD); - public static PullRequestSCMHead prMerge = new PullRequestSCMHead("", "stephenc", "yolo", "master", 1, (BranchSCMHead) master, - SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE); - - @Test - public void createHeadwithNullMergeRevision() throws Exception { - PullRequestSCMHead currentHead = prHead; - PullRequestSCMHead otherHead = prMerge; - - PullRequestSCMRevision currentRevision = new PullRequestSCMRevision( - currentHead, "master-revision", "pr-branch-revision"); - assertThat(currentRevision.toString(), is("pr-branch-revision")); - - try { - currentRevision.validateMergeHash(); - } catch (AbortException e) { - fail("Validation should succeed, but: " + e.getMessage()); - } - - // equivalence - assertTrue(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(otherHead, "master-revision-changed", "pr-branch-revision", "any"))); - - // equality - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", "any"))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision-changed", "any")))); - - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(otherHead, "master-revision", "pr-branch-revision", null)))); - } - - @Test - public void createHeadwithMergeRevision() throws Exception { - PullRequestSCMHead currentHead = prHead; - PullRequestSCMHead otherHead = prMerge; - - PullRequestSCMRevision currentRevision = new PullRequestSCMRevision( + // equivalence + assertTrue( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + otherHead, "master-revision-changed", "pr-branch-revision", "any"))); + + // equality + assertThat( + currentRevision, + is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision-changed", "any")))); + + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + otherHead, "master-revision", "pr-branch-revision", null)))); + } + + @Test + public void createHeadwithMergeRevision() throws Exception { + PullRequestSCMHead currentHead = prHead; + PullRequestSCMHead otherHead = prMerge; + + PullRequestSCMRevision currentRevision = + new PullRequestSCMRevision( currentHead, "master-revision", "pr-branch-revision", "pr-merge-revision"); - assertThat(currentRevision.toString(), is("pr-branch-revision")); - - try { - currentRevision.validateMergeHash(); - } catch (AbortException e) { - fail("Validation should succeed, but: " + e.getMessage()); - } - - // equivalence - assertTrue(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(otherHead, "master-revision-changed", "pr-branch-revision", "any"))); - - // equality - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", "any"))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision-changed", "any")))); - - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(otherHead, "master-revision", "pr-branch-revision", null)))); - } + assertThat(currentRevision.toString(), is("pr-branch-revision")); - @Test - public void createMergewithNullMergeRevision() throws Exception { - PullRequestSCMHead currentHead = prMerge; - PullRequestSCMHead otherHead = prHead; - - PullRequestSCMRevision currentRevision = new PullRequestSCMRevision( - currentHead, "master-revision", "pr-branch-revision"); - assertThat(currentRevision.toString(), is("pr-branch-revision+master-revision (UNKNOWN_MERGE_STATE)")); - - try { - currentRevision.validateMergeHash(); - } catch (AbortException e) { - fail("Validation should succeed, but: " + e.getMessage()); - } - - // equivalence - assertTrue(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(otherHead, "master-revision-changed", "pr-branch-revision", "any"))); - - // equality - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", "any"))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any")))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision-changed", "any")))); - - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(otherHead, "master-revision", "pr-branch-revision", null)))); + try { + currentRevision.validateMergeHash(); + } catch (AbortException e) { + fail("Validation should succeed, but: " + e.getMessage()); } - @Test - public void createMergewithNotMergeableRevision() throws Exception { - PullRequestSCMHead currentHead = prMerge; - PullRequestSCMHead otherHead = prHead; - - PullRequestSCMRevision currentRevision = new PullRequestSCMRevision( - currentHead, "master-revision", "pr-branch-revision", PullRequestSCMRevision.NOT_MERGEABLE_HASH); - assertThat(currentRevision.toString(), is("pr-branch-revision+master-revision (NOT_MERGEABLE)")); - - // validation should fail for this PR. - Exception abort = null; - try { - currentRevision.validateMergeHash(); - } catch (Exception e) { - abort = e; - } - assertThat(abort, instanceOf(AbortException.class)); - assertThat(abort.getMessage(), containsString("Not mergeable")); - - // equivalence - assertTrue(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(otherHead, "master-revision-changed", "pr-branch-revision", "any"))); - - // equality - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", "any"))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any")))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision-changed", "any")))); - - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(otherHead, "master-revision", "pr-branch-revision", null)))); + // equivalence + assertTrue( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + otherHead, "master-revision-changed", "pr-branch-revision", "any"))); + + // equality + assertThat( + currentRevision, + is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision-changed", "any")))); + + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + otherHead, "master-revision", "pr-branch-revision", null)))); + } + + @Test + public void createMergewithNullMergeRevision() throws Exception { + PullRequestSCMHead currentHead = prMerge; + PullRequestSCMHead otherHead = prHead; + + PullRequestSCMRevision currentRevision = + new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision"); + assertThat( + currentRevision.toString(), is("pr-branch-revision+master-revision (UNKNOWN_MERGE_STATE)")); + + try { + currentRevision.validateMergeHash(); + } catch (AbortException e) { + fail("Validation should succeed, but: " + e.getMessage()); } - @Test - public void createMergewithMergeRevision() throws Exception { - PullRequestSCMHead currentHead = prMerge; - PullRequestSCMHead otherHead = prHead; - - PullRequestSCMRevision currentRevision = new PullRequestSCMRevision( + // equivalence + assertTrue( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + otherHead, "master-revision-changed", "pr-branch-revision", "any"))); + + // equality + assertThat( + currentRevision, + is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any")))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision-changed", "any")))); + + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + otherHead, "master-revision", "pr-branch-revision", null)))); + } + + @Test + public void createMergewithNotMergeableRevision() throws Exception { + PullRequestSCMHead currentHead = prMerge; + PullRequestSCMHead otherHead = prHead; + + PullRequestSCMRevision currentRevision = + new PullRequestSCMRevision( + currentHead, + "master-revision", + "pr-branch-revision", + PullRequestSCMRevision.NOT_MERGEABLE_HASH); + assertThat( + currentRevision.toString(), is("pr-branch-revision+master-revision (NOT_MERGEABLE)")); + + // validation should fail for this PR. + Exception abort = null; + try { + currentRevision.validateMergeHash(); + } catch (Exception e) { + abort = e; + } + assertThat(abort, instanceOf(AbortException.class)); + assertThat(abort.getMessage(), containsString("Not mergeable")); + + // equivalence + assertTrue( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + otherHead, "master-revision-changed", "pr-branch-revision", "any"))); + + // equality + assertThat( + currentRevision, + is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any")))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision-changed", "any")))); + + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + otherHead, "master-revision", "pr-branch-revision", null)))); + } + + @Test + public void createMergewithMergeRevision() throws Exception { + PullRequestSCMHead currentHead = prMerge; + PullRequestSCMHead otherHead = prHead; + + PullRequestSCMRevision currentRevision = + new PullRequestSCMRevision( currentHead, "master-revision", "pr-branch-revision", "pr-merge-revision"); - assertThat(currentRevision.toString(), is("pr-branch-revision+master-revision (pr-merge-revision)")); - - try { - currentRevision.validateMergeHash(); - } catch (AbortException e) { - fail("Validation should succeed, but: " + e.getMessage()); - } - - // equivalence - assertTrue(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); - assertFalse(currentRevision.equivalent(new PullRequestSCMRevision(otherHead, "master-revision-changed", "pr-branch-revision", "any"))); - - // equality - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); - assertThat(currentRevision, - is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", "any"))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision-changed", "pr-branch-revision", "any")))); - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision-changed", "any")))); - - assertThat(currentRevision, - not(is(new PullRequestSCMRevision(otherHead, "master-revision", "pr-branch-revision", null)))); + assertThat( + currentRevision.toString(), is("pr-branch-revision+master-revision (pr-merge-revision)")); + + try { + currentRevision.validateMergeHash(); + } catch (AbortException e) { + fail("Validation should succeed, but: " + e.getMessage()); } + + // equivalence + assertTrue( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision-changed", "any"))); + assertFalse( + currentRevision.equivalent( + new PullRequestSCMRevision( + otherHead, "master-revision-changed", "pr-branch-revision", "any"))); + + // equality + assertThat( + currentRevision, + is(new PullRequestSCMRevision(currentHead, "master-revision", "pr-branch-revision", null))); + assertThat( + currentRevision, + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision", "any"))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision-changed", "pr-branch-revision", "any")))); + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + currentHead, "master-revision", "pr-branch-revision-changed", "any")))); + + assertThat( + currentRevision, + not( + is( + new PullRequestSCMRevision( + otherHead, "master-revision", "pr-branch-revision", null)))); + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTraitTest.java index e89593e57..faeea1c74 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/SSHCheckoutTraitTest.java @@ -1,5 +1,11 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + import hudson.model.Item; import hudson.model.User; import hudson.security.ACL; @@ -14,112 +20,127 @@ import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.MockFolder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - public class SSHCheckoutTraitTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); + @ClassRule public static JenkinsRule j = new JenkinsRule(); - @Test - public void given__legacyConfig__when__creatingTrait__then__convertedToModern() throws Exception { - assertThat(new SSHCheckoutTrait(GitHubSCMSource.DescriptorImpl.ANONYMOUS).getCredentialsId(), - is(nullValue())); - } + @Test + public void given__legacyConfig__when__creatingTrait__then__convertedToModern() throws Exception { + assertThat( + new SSHCheckoutTrait(GitHubSCMSource.DescriptorImpl.ANONYMOUS).getCredentialsId(), + is(nullValue())); + } - @Test - public void given__sshCheckoutWithCredentials__when__decorating__then__credentialsApplied_with_repositoryUrl() throws Exception { - SSHCheckoutTrait instance = new SSHCheckoutTrait("keyId"); - GitHubSCMSource source = new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true); - source.setCredentialsId("scanId"); - GitHubSCMBuilder probe = new GitHubSCMBuilder( source, new BranchSCMHead("master"), null); - assumeThat(probe.credentialsId(), is("scanId")); - instance.decorateBuilder(probe); - assertThat(probe.credentialsId(), is("keyId")); - } - @Test - public void given__sshCheckoutWithCredentials__when__decorating__then__credentialsApplied() throws Exception { - SSHCheckoutTrait instance = new SSHCheckoutTrait("keyId"); - GitHubSCMSource source = new GitHubSCMSource("example", "does-not-exist"); - source.setApiUri("https://github.test"); - source.setCredentialsId("scanId"); - GitHubSCMBuilder probe = new GitHubSCMBuilder( source, new BranchSCMHead("master"), null); - assumeThat(probe.credentialsId(), is("scanId")); - instance.decorateBuilder(probe); - assertThat(probe.credentialsId(), is("keyId")); - } + @Test + public void + given__sshCheckoutWithCredentials__when__decorating__then__credentialsApplied_with_repositoryUrl() + throws Exception { + SSHCheckoutTrait instance = new SSHCheckoutTrait("keyId"); + GitHubSCMSource source = + new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true); + source.setCredentialsId("scanId"); + GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); + assumeThat(probe.credentialsId(), is("scanId")); + instance.decorateBuilder(probe); + assertThat(probe.credentialsId(), is("keyId")); + } - @Test - public void given__sshCheckoutWithAgentKey__when__decorating__then__useAgentKeyApplied_with_repositoryUrl() throws Exception { - SSHCheckoutTrait instance = new SSHCheckoutTrait(null); - GitHubSCMSource source = new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true); - source.setCredentialsId("scanId"); - GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); - assumeThat(probe.credentialsId(), is("scanId")); - instance.decorateBuilder(probe); - assertThat(probe.credentialsId(), is(nullValue())); - } - @Test - public void given__sshCheckoutWithAgentKey__when__decorating__then__useAgentKeyApplied() throws Exception { - SSHCheckoutTrait instance = new SSHCheckoutTrait(null); - GitHubSCMSource source = new GitHubSCMSource("example", "does-not-exist"); - source.setApiUri("https://github.test"); - source.setCredentialsId("scanId"); - GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); - assumeThat(probe.credentialsId(), is("scanId")); - instance.decorateBuilder(probe); - assertThat(probe.credentialsId(), is(nullValue())); - } + @Test + public void given__sshCheckoutWithCredentials__when__decorating__then__credentialsApplied() + throws Exception { + SSHCheckoutTrait instance = new SSHCheckoutTrait("keyId"); + GitHubSCMSource source = new GitHubSCMSource("example", "does-not-exist"); + source.setApiUri("https://github.test"); + source.setCredentialsId("scanId"); + GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); + assumeThat(probe.credentialsId(), is("scanId")); + instance.decorateBuilder(probe); + assertThat(probe.credentialsId(), is("keyId")); + } + + @Test + public void + given__sshCheckoutWithAgentKey__when__decorating__then__useAgentKeyApplied_with_repositoryUrl() + throws Exception { + SSHCheckoutTrait instance = new SSHCheckoutTrait(null); + GitHubSCMSource source = + new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true); + source.setCredentialsId("scanId"); + GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); + assumeThat(probe.credentialsId(), is("scanId")); + instance.decorateBuilder(probe); + assertThat(probe.credentialsId(), is(nullValue())); + } + + @Test + public void given__sshCheckoutWithAgentKey__when__decorating__then__useAgentKeyApplied() + throws Exception { + SSHCheckoutTrait instance = new SSHCheckoutTrait(null); + GitHubSCMSource source = new GitHubSCMSource("example", "does-not-exist"); + source.setApiUri("https://github.test"); + source.setCredentialsId("scanId"); + GitHubSCMBuilder probe = new GitHubSCMBuilder(source, new BranchSCMHead("master"), null); + assumeThat(probe.credentialsId(), is("scanId")); + instance.decorateBuilder(probe); + assertThat(probe.credentialsId(), is(nullValue())); + } - @Test - public void given__descriptor__when__displayingCredentials__then__contractEnforced() throws Exception { - final SSHCheckoutTrait.DescriptorImpl d = j.jenkins.getDescriptorByType(SSHCheckoutTrait.DescriptorImpl.class); - final MockFolder dummy = j.createFolder("dummy"); - SecurityRealm realm = j.jenkins.getSecurityRealm(); - AuthorizationStrategy strategy = j.jenkins.getAuthorizationStrategy(); - try { - j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); - MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); - mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); - mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); - mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); - j.jenkins.setAuthorizationStrategy(mockStrategy); - try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - is("does-not-exist")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - } - try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - is("does-not-exist")); - } - try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting just the empty entry", rsp, hasSize(1)); - assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); - } - try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { - ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); - assertThat("Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); - assertThat("Expecting only the provided value so that form config unchanged", rsp.get(0).value, - is("does-not-exist")); - } - } finally { - j.jenkins.setSecurityRealm(realm); - j.jenkins.setAuthorizationStrategy(strategy); - j.jenkins.remove(dummy); - } + @Test + public void given__descriptor__when__displayingCredentials__then__contractEnforced() + throws Exception { + final SSHCheckoutTrait.DescriptorImpl d = + j.jenkins.getDescriptorByType(SSHCheckoutTrait.DescriptorImpl.class); + final MockFolder dummy = j.createFolder("dummy"); + SecurityRealm realm = j.jenkins.getSecurityRealm(); + AuthorizationStrategy strategy = j.jenkins.getAuthorizationStrategy(); + try { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); + mockStrategy.grant(Jenkins.ADMINISTER).onRoot().to("admin"); + mockStrategy.grant(Item.CONFIGURE).onItems(dummy).to("bob"); + mockStrategy.grant(Item.EXTENDED_READ).onItems(dummy).to("jim"); + j.jenkins.setAuthorizationStrategy(mockStrategy); + try (ACLContext ctx = ACL.as(User.getById("admin", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + } + try (ACLContext ctx = ACL.as(User.getById("bob", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + rsp = d.doFillCredentialsIdItems(null, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + } + try (ACLContext ctx = ACL.as(User.getById("jim", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat("Expecting just the empty entry", rsp, hasSize(1)); + assertThat("Expecting just the empty entry", rsp.get(0).value, is("")); + } + try (ACLContext ctx = ACL.as(User.getById("sue", true).impersonate())) { + ListBoxModel rsp = d.doFillCredentialsIdItems(dummy, "", "does-not-exist"); + assertThat( + "Expecting only the provided value so that form config unchanged", rsp, hasSize(1)); + assertThat( + "Expecting only the provided value so that form config unchanged", + rsp.get(0).value, + is("does-not-exist")); + } + } finally { + j.jenkins.setSecurityRealm(realm); + j.jenkins.setAuthorizationStrategy(strategy); + j.jenkins.remove(dummy); } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTraitTest.java index d5920df3f..790e1487b 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/TagDiscoveryTraitTest.java @@ -1,5 +1,10 @@ package org.jenkinsci.plugins.github_branch_source; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + import java.util.Collections; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadObserver; @@ -13,56 +18,81 @@ import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; - public class TagDiscoveryTraitTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); + @ClassRule public static JenkinsRule j = new JenkinsRule(); - @Test - public void decorateContext() throws Exception { - GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); - assertThat(probe.wantBranches(), is(false)); - assertThat(probe.wantPRs(), is(false)); - assertThat(probe.wantTags(), is(false)); - assertThat(probe.authorities(), is(Collections.>emptyList())); - new TagDiscoveryTrait().applyToContext(probe); - assertThat(probe.wantBranches(), is(false)); - assertThat(probe.wantPRs(), is(false)); - assertThat(probe.wantTags(), is(true)); - assertThat(probe.authorities(), contains(instanceOf(TagDiscoveryTrait.TagSCMHeadAuthority.class))); - } + @Test + public void decorateContext() throws Exception { + GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); + assertThat(probe.wantBranches(), is(false)); + assertThat(probe.wantPRs(), is(false)); + assertThat(probe.wantTags(), is(false)); + assertThat(probe.authorities(), is(Collections.>emptyList())); + new TagDiscoveryTrait().applyToContext(probe); + assertThat(probe.wantBranches(), is(false)); + assertThat(probe.wantPRs(), is(false)); + assertThat(probe.wantTags(), is(true)); + assertThat( + probe.authorities(), contains(instanceOf(TagDiscoveryTrait.TagSCMHeadAuthority.class))); + } - @Test - public void includeCategory() throws Exception { - assertThat(new TagDiscoveryTrait().includeCategory(ChangeRequestSCMHeadCategory.DEFAULT), is(false)); - assertThat(new TagDiscoveryTrait().includeCategory(UncategorizedSCMHeadCategory.DEFAULT), is(false)); - assertThat(new TagDiscoveryTrait().includeCategory(TagSCMHeadCategory.DEFAULT), is(true)); - } + @Test + public void includeCategory() throws Exception { + assertThat( + new TagDiscoveryTrait().includeCategory(ChangeRequestSCMHeadCategory.DEFAULT), is(false)); + assertThat( + new TagDiscoveryTrait().includeCategory(UncategorizedSCMHeadCategory.DEFAULT), is(false)); + assertThat(new TagDiscoveryTrait().includeCategory(TagSCMHeadCategory.DEFAULT), is(true)); + } - @Test - public void authority() throws Exception { - try (GitHubSCMSourceRequest probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()).newRequest(new GitHubSCMSource("does-not-exist","http://does-not-exist.test"), null)) { - TagDiscoveryTrait.TagSCMHeadAuthority instance = new TagDiscoveryTrait.TagSCMHeadAuthority(); - assertThat(instance.isTrusted(probe, new SCMHead("v1.0.0")), is(false)); - assertThat(instance.isTrusted(probe, new PullRequestSCMHead("PR-1", "does-not-exists", - "http://does-not-exist.test", "feature/1", 1, new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE)), is(false)); - assertThat(instance.isTrusted(probe, new GitHubTagSCMHead("v1.0.0", 0L)), is(true)); - } - } - @Test - public void authority_with_repositoryUrl() throws Exception { - try (GitHubSCMSourceRequest probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()).newRequest( - new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true), null)) { - TagDiscoveryTrait.TagSCMHeadAuthority instance = new TagDiscoveryTrait.TagSCMHeadAuthority(); - assertThat(instance.isTrusted(probe, new SCMHead("v1.0.0")), is(false)); - assertThat(instance.isTrusted(probe, new PullRequestSCMHead("PR-1", "does-not-exists", - "http://does-not-exist.test", "feature/1", 1, new BranchSCMHead("master"), SCMHeadOrigin.DEFAULT, ChangeRequestCheckoutStrategy.MERGE)), is(false)); - assertThat(instance.isTrusted(probe, new GitHubTagSCMHead("v1.0.0", 0L)), is(true)); - } + @Test + public void authority() throws Exception { + try (GitHubSCMSourceRequest probe = + new GitHubSCMSourceContext(null, SCMHeadObserver.collect()) + .newRequest( + new GitHubSCMSource("does-not-exist", "http://does-not-exist.test"), null)) { + TagDiscoveryTrait.TagSCMHeadAuthority instance = new TagDiscoveryTrait.TagSCMHeadAuthority(); + assertThat(instance.isTrusted(probe, new SCMHead("v1.0.0")), is(false)); + assertThat( + instance.isTrusted( + probe, + new PullRequestSCMHead( + "PR-1", + "does-not-exists", + "http://does-not-exist.test", + "feature/1", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE)), + is(false)); + assertThat(instance.isTrusted(probe, new GitHubTagSCMHead("v1.0.0", 0L)), is(true)); } + } + @Test + public void authority_with_repositoryUrl() throws Exception { + try (GitHubSCMSourceRequest probe = + new GitHubSCMSourceContext(null, SCMHeadObserver.collect()) + .newRequest( + new GitHubSCMSource("", "", "https://github.com/example/does-not-exist", true), + null)) { + TagDiscoveryTrait.TagSCMHeadAuthority instance = new TagDiscoveryTrait.TagSCMHeadAuthority(); + assertThat(instance.isTrusted(probe, new SCMHead("v1.0.0")), is(false)); + assertThat( + instance.isTrusted( + probe, + new PullRequestSCMHead( + "PR-1", + "does-not-exists", + "http://does-not-exist.test", + "feature/1", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE)), + is(false)); + assertThat(instance.isTrusted(probe, new GitHubTagSCMHead("v1.0.0", 0L)), is(true)); + } + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/WireMockRuleFactory.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/WireMockRuleFactory.java index cedc151c3..1f7d2024e 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/WireMockRuleFactory.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/WireMockRuleFactory.java @@ -25,40 +25,40 @@ package org.jenkinsci.plugins.github_branch_source; -import com.github.tomakehurst.wiremock.common.SingleRootFileSource; -import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.junit.WireMockRule; - import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import com.github.tomakehurst.wiremock.common.SingleRootFileSource; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + public class WireMockRuleFactory { - private String urlToMock = System.getProperty("wiremock.record"); + private String urlToMock = System.getProperty("wiremock.record"); - public WireMockRule getRule(int port) { - return getRule(wireMockConfig().port(port)); - } + public WireMockRule getRule(int port) { + return getRule(wireMockConfig().port(port)); + } - public WireMockRule getRule(Options options) { - if (urlToMock != null && !urlToMock.isEmpty()) { - return new WireMockRecorderRule(options, urlToMock); - } else { - return new WireMockRule(options); - } + public WireMockRule getRule(Options options) { + if (urlToMock != null && !urlToMock.isEmpty()) { + return new WireMockRecorderRule(options, urlToMock); + } else { + return new WireMockRule(options); } + } + private class WireMockRecorderRule extends WireMockRule { + // needed for WireMockRule file location + private String mappingLocation = "src/test/resources"; - private class WireMockRecorderRule extends WireMockRule { - //needed for WireMockRule file location - private String mappingLocation = "src/test/resources"; - - public WireMockRecorderRule(Options options, String url) { - super(options); - this.stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom(url))); - this.enableRecordMappings(new SingleRootFileSource(mappingLocation + "/mappings"), - new SingleRootFileSource(mappingLocation + "/__files")); - } + public WireMockRecorderRule(Options options, String url) { + super(options); + this.stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom(url))); + this.enableRecordMappings( + new SingleRootFileSource(mappingLocation + "/mappings"), + new SingleRootFileSource(mappingLocation + "/__files")); } + } }