From 51545c395c1c893412a4967c4176f377879955f8 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 1 Jun 2023 10:12:32 -0700 Subject: [PATCH] Use upstream Spotless configuration (#684) * Use upstream Spotless configuration * Activate Spotless via Maven property rather than file --- pom.xml | 377 +- .../AbstractGitHubNotificationStrategy.java | 28 +- .../ApiRateLimitChecker.java | 603 +- .../BranchDiscoveryTrait.java | 339 +- .../github_branch_source/BranchSCMHead.java | 49 +- .../github_branch_source/Connector.java | 1305 ++-- .../DefaultGitHubNotificationStrategy.java | 41 +- .../github_branch_source/Endpoint.java | 221 +- .../ExcludeArchivedRepositoriesTrait.java | 48 +- .../ExcludeForkedRepositoriesTrait.java | 48 +- .../ExcludePrivateRepositoriesTrait.java | 48 +- .../ExcludePublicRepositoriesTrait.java | 48 +- .../FillErrorResponse.java | 39 +- .../ForkPullRequestDiscoveryTrait.java | 554 +- .../GitHubAppCredentials.java | 1184 ++-- .../GitHubAppCredentialsSnapshotTaker.java | 19 +- .../GitHubBranchFilter.java | 47 +- .../GitHubBuildStatusNotification.java | 510 +- .../github_branch_source/GitHubClosable.java | 2 +- .../GitHubConfiguration.java | 346 +- .../GitHubConsoleNote.java | 53 +- .../GitHubDefaultBranch.java | 119 +- .../github_branch_source/GitHubLink.java | 145 +- .../GitHubNotificationContext.java | 399 +- .../GitHubNotificationRequest.java | 232 +- .../GitHubOrgMetadataAction.java | 151 +- .../GitHubOrgWebHook.java | 176 +- .../GitHubPermissionsSource.java | 18 +- .../GitHubPullRequestFilter.java | 47 +- .../GitHubRepoMetadataAction.java | 60 +- .../GitHubRepositoryEventSubscriber.java | 188 +- .../GitHubRepositoryInfo.java | 136 +- .../GitHubSCMBuilder.java | 508 +- .../github_branch_source/GitHubSCMFile.java | 284 +- .../GitHubSCMFileSystem.java | 442 +- .../GitHubSCMNavigator.java | 3602 ++++++----- .../GitHubSCMNavigatorContext.java | 189 +- .../GitHubSCMNavigatorRequest.java | 26 +- .../github_branch_source/GitHubSCMProbe.java | 261 +- .../github_branch_source/GitHubSCMSource.java | 5513 ++++++++--------- .../GitHubSCMSourceBuilder.java | 157 +- .../GitHubSCMSourceContext.java | 500 +- ...HubSCMSourceRepositoryNameContributor.java | 27 +- .../GitHubSCMSourceRequest.java | 836 +-- .../GitHubTagSCMHead.java | 28 +- .../HttpsRepositoryUriResolver.java | 16 +- .../IgnoreDraftPullRequestFilterTrait.java | 89 +- .../InvalidPrivateKeyException.java | 6 +- .../github_branch_source/LazyIterable.java | 33 +- .../plugins/github_branch_source/LazySet.java | 213 +- .../MergeWithGitSCMExtension.java | 12 +- .../OriginPullRequestDiscoveryTrait.java | 253 +- .../PullRequestAction.java | 78 +- .../PullRequestGHEventSubscriber.java | 571 +- .../PullRequestSCMHead.java | 566 +- .../PullRequestSCMRevision.java | 198 +- .../PullRequestSource.java | 34 +- .../PushGHEventSubscriber.java | 510 +- .../RateLimitExceededException.java | 58 +- .../RepositoryUriResolver.java | 57 +- .../SSHCheckoutTrait.java | 277 +- .../SinglePassIterable.java | 178 +- .../SshRepositoryUriResolver.java | 10 +- .../TagDiscoveryTrait.java | 109 +- .../github_branch_source/TeamSlugTrait.java | 81 +- .../github_branch_source/TopicsTrait.java | 115 +- .../UntrustedPullRequestSCMRevision.java | 34 +- .../AbstractGitHubWireMockTest.java | 128 +- .../ApiRateLimitCheckerTest.java | 1703 +++-- .../BranchDiscoveryTraitTest.java | 134 +- ...DefaultGitHubNotificationStrategyTest.java | 178 +- .../github_branch_source/EndpointTest.java | 215 +- .../github_branch_source/EventsTest.java | 380 +- .../ForkPullRequestDiscoveryTrait2Test.java | 79 +- .../ForkPullRequestDiscoveryTraitTest.java | 188 +- ...bAppCredentialsJCasCCompatibilityTest.java | 122 +- ...ubBranchSourcesJCasCCompatibilityTest.java | 56 +- .../GitHubNotificationTest.java | 135 +- .../GitHubOrgWebHookTest.java | 66 +- .../GitHubSCMBuilderTest.java | 5504 ++++++++-------- .../GitHubSCMFileSystemTest.java | 431 +- .../GitHubSCMNavigatorTest.java | 892 ++- .../GitHubSCMNavigatorTraitsTest.java | 2456 ++++---- .../GitHubSCMProbeTest.java | 338 +- .../GitHubSCMSourceHelperTest.java | 30 +- .../GitHubSCMSourceTest.java | 1817 +++--- .../GitHubSCMSourceTraitsTest.java | 3481 +++++------ .../GitSCMSourceBase.java | 33 +- ...ppCredentialsAppInstallationTokenTest.java | 177 +- .../GithubAppCredentialsTest.java | 1003 ++- .../GithubSCMSourceBranchesTest.java | 331 +- .../GithubSCMSourcePRsTest.java | 485 +- .../GithubSCMSourceTagsTest.java | 659 +- ...IgnoreDraftPullRequestFilterTraitTest.java | 108 +- .../OriginPullRequestDiscoveryTraitTest.java | 200 +- .../PullRequestSCMRevisionTest.java | 558 +- .../SSHCheckoutTraitTest.java | 218 +- .../TagDiscoveryTraitTest.java | 133 +- .../WireMockRuleFactory.java | 41 +- 99 files changed, 22076 insertions(+), 23654 deletions(-) diff --git a/pom.xml b/pom.xml index 100555780..0755a48b2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,217 +1,180 @@ - 4.0.0 - - org.jenkins-ci.plugins - plugin - 4.57 - - - github-branch-source - ${changelist} - hpi - GitHub Branch Source Plugin - https://github.com/jenkinsci/${project.artifactId}-plugin - Multibranch projects and organization folders from GitHub. Maintained by CloudBees, Inc. - - - MIT - https://opensource.org/licenses/MIT - - + 4.0.0 + + org.jenkins-ci.plugins + plugin + 4.61 + + - - 999999-SNAPSHOT - jenkinsci/${project.artifactId}-plugin - 2.2.0 - 2.361.4 - true - + github-branch-source + ${changelist} + hpi + GitHub Branch Source Plugin + Multibranch projects and organization folders from GitHub. Maintained by CloudBees, Inc. + https://github.com/jenkinsci/${project.artifactId}-plugin - - scm:git:https://github.com/${gitHubRepo}.git - scm:git:git@github.com:${gitHubRepo}.git - https://github.com/${gitHubRepo} - ${scmTag} - - - - org.jenkins-ci.plugins - github-api - - - com.coravy.hudson.plugins.github - github - 1.37.0 - - - org.jenkins-ci.plugins - credentials - - - org.jenkins-ci.plugins.workflow - workflow-support - - - io.jenkins.plugins - jjwt-api - - - org.jenkins-ci.plugins - scm-api - tests - test - - - org.jenkins-ci.plugins.workflow - workflow-multibranch - test - - - com.github.tomakehurst - wiremock-jre8-standalone - 2.35.0 - test - - - org.mockito - mockito-core - test - - - org.jenkins-ci.plugins.workflow - workflow-basic-steps - test - - - org.jenkins-ci.plugins - git - tests - test - - - org.apache.httpcomponents - httpclient - - - + + + MIT + https://opensource.org/licenses/MIT + + + + + scm:git:https://github.com/${gitHubRepo}.git + scm:git:git@github.com:${gitHubRepo}.git + ${scmTag} + https://github.com/${gitHubRepo} + + + 999999-SNAPSHOT + jenkinsci/${project.artifactId}-plugin + 2.2.0 + 2.361.4 + true + false + - - - io.jenkins - configuration-as-code - test - - - io.jenkins.configuration-as-code - test-harness - test - - - io.jenkins.plugins - pipeline-groovy-lib - test - - - org.awaitility - awaitility - 4.2.0 - test - - - org.hamcrest - hamcrest - - - - - io.jenkins.plugins - okhttp-api - + + + + io.jenkins.tools.bom + bom-2.361.x + 1935.v530f4395930f + pom + import + - - - - io.jenkins.tools.bom - bom-2.361.x - 1935.v530f4395930f - import - pom - - - - + + + + + com.coravy.hudson.plugins.github + github + 1.37.0 + + + io.jenkins.plugins + jjwt-api + + + io.jenkins.plugins + okhttp-api + + + org.jenkins-ci.plugins + credentials + + + org.jenkins-ci.plugins + github-api + + + org.jenkins-ci.plugins.workflow + workflow-support + + + com.github.tomakehurst + wiremock-jre8-standalone + 2.35.0 + test + + + io.jenkins + configuration-as-code + test + + + io.jenkins.configuration-as-code + test-harness + test + + + io.jenkins.plugins + pipeline-groovy-lib + test + + + org.awaitility + awaitility + 4.2.0 + test + + + org.jenkins-ci.plugins + git + tests + test + + + org.jenkins-ci.plugins + scm-api + tests + test + + + org.jenkins-ci.plugins.workflow + workflow-basic-steps + test + + + org.jenkins-ci.plugins.workflow + workflow-multibranch + test + + + org.mockito + mockito-core + test + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + + ci-non-windows + + + !windows + + + set.changelist + + + - - com.diffplug.spotless - spotless-maven-plugin - 2.35.0 - - - spotless-check - - - - check - - - - - - - - - 1.7 - - - - + + com.diffplug.spotless + spotless-maven-plugin + + + spotless-check + + check + + + process-sources + + + - - - - ci-non-windows - - - set.changelist - - - !windows - - - - - - com.diffplug.spotless - spotless-maven-plugin - - - spotless-check - - process-sources - - check - - - - - - - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - + + + 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 8557fc4cd..84cb1dce6 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 @@ -34,20 +34,20 @@ */ 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 11eaf621e..57ce199ad 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 @@ -21,338 +21,329 @@ @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") // https://github.com/spotbugs/spotbugs/issues/1539 public enum ApiRateLimitChecker { - /** Attempt to evenly distribute GitHub API requests. */ - ThrottleForNormalize(Messages.ApiRateLimitChecker_ThrottleForNormalize()) { - @Override - public LocalChecker getChecker(@NonNull TaskListener listener, String apiUrl) { - return new LocalChecker(listener) { + /** Attempt to evenly distribute GitHub API requests. */ + ThrottleForNormalize(Messages.ApiRateLimitChecker_ThrottleForNormalize()) { @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; + 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; + } + }; } - }; - } - }, + }, - /** 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) { + /** 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 + 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 - 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); + 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()); + + /** + * 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); + + static void setEntropy(Random random) { + ENTROPY = random; } - }, - /** - * 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; - } - }; - } + + static void setExpirationWaitMillis(int expirationWaitMillis) { + EXPIRATION_WAIT_MILLIS = expirationWaitMillis; } - }; - - /** 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<>(); - - 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 setExpirationWaitMillis(int expirationWaitMillis) { - EXPIRATION_WAIT_MILLIS = expirationWaitMillis; - } - - static void setNotificationWaitMillis(int notificationWaitMillis) { - NOTIFICATION_WAIT_MILLIS = notificationWaitMillis; - } - - private String displayName; - - ApiRateLimitChecker(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - - 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); - } - - /** - * Verify a GitHub connection - * - *

WARNING: this call is not protected by rate limit checking. It is possible to exceed the - * rate limit by calling this method. - * - *

This method should only be called from {@link Connector}. This works without any locking - * because the checker is local to this thread. - * - * @param gitHub the GitHub connection to check for validity - */ - static void verifyConnection(GitHub gitHub) throws IOException { - Objects.requireNonNull(gitHub); - LocalChecker checker = getLocalChecker(); - try { - TaskListener listener = - checker != null ? checker.listener : new LogTaskListener(LOGGER, Level.INFO); - - // Pass empty apiUrl to force no rate limit checking - localRateLimitChecker.set(NoThrottle.getChecker(listener, "")); - - gitHub.checkApiUrlValidity(); - } finally { - localRateLimitChecker.set(checker); + + static void setNotificationWaitMillis(int notificationWaitMillis) { + NOTIFICATION_WAIT_MILLIS = notificationWaitMillis; } - } - - /** For test purposes only. */ - static LocalChecker getLocalChecker() { - return localRateLimitChecker.get(); - } - - /** 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); - } - - 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 by attempting to get the - // URL from the first configured GitHub endpoint, else default to the public endpoint. - // NOTE: Defaulting to the public GitHub endpoint is insufficient for those using GitHub - // enterprise as it forces rate limit checking in those cases. - String apiUrl = GitHubServerConfig.GITHUB_URL; - List endpoints = GitHubConfiguration.get().getEndpoints(); - if (endpoints.size() > 0 && !StringUtils.isBlank(endpoints.get(0).getApiUri())) { - apiUrl = endpoints.get(0).getApiUri(); - } - configureThreadLocalChecker(new LogTaskListener(LOGGER, Level.INFO), apiUrl); - checker = getLocalChecker(); - checker.writeLog( - "LocalChecker for rate limit was not set for this thread. " - + "Configured using system settings with API URL '" - + apiUrl - + "'."); - } - return checker.checkRateLimit(rateLimitRecord, count); + + private String displayName; + + ApiRateLimitChecker(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public static void configureThreadLocalChecker(@NonNull TaskListener listener, @NonNull GitHub gitHub) { + configureThreadLocalChecker(listener, gitHub.getApiUrl()); } - } - abstract static class LocalChecker { - @NonNull private final TaskListener listener; - private long expiration; + private static void configureThreadLocalChecker(TaskListener listener, String apiUrl) { + LocalChecker checker = + GitHubConfiguration.get().getApiRateLimitChecker().getChecker(listener, apiUrl); + localRateLimitChecker.set(checker); + } - LocalChecker(@NonNull TaskListener listener) { - this.listener = Objects.requireNonNull(listener); - resetExpiration(); + /** + * Verify a GitHub connection + * + *

WARNING: this call is not protected by rate limit checking. It is possible to exceed the + * rate limit by calling this method. + * + *

This method should only be called from {@link Connector}. This works without any locking + * because the checker is local to this thread. + * + * @param gitHub the GitHub connection to check for validity + */ + static void verifyConnection(GitHub gitHub) throws IOException { + Objects.requireNonNull(gitHub); + LocalChecker checker = getLocalChecker(); + try { + TaskListener listener = checker != null ? checker.listener : new LogTaskListener(LOGGER, Level.INFO); + + // Pass empty apiUrl to force no rate limit checking + localRateLimitChecker.set(NoThrottle.getChecker(listener, "")); + + gitHub.checkApiUrlValidity(); + } finally { + localRateLimitChecker.set(checker); + } } - 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); + /** For test purposes only. */ + static LocalChecker getLocalChecker() { + return localRateLimitChecker.get(); } - // internal for testing - abstract long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) - throws InterruptedException; + /** For test purposes only. */ + static void resetLocalChecker() { + localRateLimitChecker.set(null); + } - void resetExpiration() { - expiration = Long.MIN_VALUE; + /** + * 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); } - 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; + 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 by attempting to get the + // URL from the first configured GitHub endpoint, else default to the public endpoint. + // NOTE: Defaulting to the public GitHub endpoint is insufficient for those using GitHub + // enterprise as it forces rate limit checking in those cases. + String apiUrl = GitHubServerConfig.GITHUB_URL; + List endpoints = GitHubConfiguration.get().getEndpoints(); + if (endpoints.size() > 0 + && !StringUtils.isBlank(endpoints.get(0).getApiUri())) { + apiUrl = endpoints.get(0).getApiUri(); + } + configureThreadLocalChecker(new LogTaskListener(LOGGER, Level.INFO), apiUrl); + checker = getLocalChecker(); + checker.writeLog("LocalChecker for rate limit was not set for this thread. " + + "Configured using system settings with API URL '" + + apiUrl + + "'."); + } + return checker.checkRateLimit(rateLimitRecord, count); + } } - // 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))); + abstract static class LocalChecker { + @NonNull + private final TaskListener listener; + + private long expiration; + + LocalChecker(@NonNull TaskListener listener) { + this.listener = Objects.requireNonNull(listener); + resetExpiration(); } - if (Thread.interrupted()) { - throw new 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); } - long sleep = Math.min(expiration, nextNotify) - now; - if (sleep > 0) { - Thread.sleep(sleep); + + // internal for testing + abstract long checkRateLimitImpl(@NonNull GHRateLimit.Record rateLimit, long count, long now) + throws InterruptedException; + + void resetExpiration() { + expiration = Long.MIN_VALUE; } - } else { - resetExpiration(); - } - return waiting; - } - void writeLog(String output) { - listener.getLogger().println(GitHubConsoleNote.create(System.currentTimeMillis(), output)); + 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; + } + + // 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; + } + + 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 d35b0decb..821e0276a 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,203 +52,200 @@ * @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; - } - - /** - * 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; - } - } - - /** {@inheritDoc} */ - @Override - public boolean includeCategory(@NonNull SCMHeadCategory category) { - return category.isUncategorized(); - } + /** 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; - /** Our descriptor. */ - @Symbol("gitHubBranchDiscovery") - @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { + /** + * Constructor for stapler. + * + * @param strategyId the strategy id. + */ + @DataBoundConstructor + public BranchDiscoveryTrait(int strategyId) { + this.strategyId = strategyId; + } - /** {@inheritDoc} */ - @Override - public String getDisplayName() { - return Messages.BranchDiscoveryTrait_displayName(); + /** + * 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 Class getContextClass() { - return GitHubSCMSourceContext.class; + /** + * Returns the strategy id. + * + * @return the strategy id. + */ + public int getStrategyId() { + return strategyId; } - /** {@inheritDoc} */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.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; } /** - * Populates the strategy options. + * Returns {@code true} if building branches that are filed as a PR. * - * @return the strategy options. + * @return {@code true} if building branches that are filed as a PR. */ - @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 boolean isBuildBranchesWithPR() { + return (strategyId & ONLY_PRS) != NONE; } - } - /** Trusts branches from the origin repository. */ - public static class BranchSCMHeadAuthority - extends SCMHeadAuthority { /** {@inheritDoc} */ @Override - protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull BranchSCMHead head) { - return true; + 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; + } } - /** Out descriptor. */ - @Symbol("gitHubBranchHeadAuthority") + /** {@inheritDoc} */ + @Override + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category.isUncategorized(); + } + + /** Our descriptor. */ + @Symbol("gitHubBranchDiscovery") @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); - } + @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; + } } - } - /** 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())) { + /** Trusts branches from the origin repository. */ + public static class BranchSCMHeadAuthority extends SCMHeadAuthority { + /** {@inheritDoc} */ + @Override + protected boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull BranchSCMHead head) { return true; - } } - } - return false; + + /** 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 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())) { + /** 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; + } + } + + /** 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; - } } - 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 e2ec58b9a..70f59b06a 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 @@ -39,35 +39,34 @@ * @since 2.0.0 */ public class BranchSCMHead extends SCMHead { - /** {@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} */ + public BranchSCMHead(@NonNull String name) { + super(name); } + /** {@inheritDoc} */ @Override - public SCMHead migrate(@NonNull GitHubSCMSource source, @NonNull SCMHead head) { - return new BranchSCMHead(head.getName()); + public String getPronoun() { + return Messages.BranchSCMHead_Pronoun(); } - @Override - public SCMRevision migrate( - @NonNull GitHubSCMSource source, @NonNull AbstractGitSCMSource.SCMRevisionImpl revision) { - return new AbstractGitSCMSource.SCMRevisionImpl( - migrate(source, revision.getHead()), revision.getHash()); + @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()); + } } - } } 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 3167d9152..2c5f981d1 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 @@ -91,753 +91,702 @@ /** Utilities that could perhaps be moved into {@code github-api}. */ @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") // https://github.com/spotbugs/spotbugs/issues/1539 public class Connector { - private static final Logger LOGGER = Logger.getLogger(Connector.class.getName()); - - private static final Map connections = new ConcurrentHashMap<>(); - private static final Map reverseLookup = new ConcurrentHashMap<>(); - - 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 = - JenkinsOkHttpClient.newClientBuilder(new OkHttpClient()).build(); - - 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, 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. - * @deprecated use {@link #checkScanCredentials(Item, String, String, String)} - */ - @Deprecated - public static FormValidation checkScanCredentials( - @CheckForNull Item context, String apiUri, String scanCredentialsId) { - return checkScanCredentials(context, apiUri, scanCredentialsId, null); - } - - /** - * 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. - * @param repoOwner the org/user - * @return the {@link FormValidation} results. - */ - public static FormValidation checkScanCredentials( - @CheckForNull Item context, - String apiUri, - String scanCredentialsId, - @CheckForNull String repoOwner) { - if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) - || context != null && !context.hasPermission(Item.EXTENDED_READ)) { - return FormValidation.ok(); + private static final Logger LOGGER = Logger.getLogger(Connector.class.getName()); + + private static final Map connections = new ConcurrentHashMap<>(); + private static final Map reverseLookup = new ConcurrentHashMap<>(); + + 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 = + JenkinsOkHttpClient.newClientBuilder(new OkHttpClient()).build(); + + 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, 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. + * @deprecated use {@link #checkScanCredentials(Item, String, String, String)} + */ + @Deprecated + public static FormValidation checkScanCredentials( + @CheckForNull Item context, String apiUri, String scanCredentialsId) { + return checkScanCredentials(context, apiUri, scanCredentialsId, null); } - if (!scanCredentialsId.isEmpty()) { - ListBoxModel options = listScanCredentials(context, apiUri); - boolean found = false; - for (ListBoxModel.Option b : options) { - if (scanCredentialsId.equals(b.value)) { - found = true; - break; + + /** + * 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. + * @param repoOwner the org/user + * @return the {@link FormValidation} results. + */ + public static FormValidation checkScanCredentials( + @CheckForNull Item context, String apiUri, String scanCredentialsId, @CheckForNull String repoOwner) { + if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) + || context != null && !context.hasPermission(Item.EXTENDED_READ)) { + return FormValidation.ok(); } - } - 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, - repoOwner); - 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()); + if (!scanCredentialsId.isEmpty()) { + ListBoxModel options = listScanCredentials(context, apiUri); + boolean found = false; + for (ListBoxModel.Option b : options) { + if (scanCredentialsId.equals(b.value)) { + found = true; + break; + } } - } 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, + repoOwner); + 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"); + } + } + } else { + return FormValidation.warning("Credentials are recommended"); } - } - } 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, 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} - * @deprecated use {@link #lookupScanCredentials(Item, String, String, String)} - */ - @Deprecated - @CheckForNull - public static StandardCredentials lookupScanCredentials( - @CheckForNull Item context, - @CheckForNull String apiUri, - @CheckForNull String scanCredentialsId) { - return lookupScanCredentials(context, apiUri, scanCredentialsId, null); - } - - /** - * 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. - * @param repoOwner the org/user - * @return the {@link StandardCredentials} or {@code null} - */ - @CheckForNull - public static StandardCredentials lookupScanCredentials( - @CheckForNull Item context, - @CheckForNull String apiUri, - @CheckForNull String scanCredentialsId, - @CheckForNull String repoOwner) { - if (Util.fixEmpty(scanCredentialsId) == null) { - return null; - } else { - StandardCredentials c = - 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())); - if (c instanceof GitHubAppCredentials && repoOwner != null) { - return ((GitHubAppCredentials) c).withOwner(repoOwner); - } else { - return c; - } + + /** + * 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, String)} + */ + @Deprecated + @CheckForNull + public static StandardCredentials lookupScanCredentials( + @CheckForNull SCMSourceOwner context, @CheckForNull String apiUri, @CheckForNull String scanCredentialsId) { + return lookupScanCredentials((Item) context, apiUri, scanCredentialsId); } - } - - /** - * 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 final StandardCredentials credentials) - throws IOException { - apiUri = Util.fixEmptyAndTrim(apiUri); - final String apiUrl = apiUri != null ? apiUri : GitHubServerConfig.GITHUB_URL; - final String username; - final String password; - final String hash; - final String authHash; - final GitHubAppCredentials gitHubAppCredentials; - final Jenkins jenkins = Jenkins.get(); - if (credentials == null) { - username = null; - password = null; - hash = "anonymous"; - authHash = "anonymous"; - gitHubAppCredentials = null; - } else if (credentials instanceof GitHubAppCredentials) { - password = null; - 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()); - gitHubAppCredentials = null; - } else { - // TODO OAuth support - throw new IOException("Unsupported credential type: " + credentials.getClass().getName()); + + /** + * 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} + * @deprecated use {@link #lookupScanCredentials(Item, String, String, String)} + */ + @Deprecated + @CheckForNull + public static StandardCredentials lookupScanCredentials( + @CheckForNull Item context, @CheckForNull String apiUri, @CheckForNull String scanCredentialsId) { + return lookupScanCredentials(context, apiUri, scanCredentialsId, null); + } + + /** + * 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. + * @param repoOwner the org/user + * @return the {@link StandardCredentials} or {@code null} + */ + @CheckForNull + public static StandardCredentials lookupScanCredentials( + @CheckForNull Item context, + @CheckForNull String apiUri, + @CheckForNull String scanCredentialsId, + @CheckForNull String repoOwner) { + if (Util.fixEmpty(scanCredentialsId) == null) { + return null; + } else { + StandardCredentials c = 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())); + if (c instanceof GitHubAppCredentials && repoOwner != null) { + return ((GitHubAppCredentials) c).withOwner(repoOwner); + } else { + return c; + } + } } - final ConnectionId connectionId = new ConnectionId(apiUrl, hash); + /** + * 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); + } - GitHubConnection record = - GitHubConnection.lookup( - connectionId, - () -> { - try { + /** + * 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 final StandardCredentials credentials) throws IOException { + apiUri = Util.fixEmptyAndTrim(apiUri); + final String apiUrl = apiUri != null ? apiUri : GitHubServerConfig.GITHUB_URL; + final String username; + final String password; + final String hash; + final String authHash; + final GitHubAppCredentials gitHubAppCredentials; + final Jenkins jenkins = Jenkins.get(); + if (credentials == null) { + username = null; + password = null; + hash = "anonymous"; + authHash = "anonymous"; + gitHubAppCredentials = null; + } else if (credentials instanceof GitHubAppCredentials) { + password = null; + 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()); + gitHubAppCredentials = null; + } else { + // TODO OAuth support + throw new IOException( + "Unsupported credential type: " + credentials.getClass().getName()); + } + + final ConnectionId connectionId = new ConnectionId(apiUrl, hash); + + GitHubConnection record = GitHubConnection.lookup(connectionId, () -> { + try { Cache cache = getCache(jenkins, apiUrl, authHash, username); GitHubBuilder gb = createGitHubBuilder(apiUrl, cache); if (gitHubAppCredentials != null) { - gb.withAuthorizationProvider(gitHubAppCredentials.getAuthorizationProvider()); + 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)); + // 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)); } - return new GitHubConnection( - gb.build(), cache, credentials instanceof GitHubAppCredentials); - } catch (IOException e) { + return new GitHubConnection(gb.build(), cache, credentials instanceof GitHubAppCredentials); + } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); - } - }); + } + }); - record.verifyConnection(); - 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); + record.verifyConnection(); + return record.getGitHub(); } - GitHubBuilder gb = new GitHubBuilder(); - gb.withEndpoint(apiUrl); - gb.withRateLimitChecker(new ApiRateLimitChecker.RateLimitCheckerAdapter()); - gb.withRateLimitHandler(CUSTOMIZED); - - OkHttpClient.Builder clientBuilder = baseClient.newBuilder(); - if (JenkinsJVM.isJenkinsJVM()) { - clientBuilder.proxy(getProxy(host)); + /** + * 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); } - if (cache != null) { - clientBuilder.cache(cache); + + @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); + + 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; } - 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)); + + @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.getUrlEncoder().withoutPadding().encodeToString(sha256.digest())); + } catch (NoSuchAlgorithmException e) { + // no cache for you mr non-spec compliant JVM + } + if (cacheDir != null) { + cache = new Cache(cacheDir, cacheSize * 1024L * 1024L); + } } - sha256.update("::".getBytes(StandardCharsets.UTF_8)); - sha256.update(authHash.getBytes(StandardCharsets.UTF_8)); - cacheDir = - new File( - cacheBase, Base64.getUrlEncoder().withoutPadding().encodeToString(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; } - return cache; - } - public static void release(@CheckForNull GitHub hub) { - if (hub == null) { - return; + public static void release(@CheckForNull GitHub hub) { + if (hub == null) { + return; + } + + ConnectionId connectionId = reverseLookup.get(hub); + if (connectionId == null) { + return; + } + + GitHubConnection record = connections.get(connectionId); + if (record != null) { + try { + record.release(); + } catch (IOException e) { + LOGGER.log(WARNING, "There is a mismatch in connect and release calls.", e); + } + } } - ConnectionId connectionId = reverseLookup.get(hub); - if (connectionId == null) { - return; + private static CredentialsMatcher githubScanCredentialsMatcher() { + // TODO OAuth credentials + return CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class)); } - GitHubConnection record = connections.get(connectionId); - if (record != null) { - try { - record.release(); - } catch (IOException e) { - LOGGER.log(WARNING, "There is a mismatch in connect and release calls.", e); - } + static List githubDomainRequirements(String apiUri) { + return URIRequirementBuilder.fromUri(StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL)) + .build(); } - } - - 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); + + /** + * 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); + } } - } - /** Fail immediately and throw a customized exception. */ - public static final RateLimitHandler CUSTOMIZED = - new RateLimitHandler() { + /** Fail immediately and throw a customized exception. */ + public static final RateLimitHandler CUSTOMIZED = new RateLimitHandler() { @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); - } + 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); + } } - }; - - /** - * Alternative to {@link GitHub#isCredentialValid()} that relies on the cached user object in the - * {@link GitHub} instance and hence reduced rate limit consumption. It also uses a separate - * endpoint if rate limit checking is disabled. - * - * @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 { - // If rate limit checking is disabled, use the meta endpoint instead - // of the rate limiting endpoint - GitHubConfiguration gitHubConfiguration = GitHubConfiguration.get(); - if (gitHubConfiguration != null - && gitHubConfiguration.getApiRateLimitChecker() == ApiRateLimitChecker.NoThrottle) { - gitHub.getMeta(); + }; + + /** + * Alternative to {@link GitHub#isCredentialValid()} that relies on the cached user object in the + * {@link GitHub} instance and hence reduced rate limit consumption. It also uses a separate + * endpoint if rate limit checking is disabled. + * + * @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 { - gitHub.getRateLimit(); - } - return true; - } catch (IOException e) { - if (LOGGER.isLoggable(FINE)) { - LOGGER.log(FINE, "Exception validating credentials on " + gitHub.getApiUrl(), e); + try { + // If rate limit checking is disabled, use the meta endpoint instead + // of the rate limiting endpoint + GitHubConfiguration gitHubConfiguration = GitHubConfiguration.get(); + if (gitHubConfiguration != null + && gitHubConfiguration.getApiRateLimitChecker() == ApiRateLimitChecker.NoThrottle) { + gitHub.getMeta(); + } else { + gitHub.getRateLimit(); + } + return true; + } catch (IOException e) { + if (LOGGER.isLoggable(FINE)) { + LOGGER.log(FINE, "Exception validating credentials on " + gitHub.getApiUrl(), e); + } + return false; + } } - return false; - } - } - } - - /*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))); + + /*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))); + } } - } - /*package*/ - static void configureLocalRateLimitChecker(@NonNull TaskListener listener, GitHub github) - throws IOException, InterruptedException { - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - } + /*package*/ + static void configureLocalRateLimitChecker(@NonNull TaskListener listener, GitHub github) + throws IOException, InterruptedException { + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + } - @Extension - public static class UnusedConnectionDestroyer extends PeriodicWork { + @Extension + public static class UnusedConnectionDestroyer extends PeriodicWork { - @Override - public long getRecurrencePeriod() { - return TimeUnit.MINUTES.toMillis(5); - } + @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); + @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); - GitHubConnection.removeAllUnused(unusedThreshold); + GitHubConnection.removeAllUnused(unusedThreshold); + } } - } - static class GitHubConnection { - @NonNull private final GitHub gitHub; + static class GitHubConnection { + @NonNull + private final GitHub gitHub; - @CheckForNull private final Cache cache; + @CheckForNull + private final Cache cache; - private final boolean cleanupCacheFolder; - private final AtomicInteger usageCount = new AtomicInteger(1); - private final AtomicLong lastUsed = new AtomicLong(System.currentTimeMillis()); - private long lastVerified = Long.MIN_VALUE; + private final boolean cleanupCacheFolder; + private final AtomicInteger usageCount = new AtomicInteger(1); + private final AtomicLong lastUsed = new AtomicLong(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; - } + 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; - } + /** + * Gets the {@link GitHub} instance for this connection + * + * @return the {@link GitHub} instance + */ + public GitHub getGitHub() { + return gitHub; + } - @NonNull - private static GitHubConnection lookup( - @NonNull ConnectionId connectionId, @NonNull Supplier generator) { - GitHubConnection record; - record = - connections.compute( - connectionId, - (id, connection) -> { + @NonNull + private static GitHubConnection lookup( + @NonNull ConnectionId connectionId, @NonNull Supplier generator) { + GitHubConnection record; + record = connections.compute(connectionId, (id, connection) -> { if (connection == null) { - connection = generator.get(); - reverseLookup.put(connection.gitHub, id); + connection = generator.get(); + reverseLookup.put(connection.gitHub, id); } else { - connection.usageCount.incrementAndGet(); - connection.lastUsed.set(System.currentTimeMillis()); + connection.usageCount.incrementAndGet(); + connection.lastUsed.set(System.currentTimeMillis()); } return connection; - }); - return record; - } + }); + return record; + } - private void release() throws IOException { - long count = this.usageCount.decrementAndGet(); - if (count < 0) { - // if this happens we should try not to remain in a bad state - // but there's no guarantees. - this.usageCount.incrementAndGet(); - throw new IOException( - "Tried to release a GitHubConnection that should have no references."); - } - - this.lastUsed.compareAndSet(this.lastUsed.get(), System.currentTimeMillis()); - } + private void release() throws IOException { + long count = this.usageCount.decrementAndGet(); + if (count < 0) { + // if this happens we should try not to remain in a bad state + // but there's no guarantees. + this.usageCount.incrementAndGet(); + throw new IOException("Tried to release a GitHubConnection that should have no references."); + } - private static void removeAllUnused(long threshold) throws IOException { - for (ConnectionId connectionId : connections.keySet()) { - connections.computeIfPresent( - connectionId, - (id, record) -> { - long lastUse = record.lastUsed.get(); - if (record.usageCount.get() == 0 && lastUse < threshold) { - try { - if (record.cache != null && record.cleanupCacheFolder) { - record.cache.delete(); - record.cache.close(); - } - } catch (IOException e) { - LOGGER.log( - WARNING, - "Exception removing cache directory for unused connection: " + id, - e); - } - reverseLookup.remove(record.gitHub); + this.lastUsed.compareAndSet(this.lastUsed.get(), System.currentTimeMillis()); + } - // returning null will remove the connection - record = null; - } + private static void removeAllUnused(long threshold) throws IOException { + for (ConnectionId connectionId : connections.keySet()) { + connections.computeIfPresent(connectionId, (id, record) -> { + long lastUse = record.lastUsed.get(); + if (record.usageCount.get() == 0 && lastUse < threshold) { + try { + if (record.cache != null && record.cleanupCacheFolder) { + record.cache.delete(); + record.cache.close(); + } + } catch (IOException e) { + LOGGER.log(WARNING, "Exception removing cache directory for unused connection: " + id, e); + } + reverseLookup.remove(record.gitHub); + + // returning null will remove the connection + record = null; + } + + return record; + }); + } + } - return record; - }); - } - } + public void verifyConnection() throws IOException { + synchronized (this) { + if (lastVerified > System.currentTimeMillis() - API_URL_REVALIDATE_MILLIS) { + return; + } + + // Connection verification should ignore rate limits + // It is possible this method will exceed the rate limit, + // but very unlikely. + ApiRateLimitChecker.verifyConnection(gitHub); - public void verifyConnection() throws IOException { - synchronized (this) { - if (lastVerified > System.currentTimeMillis() - API_URL_REVALIDATE_MILLIS) { - return; + lastVerified = System.currentTimeMillis(); + } } + } - // Connection verification should ignore rate limits - // It is possible this method will exceed the rate limit, - // but very unlikely. - ApiRateLimitChecker.verifyConnection(gitHub); + private static class ConnectionId { + private final String apiUrl; + private final String credentialsHash; - lastVerified = System.currentTimeMillis(); - } - } - } + ConnectionId(String apiUrl, String credentialsHash) { + this.apiUrl = apiUrl; + this.credentialsHash = credentialsHash; + } - private static class ConnectionId { - private final String apiUrl; - private final String credentialsHash; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - ConnectionId(String apiUrl, String credentialsHash) { - this.apiUrl = apiUrl; - this.credentialsHash = credentialsHash; - } + ConnectionId that = (ConnectionId) o; - @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); - } + if (!Objects.equals(apiUrl, that.apiUrl)) { + return false; + } + return StringUtils.equals(credentialsHash, that.credentialsHash); + } - @Override - public int hashCode() { - return apiUrl != null ? apiUrl.hashCode() : 0; - } + @Override + public int hashCode() { + return apiUrl != null ? apiUrl.hashCode() : 0; + } - @Override - public String toString() { - return "ConnectionId{" - + "apiUrl='" - + apiUrl - + '\'' - + ", credentialsHash=" - + credentialsHash - + '}'; + @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 12d5baaad..1ddfe3bff 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 @@ -35,27 +35,26 @@ */ 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 d2ceee091..548a8e3cd 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 @@ -52,138 +52,127 @@ /** @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; - } - - @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; + private Object readResolve() throws ObjectStreamException { + if (!apiUri.equals(GitHubConfiguration.normalizeApiUri(apiUri))) { + return new Endpoint(apiUri, name); + } + return this; } - if (!(o instanceof Endpoint)) { - return false; + + @NonNull + public String getApiUri() { + return apiUri; } - Endpoint endpoint = (Endpoint) o; + @CheckForNull + public String getName() { + return name; + } - if (!Objects.equals(apiUri, endpoint.apiUri)) { - 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(); } - return true; - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Endpoint)) { + return false; + } - @Override - public int hashCode() { - return apiUri != null ? apiUri.hashCode() : 0; - } + Endpoint endpoint = (Endpoint) o; - @Extension - public static class DescriptorImpl extends Descriptor { + if (!Objects.equals(apiUri, endpoint.apiUri)) { + return false; + } - private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); + return true; + } @Override - public String getDisplayName() { - return ""; + public int hashCode() { + return apiUri != null ? apiUri.hashCode() : 0; } - @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"); + @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)"); + } } - 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 05fe4f7a8..c553019be 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 @@ -15,33 +15,33 @@ */ public class ExcludeArchivedRepositoriesTrait extends SCMNavigatorTrait { - /** Constructor for stapler. */ - @DataBoundConstructor - public ExcludeArchivedRepositoriesTrait() {} - - /** {@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 { + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludeArchivedRepositoriesTrait() {} + /** {@inheritDoc} */ @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludeArchivedRepositories(true); } - @NonNull - @Override - public String getDisplayName() { - return Messages.ExcludeArchivedRepositoriesTrait_displayName(); + /** Exclude archived repositories filter */ + @Symbol("gitHubExcludeArchivedRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } + + @NonNull + @Override + public String getDisplayName() { + return Messages.ExcludeArchivedRepositoriesTrait_displayName(); + } } - } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeForkedRepositoriesTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeForkedRepositoriesTrait.java index e97aeea78..1602d5f57 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeForkedRepositoriesTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludeForkedRepositoriesTrait.java @@ -14,33 +14,33 @@ */ public class ExcludeForkedRepositoriesTrait extends SCMNavigatorTrait { - /** Constructor for stapler. */ - @DataBoundConstructor - public ExcludeForkedRepositoriesTrait() {} - - /** {@inheritDoc} */ - @Override - protected void decorateContext(SCMNavigatorContext context) { - super.decorateContext(context); - GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; - ctx.setExcludeForkedRepositories(true); - } - - /** Exclude forked repositories filter */ - @Symbol("gitHubExcludeForkedRepositories") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludeForkedRepositoriesTrait() {} + /** {@inheritDoc} */ @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludeForkedRepositories(true); } - @NonNull - @Override - public String getDisplayName() { - return Messages.ExcludeForkedRepositoriesTrait_displayName(); + /** Exclude forked repositories filter */ + @Symbol("gitHubExcludeForkedRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } + + @NonNull + @Override + public String getDisplayName() { + return Messages.ExcludeForkedRepositoriesTrait_displayName(); + } } - } } diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePrivateRepositoriesTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePrivateRepositoriesTrait.java index bada3639b..b78a3d89f 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePrivateRepositoriesTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/ExcludePrivateRepositoriesTrait.java @@ -12,33 +12,33 @@ /** A {@link Selection} trait that will restrict the discovery of repositories that are private. */ public class ExcludePrivateRepositoriesTrait extends SCMNavigatorTrait { - /** Constructor for stapler. */ - @DataBoundConstructor - public ExcludePrivateRepositoriesTrait() {} - - /** {@inheritDoc} */ - @Override - protected void decorateContext(SCMNavigatorContext context) { - super.decorateContext(context); - GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; - ctx.setExcludePrivateRepositories(true); - } - - /** Exclude private repositories filter */ - @Symbol("gitHubExcludePrivateRepositories") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludePrivateRepositoriesTrait() {} + /** {@inheritDoc} */ @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludePrivateRepositories(true); } - @NonNull - @Override - public String getDisplayName() { - return Messages.ExcludePrivateRepositoriesTrait_displayName(); + /** Exclude private repositories filter */ + @Symbol("gitHubExcludePrivateRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } + + @NonNull + @Override + public String getDisplayName() { + return Messages.ExcludePrivateRepositoriesTrait_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 d465f3c7f..ebddc9c4b 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 @@ -12,33 +12,33 @@ /** 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() {} - - /** {@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 { + /** Constructor for stapler. */ + @DataBoundConstructor + public ExcludePublicRepositoriesTrait() {} + /** {@inheritDoc} */ @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + protected void decorateContext(SCMNavigatorContext context) { + super.decorateContext(context); + GitHubSCMNavigatorContext ctx = (GitHubSCMNavigatorContext) context; + ctx.setExcludePublicRepositories(true); } - @NonNull - @Override - public String getDisplayName() { - return Messages.ExcludePublicRepositoriesTrait_displayName(); + /** Exclude archived repositories filter */ + @Symbol("gitHubExcludePublicRepositories") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @Override + public Class getContextClass() { + return GitHubSCMNavigatorContext.class; + } + + @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 a52b75914..c2513a629 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,26 +12,25 @@ // 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 5037b77d8..f7bf3e7d0 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 @@ -57,330 +57,324 @@ * @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< - ? 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(); - } + /** 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; - /** {@inheritDoc} */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; + /** + * 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; } - /** {@inheritDoc} */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; + /** + * 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); } /** - * Populates the strategy options. + * Gets the strategy id. * - * @return the strategy options. + * @return the strategy id. */ - @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 int getStrategyId() { + return strategyId; } /** - * Returns the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. + * Returns the strategies. * - * @return the list of appropriate {@link SCMHeadAuthorityDescriptor} instances. + * @return the strategies. */ @NonNull - @SuppressWarnings("unused") // stapler - public List getTrustDescriptors() { - return SCMHeadAuthority._for( - GitHubSCMSourceRequest.class, - PullRequestSCMHead.class, - PullRequestSCMRevision.class, - SCMHeadOrigin.Fork.class); + 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); + } } /** - * Returns the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. + * Gets the authority. * - * @return the default trust for new instances of {@link ForkPullRequestDiscoveryTrait}. + * @return the authority. */ @NonNull - @SuppressWarnings("unused") // stapler - public SCMHeadAuthority getDefaultTrust() { - return new TrustPermission(); + public SCMHeadAuthority + getTrust() { + return trust; } - } - - /** An {@link SCMHeadAuthority} that trusts nothing. */ - public static class TrustNobody - extends SCMHeadAuthority { - - /** Constructor. */ - @DataBoundConstructor - public TrustNobody() {} /** {@inheritDoc} */ @Override - public boolean checkTrusted( - @NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { - 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); - } + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantForkPRs(true); + ctx.withAuthority(trust); + ctx.withForkPRStrategies(getStrategies()); } - } - - /** 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()); + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category instanceof ChangeRequestSCMHeadCategory; } /** Our descriptor. */ - @Symbol("gitHubTrustContributors") + @Symbol("gitHubForkDiscovery") @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); - } + @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; + } + + /** + * 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); + } + + /** + * 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(); + } } - } - /** An {@link SCMHeadAuthority} that trusts those with write permission to the repository. */ - public static class TrustPermission - extends SCMHeadAuthority { + /** An {@link SCMHeadAuthority} that trusts nothing. */ + public static class TrustNobody + extends SCMHeadAuthority { - /** Constructor. */ - @DataBoundConstructor - public TrustPermission() {} + /** Constructor. */ + @DataBoundConstructor + public TrustNobody() {} - /** {@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: + /** {@inheritDoc} */ + @Override + public boolean checkTrusted(@NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { return false; } - } - 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); + } + } } - /** 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 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); + } + } } - } - /** An {@link SCMHeadAuthority} that trusts everyone. */ - public static class TrustEveryone - extends SCMHeadAuthority { - /** Constructor. */ - @DataBoundConstructor - public TrustEveryone() {} + /** 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; + } - /** {@inheritDoc} */ - @Override - protected boolean checkTrusted( - @NonNull SCMSourceRequest request, @NonNull PullRequestSCMHead head) { - return true; + /** 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); + } + } } - /** 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); - } + /** 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); + } + } } - } } 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 d3136c528..c4f45cf6b 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 @@ -51,687 +51,657 @@ import org.kohsuke.stapler.verb.POST; @SuppressFBWarnings(value = "SE_NO_SERIALVERSIONID", justification = "XStream") -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; - private static final String ERROR_NO_OWNER_MATCHING = - "Found multiple installations for GitHub app ID %s but none match credential owner \"%s\". " - + "Set the right owner in the credential advanced options"; - - /** - * 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; - - @SuppressFBWarnings( - value = "IS2_INCONSISTENT_SYNC", - justification = "#withOwner locking only for #byOwner") - private String owner; - - private transient AppInstallationToken cachedToken; - - /** - * Cache of credentials specialized by {@link #owner}, so that {@link #cachedToken} is preserved. - */ - private transient Map byOwner; - - @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); - } - } +public class GitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { - private abstract static class TokenProvider extends GitHub.DependentAuthorizationProvider { + private static final Logger LOGGER = Logger.getLogger(GitHubAppCredentials.class.getName()); - protected TokenProvider(String appID, String privateKey) { - super(createJwtProvider(appID, privateKey)); - } + 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; + private static final String ERROR_NO_OWNER_MATCHING = + "Found multiple installations for GitHub app ID %s but none match credential owner \"%s\". " + + "Set the right owner in the credential advanced options"; /** - * Create and return the specialized GitHub instance to be used for refreshing - * AppInstallationToken + * 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. * - *

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. + *

Provided as one more possible avenue for debugging/stabilizing JENKINS-62249. */ - 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(); + private static long AFTER_TOKEN_GENERATION_DELAY_SECONDS = + Long.getLong(GitHubAppCredentials.class.getName() + ".AFTER_TOKEN_GENERATION_DELAY_SECONDS", 0); - return provider.gitHub(); - } - } + @NonNull + private final String appID; - private static class CredentialsTokenProvider extends TokenProvider { - private final GitHubAppCredentials credentials; + @NonNull + private final Secret privateKey; - CredentialsTokenProvider(GitHubAppCredentials credentials) { - super(credentials.appID, credentials.privateKey.getPlainText()); - this.credentials = credentials; - } + private String apiUri; - 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 { - final String ownerOrEmpty = owner != null ? owner : ""; - appInstallation = - appInstallations.stream() - .filter( - installation -> - installation - .getAccount() - .getLogin() - .toLowerCase(Locale.ROOT) - .equals(ownerOrEmpty.toLowerCase(Locale.ROOT))) - .findAny() - .orElseThrow( - () -> - new IllegalArgumentException( - String.format(ERROR_NO_OWNER_MATCHING, appId, ownerOrEmpty))); - } - - 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); + @SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "#withOwner locking only for #byOwner") + private String owner; - return cachedToken; - } - } - - /** {@inheritDoc} */ - @NonNull - @Override - public Secret getPassword() { - return this.getToken(null).getToken(); - } - - /** {@inheritDoc} */ - @NonNull - @Override - public String getUsername() { - return appID; - } - - @NonNull - public synchronized GitHubAppCredentials withOwner(@NonNull String owner) { - if (this.owner != null) { - if (!owner.equals(this.owner)) { - throw new IllegalArgumentException("Owner mismatch: " + this.owner + " vs. " + owner); - } - return this; - } - if (byOwner == null) { - byOwner = new HashMap<>(); + private transient AppInstallationToken cachedToken; + + /** + * Cache of credentials specialized by {@link #owner}, so that {@link #cachedToken} is preserved. + */ + private transient Map byOwner; + + @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 byOwner.computeIfAbsent( - owner, - k -> { - GitHubAppCredentials clone = - new GitHubAppCredentials(getScope(), getId(), getDescription(), appID, privateKey); - clone.apiUri = apiUri; - clone.owner = owner; - return clone; - }); - } - @NonNull - @Override - public Credentials forRun(Run context) { - if (owner != null) { - return this; + public String getApiUri() { + return apiUri; } - Job job = context.getParent(); - SCMSource src = SCMSource.SourceByItem.findSource(job); - if (src instanceof GitHubSCMSource) { - return withOwner(((GitHubSCMSource) src).getRepoOwner()); + + @DataBoundSetter + public void setApiUri(String apiUri) { + this.apiUri = apiUri; } - GitHubRepositoryName ghrn = - GitHubRepositoryName.create(job.getProperty(GithubProjectProperty.class)); - if (ghrn != null) { - return withOwner(ghrn.userName); + + @NonNull + public String getAppID() { + return appID; } - return this; - } - private AppInstallationToken getCachedToken() { - synchronized (this) { - return cachedToken; + @NonNull + public Secret getPrivateKey() { + return privateKey; } - } - 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. + * Owner of this installation, i.e. a user or organisation, used to differeniate app installations + * when the app is installed to multiple organisations / users. * - *

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. + *

If this is null then call listInstallations and if there's only one in the list then use + * that installation. * - *

This is a back stop to ensure that, in case of unforeseen error, expired tokens are not - * accidentally retained past their expiration. + * @return the owner of the organisation or null. */ - static final long STALE_AFTER_SECONDS = Duration.ofMinutes(30).getSeconds(); + @CheckForNull + public String getOwner() { + return owner; + } - /** - * 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()); + @DataBoundSetter + public void setOwner(String owner) { + this.owner = Util.fixEmpty(owner); + } - private final Secret token; - private final long expirationEpochSeconds; - private final long staleEpochSeconds; + @SuppressWarnings("deprecation") + AuthorizationProvider getAuthorizationProvider() { + return new CredentialsTokenProvider(this); + } - /** - * 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 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); + } + } - long minimumAllowedAge = Math.max(1, NOT_STALE_MINIMUM_SECONDS); - long maximumAllowedAge = Math.max(1, 1 + STALE_AFTER_SECONDS); + private abstract static class TokenProvider extends GitHub.DependentAuthorizationProvider { - // Tokens go stale a while before they will expire - long secondsUntilStale = secondsUntilExpiration - STALE_BEFORE_EXPIRATION_SECONDS; + protected TokenProvider(String appID, String privateKey) { + super(createJwtProvider(appID, privateKey)); + } - // Tokens are never stale as soon as they are made - if (secondsUntilStale < minimumAllowedAge) { - secondsUntilStale = minimumAllowedAge; - } + /** + * 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 have a maximum age at which they go stale - if (secondsUntilStale > maximumAllowedAge) { - secondsUntilStale = maximumAllowedAge; - } + private static class CredentialsTokenProvider extends TokenProvider { + private final GitHubAppCredentials credentials; - LOGGER.log(Level.FINER, "Token will become stale after " + secondsUntilStale + " seconds"); + CredentialsTokenProvider(GitHubAppCredentials credentials) { + super(credentials.appID, credentials.privateKey.getPlainText()); + this.credentials = credentials; + } - this.token = token; - this.expirationEpochSeconds = expirationEpochSeconds; - this.staleEpochSeconds = now + secondsUntilStale; + public String getEncodedAuthorization() throws IOException { + Secret token = credentials.getToken(gitHub()).getToken(); + return String.format("token %s", token.getPlainText()); + } } - public Secret getToken() { - return token; - } + @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 - /** - * 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; - } + try (Timeout ignored = Timeout.limit(30, TimeUnit.SECONDS)) { + if (gitHubApp == null) { + gitHubApp = TokenProvider.createTokenRefreshGitHub(appId, appPrivateKey, apiUrl); + } - public boolean isExpired() { - return Instant.now().getEpochSecond() >= expirationEpochSeconds; + 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 { + final String ownerOrEmpty = owner != null ? owner : ""; + appInstallation = appInstallations.stream() + .filter(installation -> installation + .getAccount() + .getLogin() + .toLowerCase(Locale.ROOT) + .equals(ownerOrEmpty.toLowerCase(Locale.ROOT))) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + String.format(ERROR_NO_OWNER_MATCHING, appId, ownerOrEmpty))); + } + + 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); + } } - long getTokenStaleEpochSeconds() { - return staleEpochSeconds; + 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; + } } - } - - /** - * 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; + + @NonNull + String actualApiUri() { + return Util.fixEmpty(apiUri) == null ? "https://api.github.com" : apiUri; } - return new DelegatingGitHubAppCredentials(this); - } - private static final class DelegatingGitHubAppCredentials extends BaseStandardCredentials - implements StandardUsernamePasswordCredentials { + 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); - 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(); + return cachedToken; + } } - private Object readResolve() { - JenkinsJVM.checkNotJenkinsJVM(); - synchronized (this) { - ch = Channel.currentOrFail(); - } - return this; + /** {@inheritDoc} */ + @NonNull + @Override + public Secret getPassword() { + return this.getToken(null).getToken(); } + /** {@inheritDoc} */ @NonNull @Override public String getUsername() { - return appID; + return appID; } - @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; + @NonNull + public synchronized GitHubAppCredentials withOwner(@NonNull String owner) { + if (this.owner != null) { + if (!owner.equals(this.owner)) { + throw new IllegalArgumentException("Owner mismatch: " + this.owner + " vs. " + owner); } - } - LOGGER.log( - Level.FINEST, - "Returned GitHub App Installation Token for app ID {0} on agent", - appID); + return this; + } + if (byOwner == null) { + byOwner = new HashMap<>(); + } + return byOwner.computeIfAbsent(owner, k -> { + GitHubAppCredentials clone = + new GitHubAppCredentials(getScope(), getId(), getDescription(), appID, privateKey); + clone.apiUri = apiUri; + clone.owner = owner; + return clone; + }); + } - return cachedToken.getToken(); + @NonNull + @Override + public Credentials forRun(Run context) { + if (owner != null) { + return this; + } + Job job = context.getParent(); + SCMSource src = SCMSource.SourceByItem.findSource(job); + if (src instanceof GitHubSCMSource) { + return withOwner(((GitHubSCMSource) src).getRepoOwner()); } + GitHubRepositoryName ghrn = GitHubRepositoryName.create(job.getProperty(GithubProjectProperty.class)); + if (ghrn != null) { + return withOwner(ghrn.userName); + } + return this; + } - } catch (IOException | InterruptedException x) { - throw new RuntimeException(x); - } + private AppInstallationToken getCachedToken() { + synchronized (this) { + return cachedToken; + } } - private static final class GetToken - extends SlaveToMasterCallable { + 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; + } - private final String data; + // Tokens have a maximum age at which they go stale + if (secondsUntilStale > maximumAllowedAge) { + secondsUntilStale = maximumAllowedAge; + } - GetToken(String data) { - this.data = data; - } + LOGGER.log(Level.FINER, "Token will become stale after " + secondsUntilStale + " seconds"); - @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; - } - } - } + this.token = token; + this.expirationEpochSeconds = expirationEpochSeconds; + this.staleEpochSeconds = now + secondsUntilStale; + } - /** {@inheritDoc} */ - @Extension - public static class DescriptorImpl extends BaseStandardCredentialsDescriptor { + public Secret getToken() { + return token; + } - /** {@inheritDoc} */ - @Override - public String getDisplayName() { - return Messages.GitHubAppCredentials_displayName(); - } + /** + * 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; + } - /** {@inheritDoc} */ - @Override - public String getIconClassName() { - return "icon-github-logo"; - } + public boolean isExpired() { + return Instant.now().getEpochSecond() >= expirationEpochSeconds; + } - @SuppressWarnings("unused") // jelly - public boolean isApiUriSelectable() { - return !GitHubConfiguration.get().getEndpoints().isEmpty(); + long getTokenStaleEpochSeconds() { + return staleEpochSeconds; + } } /** - * Returns the available GitHub endpoint items. + * Ensures that the credentials state as serialized via Remoting to an agent calls back to the + * controller. Benefits: * - * @return the available GitHub endpoint items. + *

    + *
  • 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. + *
*/ - @SuppressWarnings("unused") // stapler - @Restricted(NoExternalUse.class) // stapler - public ListBoxModel doFillApiUriItems() { - return getPossibleApiUriItems(); + private Object writeReplace() { + if ( + /* XStream */ Channel.current() == null) { + return this; + } + return new DelegatingGitHubAppCredentials(this); } - 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"); + 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 Object readResolve() { + JenkinsJVM.checkNotJenkinsJVM(); + synchronized (this) { + ch = Channel.currentOrFail(); + } + return this; + } + + @NonNull + @Override + public String getUsername() { + return appID; + } + + @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); + } + } + + private static final class GetToken extends SlaveToMasterCallable { + + private final String data; + + GetToken(String data) { + this.data = data; + } + + @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; + } } - } - 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); + /** {@inheritDoc} */ + @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(); + } + + 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)); + } } - } 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/GitHubAppCredentialsSnapshotTaker.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsSnapshotTaker.java index 8c1fdb388..f2545f51e 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsSnapshotTaker.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentialsSnapshotTaker.java @@ -15,16 +15,15 @@ * the status quo allowing the Credentials to be replaced using the existing mechanism. */ @Extension -public class GitHubAppCredentialsSnapshotTaker - extends CredentialsSnapshotTaker { +public class GitHubAppCredentialsSnapshotTaker extends CredentialsSnapshotTaker { - @Override - public GitHubAppCredentials snapshot(GitHubAppCredentials credentials) { - return credentials; - } + @Override + public GitHubAppCredentials snapshot(GitHubAppCredentials credentials) { + return credentials; + } - @Override - public Class type() { - return GitHubAppCredentials.class; - } + @Override + public Class type() { + return GitHubAppCredentials.class; + } } 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 05709ca99..268b97928 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,33 +39,32 @@ * @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); - } + /** {@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; } - 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 20600f1df..b6aaf3e78 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 @@ -66,295 +66,283 @@ */ 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()); - 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(); + 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 { - 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); + 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"); + } } - } + } 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()); - } + /** + * 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; } - 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()) { + /** + * 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(), source.getRepoOwner())); + } + } return null; - } - if (source.getScanCredentialsId() != null) { - return Connector.connect( - source.getApiUri(), - Connector.lookupScanCredentials( - job, source.getApiUri(), source.getScanCredentialsId(), source.getRepoOwner())); - } } - 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 { - /** 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 + /** + * With this listener one notifies to GitHub when a Job has been scheduled. + * + *

Sends: GHCommitState.PENDING + */ + @Extension + public static class JobScheduledListener extends QueueListener { - // 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(); + /** 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 { - repo.createCommitStatus( - hash, - request.getState(), - request.getUrl(), - request.getMessage(), - request.getContext()); - } catch (FileNotFoundException e) { - if (!ignoreErrors) { - LOGGER.log( + 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, "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 { + /** + * 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); + @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> { + /** + * 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); + @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 df4ba5218..b509de8db 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 @@ -31,5 +31,5 @@ * 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 cc320b748..f88f197ab 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 @@ -45,199 +45,191 @@ @Extension public class GitHubConfiguration extends GlobalConfiguration { - public static GitHubConfiguration get() { - return GlobalConfiguration.all().get(GitHubConfiguration.class); - } - - private List endpoints; - - private ApiRateLimitChecker apiRateLimitChecker; + public static GitHubConfiguration get() { + return GlobalConfiguration.all().get(GitHubConfiguration.class); + } - public GitHubConfiguration() { - load(); - } + private List endpoints; - @Override - public boolean configure(StaplerRequest req, JSONObject json) throws FormException { - req.bindJSON(this, json); - return true; - } + private ApiRateLimitChecker apiRateLimitChecker; - @NonNull - public synchronized List getEndpoints() { - return endpoints == null ? Collections.emptyList() : Collections.unmodifiableList(endpoints); - } + public GitHubConfiguration() { + load(); + } - @NonNull - public synchronized ApiRateLimitChecker getApiRateLimitChecker() { - if (apiRateLimitChecker == null) { - return ApiRateLimitChecker.ThrottleForNormalize; + @Override + public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + req.bindJSON(this, json); + return true; } - 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; + + @NonNull + public synchronized List getEndpoints() { + return endpoints == null ? Collections.emptyList() : Collections.unmodifiableList(endpoints); } - 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; + + @NonNull + public synchronized ApiRateLimitChecker getApiRateLimitChecker() { + if (apiRateLimitChecker == null) { + return ApiRateLimitChecker.ThrottleForNormalize; } - 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 apiRateLimitChecker; } - 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()); + + public synchronized void setApiRateLimitChecker(@CheckForNull ApiRateLimitChecker apiRateLimitChecker) { + this.apiRateLimitChecker = apiRateLimitChecker; + save(); } - 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; + + /** + * 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; + } + 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("/$", ""); } - List endpoints = new ArrayList<>(getEndpoints()); - for (Endpoint ep : endpoints) { - if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) { - return false; - } + + 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(); } - 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; + + /** + * 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; } - 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; - } + + /** + * 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); } - if (!found) { - endpoints.add(endpoint); + + /** + * 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()); } - 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; - } + + /** + * 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; } - 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; - } + + /** + * 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; } - return null; - } - public ListBoxModel doFillApiRateLimitCheckerItems() { - ListBoxModel items = new ListBoxModel(); - for (ApiRateLimitChecker mode : ApiRateLimitChecker.values()) { - items.add(mode.getDisplayName(), mode.name()); + public ListBoxModel doFillApiRateLimitCheckerItems() { + ListBoxModel items = new ListBoxModel(); + for (ApiRateLimitChecker mode : ApiRateLimitChecker.values()) { + items.add(mode.getDisplayName(), mode.name()); + } + return items; } - 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 fbaa43882..d95bd1cc5 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 @@ -39,40 +39,37 @@ @Restricted(NoExternalUse.class) public class GitHubConsoleNote extends ConsoleNote { - private static final Logger LOGGER = Logger.getLogger(GitHubConsoleNote.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GitHubConsoleNote.class.getName()); - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private final long timestamp; + private final long timestamp; - public GitHubConsoleNote(long timestamp) { - this.timestamp = timestamp; - } + public GitHubConsoleNote(long timestamp) { + this.timestamp = timestamp; + } - @Override - public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { - text.addMarkup( - 0, - text.length(), - String.format("%tT ", timestamp), - ""); - return null; - } + @Override + public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { + text.addMarkup( + 0, text.length(), String.format("%tT ", timestamp), ""); + return null; + } - 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; + 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"; + @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 e9e3deb55..260bdb6e3 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 @@ -30,73 +30,78 @@ /** @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; + private static final long serialVersionUID = 1L; - public GitHubDefaultBranch( - @NonNull String repoOwner, @NonNull String repository, @NonNull String defaultBranch) { - this.repoOwner = repoOwner; - this.repository = repository; - this.defaultBranch = defaultBranch; - } + @NonNull + private final String repoOwner; - @NonNull - public String getRepoOwner() { - return repoOwner; - } + @NonNull + private final String repository; - @NonNull - public String getRepository() { - return repository; - } + @NonNull + private final String defaultBranch; - @NonNull - public String getDefaultBranch() { - return defaultBranch; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + public GitHubDefaultBranch(@NonNull String repoOwner, @NonNull String repository, @NonNull String defaultBranch) { + this.repoOwner = repoOwner; + this.repository = repository; + this.defaultBranch = defaultBranch; } - if (o == null || getClass() != o.getClass()) { - return false; + + @NonNull + public String getRepoOwner() { + return repoOwner; } - GitHubDefaultBranch that = (GitHubDefaultBranch) o; + @NonNull + public String getRepository() { + return repository; + } - if (!repoOwner.equals(that.repoOwner)) { - return false; + @NonNull + public String getDefaultBranch() { + return defaultBranch; } - if (!repository.equals(that.repository)) { - return false; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + GitHubDefaultBranch that = (GitHubDefaultBranch) o; + + if (!repoOwner.equals(that.repoOwner)) { + return false; + } + if (!repository.equals(that.repository)) { + return false; + } + return defaultBranch.equals(that.defaultBranch); } - 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 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 - + '\'' - + '}'; - } + @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 82324fd3e..f8d777a70 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,82 +40,83 @@ * @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; - } - - 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); - } + /** 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()); } - 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; + + @NonNull + public String getUrl() { + return url; + } + + @Override + public String getIconClassName() { + return iconClassName; } - if (o == null || getClass() != o.getClass()) { - return false; + + @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; } - GitHubLink that = (GitHubLink) o; + @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; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + GitHubLink that = (GitHubLink) o; + + if (!iconClassName.equals(that.iconClassName)) { + return false; + } + return url.equals(that.url); + } + + @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 284b32b2b..67a5f13b0 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 @@ -45,209 +45,206 @@ * @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; - } - - 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"; + 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; } - } - - /** - * 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."); + + 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; } - 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 run, if any, associated with the planned notification event + * + * @return Run + * @since 2.3.2 + */ + public Run getBuild() { + return build; } - 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; - } + + /** + * 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 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 439f33912..377cf3db7 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 @@ -40,120 +40,120 @@ */ 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 460704eec..78f55e6d4 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 @@ -43,86 +43,87 @@ * @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/" - + "/github-logo.svg"); - } 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()); } - } - - /** {@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; + + public GitHubOrgMetadataAction(@CheckForNull String avatar) { + this.avatar = Util.fixEmpty(avatar); } - if (o == null || getClass() != o.getClass()) { - return false; + + public GitHubOrgMetadataAction(@NonNull GitHubOrgMetadataAction that) { + this(that.getAvatar()); } - GitHubOrgMetadataAction that = (GitHubOrgMetadataAction) o; + private Object readResolve() throws ObjectStreamException { + if (avatar != null && StringUtils.isBlank(avatar)) return new GitHubOrgMetadataAction(this); + return this; + } - return Objects.equals(avatar, that.avatar); - } + @CheckForNull + public String getAvatar() { + return Util.fixEmpty(avatar); + } - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (avatar != null ? avatar.hashCode() : 0); - } + /** {@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/" + + "/github-logo.svg"); + } 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 toString() { - return "GitHubOrgMetadataAction{" + ", avatar='" + avatar + '\'' + "}"; - } + /** {@inheritDoc} */ + @Override + public String getAvatarDescription() { + return Messages.GitHubOrgMetadataAction_IconDescription(); + } + + /** {@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); + } + + /** {@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 d027766d0..70b8c9514 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 @@ -43,105 +43,95 @@ /** 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; - } - 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; - } + 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; } - 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. + 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); + } } - 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); - } - - public static void deregister(GitHub hub, String orgName) throws IOException { - String rootUrl = Jenkins.get().getRootUrl(); - if (rootUrl == null) { - return; + private static File getTrackingFile(String orgName) { + return new File(Jenkins.get().getRootDir(), "github-webhooks/GitHubOrgHook." + orgName); } - 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. - } + + 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); + } } - 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 c62d57590..3fec42226 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 b93f065ac..f4cd60ca7 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,33 +39,32 @@ * @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); - } + /** {@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; } - 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 f0bffb586..73b9001d5 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,39 +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; + /** {@inheritDoc} */ + @Override + public String getAvatarDescription() { + return Messages.GitHubRepoMetadataAction_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; + } + return true; } - return true; - } - /** {@inheritDoc} */ - @Override - public int hashCode() { - return 0; - } + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 0; + } - /** {@inheritDoc} */ - @Override - public String toString() { - return "GitHubRepoMetadataAction{}"; - } + /** {@inheritDoc} */ + @Override + public String toString() { + return "GitHubRepoMetadataAction{}"; + } } 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 1976b9a27..b267c8568 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 @@ -56,116 +56,108 @@ @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; + @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); } - 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 9b99cc39e..db74b0720 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 @@ -39,83 +39,87 @@ * 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 repoOwner; + @NonNull + private final String apiUri; - @NonNull private final String repository; + @NonNull + private final String repoOwner; - @NonNull private final String repositoryUrl; + @NonNull + private final String repository; - private GitHubRepositoryInfo( - String apiUri, String repoOwner, String repository, String repositoryUrl) { - this.apiUri = apiUri; - this.repoOwner = repoOwner; - this.repository = repository; - this.repositoryUrl = repositoryUrl; - } + @NonNull + private final String repositoryUrl; - public String getApiUri() { - return apiUri; - } - - public String getRepoOwner() { - return repoOwner; - } - - public String getRepository() { - return repository; - } - - 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"); + private GitHubRepositoryInfo(String apiUri, String repoOwner, String repository, String repositoryUrl) { + this.apiUri = apiUri; + this.repoOwner = repoOwner; + this.repository = repository; + this.repositoryUrl = repositoryUrl; } - URL url; - try { - url = new URL(trimmedRepoUrl); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); + + public String getApiUri() { + return apiUri; } - if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { - throw new IllegalArgumentException( - "Invalid repository URL scheme (must be HTTPS or HTTP): " + url.getProtocol()); + + public String getRepoOwner() { + return repoOwner; } - 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()); + + public String getRepository() { + return repository; } - } - 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()); + public String getRepositoryUrl() { + return repositoryUrl; } - if (repositoryUrl.getPort() != -1) { - sb.append(':'); - sb.append(repositoryUrl.getPort()); + + @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()); + } } - if (!isGitHub) { - sb.append('/').append(GitHubSCMBuilder.API_V3); + + 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()); } - 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 79b71e9fd..bf867d7ac 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 @@ -62,278 +62,276 @@ @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") // https://github.com/spotbugs/spotbugs/issues/1539 public class GitHubSCMBuilder extends GitSCMBuilder { - private static final Random ENTROPY = new Random(); - /** 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; + private static final Random ENTROPY = new Random(); + /** 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)); + /** + * 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); } - withCredentials(credentialsId(), 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); + /** + * 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"; } - return apiUri + "/" + source.getRepoOwner() + "/" + source.getRepository() + ".git"; - } - /** - * 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; + /** + * 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; } - return null; - } - - /** - * Returns a {@link RepositoryUriResolver} according to credentials configuration. - * - * @return a {@link RepositoryUriResolver} - */ - @NonNull - public final RepositoryUriResolver uriResolver() { - return uriResolver; - } - /** - * 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); } - } - /** - * 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); + /** + * 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; + } + } } - if (repoUrl != null) { - withBrowser(new GithubWeb(repoUrl)); + + /** + * 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; } - 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 (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)) { + 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 (localNames.contains(localName)) { - 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); } - } - 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 bc9d91dbc..205fc8565 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,170 +39,164 @@ 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; + 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; + 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 void checkOpen() throws IOException { - if (!closable.isOpen() || (!resolved && repo == null)) { - throw new IOException("Closed"); + 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 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); + 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; - 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(); + } + } + + private void checkOpen() throws IOException { + if (!closable.isOpen() || (!resolved && repo == null)) { + throw new IOException("Closed"); + } + } + + private Object metadata() throws IOException { + if (metadata == null && !resolved) { 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; + 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; resolved = true; - } else { - throw e; - } } - break; } - } catch (FileNotFoundException e) { - metadata = null; - resolved = true; - } + return metadata; } - 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 + protected SCMFile newChild(String name, boolean assumeIsDirectory) { + return new GitHubSCMFile(this, name, assumeIsDirectory ? TypeInfo.DIRECTORY_ASSUMED : TypeInfo.UNRESOLVED); } - 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; + @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; } - 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; + + @Override + public long lastModified() throws IOException, InterruptedException { + // TODO see if we can find a way to implement it + return 0L; } - return Type.NONEXISTENT; - } - @NonNull - @Override - public InputStream content() throws IOException, InterruptedException { - Object metadata = metadata(); - if (metadata instanceof List) { - throw new IOException("Directory"); + @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) { - return ((GHContent) metadata).read(); + + @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()); } - throw new FileNotFoundException(getPath()); - } - private enum TypeInfo { - UNRESOLVED, - DIRECTORY_ASSUMED, - DIRECTORY_CONFIRMED, - NON_DIRECTORY_CONFIRMED; - } + 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 29bf35a43..cdecf79ea 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 @@ -56,258 +56,258 @@ /** 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(); + /** + * 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; + } } else { - this.ref = prRev.getPullHash(); + this.ref = refName; } - } 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 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 boolean supports(SCM source) { - // TODO implement a GitHubSCM so we can work for those - return false; + public void close() throws IOException { + synchronized (this) { + if (!open) { + return; + } + open = false; + } + Connector.release(gitHub); } + /** {@inheritDoc} */ @Override - protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) { - // TODO implement a GitHubSCM so we can work for those - return false; + public synchronized boolean isOpen() { + return open; } /** {@inheritDoc} */ @Override - public boolean supports(SCMSource source) { - return source instanceof GitHubSCMSource; + public long lastModified() throws IOException { + return repo.getCommit(ref).getCommitDate().getTime(); } + /** {@inheritDoc} */ @Override - protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) { - return scmSourceDescriptor instanceof GitHubSCMSource.DescriptorImpl; + 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 SCMFileSystem build( - @NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) { - return null; + public SCMFile getRoot() { + return new GitHubSCMFile(this, repo, ref); } - /** {@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(), src.getRepoOwner()); + @Extension + public static class BuilderImpl extends SCMFileSystem.Builder { - // Github client and validation - GitHub github = Connector.connect(apiUri, credentials); - try { - String refName; + /** {@inheritDoc} */ + @Override + public boolean supports(SCM source) { + // TODO implement a GitHubSCM so we can work for those + return false; + } - 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(SCMDescriptor scmDescriptor) { + // TODO implement a GitHubSCM so we can work for those + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean supports(SCMSource source) { + return source instanceof GitHubSCMSource; } - GHUser user = github.getUser(src.getRepoOwner()); - if (user == null) { - return null; + @Override + protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) { + return scmSourceDescriptor instanceof GitHubSCMSource.DescriptorImpl; } - GHRepository repo = user.getRepository(src.getRepository()); - if (repo == null) { - return null; + + /** {@inheritDoc} */ + @Override + public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) { + 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()); + /** {@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(), src.getRepoOwner()); + + // 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); } - } 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 f08408661..a69877376 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 @@ -107,1996 +107,1856 @@ 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<>(); - } - - /** - * 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; + /** 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<>(); } - 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); + + /** + * 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)); } - 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; } - if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { - setApiUri(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(); - } + + /** + * Gets the API endpoint for the GitHub server. + * + * @return the API endpoint for the GitHub server. + */ + @CheckForNull + public String getApiUri() { + return apiUri; } - 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); + + /** + * 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 { - traits.set(i, new WildcardSCMHeadFilterTrait(includes, existing.getExcludes())); + this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); } - return; - } } - if (!"*".equals(includes)) { - traits.add(new WildcardSCMHeadFilterTrait(includes, "")); + + /** + * 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; } - } - - /** - * 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; - } + + /** + * 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); } - if (!"".equals(excludes)) { - traits.add(new WildcardSCMHeadFilterTrait("*", excludes)); + + /** + * 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; } - } - - /** - * 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(); - } + + /** + * 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); } - 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); + + /** + * 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); + } } - 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); + + /** + * 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; } - 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); + 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; } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } - } - if (buildOriginPRMerge) { - traits.add( - new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); - } - } - - /** - * 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; - } - - /** - * 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); + if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) { + setApiUri(apiUri); } - traits.set(i, new OriginPullRequestDiscoveryTrait(s)); - return; - } + return this; } - if (buildOriginPRHead) { - traits.add( - new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.HEAD))); + + /** + * Legacy getter. + * + * @return {@link #getCredentialsId()}. + * @deprecated use {@link #getCredentialsId()}. + */ + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + @CheckForNull + public String getScanCredentialsId() { + return credentialsId; } - } - - /** - * 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); - } + + /** + * 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; } - 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); + + /** + * 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(); + } } - traits.set(i, new ForkPullRequestDiscoveryTrait(s, forkTrait.getTrust())); - return; - } - } - if (buildForkPRMerge) { - traits.add( - new ForkPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), - new ForkPullRequestDiscoveryTrait.TrustPermission())); + return "*"; } - } - - /** - * 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); + + /** + * 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(); + } } - 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 ""; } - 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)); + + /** + * 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, "")); } - return; - } - } - if (!".*".equals(pattern)) { - traits.add(new RegexSCMSourceFilterTrait(pattern)); - } - } - - /** {@inheritDoc} */ - @NonNull - @Override - protected String id() { - final GitHubSCMNavigatorContext gitHubSCMNavigatorContext = - new GitHubSCMNavigatorContext().withTraits(traits); - if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { - return StringUtils.defaultIfBlank(apiUri, GitHubSCMSource.GITHUB_URL) - + "::" - + repoOwner - + "::" - + String.join("::", gitHubSCMNavigatorContext.getTopics()); - } - 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(); - // Input data validation - if (repoOwner.isEmpty()) { - throw new AbortException("Must specify user or organization"); + /** + * 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)); + } } - StandardCredentials credentials = - Connector.lookupScanCredentials( - (Item) observer.getContext(), apiUri, credentialsId, repoOwner); - - // 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(getTraits()); - - 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))); - final Iterable repositories; - if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Looking up repositories for topics: '%s'", - gitHubSCMNavigatorContext.getTopics()))); - repositories = searchRepositories(github, gitHubSCMNavigatorContext); - } else { - repositories = myself.listRepositories(100); + /** + * 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; + } - for (GHRepository repo : repositories) { - if (!repoOwner.equals(repo.getOwnerName())) { - 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 (repo.isPrivate() - && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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 if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Looking up repositories for topics: '%s'", - gitHubSCMNavigatorContext.getTopics()))); - repositories = searchRepositories(github, gitHubSCMNavigatorContext); - } 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 (repo.isPrivate() - && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); - - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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()))); + /** + * 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; } - } - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format("%d repositories were processed", witness.getCount()))); - return; } + if (buildOriginBranch) { + traits.add(new BranchDiscoveryTrait(buildOriginBranch, false)); + } + } - 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 (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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()))); + /** + * 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(); } - } - 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 Iterable searchRepositories( - final GitHub github, final GitHubSCMNavigatorContext context) { - final GHRepositorySearchBuilder ghRepositorySearchBuilder = github.searchRepositories(); - context.getTopics().forEach(ghRepositorySearchBuilder::topic); - ghRepositorySearchBuilder.org(getRepoOwner()); - if (!context.isExcludeForkedRepositories()) { - ghRepositorySearchBuilder.q("fork:true"); - } - return ghRepositorySearchBuilder.list().withPageSize(100); - } - - 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"); + } + return false; } - StandardCredentials credentials = - Connector.lookupScanCredentials( - (Item) observer.getContext(), apiUri, credentialsId, repoOwner); - - // Github client and validation - GitHub github; - try { - github = Connector.connect(apiUri, credentials); - } catch (HttpException e) { - throw new AbortException(e.getMessage()); + /** + * 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)); + } } - 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 (repo.isPrivate() - && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); - - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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 (repo.isPrivate() - && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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()))); + /** + * 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); } - } - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format("%d repositories were processed", witness.getCount()))); - return; } + return false; + } - 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 (repo.isPrivate() - && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is private", repo.getName()))); - - } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() - && repo.getSource() != null) { - witness.record(repo.getName(), false); - listener - .getLogger() - .println( - GitHubConsoleNote.create( - System.currentTimeMillis(), - String.format( - "Skipping repository %s because it is a fork", 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()))); + /** + * 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; } - } - 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; - } - - /** {@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<>(); - String apiUri = Util.fixEmptyAndTrim(getApiUri()); - StandardCredentials credentials = - Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner); - GitHub hub = Connector.connect(getApiUri(), credentials); - boolean privateMode = determinePrivateMode(apiUri); - 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)); - if (privateMode) { - result.add(new GitHubOrgMetadataAction((String) null)); - } else { - 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); + } + if (buildOriginPRMerge) { + traits.add(new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE))); + } } - } - private static boolean determinePrivateMode(String apiUri) { - if (apiUri == null || apiUri.equals(GitHubServerConfig.GITHUB_URL)) { - return false; - } - try { - GitHub.connectToEnterpriseAnonymously(apiUri).checkApiUrlValidity(); - } catch (MalformedURLException e) { - // URL is bogus so there is never going to be an avatar - or anything else come to think of it - return true; - } catch (IOException e) { - if (e.getMessage().contains("private mode enabled")) { - return true; - } - } - return false; - } - - /** {@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, repoOwner); - 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); + /** + * 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; } - } - - @Symbol("github") - @Extension - public static class DescriptorImpl extends SCMNavigatorDescriptor implements IconSpec { - - private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); + /** + * Legacy setter. + * + * @param buildOriginPRHead see {@link + * OriginPullRequestDiscoveryTrait#OriginPullRequestDiscoveryTrait(Set)}. + * @deprecated use {@link OriginPullRequestDiscoveryTrait} + */ @Deprecated @Restricted(DoNotUse.class) @RestrictedSince("2.2.0") - public static final String defaultIncludes = "*"; + @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 static final String defaultExcludes = ""; - - public static final String SAME = GitHubSCMSource.DescriptorImpl.SAME; + 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") - public static final boolean defaultBuildOriginBranch = true; + @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 static final boolean defaultBuildOriginBranchWithPR = true; + 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") - public static final boolean defaultBuildOriginPRMerge = false; + @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 static final boolean defaultBuildOriginPRHead = false; + 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 static final boolean defaultBuildForkPRMerge = false; + 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") - public static final boolean defaultBuildForkPRHead = false; - - @Inject private GitHubSCMSource.DescriptorImpl delegate; + @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 - public String getPronoun() { - return Messages.GitHubSCMNavigator_Pronoun(); + protected String id() { + final GitHubSCMNavigatorContext gitHubSCMNavigatorContext = new GitHubSCMNavigatorContext().withTraits(traits); + if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { + return StringUtils.defaultIfBlank(apiUri, GitHubSCMSource.GITHUB_URL) + + "::" + + repoOwner + + "::" + + String.join("::", gitHubSCMNavigatorContext.getTopics()); + } + return StringUtils.defaultIfBlank(apiUri, GitHubSCMSource.GITHUB_URL) + "::" + repoOwner; } /** {@inheritDoc} */ @Override - public String getDisplayName() { - return Messages.GitHubSCMNavigator_DisplayName(); + 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, repoOwner); + + // 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(getTraits()); + + 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))); + final Iterable repositories; + if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Looking up repositories for topics: '%s'", + gitHubSCMNavigatorContext.getTopics()))); + repositories = searchRepositories(github, gitHubSCMNavigatorContext); + } else { + repositories = myself.listRepositories(100); + } + + for (GHRepository repo : repositories) { + if (!repoOwner.equals(repo.getOwnerName())) { + 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 (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", + repo.getName()))); + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", + 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 if (!gitHubSCMNavigatorContext.getTopics().isEmpty()) { + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Looking up repositories for topics: '%s'", + gitHubSCMNavigatorContext.getTopics()))); + repositories = searchRepositories(github, gitHubSCMNavigatorContext); + } 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 (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", repo.getName()))); + + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", 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 (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", 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); + } } - /** {@inheritDoc} */ - @Override - public String getDescription() { - return Messages.GitHubSCMNavigator_Description(); + private Iterable searchRepositories(final GitHub github, final GitHubSCMNavigatorContext context) { + final GHRepositorySearchBuilder ghRepositorySearchBuilder = github.searchRepositories(); + context.getTopics().forEach(ghRepositorySearchBuilder::topic); + ghRepositorySearchBuilder.org(getRepoOwner()); + if (!context.isExcludeForkedRepositories()) { + ghRepositorySearchBuilder.q("fork:true"); + } + return ghRepositorySearchBuilder.list().withPageSize(100); } - /** {@inheritDoc} */ - @Override - public String getIconFilePathPattern() { - return "plugin/github-branch-source/images/github-scmnavigator.svg"; + 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 String getIconClassName() { - return "icon-github-scm-navigator"; + 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, repoOwner); + + // 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 (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", + repo.getName()))); + + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", + 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 (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", repo.getName()))); + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", 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 (repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePrivateRepositories()) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is private", repo.getName()))); + + } else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories() + && repo.getSource() != null) { + witness.record(repo.getName(), false); + listener.getLogger() + .println(GitHubConsoleNote.create( + System.currentTimeMillis(), + String.format( + "Skipping repository %s because it is a fork", 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); + } } - /** {@inheritDoc} */ - @SuppressWarnings("unchecked") - @Override - public SCMNavigator newInstance(String name) { - GitHubSCMNavigator navigator = new GitHubSCMNavigator(name); - navigator.setTraits(getTraitsDefaults()); - navigator.setApiUri(GitHubServerConfig.GITHUB_URL); - return navigator; + 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 - protected SCMSourceCategory[] createCategories() { - return new SCMSourceCategory[] { - new UncategorizedSCMSourceCategory(Messages._GitHubSCMNavigator_UncategorizedCategory()) - // TODO add support for forks - }; + 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<>(); + String apiUri = Util.fixEmptyAndTrim(getApiUri()); + StandardCredentials credentials = + Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner); + GitHub hub = Connector.connect(getApiUri(), credentials); + boolean privateMode = determinePrivateMode(apiUri); + 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)); + if (privateMode) { + result.add(new GitHubOrgMetadataAction((String) null)); + } else { + 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); + } } - /** - * 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, - @QueryParameter String repoOwner) { - return Connector.checkScanCredentials(context, apiUri, credentialsId, repoOwner); + private static boolean determinePrivateMode(String apiUri) { + if (apiUri == null || apiUri.equals(GitHubServerConfig.GITHUB_URL)) { + return false; + } + try { + GitHub.connectToEnterpriseAnonymously(apiUri).checkApiUrlValidity(); + } catch (MalformedURLException e) { + // URL is bogus so there is never going to be an avatar - or anything else come to think of it + return true; + } catch (IOException e) { + if (e.getMessage().contains("private mode enabled")) { + return true; + } + } + return false; } - /** - * 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); + /** {@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, repoOwner); + 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); + } } - /** - * Returns the available GitHub endpoint items. - * - * @return the available GitHub endpoint items. - */ - @Restricted(NoExternalUse.class) // stapler - @SuppressWarnings("unused") // stapler - public ListBoxModel doFillApiUriItems() { - return getPossibleApiUriItems(); - } + @Symbol("github") + @Extension + public static class DescriptorImpl extends SCMNavigatorDescriptor implements IconSpec { - 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; - } + private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName()); - /** - * 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(); - } + @Deprecated + @Restricted(DoNotUse.class) + @RestrictedSince("2.2.0") + public static final String defaultIncludes = "*"; - /** - * 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; + @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/github-scmnavigator.svg"; + } + + /** {@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, + @QueryParameter String repoOwner) { + return Connector.checkScanCredentials(context, apiUri, credentialsId, repoOwner); + } + + /** + * 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); } - }, - 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; - } + return Connector.listScanCredentials(context, apiUri); + } - @SuppressWarnings("unused") // jelly - @NonNull - public List>> getTraitsDefaults() { - return new ArrayList<>(delegate.getTraitsDefaults()); - } + /** + * 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 { - IconSet.icons.addIcon( - new Icon( - "icon-github-scm-navigator icon-sm", - "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", - Icon.ICON_SMALL_STYLE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-scm-navigator icon-md", - "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", - Icon.ICON_MEDIUM_STYLE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-scm-navigator icon-lg", - "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", - Icon.ICON_LARGE_STYLE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-scm-navigator icon-xlg", - "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", - Icon.ICON_XLARGE_STYLE)); - - IconSet.icons.addIcon( - new Icon( - "icon-github-logo icon-sm", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", - Icon.ICON_SMALL_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-logo icon-md", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", - Icon.ICON_MEDIUM_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-logo icon-lg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", - Icon.ICON_LARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-logo icon-xlg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", - Icon.ICON_XLARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - - IconSet.icons.addIcon( - new Icon( - "icon-github-repo icon-sm", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", - Icon.ICON_SMALL_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-repo icon-md", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", - Icon.ICON_MEDIUM_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-repo icon-lg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", - Icon.ICON_LARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-repo icon-xlg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", - Icon.ICON_XLARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - - IconSet.icons.addIcon( - new Icon( - "icon-github-branch icon-sm", - "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", - Icon.ICON_SMALL_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-branch icon-md", - "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", - Icon.ICON_MEDIUM_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-branch icon-lg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", - Icon.ICON_LARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - IconSet.icons.addIcon( - new Icon( - "icon-github-branch icon-xlg", - "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", - Icon.ICON_XLARGE_STYLE, - IconFormat.EXTERNAL_SVG_SPRITE)); - } - } + 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; + } - /** 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; + /** + * 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(); + } - /** - * Constructor. - * - * @param listener the listener to log to. - */ - public WitnessImpl(@NonNull TaskListener listener) { - this.listener = listener; - } + /** + * 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; + } - /** {@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); - } - } + @SuppressWarnings("unused") // jelly + @NonNull + public List>> getTraitsDefaults() { + return new ArrayList<>(delegate.getTraitsDefaults()); + } - /** - * Returns the count of repositories matches. - * - * @return the count of repositories matches. - */ - public synchronized int getCount() { - return count; + static { + IconSet.icons.addIcon(new Icon( + "icon-github-scm-navigator icon-sm", + "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon(new Icon( + "icon-github-scm-navigator icon-md", + "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon(new Icon( + "icon-github-scm-navigator icon-lg", + "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon(new Icon( + "icon-github-scm-navigator icon-xlg", + "plugin/github-branch-source/images/svgs/github-scmnavigator.svg", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon(new Icon( + "icon-github-logo icon-sm", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", + Icon.ICON_SMALL_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-logo icon-md", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", + Icon.ICON_MEDIUM_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-logo icon-lg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", + Icon.ICON_LARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-logo icon-xlg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-logo", + Icon.ICON_XLARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + + IconSet.icons.addIcon(new Icon( + "icon-github-repo icon-sm", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", + Icon.ICON_SMALL_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-repo icon-md", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", + Icon.ICON_MEDIUM_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-repo icon-lg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", + Icon.ICON_LARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-repo icon-xlg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#github-repo", + Icon.ICON_XLARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + + IconSet.icons.addIcon(new Icon( + "icon-github-branch icon-sm", + "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", + Icon.ICON_SMALL_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-branch icon-md", + "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", + Icon.ICON_MEDIUM_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-branch icon-lg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", + Icon.ICON_LARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + IconSet.icons.addIcon(new Icon( + "icon-github-branch icon-xlg", + "plugin/github-branch-source/images/svgs/sprite-github.svg#git-branch", + Icon.ICON_XLARGE_STYLE, + IconFormat.EXTERNAL_SVG_SPRITE)); + } } - } - /** 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. */ + 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; + } - /** - * Constructor. - * - * @param request the request to decorate {@link SCMSource} instances with. - */ - public SourceFactory(GitHubSCMNavigatorRequest request) { - this.request = request; + /** {@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; + } } - /** {@inheritDoc} */ - @NonNull - @Override - public SCMSource create(@NonNull String name) { - return new GitHubSCMSourceBuilder( - getId() + "::" + name, apiUri, credentialsId, repoOwner, name) - .withRequest(request) - .build(); + /** 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(); + } } - } } 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 7af5f7f4c..0fcb48a4a 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 @@ -36,99 +36,98 @@ * @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 List topics = new ArrayList<>(); - - /** If true, archived repositories will be ignored. */ - private boolean excludeArchivedRepositories; - - /** If true, public repositories will be ignored. */ - private boolean excludePublicRepositories; - - /** If true, private repositories will be ignored. */ - private boolean excludePrivateRepositories; - - /** If true, forked repositories will be ignored. */ - private boolean excludeForkedRepositories; - - /** {@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(List 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; - } - - /** @return True if private repositories should be ignored, false if they should be included. */ - public boolean isExcludePrivateRepositories() { - return excludePrivateRepositories; - } - - /** @return True if forked repositories should be ignored, false if they should be included. */ - public boolean isExcludeForkedRepositories() { - return excludeForkedRepositories; - } - - /** @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; - } - - /** @param excludePrivateRepositories Set true to exclude private repositories */ - public void setExcludePrivateRepositories(boolean excludePrivateRepositories) { - this.excludePrivateRepositories = excludePrivateRepositories; - } - - /** @param excludeForkedRepositories Set true to exclude archived repositories */ - public void setExcludeForkedRepositories(boolean excludeForkedRepositories) { - this.excludeForkedRepositories = excludeForkedRepositories; - } + extends SCMNavigatorContext { + + /** The team name of the repositories to navigate. */ + private String teamSlug = ""; + + /** The topic which the repositories must have. */ + private List topics = new ArrayList<>(); + + /** If true, archived repositories will be ignored. */ + private boolean excludeArchivedRepositories; + + /** If true, public repositories will be ignored. */ + private boolean excludePublicRepositories; + + /** If true, private repositories will be ignored. */ + private boolean excludePrivateRepositories; + + /** If true, forked repositories will be ignored. */ + private boolean excludeForkedRepositories; + + /** {@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(List 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; + } + + /** @return True if private repositories should be ignored, false if they should be included. */ + public boolean isExcludePrivateRepositories() { + return excludePrivateRepositories; + } + + /** @return True if forked repositories should be ignored, false if they should be included. */ + public boolean isExcludeForkedRepositories() { + return excludeForkedRepositories; + } + + /** @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; + } + + /** @param excludePrivateRepositories Set true to exclude private repositories */ + public void setExcludePrivateRepositories(boolean excludePrivateRepositories) { + this.excludePrivateRepositories = excludePrivateRepositories; + } + + /** @param excludeForkedRepositories Set true to exclude archived repositories */ + public void setExcludeForkedRepositories(boolean excludeForkedRepositories) { + this.excludeForkedRepositories = excludeForkedRepositories; + } } 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 5d1f3a9bc..afa4615e3 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,17 +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 3f0a1d11b..9599084a4 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 @@ -47,153 +47,148 @@ @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; + @Override + public void close() throws IOException { + if (gitHub == null || repo == null) { + return; + } + synchronized (this) { + if (!open) { + return; + } + open = false; + } + Connector.release(gitHub); } - Connector.release(gitHub); - } - private synchronized void checkOpen() throws IOException { - if (!open) { - throw new IOException("Closed"); - } - if (repo == null) { - throw new IOException("No connection available"); + private synchronized void checkOpen() throws IOException { + if (!open) { + throw new IOException("Closed"); + } + if (repo == null) { + throw new IOException("No connection available"); + } } - } - @Override - public String name() { - return name; - } - - @Override - public long lastModified() { - if (repo == null) { - return 0L; - } - synchronized (this) { - if (!open) { - return 0L; - } + @Override + public String name() { + return name; } - 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; - } - @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); - } + @Override + public long lastModified() { + if (repo == null) { + return 0L; + } + synchronized (this) { + if (!open) { + return 0L; + } } - } - for (GHContent content : directoryContent) { - if (content.getPath().equalsIgnoreCase(path)) { - return SCMProbeStat.fromAlternativePath(content.getPath()); + 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 + } } - } - } catch (FileNotFoundException fnf) { - // means that does not exist and this is handled below this try/catch block. + return 0; } - return SCMProbeStat.fromType(SCMFile.Type.NONEXISTENT); - } - @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); + } + } + } + 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); } - 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; + + @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); } - return new GitHubSCMFile(this, repo, ref); - } - @Override - public synchronized boolean isOpen() { - return open; - } + @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 4b6672037..ce2e3cc83 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 @@ -149,2872 +149,2795 @@ 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-_]{1,39}$)([A-Za-z0-9]((?:[A-Za-z0-9]+|-(?=[A-Za-z0-9]+))*)(_(?:[A-Za-z0-9]+))?)"; - 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(); - - /** Number of times we will retry asking GitHub for the mergeable status of a PR. */ - private static /* mostly final */ int mergeableStatusRetries = - SystemProperties.getInteger( - GitHubSCMSource.class.getName() + ".mergeableStatusRetries", Integer.valueOf(4)); - - ////////////////////////////////////////////////////////////////////// - // 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())); - } - } - - /** - * Simple method to iterate a set of {@link SCMHeadObserver#getIncludes()} branches/tags/pr that - * will be possible observed and to check if at least one element is an instance of a provided - * class. - * - * @param observer {@link SCMHeadObserver} with an include list that are possible going to be - * observed. - * @param t Class type to compare the set elements to. - * @return true if the observer includes list contains at least one element with the provided - * class type. - */ - public boolean checkObserverIncludesType(@NonNull SCMHeadObserver observer, @NonNull Class t) { - Set includes = observer.getIncludes(); - if (includes != null) { - for (SCMHead head : includes) { - if (t.isInstance(head)) { - return true; - } - } - } - return false; - } - - /** - * Method to verify if the conditions to retrieve information regarding a SCMHead class are met. - * - * @param observer {@link SCMHeadObserver} with the events to be observed. - * @param event {@link SCMHeadEvent} with the event triggered. - * @param t Class type of analyzed SCMHead. - * @return true if a retrieve should be executed form a given SCMHead Class. - */ - public boolean shouldRetrieve( - @NonNull SCMHeadObserver observer, @CheckForNull SCMHeadEvent event, @NonNull Class t) { - - // JENKINS-65071 - // Observer has information about the events to analyze. To avoid unnecessary processing - // and GitHub API requests, - // it is necessary to check if this event contains a set of {@link SCMHead} instances of a - // type. - // When we open or close a Pull request we don't need a TAG examination because the event - // doesn't have any TAG. So, we only trigger a - // examination if the observer has any include event of each type BranchSCMHead, - // PullRequestSCMHead or GitHubTagSCMHead. - // But when a project scan is triggered we don't have any event so a full examination - // should happen. - - if (event == null) { - return true; - } - - return checkObserverIncludesType(observer, t); - } - - @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, repoOwner); - // 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() - && this.shouldRetrieve(observer, event, BranchSCMHead.class)) { - 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() - && this.shouldRetrieve(observer, event, PullRequestSCMHead.class)) { - 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() - && this.shouldRetrieve(observer, event, GitHubTagSCMHead.class)) { - 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); - } - } + public static final String VALID_GITHUB_REPO_NAME = "^[0-9A-Za-z._-]+$"; + public static final String VALID_GITHUB_USER_NAME = + "^(?=[A-Za-z0-9-_]{1,39}$)([A-Za-z0-9]((?:[A-Za-z0-9]+|-(?=[A-Za-z0-9]+))*)(_(?:[A-Za-z0-9]+))?)"; + 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(); - 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, repoOwner); - // 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, repoOwner); - // 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, repoOwner); - // 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, repoOwner); - - // 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 = mergeableStatusRetries; - - 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); - 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, repoOwner); - 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, repoOwner); - 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 { + /** Number of times we will retry asking GitHub for the mergeable status of a PR. */ + private static /* mostly final */ int mergeableStatusRetries = SystemProperties.getInteger( + GitHubSCMSource.class.getName() + ".mergeableStatusRetries", Integer.valueOf(4)); - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultIncludes = "*"; + ////////////////////////////////////////////////////////////////////// + // Configuration fields + ////////////////////////////////////////////////////////////////////// - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final String defaultExcludes = ""; + /** The GitHub end-point. Defaults to {@link #GITHUB_URL}. */ + @NonNull + private String apiUri; - 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; + /** + * Credentials for GitHub API; currently only supports username/password (personal access token). + * + * @since 2.2.0 + */ + @CheckForNull + private String credentialsId; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginBranchWithPR = true; + /** The repository owner. */ + @NonNull + private final String repoOwner; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRMerge = false; + /** The repository */ + @NonNull + private final String repository; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildOriginPRHead = false; + /** HTTPS URL for the repository, if specified by the user. */ + @CheckForNull + private final String repositoryUrl; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRMerge = true; + /** + * The behaviours to apply to this source. + * + * @since 2.2.0 + */ + @NonNull + private List traits; - @Deprecated - @Restricted(DoNotUse.class) - @RestrictedSince("2.2.0") - public static final boolean defaultBuildForkPRHead = false; + ////////////////////////////////////////////////////////////////////// + // Legacy Configuration fields + ////////////////////////////////////////////////////////////////////// - @Initializer(before = InitMilestone.PLUGINS_STARTED) - public static void addAliases() { - XSTREAM2.addCompatibilityAlias( - "org.jenkinsci.plugins.github_branch_source.OriginGitHubSCMSource", - GitHubSCMSource.class); - } + /** 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; - @Override - public String getDisplayName() { - return Messages.GitHubSCMSource_DisplayName(); - } + ////////////////////////////////////////////////////////////////////// + // 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 - public Map customInstantiate(@NonNull Map arguments) { - Map arguments2 = new TreeMap<>(arguments); - arguments2.remove("repositoryUrl"); - arguments2.remove("configuredByUrl"); - return arguments2; - } - + private transient /*effectively final*/ Map pullRequestMetadataCache; + /** The cache of {@link ObjectMetadataAction} instances for each open PR. */ @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 repoOwner, - @QueryParameter String value, - @QueryParameter boolean configuredByUrl) { - - if (!configuredByUrl) { - return Connector.checkScanCredentials(context, apiUri, value, repoOwner); - } else if (value.isEmpty()) { - return FormValidation.warning("Credentials are recommended"); - } else { - // Using the URL-based configuration, that has its own "Validate" button - return FormValidation.ok(); - } - } - - @RequirePOST - @Restricted(NoExternalUse.class) - public FormValidation doValidateRepositoryUrlAndCredentials( - @CheckForNull @AncestorInPath Item context, - @QueryParameter String repositoryUrl, - @QueryParameter String credentialsId, - @QueryParameter String repoOwner) { - 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, repoOwner); - 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); + 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(); } - } catch (IOException e) { - return FormValidation.error(e, "Error validating repository information. " + sb.toString()); - } - return FormValidation.ok(sb.toString()); + pullRequestMetadataCache = new ConcurrentHashMap<>(); + pullRequestContributorCache = new ConcurrentHashMap<>(); + this.traits = new ArrayList<>(); } - @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(); + /** + * 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 doCheckScanCredentialsId( - @CheckForNull @AncestorInPath Item context, - @QueryParameter String apiUri, - @QueryParameter String scanCredentialsId, - @QueryParameter String repoOwner, - @QueryParameter boolean configuredByUrl) { - return doCheckCredentialsId(context, apiUri, scanCredentialsId, repoOwner, configuredByUrl); + /** + * 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 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(); + public boolean isConfiguredByUrl() { + return repositoryUrl != null; } - @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(); + /** + * Returns the GitHub API end-point. + * + * @return the GitHub API end-point. + */ + @NonNull + public String getApiUri() { + return apiUri; } - @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, - @QueryParameter String repoOwner) - 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, repoOwner); - 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, repoOwner); - 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); + /** + * 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; } - } 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); + apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri)); + if (apiUri == null) { + apiUri = GITHUB_URL; + } + this.apiUri = apiUri; } + /** - * 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}.) + * Forces the apiUri to a specific value. FOR TESTING ONLY. + * + * @param apiUri the api uri */ - 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.HEAD)), - new ForkPullRequestDiscoveryTrait( - EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), - new ForkPullRequestDiscoveryTrait.TrustPermission())); + void forceApiUri(@NonNull String apiUri) { + this.apiUri = apiUri; } - @NonNull + /** + * 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 - protected SCMHeadCategory[] createCategories() { - return new SCMHeadCategory[] { - new UncategorizedSCMHeadCategory(Messages._GitHubSCMSource_UncategorizedCategory()), - new ChangeRequestSCMHeadCategory(Messages._GitHubSCMSource_ChangeRequestCategory()), - new TagSCMHeadCategory(Messages._GitHubSCMSource_TagCategory()) - }; + @CheckForNull + public String getCredentialsId() { + return credentialsId; } - } - @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; + /** + * 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); } - @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); - } + /** + * Gets the repository owner. + * + * @return the repository owner. + */ + @Exported + @NonNull + public String getRepoOwner() { + return repoOwner; } - @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; + /** + * Gets the repository name. + * + * @return the repository name. + */ + @Exported + @NonNull + public String getRepository() { + return repository; } - @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); - } + /** + * 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) - 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; + /** + * {@inheritDoc} + * + * @since 2.2.0 + */ + @Override + public List getTraits() { + return traits; } - @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(); + /** + * 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)); } - 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; - } + if (buildOriginPRMerge || buildOriginPRHead) { + EnumSet s = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); + if (buildOriginPRMerge) { + s.add(ChangeRequestCheckoutStrategy.MERGE); } - } - - @Override - public GHRef next() { - if (!hasNext()) { - throw new NoSuchElementException(); + if (buildOriginPRHead) { + s.add(ChangeRequestCheckoutStrategy.HEAD); } - return iterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - }; - } - }; - } catch (IOException e) { - throw new GitHubSCMSource.WrappedException(e); - } + 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; } - } - private static class CriteriaWitness implements SCMSourceRequest.Witness { - private final TaskListener listener; - - public CriteriaWitness(TaskListener listener) { - this.listener = listener; + /** + * 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; } - @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"); - } + /** + * 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)); } - } - 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; + /** + * 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; } - @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; + /** + * 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} */ - @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; + public String getRemote() { + return GitHubSCMBuilder.uriResolver(getOwner(), apiUri, credentialsId) + .getRepositoryUri(apiUri, repoOwner, repository); } /** {@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, repoOwner); - // 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); - } + public String getPronoun() { + return Messages.GitHubSCMSource_Pronoun(); } - } - - private class DeferredPermissionsSource extends GitHubPermissionsSource implements Closeable { - private final TaskListener listener; - private GitHub github; - private GHRepository repo; - - public DeferredPermissionsSource(TaskListener listener) { - this.listener = listener; + /** + * 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())); + } + } + + /** + * Simple method to iterate a set of {@link SCMHeadObserver#getIncludes()} branches/tags/pr that + * will be possible observed and to check if at least one element is an instance of a provided + * class. + * + * @param observer {@link SCMHeadObserver} with an include list that are possible going to be + * observed. + * @param t Class type to compare the set elements to. + * @return true if the observer includes list contains at least one element with the provided + * class type. + */ + public boolean checkObserverIncludesType(@NonNull SCMHeadObserver observer, @NonNull Class t) { + Set includes = observer.getIncludes(); + if (includes != null) { + for (SCMHead head : includes) { + if (t.isInstance(head)) { + return true; + } + } + } + return false; + } + + /** + * Method to verify if the conditions to retrieve information regarding a SCMHead class are met. + * + * @param observer {@link SCMHeadObserver} with the events to be observed. + * @param event {@link SCMHeadEvent} with the event triggered. + * @param t Class type of analyzed SCMHead. + * @return true if a retrieve should be executed form a given SCMHead Class. + */ + public boolean shouldRetrieve( + @NonNull SCMHeadObserver observer, @CheckForNull SCMHeadEvent event, @NonNull Class t) { + + // JENKINS-65071 + // Observer has information about the events to analyze. To avoid unnecessary processing + // and GitHub API requests, + // it is necessary to check if this event contains a set of {@link SCMHead} instances of a + // type. + // When we open or close a Pull request we don't need a TAG examination because the event + // doesn't have any TAG. So, we only trigger a + // examination if the observer has any include event of each type BranchSCMHead, + // PullRequestSCMHead or GitHubTagSCMHead. + // But when a project scan is triggered we don't have any event so a full examination + // should happen. + + if (event == null) { + return true; + } + + return checkObserverIncludesType(observer, t); + } + + @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, repoOwner); + // 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() + && this.shouldRetrieve(observer, event, BranchSCMHead.class)) { + 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() + && this.shouldRetrieve(observer, event, PullRequestSCMHead.class)) { + 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() + && this.shouldRetrieve(observer, event, GitHubTagSCMHead.class)) { + 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); + } + } + + 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, repoOwner); + // 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, repoOwner); + // 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, repoOwner); + // 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, repoOwner); + + // 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 = mergeableStatusRetries; + + 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); + 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, repoOwner); + 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 - 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); + 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, repoOwner); - github = Connector.connect(apiUri, credentials); - String fullName = repoOwner + "/" + repository; - repo = github.getRepository(fullName); - } - return repo.getPermission(username); + Connector.lookupScanCredentials((Item) getOwner(), apiUri, credentialsId, repoOwner); + 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 close() throws IOException { - if (github != null) { - Connector.release(github); - github = null; - repo = null; - } - } - } + 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 repoOwner, + @QueryParameter String value, + @QueryParameter boolean configuredByUrl) { + + if (!configuredByUrl) { + return Connector.checkScanCredentials(context, apiUri, value, repoOwner); + } else if (value.isEmpty()) { + return FormValidation.warning("Credentials are recommended"); + } else { + // Using the URL-based configuration, that has its own "Validate" button + return FormValidation.ok(); + } + } + + @RequirePOST + @Restricted(NoExternalUse.class) + public FormValidation doValidateRepositoryUrlAndCredentials( + @CheckForNull @AncestorInPath Item context, + @QueryParameter String repositoryUrl, + @QueryParameter String credentialsId, + @QueryParameter String repoOwner) { + 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, repoOwner); + 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, + @QueryParameter String repoOwner, + @QueryParameter boolean configuredByUrl) { + return doCheckCredentialsId(context, apiUri, scanCredentialsId, repoOwner, configuredByUrl); + } + + @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, + @QueryParameter String repoOwner) + 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, repoOwner); + 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, repoOwner); + 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.HEAD)), + new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.HEAD), + 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, repoOwner); + // 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, repoOwner); + 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; + } + } + } } 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 dc42c8a78..09c308bf7 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,87 +32,90 @@ * * @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 0ed5599a4..6e764c68a 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 @@ -42,278 +42,270 @@ * * @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<>(); +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<>(); - /** - * Constructor. - * - * @param criteria (optional) criteria. - * @param observer the {@link SCMHeadObserver}. - */ - public GitHubSCMSourceContext( - @CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer) { - super(criteria, observer); - } + /** + * 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; - } + /** + * 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 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 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 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 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 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 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 {@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()); + /** + * 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; } - 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); - } + /** + * 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; } - 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); + /** + * 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; } - 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 54a2725a3..58c358e28 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 @@ -40,20 +40,19 @@ @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 7e2b1bae8..ea2329eec 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,429 +55,439 @@ * @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()); + /** {@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; } - } - 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(); + + /** + * 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(); } - 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 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 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)); + + /** + * 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; } - } - - /** - * 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 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; } - if (branches instanceof Closeable) { - ((Closeable) branches).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 (permissionsSource instanceof Closeable) { - ((Closeable) permissionsSource).close(); + + /** + * Provides the requests with the pull request details. + * + * @param pullRequests the pull request details. + */ + public void setPullRequests(@CheckForNull Iterable pullRequests) { + this.pullRequests = 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); - } + + /** + * 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); } - if (permissionsSource != null) { - GHPermissionType result = permissionsSource.fetch(username); - synchronized (permissions) { - permissions.put(username, result); - } - return result; + + /** + * 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 (repository != null && username.equalsIgnoreCase(repository.getOwnerName())) { - return GHPermissionType.ADMIN; + + /** + * Provides the requests with the tag details. + * + * @param tags the tag details. + */ + public final void setTags(@CheckForNull Iterable tags) { + this.tags = tags; } - if (collaboratorNames != null && collaboratorNames.contains(username)) { - return GHPermissionType.WRITE; + + /** + * 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; } - 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 23765d424..34d853475 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,19 +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); - } + /** + * 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(); - } + /** {@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 e43f941f2..569b5a80d 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 @@ -27,13 +27,13 @@ /** 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/IgnoreDraftPullRequestFilterTrait.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTrait.java index 7d76bafba..1d1ebc121 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTrait.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTrait.java @@ -18,59 +18,54 @@ /** Trait used to filter any pull requests current set as a draft from building. */ public class IgnoreDraftPullRequestFilterTrait extends SCMSourceTrait { - @DataBoundConstructor - public IgnoreDraftPullRequestFilterTrait() {} + @DataBoundConstructor + public IgnoreDraftPullRequestFilterTrait() {} - protected void decorateContext(SCMSourceContext context) { - context.withFilter( - new SCMHeadFilter() { - @Override - public boolean isExcluded( - @NonNull final SCMSourceRequest request, @NonNull final SCMHead head) - throws IOException { - if (!(request instanceof GitHubSCMSourceRequest) - || !(head instanceof PullRequestSCMHead)) { - return false; + protected void decorateContext(SCMSourceContext context) { + context.withFilter(new SCMHeadFilter() { + @Override + public boolean isExcluded(@NonNull final SCMSourceRequest request, @NonNull final SCMHead head) + throws IOException { + if (!(request instanceof GitHubSCMSourceRequest) || !(head instanceof PullRequestSCMHead)) { + return false; + } + GitHubSCMSourceRequest githubRequest = (GitHubSCMSourceRequest) request; + PullRequestSCMHead prHead = (PullRequestSCMHead) head; + for (GHPullRequest pullRequest : githubRequest.getPullRequests()) { + if (pullRequest.getNumber() != prHead.getNumber()) { + continue; + } + if (pullRequest.isDraft()) { + request.listener() + .getLogger() + .format("%n Won't Build PR %s. Marked as a draft.%n", "#" + prHead.getNumber()); + return true; + } + return false; + } + return false; } - GitHubSCMSourceRequest githubRequest = (GitHubSCMSourceRequest) request; - PullRequestSCMHead prHead = (PullRequestSCMHead) head; - for (GHPullRequest pullRequest : githubRequest.getPullRequests()) { - if (pullRequest.getNumber() != prHead.getNumber()) { - continue; - } - if (pullRequest.isDraft()) { - request - .listener() - .getLogger() - .format( - "%n Won't Build PR %s. Marked as a draft.%n", "#" + prHead.getNumber()); - return true; - } - return false; - } - return false; - } }); - } + } - @Symbol({"gitHubIgnoreDraftPullRequestFilter"}) - @Extension - @Selection - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - public DescriptorImpl() {} + @Symbol({"gitHubIgnoreDraftPullRequestFilter"}) + @Extension + @Selection + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + public DescriptorImpl() {} - public String getDisplayName() { - return Messages.IgnoreDraftPullRequestFilterTrait_DisplayName(); - } + public String getDisplayName() { + return Messages.IgnoreDraftPullRequestFilterTrait_DisplayName(); + } - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; - } + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.class; + } - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; + @Override + public Class getSourceClass() { + return GitHubSCMSource.class; + } } - } } 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 5a3c8cc69..cc1f8d65f 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 1af8d5acc..e9bd786c3 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,23 +34,24 @@ * @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(); + /** {@inheritDoc} */ + @Override + public synchronized Iterator iterator() { + if (delegate == null) { + delegate = create(); + } + return delegate.iterator(); } - 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 c91baae94..4f30e4683 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,111 +36,112 @@ * @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(); + /** 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(); } - 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 10e907417..d7619d264 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 72d1f42bc..540ec1c62 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 @@ -54,152 +54,149 @@ * @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; - } - - /** - * 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 { + /** 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; - /** {@inheritDoc} */ - @Override - public String getDisplayName() { - return Messages.OriginPullRequestDiscoveryTrait_discoverPullRequestsFromOrigin(); + /** 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; } - /** {@inheritDoc} */ - @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; + /** + * 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 getSourceClass() { - return GitHubSCMSource.class; + /** + * Gets the strategy id. + * + * @return the strategy id. + */ + public int getStrategyId() { + return strategyId; } /** - * Populates the strategy options. + * Returns the strategies. * - * @return the strategy options. + * @return the strategies. */ @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 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); + } } - } - /** 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()); + 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 - 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); - } + @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); + } + } } - } } 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 ba723fa6e..96c5d8c86 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 @@ -42,50 +42,50 @@ @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 102e7fb2a..18ab574ce 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 @@ -70,335 +70,298 @@ @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; + } + } + } + if (project.getParent() instanceof SCMSourceOwner) { + SCMSourceOwner owner = (SCMSourceOwner) project.getParent(); + for (SCMSource source : owner.getSCMSources()) { + if (source instanceof GitHubSCMSource) { + return true; + } + } + } } - } + return false; } - return false; - } - - /** @return set with only PULL_REQUEST event */ - @Override - protected Set events() { - return immutableEnumSet(PULL_REQUEST); - } - @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; - } - - 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) - || "ready_for_review".equals(action) - || "converted_to_draft".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())); - } - } - - } 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); + /** @return set with only PULL_REQUEST event */ + @Override + protected Set events() { + return immutableEnumSet(PULL_REQUEST); } - } - private void fireAfterDelay(final SCMHeadEventImpl e) { - SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); - } + @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; + } - private static class SCMHeadEventImpl extends SCMHeadEvent { - private final String repoHost; - private final String repoOwner; - private final String repository; + 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) + || "ready_for_review".equals(action) + || "converted_to_draft".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())); + } + } - 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(); + } 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 boolean isApiMatch(String apiUri) { - return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); + private void fireAfterDelay(final SCMHeadEventImpl e) { + SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); } - @Override - public boolean isMatch(@NonNull SCMNavigator navigator) { - return navigator instanceof GitHubSCMNavigator - && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); - } + private static class SCMHeadEventImpl extends SCMHeadEvent { + private final String repoHost; + private final String repoOwner; + private final String repository; - @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; + 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(); } - } - return "Pull request #" + getPayload().getNumber() + " event in repository " + 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"; + private boolean isApiMatch(String apiUri) { + return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); } - } - return "Pull request #" + getPayload().getNumber() + " event"; - } - @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; + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + return navigator instanceof GitHubSCMNavigator + && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); } - } - return "Pull request #" - + getPayload().getNumber() - + " event in repository " - + repoOwner - + "/" - + repository; - } - - @NonNull - @Override - public String getSourceName() { - return 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(); - } + @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; + } - boolean fork = !src.getRepoOwner().equalsIgnoreCase(prOwnerName); + @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"; + } - 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; - } + @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; } - if (!excluded) { - SCMRevision hash = - new AbstractGitSCMSource.SCMRevisionImpl(head, ghPullRequest.getHead().getSha()); - result.put(head, hash); + + @NonNull + @Override + public String getSourceName() { + return repository; } - } - 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; + + @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(); } - } - if (!excluded) { - result.put(head, revision); - } + 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(); + } + + 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; + } + } + if (!excluded) { + result.put(head, revision); + } + } + } + return result; } - } - 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 6726ba0ba..6cdd30efe 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 @@ -47,318 +47,312 @@ */ 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; - } - - 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; + 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; } - 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())); + + 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()); + } } - if (origin == null && !(this instanceof FixOrigin)) { - // Upgrade from 2.0.x - // we need the help of FixOriginMigration - return new FixOrigin(this); + 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; } - 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; + + /** {@inheritDoc} */ + @Override + public String getPronoun() { + return Messages.PullRequestSCMHead_Pronoun(); } public int getNumber() { - return number; + return number; } - public String getUrl() { - return url; + /** + * 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 getUserLogin() { - return userLogin; + /** + * 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 getBaseRef() { - return baseRef; - } - } - - /** - * 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 ChangeRequestCheckoutStrategy getCheckoutStrategy() { + return merge ? ChangeRequestCheckoutStrategy.MERGE : ChangeRequestCheckoutStrategy.HEAD; } - } - - /** - * 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 getId() { + return Integer.toString(number); } + /** {@inheritDoc} */ + @NonNull @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()); + public BranchSCMHead getTarget() { + return target; } + /** {@inheritDoc} */ + @NonNull @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; + public String getOriginName() { + return sourceBranch; } - } - - /** - * 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); + + public String getSourceOwner() { + return sourceOwner; } - } - - /** - * 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); + + public String getSourceBranch() { + return sourceBranch; } - @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()); + public String getSourceRepo() { + return sourceRepo; } + @NonNull @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; + 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; + } + + public String getUrl() { + return url; + } + + public String getUserLogin() { + return userLogin; + } + + public String getBaseRef() { + return baseRef; + } + } + + /** + * 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 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); + } + } + + /** + * 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; + } } - } } 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 b0a680d63..9931447a9 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 @@ -38,114 +38,102 @@ /** 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); - } - - 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); + 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); + } + 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 (NOT_MERGEABLE_HASH.equals(this.mergeHash)) { - 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; + } + + /** + * 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 (NOT_MERGEABLE_HASH.equals(this.mergeHash)) { + 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 boolean equivalent(ChangeRequestSCMRevision o) { - if (!(o instanceof PullRequestSCMRevision)) { - return false; + @Override + public int _hashCode() { + return pullHash.hashCode(); } - 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") - + ")"; + + @Override + public String toString() { + String result = pullHash; + if (getHead() instanceof PullRequestSCMHead && ((PullRequestSCMHead) getHead()).isMerge()) { + result += "+" + baseHash + " (" + StringUtils.defaultIfBlank(mergeHash, "UNKNOWN_MERGE_STATE") + ")"; + } + return result; } - 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 8754c415a..9e9105ba5 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 @@ -30,25 +30,25 @@ */ @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 649453c03..812ebd783 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 @@ -65,296 +65,272 @@ @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?://([^/]+)/([^/]+)/([^/]+)"); - - /** {@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; - } - } - } - } - return false; - } - - /** - * {@inheritDoc} - * - * @return set with only PULL_REQUEST event - */ - @Override - protected Set events() { - return immutableEnumSet(PUSH); - } - - /** {@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; - } - - 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 void fireAfterDelay(final SCMHeadEventImpl e) { - SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); - } - - 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; - - 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 boolean isApiMatch(String apiUri) { - return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); - } + /** 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 - public boolean isMatch(@NonNull SCMNavigator navigator) { - return navigator instanceof GitHubSCMNavigator - && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); + 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; + } + } + } + } + return false; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @return set with only PULL_REQUEST event + */ @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; + protected Set events() { + return immutableEnumSet(PUSH); } /** {@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; - } + 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} */ - @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; + 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} */ - @NonNull - @Override - public String getSourceName() { - return repository; + private void fireAfterDelay(final SCMHeadEventImpl e) { + SCMHeadEvent.fireLater(e, GitHubSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS); } - /** {@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(); - } + 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; - /* - * 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 - */ + 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(); + } - 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); + private boolean isApiMatch(String apiUri) { + return repoHost.equalsIgnoreCase(RepositoryUriResolver.hostnameFromApiUri(apiUri)); } - boolean excluded = false; - for (SCMHeadPrefilter prefilter : context.prefilters()) { - if (prefilter.isExcluded(source, head)) { - excluded = true; - break; - } + + /** {@inheritDoc} */ + @Override + public boolean isMatch(@NonNull SCMNavigator navigator) { + return navigator instanceof GitHubSCMNavigator + && repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner()); } - if (!excluded) { - return Collections.singletonMap( - head, new AbstractGitSCMSource.SCMRevisionImpl(head, push.getHead())); + + /** {@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; } - } - 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. - 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; - } + /** {@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; } - if (!excluded) { - return Collections.singletonMap(head, new GitTagSCMRevision(head, push.getHead())); + + /** {@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; } - } - return Collections.emptyMap(); - } - /** {@inheritDoc} */ - @Override - public boolean isMatch(@NonNull SCM scm) { - return false; + /** {@inheritDoc} */ + @NonNull + @Override + public String getSourceName() { + return 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(); + } + + /* + * 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 + */ + + 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. + + 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 bbcd825a2..e24e4c922 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,43 +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 getRemaining() { + return remaining; + } - public long getLimit() { - return limit; - } + 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 0e3019c98..46bb70e75 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 @@ -32,36 +32,35 @@ /** 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(); + /** + * 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 + } } - } catch (MalformedURLException e) { - // ignore - } + return "github.com"; } - 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 c7a3205f0..5737ab5a5 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 @@ -64,157 +64,156 @@ */ 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; - } - /** {@inheritDoc} */ - @Override - protected void decorateBuilder(SCMBuilder builder) { - ((GitHubSCMBuilder) builder).withCredentials(credentialsId, GitHubSCMBuilder.SSH); - } - - /** Our descriptor. */ - @Symbol("gitHubSshCheckout") - @Extension - public static class DescriptorImpl extends SCMSourceTraitDescriptor { - - /** {@inheritDoc} */ - @NonNull - @Override - public String getDisplayName() { - return Messages.SSHCheckoutTrait_displayName(); + /** + * 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 - public Class getContextClass() { - return GitHubSCMSourceContext.class; + protected void decorateBuilder(SCMBuilder builder) { + ((GitHubSCMBuilder) builder).withCredentials(credentialsId, GitHubSCMBuilder.SSH); } - /** {@inheritDoc} */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } + /** Our descriptor. */ + @Symbol("gitHubSshCheckout") + @Extension + public static class DescriptorImpl extends SCMSourceTraitDescriptor { - /** {@inheritDoc} */ - @Override - public Class getBuilderClass() { - return GitSCMBuilder.class; - } + /** {@inheritDoc} */ + @NonNull + @Override + public String getDisplayName() { + return Messages.SSHCheckoutTrait_displayName(); + } - /** {@inheritDoc} */ - @Override - public Class getScmClass() { - return GitSCM.class; - } + /** {@inheritDoc} */ + @Override + public Class getContextClass() { + return GitHubSCMSourceContext.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)); - } + /** {@inheritDoc} */ + @Override + public Class getSourceClass() { + return GitHubSCMSource.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()); + /** {@inheritDoc} */ + @Override + public Class getBuilderClass() { + return GitSCMBuilder.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)); + } + + /** + * 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 51a35d872..3d6582d09 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 @@ -16,105 +16,105 @@ * @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 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(); - } + /** + * Constructor. + * + * @param delegate the {@link Iterable}. + */ + public SinglePassIterable(@NonNull Iterable delegate) { + this(delegate.iterator()); } - return new Iterator() { - int index = 0; - /** {@inheritDoc} */ - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + /** + * Constructor. + * + * @param delegate the {@link Iterator}. + */ + public SinglePassIterable(@NonNull Iterator delegate) { + this.delegate = delegate; + items = new ArrayList<>(); + } - /** {@inheritDoc} */ - @Override - public boolean hasNext() { + /** {@inheritDoc} */ + @Override + public final Iterator iterator() { synchronized (items) { - if (index < items.size()) { - return true; - } - if (delegate != null) { - if (delegate.hasNext()) { - return true; + if (delegate == null || !delegate.hasNext()) { + // we have walked the iterator once, so now items is complete + return Collections.unmodifiableList(items).iterator(); } - delegate = null; - completed(); - } - return false; } - } + return new Iterator() { + int index = 0; - /** {@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 void remove() { + throw new UnsupportedOperationException(); } - } catch (NoSuchElementException e) { - if (delegate != null) { - delegate = null; - completed(); + + /** {@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; + } } - throw e; - } - } - } - }; - } - /** - * Callback for each element observed from the delegate. - * - * @param v the element. - */ - protected void observe(V v) {} + /** {@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 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 88a1b8ad7..cffb7e85d 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 @@ -27,9 +27,9 @@ /** 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 b33f8325c..29b05a5b3 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,73 +46,72 @@ * @since 2.3.0 */ public class TagDiscoveryTrait extends SCMSourceTrait { - /** 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; - } - - /** Our descriptor. */ - @Symbol("gitHubTagDiscovery") - @Extension - @Discovery - public static class DescriptorImpl extends SCMSourceTraitDescriptor { + /** Constructor for stapler. */ + @DataBoundConstructor + public TagDiscoveryTrait() {} /** {@inheritDoc} */ @Override - public String getDisplayName() { - return Messages.TagDiscoveryTrait_displayName(); + protected void decorateContext(SCMSourceContext context) { + GitHubSCMSourceContext ctx = (GitHubSCMSourceContext) context; + ctx.wantTags(true); + ctx.withAuthority(new TagSCMHeadAuthority()); } /** {@inheritDoc} */ @Override - public Class getContextClass() { - return GitHubSCMSourceContext.class; + public boolean includeCategory(@NonNull SCMHeadCategory category) { + return category instanceof TagSCMHeadCategory; } - /** {@inheritDoc} */ - @Override - public Class getSourceClass() { - return GitHubSCMSource.class; - } - } + /** Our descriptor. */ + @Symbol("gitHubTagDiscovery") + @Extension + @Discovery + public static class DescriptorImpl extends SCMSourceTraitDescriptor { - /** 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; + /** {@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; + } } - /** Out descriptor. */ - @Extension - public static class DescriptorImpl extends SCMHeadAuthorityDescriptor { - /** {@inheritDoc} */ - @Override - public String getDisplayName() { - return Messages.TagDiscoveryTrait_authorityDisplayName(); - } + /** 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(); + } - /** {@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 fe04f541a..805c1ce28 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 @@ -15,51 +15,52 @@ */ public class TeamSlugTrait extends SCMNavigatorTrait { - /** 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; - } - - /** - * Returns the teamSlug. - * - * @return the teamSlug. - */ - @NonNull - public String getTeamSlug() { - return teamSlug; - } + /** The team slug. */ + @NonNull + private final String teamSlug; - @Override - protected void decorateContext(final SCMNavigatorContext context) { - super.decorateContext(context); - ((GitHubSCMNavigatorContext) context).setTeamSlug(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; + } - /** TeamSlug descriptor. */ - @Symbol("teamSlugFilter") - @Extension - @Selection - public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + /** + * Returns the teamSlug. + * + * @return the teamSlug. + */ + @NonNull + public String getTeamSlug() { + return teamSlug; + } @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + protected void decorateContext(final SCMNavigatorContext context) { + super.decorateContext(context); + ((GitHubSCMNavigatorContext) context).setTeamSlug(teamSlug); } - @NonNull - @Override - public String getDisplayName() { - return Messages.TeamSlugTrait_displayName(); + /** TeamSlug descriptor. */ + @Symbol("teamSlugFilter") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @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 11890a92f..903f9d3b5 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 @@ -14,74 +14,75 @@ /** Decorates a {@link SCMNavigatorContext} with GitHub topics */ public class TopicsTrait extends SCMNavigatorTrait { - /** The topics */ - @NonNull private transient List topics; + /** The topics */ + @NonNull + private transient List topics; - private final String topicList; + private final String topicList; - /** - * Stapler constructor. - * - * @param topicList a comma-separated list of topics - */ - @DataBoundConstructor - public TopicsTrait(@NonNull String topicList) { - this.topicList = topicList; - this.topics = new ArrayList<>(); + /** + * 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()); + for (String topic : topicList.split(",")) { + this.topics.add(topic.trim()); + } } - } - - /** - * Returns the topics - * - * @return the topics - */ - @NonNull - public List getTopics() { - return topics; - } - @NonNull - public String getTopicList() { - return topicList; - } - - @Override - protected void decorateContext(final SCMNavigatorContext context) { - super.decorateContext(context); - ((GitHubSCMNavigatorContext) context).setTopics(topics); - } + /** + * Returns the topics + * + * @return the topics + */ + @NonNull + public List getTopics() { + return topics; + } - private Object readResolve() { - if (this.topicList != null) { - List tmpTopics = new ArrayList<>(); - for (String topic : topicList.split(",")) { - tmpTopics.add(topic.trim()); - } - topics = tmpTopics; + @NonNull + public String getTopicList() { + return topicList; } - return this; - } + @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 { + private Object readResolve() { + if (this.topicList != null) { + List tmpTopics = new ArrayList<>(); + for (String topic : topicList.split(",")) { + tmpTopics.add(topic.trim()); + } + topics = tmpTopics; + } - @Override - public Class getContextClass() { - return GitHubSCMNavigatorContext.class; + return this; } - @NonNull - @Override - public String getDisplayName() { - return Messages.TopicsTrait_displayName(); + /** Topics descriptor. */ + @Symbol("gitHubTopicsFilter") + @Extension + @Selection + public static class DescriptorImpl extends SCMNavigatorTraitDescriptor { + + @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 c7af274ea..ab3e36b72 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 @@ -30,26 +30,26 @@ @Deprecated class UntrustedPullRequestSCMRevision extends AbstractGitSCMSource.SCMRevisionImpl { - private static final long serialVersionUID = -6961458604178249880L; + private static final long serialVersionUID = -6961458604178249880L; - final String baseHash; + final String baseHash; - private UntrustedPullRequestSCMRevision(SCMHead head, String hash, String baseHash) { - super(head, hash); - this.baseHash = 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 boolean equals(Object o) { + return super.equals(o) && baseHash.equals(((UntrustedPullRequestSCMRevision) o).baseHash); + } - @Override - public int hashCode() { - return super.hashCode(); // good enough - } + @Override + public int hashCode() { + return super.hashCode(); // good enough + } - private Object readResolve() { - return new PullRequestSCMRevision((PullRequestSCMHead) getHead(), baseHash, getHash()); - } + 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 09ef65e1d..f4636fa52 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 @@ -20,85 +20,73 @@ /** @author Liam Newman */ public abstract class AbstractGitHubWireMockTest { - // 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"); + // 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 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 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() + "/")) - .build(); - } - return response; + .but() + .body(response.getBodyAsString() + .replace( + "https://api.github.com/", "http://localhost:" + githubApi.port() + "/") + .replace( + "https://raw.githubusercontent.com/", + "http://localhost:" + githubRaw.port() + "/")) + .build(); } + return response; + } - @Override - public String getName() { - return "url-rewrite"; - } - })); + @Override + public String getName() { + return "url-rewrite"; + } + })); - @Before - public void prepareMockGitHub() { - prepareMockGitHubFileMappings(); + @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/"))); + 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")); - } + 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 a2dec2daf..a47d6eb4c 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 @@ -34,926 +34,905 @@ 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(); - } - - @After - public void tearDown() throws Exception { - GitHubConfiguration.get().setEndpoints(new ArrayList<>()); - } - - 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); + + @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(); } - 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, countOfOutputLinesContaining("with API URL 'https://api.github.com'")); - 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)); - } - - @Test - public void NoCheckerConfiguredWithEndpoint() 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); - - List endpoints = new ArrayList<>(); - endpoints.add(new Endpoint("https://git.company.com/api/v3", "Company GitHub")); - endpoints.add(new Endpoint("https://git2.company.com/api/v3", "Company GitHub 2")); - GitHubConfiguration.get().setEndpoints(endpoints); - - GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); - github.getMeta(); - - assertEquals( - 1, - countOfOutputLinesContaining("LocalChecker for rate limit was not set for this thread.")); - assertEquals(1, countOfOutputLinesContaining("with API URL 'https://git.company.com/api/v3'")); - // ThrottleOnOver should not be used for NoThrottle since it is not the public GitHub endpoint - assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); - - assertEquals(initialRequestCount + 2, 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(); + @After + public void tearDown() throws Exception { + GitHubConfiguration.get().setEndpoints(new ArrayList<>()); } - 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(); + 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); } - 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(); + @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, countOfOutputLinesContaining("with API URL 'https://api.github.com'")); + 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)); } - // 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)); + @Test + public void NoCheckerConfiguredWithEndpoint() 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); + + List endpoints = new ArrayList<>(); + endpoints.add(new Endpoint("https://git.company.com/api/v3", "Company GitHub")); + endpoints.add(new Endpoint("https://git2.company.com/api/v3", "Company GitHub 2")); + GitHubConfiguration.get().setEndpoints(endpoints); + + GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.NoThrottle); + github.getMeta(); + + assertEquals(1, countOfOutputLinesContaining("LocalChecker for rate limit was not set for this thread.")); + assertEquals(1, countOfOutputLinesContaining("with API URL 'https://git.company.com/api/v3'")); + // ThrottleOnOver should not be used for NoThrottle since it is not the public GitHub endpoint + assertEquals(0, countOfOutputLinesContaining("ThrottleOnOver will be used instead")); + + assertEquals(initialRequestCount + 2, 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 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)); + } - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + /** + * 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)); + } - // 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)); + /** + * 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)); } - // should be no output - assertEquals(0, countOfOutputLines(m -> m.matches(".*[sS]leeping.*"))); + /** + * 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))); - assertEquals(initialRequestCount + 11, getRequestCount(githubApi)); + ApiRateLimitChecker.configureThreadLocalChecker(listener, github); - // check rate limit to hit the next 9 scenarios - for (int i = 0; i < 10; i++) { - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); + 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)); } - // 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 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)); } - assertEquals(initialRequestCount + 5, handler.getView().size()); - - // 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 + 5, 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 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)); } - // finally, stop throttling by restoring quota - scenarios.add(new RateLimit(limit, limit, new Date(soon.getTime() + 2000))); - setupStubs(scenarios); + /** + * 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); - ApiRateLimitChecker.configureThreadLocalChecker(listener, github); + // Approximate the ideal here + int approximateIdeal = 4000; - long start = System.currentTimeMillis(); + // 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 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(); - } + // 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)); - // (rate_limit + meta) x 6 - assertEquals(initialRequestCount + 12, getRequestCount(githubApi)); + // 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))); - // should be no output - assertEquals(0, countOfOutputLinesContaining("Sleeping")); + // "Less" under the ideal but should recheck and throttle again + scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); - ApiRateLimitChecker.LocalChecker currentChecker = ApiRateLimitChecker.getLocalChecker(); + // Check that we are under our ideal so we should throttle + scenarios.add(new RateLimit(limit, approximateIdeal - 99, new Date(soon.getTime() + 2000))); - // check rate limit to hit the next 5 scenarios - for (int i = 0; i < 5; i++) { - assertTrue(currentChecker.checkRateLimit(github.getRateLimit().getCore(), i)); + // 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)); + } + + assertEquals(initialRequestCount + 5, handler.getView().size()); + + // 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 + 5, handler.getView().size()); } - // 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 + + /** + * Verify OnNormal throttling when past the buffer + * + * @author Julian V. Modesto */ - 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)))); + @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)); } - /* - * With the limit at 400: the burst will be limit/10 and buffer will be limit/20 + + /** + * Verify throttle in "OnOver" and the wait happens for the correct amount of time + * + * @author Alex Taylor */ - 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)))); + @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")); } - /* - * With the limit at 1000: the burst will be limit/5 and buffer will be 15 + + /** + * Verify the "OnNormalize" throttle and wait is happening for the correct amount of time + * + * @author Alex Taylor */ - 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)))); + @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")); } - 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(); + /** + * 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.*"))); } - // 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 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.")); } - // 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) { + /** + * 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(); - } - // 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.")); - } - - /** - * 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); + while (System.currentTimeMillis() + 6000 < start) { + Thread.sleep(25); + } - // First server warm up - github.getRateLimit(); - github.getRateLimit(); + github.getMeta(); - while (System.currentTimeMillis() + 6000 < start) { - Thread.sleep(25); + // Expect a triggered throttle for normalize + assertEquals(2, countOfOutputLinesContaining("Current quota")); + assertEquals(2, countOfOutputLinesContaining("Still sleeping")); + assertEquals(initialRequestCount + 7, getRequestCount(githubApi)); } - 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(); + /** + * 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); - while (System.currentTimeMillis() + 6000 < start) { - Thread.sleep(25); - } + while (System.currentTimeMillis() + 6000 < start) { + Thread.sleep(25); + } - github.getMeta(); + 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)); - } + // 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 dc9764646..2b1540fcf 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 @@ -18,81 +18,69 @@ import org.jvnet.hudson.test.JenkinsRule; 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 7562b73c4..c5b50f679 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 @@ -41,104 +41,94 @@ import org.jvnet.hudson.test.JenkinsRule; 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 2d064e476..2e4945442 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 @@ -36,127 +36,124 @@ 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/"); - } - - @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()); + @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/"); } - 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()); - } - 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(); - } - - 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); - } - @TestExtension - public static class TestRoot implements UnprotectedRootAction { + @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); + } - boolean visited = false; + @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); + } - @Override - public String getIconFileName() { - return null; + @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); } - @Override - public String getDisplayName() { - return null; + private String appendCrumb(String url) { + return url + "&" + getCrumb(); } - @Override - public String getUrlName() { - return "testroot"; + private String getCrumb() { + return Functions.getCrumbRequestField() + "=" + Functions.getCrumb(null); } - public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException { - visited = true; - response.getWriter().println("OK"); + 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); } - static TestRoot get() { - return ExtensionList.lookup(UnprotectedRootAction.class).get(TestRoot.class); + @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"); + } + + 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; + + @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 85994ffd3..1f92a3fa1 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 @@ -48,213 +48,209 @@ public class EventsTest { - /** 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(0); // fire immediately without delay - } - - @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(); - - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdated.json"); - waitAndAssertReceived(true); - } - - @Test - public void given_ghPullRequestEventSync_then_updatedHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedSync.json"); - waitAndAssertReceived(true); - } - - @Test - public void given_ghPullRequestEventConvertedToDraft_then_updatedHeadEventFired() - throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedConvertedToDraft.json"); - waitAndAssertReceived(true); - } - - @Test - public void given_ghPullRequestEventReadyForReview_then_updatedHeadEventFired() throws Exception { - PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); - - firedEventType = SCMEvent.Type.UPDATED; - ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedReadyForReview.json"); - waitAndAssertReceived(true); - } - - @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); - } - - @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); - } - - 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, received ? 20 : 200, TimeUnit.MILLISECONDS); - - if (received) { - TestSCMEventListener.awaitUntilReceived(); + /** 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(0); // fire immediately without delay } - assertEquals( - "Event should have " + ((!received) ? "not " : "") + "been received", - received, - TestSCMEventListener.didReceive()); - } + @Before + public void resetFiredEvent() { + firedEventType = null; + ghEvent = null; + TestSCMEventListener.setReceived(false); + } - @TestExtension - public static class TestSCMEventListener extends jenkins.scm.api.SCMEventListener { + @AfterClass + public static void resetDelay() { + GitHubSCMSource.setEventDelaySeconds(defaultFireDelayInSeconds); + } - private static boolean eventReceived = false; + @Test + public void given_ghPushEventCreated_then_createdHeadEventFired() throws Exception { + PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); - public void onSCMHeadEvent(SCMHeadEvent event) { - receiveEvent(event.getType(), event.getOrigin()); + firedEventType = SCMEvent.Type.CREATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pushEventCreated.json"); + waitAndAssertReceived(true); } - public void onSCMSourceEvent(SCMSourceEvent event) { - receiveEvent(event.getType(), event.getOrigin()); + @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); } - private void receiveEvent(SCMEvent.Type type, String origin) { - eventReceived = true; + @Test + public void given_ghPushEventUpdated_then_updatedHeadEventFired() throws Exception { + PushGHEventSubscriber subscriber = new PushGHEventSubscriber(); - assertEquals("Event type should be the same", type, firedEventType); - assertEquals("Event origin should be the same", origin, ghEvent.getOrigin()); + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pushEventUpdated.json"); + waitAndAssertReceived(true); } - public static boolean didReceive() { - return eventReceived; + @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); } - public static void setReceived(boolean received) { - eventReceived = received; + @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(); + + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdated.json"); + waitAndAssertReceived(true); } - public static void awaitUntilReceived() { - Awaitility.await() - .pollInterval(10, MILLISECONDS) - .atMost(1, MINUTES) - .until(() -> eventReceived); + @Test + public void given_ghPullRequestEventSync_then_updatedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedSync.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPullRequestEventConvertedToDraft_then_updatedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedConvertedToDraft.json"); + waitAndAssertReceived(true); + } + + @Test + public void given_ghPullRequestEventReadyForReview_then_updatedHeadEventFired() throws Exception { + PullRequestGHEventSubscriber subscriber = new PullRequestGHEventSubscriber(); + + firedEventType = SCMEvent.Type.UPDATED; + ghEvent = callOnEvent(subscriber, "EventsTest/pullRequestEventUpdatedReadyForReview.json"); + waitAndAssertReceived(true); + } + + @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); + } + + @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); + } + + 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, received ? 20 : 200, TimeUnit.MILLISECONDS); + + if (received) { + TestSCMEventListener.awaitUntilReceived(); + } + + 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; + } + + public static void awaitUntilReceived() { + Awaitility.await().pollInterval(10, MILLISECONDS).atMost(1, MINUTES).until(() -> eventReceived); + } } - } } 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 1fd2ba058..91ef0c4d5 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 @@ -42,51 +42,50 @@ @For(ForkPullRequestDiscoveryTrait.class) public class ForkPullRequestDiscoveryTrait2Test { - @Rule public JenkinsRule r = new JenkinsRule(); + @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); + @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); - 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(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), false); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), false); + } - @Test - public void configRoundtripWithRawUrl() 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(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), true); - assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), true); - } + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustNobody(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustEveryone(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustContributors(), true); + assertRoundTrip(p, new ForkPullRequestDiscoveryTrait.TrustPermission(), true); + } - private void assertRoundTrip( - WorkflowMultiBranchProject p, - SCMHeadAuthority< - ? super GitHubSCMSourceRequest, - ? extends ChangeRequestSCMHead2, - ? extends SCMRevision> - trust, - boolean configuredByUrl) - throws Exception { + private void assertRoundTrip( + WorkflowMultiBranchProject p, + SCMHeadAuthority + trust, + boolean configuredByUrl) + throws Exception { - GitHubSCMSource s; - if (configuredByUrl) s = new GitHubSCMSource("", "", "https://github.com/nobody/nowhere", true); - else s = new GitHubSCMSource("nobody", "nowhere", null, false); + 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()); - } + 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 86f30bb98..dbf93e8ee 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 @@ -18,118 +18,86 @@ import org.junit.Test; 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 6204e87d6..473b79da5 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 @@ -28,66 +28,64 @@ 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 8840d3a63..a76d1b614 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 @@ -13,33 +13,33 @@ 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 0a353d76f..bba0c496b 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 @@ -37,85 +37,78 @@ 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__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__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__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__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__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__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__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__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)); + } - private final class TestNotificationStrategy extends AbstractGitHubNotificationStrategy { + private final class TestNotificationStrategy extends AbstractGitHubNotificationStrategy { - @Override - public List notifications( - GitHubNotificationContext notificationContext, TaskListener listener) { - return null; - } + @Override + public List notifications( + GitHubNotificationContext notificationContext, TaskListener listener) { + return null; + } - @Override - public boolean equals(Object o) { - return false; - } + @Override + public boolean equals(Object o) { + return false; + } - @Override - public int hashCode() { - return 0; + @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 fcf548fad..61e64bf1f 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 @@ -36,44 +36,38 @@ public class GitHubOrgWebHookTest { - @Rule public JenkinsRule r = new JenkinsRule(); - @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + @Rule + public JenkinsRule r = new JenkinsRule(); - @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 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))); - 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); + // 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); + } } - } } 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 0c7d572aa..98a1bbdbc 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 @@ -52,2905 +52,2611 @@ @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); + @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")); + } + + @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")); } - 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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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 " - + "+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); - } + + @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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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 " + + "+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; } - 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 abfe75032..7e921f0d8 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 @@ -55,228 +55,211 @@ @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 e8c2acc5d..856d087b9 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 @@ -72,476 +72,450 @@ 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")); - - 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_ExcludeForks() - throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits( - Arrays.asList(new TopicsTrait("api"), new ExcludeForkedRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(observer); - - assertEquals(Collections.singleton("yolo-archived"), projectNames); - } - - @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_ExcludingPrivate() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new ExcludePrivateRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); - - assertThat(projectNames, containsInAnyOrder("yolo")); - } - - @Test - public void fetchOneRepo_ExcludingForked() throws Exception { - setCredentials(Collections.singletonList(credentials)); - navigator = navigatorForRepoOwner("stephenc", credentials.getId()); - navigator.setTraits(Collections.singletonList(new ExcludeForkedRepositoriesTrait())); - 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); - - 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); + @Mock + private SCMSourceOwner scmSourceOwner; - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + private BaseStandardCredentials credentials = new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, "authenticated-user", null, "git-user", "git-secret"); - assertThat(projectNames, empty()); - } - - @Test - public void fetchOneRepo_BelongingToUser() throws Exception { - navigator = navigatorForRepoOwner("stephenc", null); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); + private GitHubSCMNavigator navigator; - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + @Before + @Override + public void prepareMockGitHub() { + super.prepareMockGitHub(); + setCredentials(Collections.emptyList()); + navigator = navigatorForRepoOwner("cloudbeers", null); + } - assertThat(projectNames, containsInAnyOrder("yolo-archived")); - } + private GitHubSCMNavigator navigatorForRepoOwner(String repoOwner, @Nullable String credentialsId) { + GitHubSCMNavigator navigator = new GitHubSCMNavigator(repoOwner); + navigator.setApiUri("http://localhost:" + githubApi.port()); + navigator.setCredentialsId(credentialsId); + return navigator; + } - @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); + private void setCredentials(List credentials) { + SystemCredentialsProvider.getInstance() + .setDomainCredentialsMap(Collections.singletonMap(Domain.global(), credentials)); + } - navigator.visitSources(SCMSourceObserver.filter(observer, "yolo-archived")); + @Test + public void fetchSmokes() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - assertThat(projectNames, empty()); - } + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); - @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); + assertThat(projectNames, contains("yolo")); + } - navigator.visitSources(observer); + @Test + public void fetchReposWithoutTeamSlug() throws Exception { + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); - assertThat(projectNames, containsInAnyOrder("yolo", "yolo-archived")); - } + 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")); - @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_BelongingToOrg_ExcludingPrivate() throws Exception { - navigator.setTraits(Collections.singletonList(new ExcludePrivateRepositoriesTrait())); - final Set projectNames = new HashSet<>(); - final SCMSourceObserver observer = getObserver(projectNames); - - navigator.visitSources(SCMSourceObserver.filter(observer, "basic", "advanced", "yolo-private")); - - assertThat(projectNames, containsInAnyOrder("basic", "advanced")); - } - - @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)); + 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_ExcludeForks() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Arrays.asList(new TopicsTrait("api"), new ExcludeForkedRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(observer); + + assertEquals(Collections.singleton("yolo-archived"), projectNames); + } + + @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_ExcludingPrivate() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new ExcludePrivateRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "yolo")); + + assertThat(projectNames, containsInAnyOrder("yolo")); + } + + @Test + public void fetchOneRepo_ExcludingForked() throws Exception { + setCredentials(Collections.singletonList(credentials)); + navigator = navigatorForRepoOwner("stephenc", credentials.getId()); + navigator.setTraits(Collections.singletonList(new ExcludeForkedRepositoriesTrait())); + 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); + + 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(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_BelongingToOrg_ExcludingPrivate() throws Exception { + navigator.setTraits(Collections.singletonList(new ExcludePrivateRepositoriesTrait())); + final Set projectNames = new HashSet<>(); + final SCMSourceObserver observer = getObserver(projectNames); + + navigator.visitSources(SCMSourceObserver.filter(observer, "basic", "advanced", "yolo-private")); + + assertThat(projectNames, containsInAnyOrder("basic", "advanced")); + } + + @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( - "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 {} - }; - } + 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 {} + }; + } } 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 ed7945adc..0caab92bc 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 @@ -31,1274 +31,1206 @@ import org.jvnet.hudson.test.WithoutJenkins; 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( + @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("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") + 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( + 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("*"); - 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/*", "") + 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( + 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("*"); - 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") + 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())); - } + 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 fecc483be..63917dacd 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 @@ -23,183 +23,169 @@ 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); + @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/\"}"))); } - 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")); - } - - @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\""))); + 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")); + } + + @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\""))); + } } - } } 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 669eca15f..284ff6143 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 @@ -7,21 +7,19 @@ public class GitHubSCMSourceHelperTest { - @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_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")); - } + @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 f3a10aaf8..b91212b9f 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 @@ -86,1002 +86,883 @@ @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")) - .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")) - .willSetStateTo("Pull Request Merge Hash - retry 1")); - - 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")) - .willSetStateTo("Pull Request Merge Hash - retry 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")) - .willSetStateTo("Pull Request Merge Hash - retry 2")); - } - - SCMHeadEvent pushEvent = - new SCMHeadEvent( - SCMEvent.Type.CREATED, System.currentTimeMillis(), null, null) { - @Override - public boolean isMatch(@NonNull SCMNavigator scmNavigator) { - return false; + 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")) + .willSetStateTo("Pull Request Merge Hash - retry 1")); + + 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")) + .willSetStateTo("Pull Request Merge Hash - retry 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")) + .willSetStateTo("Pull Request Merge Hash - retry 2")); + } + + SCMHeadEvent pushEvent = + new SCMHeadEvent(SCMEvent.Type.CREATED, System.currentTimeMillis(), null, null) { + @Override + public boolean isMatch(@NonNull SCMNavigator scmNavigator) { + return false; + } + + @NonNull + @Override + public String getSourceName() { + return null; + } + + @NonNull + @Override + public Map heads(@NonNull SCMSource scmSource) { + return null; + } + + @Override + public boolean isMatch(@NonNull SCM scm) { + return false; + } + }; + + SCMHeadEvent pullRequestEvent = + new SCMHeadEvent( + SCMEvent.Type.CREATED, System.currentTimeMillis(), null, null) { + @Override + public boolean isMatch(@NonNull SCMNavigator scmNavigator) { + return false; + } + + @NonNull + @Override + public String getSourceName() { + return null; + } + + @NonNull + @Override + public Map heads(@NonNull SCMSource scmSource) { + return null; + } + + @Override + public boolean isMatch(@NonNull SCM scm) { + return false; + } + }; + + @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("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()); + } + + @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")); - @NonNull - @Override - public String getSourceName() { - return null; + 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")), + "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")); - @NonNull - @Override - public Map heads(@NonNull SCMSource scmSource) { - return null; + 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")) + .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()); } - @Override - public boolean isMatch(@NonNull SCM scm) { - return false; + 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")), + "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; } - }; - - SCMHeadEvent pullRequestEvent = - new SCMHeadEvent( - SCMEvent.Type.CREATED, System.currentTimeMillis(), null, null) { - @Override - public boolean isMatch(@NonNull SCMNavigator scmNavigator) { - return false; + 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")) + .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. - @NonNull - @Override - public String getSourceName() { - return null; + 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"))); + } - @NonNull - @Override - public Map heads(@NonNull SCMSource scmSource) { - return null; + @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")) + .willSetStateTo(Scenario.STARTED)); + + 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")) + .willSetStateTo("Master 200")); + + 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")) + .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")); - @Override - public boolean isMatch(@NonNull SCM scm) { - return false; + // 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; } - }; - - @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("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()); - } - - @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(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"))); } - 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")), - "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; + + @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")), + "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")), + "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"))); + } + + @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()); + } + + @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")), + "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")), + "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); } - 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")) - .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()); + + @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); + } } - 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")), - "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; + @Test + @Issue("JENKINS-68633") + public void doCheckCredentialsId() { + GitHubSCMSource.DescriptorImpl descriptor = (GitHubSCMSource.DescriptorImpl) source.getDescriptor(); + + // If no credentials are supplied, display the warning + FormValidation test = descriptor.doCheckCredentialsId(null, "", "", "", true); + assertThat(test.kind, is(FormValidation.Kind.WARNING)); + assertThat(test.getMessage(), is("Credentials are recommended")); + test = descriptor.doCheckCredentialsId(null, "", "", "", false); + assertThat(test.kind, is(FormValidation.Kind.WARNING)); + assertThat(test.getMessage(), is("Credentials are recommended")); + + // If configureByUrl and credentials provided, always return OK + test = descriptor.doCheckCredentialsId(null, "", "", "test", true); + assertThat(test.kind, is(FormValidation.Kind.OK)); } - 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")) - .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()); + + @Test + @Issue("JENKINS-65071") + public void testCheckIncludesBranchSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); + + assertTrue(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); } - 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; + + @Test + @Issue("JENKINS-65071") + public void testCheckIncludesPullRequestSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new PullRequestSCMHead( + "PR-1", + "rfvm", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + + assertTrue(this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); } - 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")) - .willSetStateTo(Scenario.STARTED)); - - 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")) - .willSetStateTo("Master 200")); - - 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")) - .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()); + + @Test + @Issue("JENKINS-65071") + public void testCheckIncludesGitHubTagSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton(new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); + + assertTrue(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); } - 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; + + @Test + @Issue("JENKINS-65071") + public void testCheckIncludesEmpty() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections.emptySet()); + + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); + } + + @Test + @Issue("JENKINS-65071") + public void testCheckIncludesNull() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(null); + + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); + assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); } - 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()); + + @Test + @Issue("JENKINS-65071") + public void testShouldRetrieveBranchSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); + + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, BranchSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, PullRequestSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, GitHubTagSCMHead.class)); } - 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")), - "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; + @Test + @Issue("JENKINS-65071") + public void testShouldRetrievePullRequestSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn(Collections.singleton(new PullRequestSCMHead( + "PR-1", + "rfvm", + "http://localhost:" + githubApi.port(), + "master", + 1, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE))); + + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, PullRequestSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, BranchSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, GitHubTagSCMHead.class)); } - 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 + @Issue("JENKINS-65071") + public void testShouldRetrieveTagSCMHeadType() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton(new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); + + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, GitHubTagSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, PullRequestSCMHead.class)); + assertFalse(this.source.shouldRetrieve(mockSCMHeadObserver, this.pullRequestEvent, BranchSCMHead.class)); } - 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 + @Issue("JENKINS-65071") + public void testShouldRetrieveNullEvent() throws Exception { + SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); + Mockito.when(mockSCMHeadObserver.getIncludes()) + .thenReturn( + Collections.singleton(new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); + + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, GitHubTagSCMHead.class)); + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, PullRequestSCMHead.class)); + assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, BranchSCMHead.class)); } - 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")), - "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); + + @Test + @Issue("JENKINS-67946") + public void testUserNamesWithAndWithoutUnderscores() { + // https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/managing-iam-for-your-enterprise/username-considerations-for-external-authentication#about-usernames-for-managed-user-accounts + // https://github.com/github/docs/blob/bfe96c289aee3113724495a2e498c21e2ec404e4/content/admin/identity-and-access-management/using-enterprise-managed-users-for-iam/about-enterprise-managed-users.md#about--data-variablesproductprodname_emus- + assertTrue("user_organization".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("username".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user-name".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user-name_organization".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("abcd".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("1234".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user123-org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("123-456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user123_org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user123-org456-code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("user123-org456_code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("abcdefghijqlmnopkrstuvwxyz-123456789012".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("a".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("0".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertTrue("a-b-c-d-e-f-g".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + + // Valid names should contain alphanumeric characters or single hyphens, and cannot begin or end + // with a hyphen, and have a 39 char limit + assertFalse("abcdefghijqlmnopkrstuvwxyz-1234567890123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123@org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123.org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123--org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123-".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("-user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123__org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123_".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("_user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123-_org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123_org456-code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); + assertFalse("user123_org456_code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); } - } - - @Test - @Issue("JENKINS-68633") - public void doCheckCredentialsId() { - GitHubSCMSource.DescriptorImpl descriptor = - (GitHubSCMSource.DescriptorImpl) source.getDescriptor(); - - // If no credentials are supplied, display the warning - FormValidation test = descriptor.doCheckCredentialsId(null, "", "", "", true); - assertThat(test.kind, is(FormValidation.Kind.WARNING)); - assertThat(test.getMessage(), is("Credentials are recommended")); - test = descriptor.doCheckCredentialsId(null, "", "", "", false); - assertThat(test.kind, is(FormValidation.Kind.WARNING)); - assertThat(test.getMessage(), is("Credentials are recommended")); - - // If configureByUrl and credentials provided, always return OK - test = descriptor.doCheckCredentialsId(null, "", "", "test", true); - assertThat(test.kind, is(FormValidation.Kind.OK)); - } - - @Test - @Issue("JENKINS-65071") - public void testCheckIncludesBranchSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); - - assertTrue(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); - assertFalse( - this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testCheckIncludesPullRequestSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn( - Collections.singleton( - new PullRequestSCMHead( - "PR-1", - "rfvm", - "http://localhost:" + githubApi.port(), - "master", - 1, - new BranchSCMHead("master"), - SCMHeadOrigin.DEFAULT, - ChangeRequestCheckoutStrategy.MERGE))); - - assertTrue( - this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testCheckIncludesGitHubTagSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn( - Collections.singleton( - new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); - - assertTrue(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); - assertFalse( - this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testCheckIncludesEmpty() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(Collections.emptySet()); - - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); - assertFalse( - this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testCheckIncludesNull() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()).thenReturn(null); - - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, GitHubTagSCMHead.class)); - assertFalse( - this.source.checkObserverIncludesType(mockSCMHeadObserver, PullRequestSCMHead.class)); - assertFalse(this.source.checkObserverIncludesType(mockSCMHeadObserver, BranchSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testShouldRetrieveBranchSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn(Collections.singleton(new BranchSCMHead("existent-branch"))); - - assertTrue( - this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, BranchSCMHead.class)); - assertFalse( - this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, PullRequestSCMHead.class)); - assertFalse( - this.source.shouldRetrieve(mockSCMHeadObserver, this.pushEvent, GitHubTagSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testShouldRetrievePullRequestSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn( - Collections.singleton( - new PullRequestSCMHead( - "PR-1", - "rfvm", - "http://localhost:" + githubApi.port(), - "master", - 1, - new BranchSCMHead("master"), - SCMHeadOrigin.DEFAULT, - ChangeRequestCheckoutStrategy.MERGE))); - - assertTrue( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, PullRequestSCMHead.class)); - assertFalse( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, BranchSCMHead.class)); - assertFalse( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, GitHubTagSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testShouldRetrieveTagSCMHeadType() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn( - Collections.singleton( - new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); - - assertTrue( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, GitHubTagSCMHead.class)); - assertFalse( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, PullRequestSCMHead.class)); - assertFalse( - this.source.shouldRetrieve( - mockSCMHeadObserver, this.pullRequestEvent, BranchSCMHead.class)); - } - - @Test - @Issue("JENKINS-65071") - public void testShouldRetrieveNullEvent() throws Exception { - SCMHeadObserver mockSCMHeadObserver = Mockito.mock(SCMHeadObserver.class); - Mockito.when(mockSCMHeadObserver.getIncludes()) - .thenReturn( - Collections.singleton( - new GitHubTagSCMHead("non-existent-tag", System.currentTimeMillis()))); - - assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, GitHubTagSCMHead.class)); - assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, PullRequestSCMHead.class)); - assertTrue(this.source.shouldRetrieve(mockSCMHeadObserver, null, BranchSCMHead.class)); - } - - @Test - @Issue("JENKINS-67946") - public void testUserNamesWithAndWithoutUnderscores() { - // https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/managing-iam-for-your-enterprise/username-considerations-for-external-authentication#about-usernames-for-managed-user-accounts - // https://github.com/github/docs/blob/bfe96c289aee3113724495a2e498c21e2ec404e4/content/admin/identity-and-access-management/using-enterprise-managed-users-for-iam/about-enterprise-managed-users.md#about--data-variablesproductprodname_emus- - assertTrue("user_organization".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("username".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user-name".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user-name_organization".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("abcd".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("1234".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user123-org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("123-456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user123_org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user123-org456-code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("user123-org456_code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue( - "abcdefghijqlmnopkrstuvwxyz-123456789012".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("a".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("0".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertTrue("a-b-c-d-e-f-g".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - - // Valid names should contain alphanumeric characters or single hyphens, and cannot begin or end - // with a hyphen, and have a 39 char limit - assertFalse( - "abcdefghijqlmnopkrstuvwxyz-1234567890123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123@org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123.org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123--org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123-".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("-user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123__org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123_".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("_user123".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123-_org456".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123_org456-code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - assertFalse("user123_org456_code789".matches(GitHubSCMSource.VALID_GITHUB_USER_NAME)); - } } 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 cf91cf63c..e6de59ed0 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 @@ -31,1825 +31,1664 @@ import org.jvnet.hudson.test.JenkinsRule; 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"); - String trust; - if (r.jenkins.getPlugin("gitlab-branch-source") != null) { - trust = - "org.jenkinsci.plugins.github_branch_source.ForkPullRequestDiscoveryTrait$TrustPermission"; - } else { - trust = "TrustPermission"; - } - 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$" - + trust - + "()), " - + "@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" - + "::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" - + "::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" - + "::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" - + "::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" - + "::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" - + "::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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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( - 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))))); - } + /** 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"); + String trust; + if (r.jenkins.getPlugin("gitlab-branch-source") != null) { + trust = "org.jenkinsci.plugins.github_branch_source.ForkPullRequestDiscoveryTrait$TrustPermission"; + } else { + trust = "TrustPermission"; + } + 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$" + + trust + + "()), " + + "@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" + + "::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" + + "::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" + + "::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" + + "::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" + + "::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" + + "::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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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))))); + } } 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 b576fb005..481984c2f 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 @@ -9,22 +9,21 @@ 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 a58cb6890..08751db81 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 @@ -15,96 +15,91 @@ public class GithubAppCredentialsAppInstallationTokenTest { - @Test - public void testAppInstallationTokenStale() throws Exception { - - 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(), - closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); - - now = Instant.now().getEpochSecond(); - token = - new GitHubAppCredentials.AppInstallationToken( - secret, now + Duration.ofMinutes(15).getSeconds()); - assertThat(token.isStale(), is(false)); - assertThat( - token.getTokenStaleEpochSeconds(), - closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); - - 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(), - closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); - - 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(), closeTo(now + Duration.ofMinutes(7).getSeconds(), 3)); - - now = Instant.now().getEpochSecond(); - token = - new GitHubAppCredentials.AppInstallationToken( - secret, now + Duration.ofMinutes(90).getSeconds()); - assertThat(token.isStale(), is(false)); - assertThat( - token.getTokenStaleEpochSeconds(), - closeTo(now + GitHubAppCredentials.AppInstallationToken.STALE_AFTER_SECONDS + 1, 3)); - - // TODO use FlagRule - 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(), closeTo(now + 1, 3)); - - // Verify goes stale - Thread.sleep(1000); - assertThat(token.isStale(), is(true)); - } finally { - GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; + @Test + public void testAppInstallationTokenStale() throws Exception { + + 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(), + closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); + + now = Instant.now().getEpochSecond(); + token = new GitHubAppCredentials.AppInstallationToken( + secret, now + Duration.ofMinutes(15).getSeconds()); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); + + 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(), + closeTo(now + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS, 3)); + + 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(), + closeTo(now + Duration.ofMinutes(7).getSeconds(), 3)); + + now = Instant.now().getEpochSecond(); + token = new GitHubAppCredentials.AppInstallationToken( + secret, now + Duration.ofMinutes(90).getSeconds()); + assertThat(token.isStale(), is(false)); + assertThat( + token.getTokenStaleEpochSeconds(), + closeTo(now + GitHubAppCredentials.AppInstallationToken.STALE_AFTER_SECONDS + 1, 3)); + + // TODO use FlagRule + 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(), closeTo(now + 1, 3)); + + // Verify goes stale + Thread.sleep(1000); + assertThat(token.isStale(), is(true)); + } finally { + GitHubAppCredentials.AppInstallationToken.NOT_STALE_MINIMUM_SECONDS = notStaleSeconds; + } + } + + private static Matcher closeTo(long operand, long error) { + BigDecimalCloseTo delegate = new BigDecimalCloseTo(new BigDecimal(operand), new BigDecimal(error)); + return new TypeSafeMatcher(Long.class) { + @Override + protected boolean matchesSafely(Long item) { + return delegate.matches(new BigDecimal(item)); + } + + @Override + protected void describeMismatchSafely(Long item, Description mismatchDescription) { + delegate.describeMismatchSafely(new BigDecimal(item), mismatchDescription); + } + + @Override + public void describeTo(Description description) { + delegate.describeTo(description); + } + }; } - } - - private static Matcher closeTo(long operand, long error) { - BigDecimalCloseTo delegate = - new BigDecimalCloseTo(new BigDecimal(operand), new BigDecimal(error)); - return new TypeSafeMatcher(Long.class) { - @Override - protected boolean matchesSafely(Long item) { - return delegate.matches(new BigDecimal(item)); - } - - @Override - protected void describeMismatchSafely(Long item, Description mismatchDescription) { - delegate.describeMismatchSafely(new BigDecimal(item), mismatchDescription); - } - - @Override - public void describeTo(Description description) { - delegate.describeTo(description); - } - }; - } } 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 ae2e73153..071c164aa 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 @@ -49,541 +49,480 @@ public class GithubAppCredentialsTest extends AbstractGitHubWireMockTest { - private static Slave agent; - private static final String myAppCredentialsId = "myAppCredentialsId"; - private static final String myAppCredentialsNoOwnerId = "myAppCredentialsNoOwnerId"; - private static CredentialsStore store; - private static GitHubAppCredentials appCredentials, appCredentialsNoOwner; - 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(); - - @Rule public BuildWatcher buildWatcher = new BuildWatcher(); - - // here to aid debugging - we can not use LoggerRule for the test assertion as it only captures - // logs from the controller - @ClassRule - public static LoggerRule loggerRule = - new LoggerRule().record(GitHubAppCredentials.class, Level.FINE); - - @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); - appCredentialsNoOwner = - new GitHubAppCredentials( - CredentialsScope.GLOBAL, - myAppCredentialsNoOwnerId, - "sample", - "54321", - Secret.fromString(PKCS8_PRIVATE_KEY)); - store.addCredentials(Domain.global(), appCredentialsNoOwner); - - // Add agent - agent = r.createOnlineSlave(Label.get("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.getRecorders().add(logRecorder); - LogRecorder.Target t = new LogRecorder.Target(GitHubAppCredentials.class.getName(), Level.FINE); - logRecorder.getLoggers().add(t); - logRecorder.save(); - t.enable(); - // but even though we can not capture the logs we want to echo them - r.showAgentLogs(agent, loggerRule); - } - - @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 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 - "}"))); - } - - @Test - public void testProviderRefresh() throws Exception { - final 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(); + private static Slave agent; + private static final String myAppCredentialsId = "myAppCredentialsId"; + private static final String myAppCredentialsNoOwnerId = "myAppCredentialsNoOwnerId"; + private static CredentialsStore store; + private static GitHubAppCredentials appCredentials, appCredentialsNoOwner; + 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(); + + @Rule + public BuildWatcher buildWatcher = new BuildWatcher(); + + // here to aid debugging - we can not use LoggerRule for the test assertion as it only captures + // logs from the controller + @ClassRule + public static LoggerRule loggerRule = new LoggerRule().record(GitHubAppCredentials.class, Level.FINE); + + @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); + appCredentialsNoOwner = new GitHubAppCredentials( + CredentialsScope.GLOBAL, + myAppCredentialsNoOwnerId, + "sample", + "54321", + Secret.fromString(PKCS8_PRIVATE_KEY)); + store.addCredentials(Domain.global(), appCredentialsNoOwner); + + // Add agent + agent = r.createOnlineSlave(Label.get("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.getRecorders().add(logRecorder); + LogRecorder.Target t = new LogRecorder.Target(GitHubAppCredentials.class.getName(), Level.FINE); + logRecorder.getLoggers().add(t); + logRecorder.save(); + t.enable(); + // but even though we can not capture the logs we want to echo them + r.showAgentLogs(agent, loggerRule); } - } - - @Test - public void testAgentRefresh() throws Exception { - final 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, true)); - job.addProperty( - new ParametersDefinitionProperty( - new StringParameterDefinition("REPO", sampleRepo.toString()))); - - WorkflowRun run = job.scheduleBuild2(0).waitForStart(); - r.waitUntilNoActivity(); - - 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( - // node ('my-agent') { - // echo 'First Checkout on agent should use cached token passed via remoting' - // git url: REPO, credentialsId: 'myAppCredentialsId' - "Generating App Installation Token for app ID 54321", - // echo 'Multiple checkouts in quick succession should use cached token' - // git .... - // (No token generation) - // sleep - // echo 'Checkout after token is stale refreshes via remoting - fallback due to - // unexpired token' - // git .... - "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", - "Generating App Installation Token for app ID 54321 for agent", - "Failed to generate new GitHub App Installation Token for app ID 54321 on agent: cached token is stale but has not expired", - // echo 'Checkout after error will refresh again on controller - new token expired - // but not stale' - // git .... - "Generating App Installation Token for app ID 54321", - // sleep - // echo 'Checkout after token is stale refreshes via remoting - error on controller - // is not catastrophic' - // git .... - "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", - "Generating App Installation Token for app ID 54321 for agent", - // echo 'Checkout after error will refresh again on controller - new token expired - // but not stale' - // git .... - "Generating App Installation Token for app ID 54321" - // echo 'Multiple checkouts in quick succession should use cached token' - // git .... - // (No token generation) - )); - - // Check success after output. Output will be more informative if something goes wrong. - assertThat( - "Run should be success, log: " + run.getLog() + System.lineSeparator() + " end of log", - 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(); + + @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 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 + "}"))); + } + + @Test + public void testProviderRefresh() throws Exception { + final 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 { + final 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, true)); + job.addProperty( + new ParametersDefinitionProperty(new StringParameterDefinition("REPO", sampleRepo.toString()))); + + WorkflowRun run = job.scheduleBuild2(0).waitForStart(); + r.waitUntilNoActivity(); + + 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( + // node ('my-agent') { + // echo 'First Checkout on agent should use cached token passed via remoting' + // git url: REPO, credentialsId: 'myAppCredentialsId' + "Generating App Installation Token for app ID 54321", + // echo 'Multiple checkouts in quick succession should use cached token' + // git .... + // (No token generation) + // sleep + // echo 'Checkout after token is stale refreshes via remoting - fallback due to + // unexpired token' + // git .... + "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", + "Generating App Installation Token for app ID 54321 for agent", + "Failed to generate new GitHub App Installation Token for app ID 54321 on agent: cached token is stale but has not expired", + // echo 'Checkout after error will refresh again on controller - new token expired + // but not stale' + // git .... + "Generating App Installation Token for app ID 54321", + // sleep + // echo 'Checkout after token is stale refreshes via remoting - error on controller + // is not catastrophic' + // git .... + "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", + "Generating App Installation Token for app ID 54321 for agent", + // echo 'Checkout after error will refresh again on controller - new token expired + // but not stale' + // git .... + "Generating App Installation Token for app ID 54321" + // echo 'Multiple checkouts in quick succession should use cached token' + // git .... + // (No token generation) + )); + + // Check success after output. Output will be more informative if something goes wrong. + assertThat( + "Run should be success, log: " + run.getLog() + System.lineSeparator() + " end of log", + 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"))); - - // Test credentials when owner is not set - appCredentialsNoOwner.setApiUri(githubApi.baseUrl()); - IllegalArgumentException expected = - assertThrows(IllegalArgumentException.class, () -> appCredentialsNoOwner.getPassword()); - assertThat( - expected.getMessage(), - is( - "Found multiple installations for GitHub app ID 54321 but none match credential owner \"\". " - + "Set the right owner in the credential advanced options")); - } 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"))); + + // Test credentials when owner is not set + appCredentialsNoOwner.setApiUri(githubApi.baseUrl()); + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> appCredentialsNoOwner.getPassword()); + assertThat( + expected.getMessage(), + is("Found multiple installations for GitHub app ID 54321 but none match credential owner \"\". " + + "Set the right owner in the credential advanced options")); + } 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); + } + + // sort the logs into chronological order + // then just format the message. + return result.stream() + .sorted(Comparator.comparingLong(LogRecord::getMillis)) + .map(formatter::formatMessage) + .collect(Collectors.toList()); } - // sort the logs into chronological order - // then just format the message. - return result.stream() - .sorted(Comparator.comparingLong(LogRecord::getMillis)) - .map(formatter::formatMessage) - .collect(Collectors.toList()); - } - - static String printDate(Date dt) { - return DateTimeFormatter.ISO_INSTANT.format( - Instant.ofEpochMilli(dt.getTime()).truncatedTo(ChronoUnit.SECONDS)); - } + 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 88e9a3aa9..87b1d29be 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 @@ -18,186 +18,171 @@ public class GithubSCMSourceBranchesTest extends GitSCMSourceBase { - public GithubSCMSourceBranchesTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); - } + 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()); - } + @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 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 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 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 73793df15..d1c1031ad 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 @@ -17,279 +17,248 @@ public class GithubSCMSourcePRsTest extends GitSCMSourceBase { - public GithubSCMSourcePRsTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); - } + public GithubSCMSourcePRsTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + } - @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()); - } + @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()); + } - // 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()); + // 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()); - } + 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")); + 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()); + // 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(); + @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(); + // 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()); + // 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(); + @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(); + // 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()); + // 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 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()); - } + // 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 d6585a1f1..806812fe8 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 @@ -26,354 +26,329 @@ public class GithubSCMSourceTagsTest extends GitSCMSourceBase { - 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()); + public GithubSCMSourceTagsTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); } - } - - @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 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()); } - 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 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 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()); + } } - } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTraitTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTraitTest.java index 39c132029..245308e1b 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTraitTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/IgnoreDraftPullRequestFilterTraitTest.java @@ -18,61 +18,59 @@ public class IgnoreDraftPullRequestFilterTraitTest extends GitSCMSourceBase { - public IgnoreDraftPullRequestFilterTraitTest() { - this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); - } + public IgnoreDraftPullRequestFilterTraitTest() { + this.source = new GitHubSCMSource("cloudbeers", "yolo", null, false); + } - @Test - public void testTraitFiltersDraft() throws IOException, InterruptedException { - GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); - IgnoreDraftPullRequestFilterTrait instance = new IgnoreDraftPullRequestFilterTrait(); - instance.decorateContext(probe); - List filters = probe.filters(); - assertThat(filters, hasSize(1)); - SCMHeadFilter filter = filters.get(0); - SCMHead scmHead = - new PullRequestSCMHead( - "PR-5", - "cloudbeers", - "http://localhost:" + githubApi.port(), - "feature/5", - 5, - new BranchSCMHead("master"), - SCMHeadOrigin.DEFAULT, - ChangeRequestCheckoutStrategy.MERGE); - GitHubSCMSourceRequest request = new GitHubSCMSourceRequest(source, probe, null); - // Situation: Hitting the Github API for a PR and getting a PR that is a draft - GHPullRequest pullRequest = Mockito.mock(GHPullRequest.class); - Mockito.when(pullRequest.getNumber()).thenReturn(5); - Mockito.when(pullRequest.isDraft()).thenReturn(true); - request.setPullRequests(Collections.singleton(pullRequest)); - assertThat(filter.isExcluded(request, scmHead), equalTo(true)); - } + @Test + public void testTraitFiltersDraft() throws IOException, InterruptedException { + GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); + IgnoreDraftPullRequestFilterTrait instance = new IgnoreDraftPullRequestFilterTrait(); + instance.decorateContext(probe); + List filters = probe.filters(); + assertThat(filters, hasSize(1)); + SCMHeadFilter filter = filters.get(0); + SCMHead scmHead = new PullRequestSCMHead( + "PR-5", + "cloudbeers", + "http://localhost:" + githubApi.port(), + "feature/5", + 5, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE); + GitHubSCMSourceRequest request = new GitHubSCMSourceRequest(source, probe, null); + // Situation: Hitting the Github API for a PR and getting a PR that is a draft + GHPullRequest pullRequest = Mockito.mock(GHPullRequest.class); + Mockito.when(pullRequest.getNumber()).thenReturn(5); + Mockito.when(pullRequest.isDraft()).thenReturn(true); + request.setPullRequests(Collections.singleton(pullRequest)); + assertThat(filter.isExcluded(request, scmHead), equalTo(true)); + } - @Test - public void testTraitDoesNotFilterNonDraft() throws IOException, InterruptedException { - GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); - IgnoreDraftPullRequestFilterTrait instance = new IgnoreDraftPullRequestFilterTrait(); - instance.decorateContext(probe); - List filters = probe.filters(); - assertThat(filters, hasSize(1)); - SCMHeadFilter filter = filters.get(0); - SCMHead scmHead = - new PullRequestSCMHead( - "PR-5", - "cloudbeers", - "http://localhost:" + githubApi.port(), - "feature/5", - 5, - new BranchSCMHead("master"), - SCMHeadOrigin.DEFAULT, - ChangeRequestCheckoutStrategy.MERGE); - GitHubSCMSourceRequest request = new GitHubSCMSourceRequest(source, probe, null); - // Situation: Hitting the Github API for a PR and getting a PR that is not a draft - GHPullRequest pullRequest = Mockito.mock(GHPullRequest.class); - Mockito.when(pullRequest.getNumber()).thenReturn(5); - Mockito.when(pullRequest.isDraft()).thenReturn(false); - request.setPullRequests(Collections.singleton(pullRequest)); - assertThat(filter.isExcluded(request, scmHead), equalTo(false)); - } + @Test + public void testTraitDoesNotFilterNonDraft() throws IOException, InterruptedException { + GitHubSCMSourceContext probe = new GitHubSCMSourceContext(null, SCMHeadObserver.collect()); + IgnoreDraftPullRequestFilterTrait instance = new IgnoreDraftPullRequestFilterTrait(); + instance.decorateContext(probe); + List filters = probe.filters(); + assertThat(filters, hasSize(1)); + SCMHeadFilter filter = filters.get(0); + SCMHead scmHead = new PullRequestSCMHead( + "PR-5", + "cloudbeers", + "http://localhost:" + githubApi.port(), + "feature/5", + 5, + new BranchSCMHead("master"), + SCMHeadOrigin.DEFAULT, + ChangeRequestCheckoutStrategy.MERGE); + GitHubSCMSourceRequest request = new GitHubSCMSourceRequest(source, probe, null); + // Situation: Hitting the Github API for a PR and getting a PR that is not a draft + GHPullRequest pullRequest = Mockito.mock(GHPullRequest.class); + Mockito.when(pullRequest.getNumber()).thenReturn(5); + Mockito.when(pullRequest.isDraft()).thenReturn(false); + request.setPullRequests(Collections.singleton(pullRequest)); + assertThat(filter.isExcluded(request, scmHead), equalTo(false)); + } } 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 0ae939045..356e142f1 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 @@ -17,119 +17,95 @@ import org.junit.Test; 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 3ba6df1c3..49448a40a 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 @@ -44,340 +44,254 @@ import org.kohsuke.github.GitHub; 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"); - } - - 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()); + 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"); } - // equivalence - assertTrue( - currentRevision.equivalent( - new PullRequestSCMRevision( - currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse( - currentRevision.equivalent( - new PullRequestSCMRevision( + 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( - 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()); + 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)))); } - // equivalence - assertTrue( - currentRevision.equivalent( - new PullRequestSCMRevision( - currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse( - currentRevision.equivalent( - new PullRequestSCMRevision( + @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)))); - } - - @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()); + 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)))); } - // equivalence - assertTrue( - currentRevision.equivalent( - new PullRequestSCMRevision( - currentHead, "master-revision-changed", "pr-branch-revision", "any"))); - assertFalse( - currentRevision.equivalent( - new PullRequestSCMRevision( + @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)))); - } - - @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; + 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(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( + + @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()); + 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( + @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)))); - } + 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 faeea1c74..4d601d656 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 @@ -21,126 +21,114 @@ import org.jvnet.hudson.test.MockFolder; 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_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() 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_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__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 790e1487b..e4e52e83c 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 @@ -19,80 +19,73 @@ import org.jvnet.hudson.test.JenkinsRule; 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() 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_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 1f7d2024e..48c485dee 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 @@ -35,30 +35,31 @@ 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")); + } } - } }