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 ac1c480fc..2944cf869 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 @@ -2581,28 +2581,60 @@ public void observe(GHPullRequest pr) { // looked up this user already user = users.get(login); } + // Try to get user metadata, but be tolerant to API failures + String userName = login; + String userEmail = login + "@users.noreply.github.com"; + try { + userName = user.getName(); + if (userName == null || userName.isEmpty()) { + userName = login; + } + userEmail = user.getEmail(); + if (userEmail == null || userEmail.isEmpty()) { + userEmail = login + "@users.noreply.github.com"; + } + } catch (FileNotFoundException e) { + // User metadata not accessible (e.g., bot users like Copilot) + // Log warning but continue with default values + request.listener() + .getLogger() + .format( + "%n Could not find user metadata for %s (PR #%d). Using default values.%n", + login, number); + } catch (IOException e) { + // Other IO errors, use default values but log + request.listener() + .getLogger() + .format( + "%n IO error fetching user metadata for %s (PR #%d): %s. Using default values.%n", + login, number, e.getMessage()); + } ContributorMetadataAction contributor = - new ContributorMetadataAction(login, user.getName(), user.getEmail()); + new ContributorMetadataAction(login, userName, userEmail); // store the populated user record now that we have it pullRequestContributorCache.put(number, contributor); users.put(login, user); } + // Store PR metadata + pullRequestMetadataCache.put( + number, + new ObjectMetadataAction( + pr.getTitle(), pr.getBody(), pr.getHtmlUrl().toExternalForm())); + pullRequestMetadataKeys.add(number); + } catch (FileNotFoundException e) { + // PR user not found at all - log and skip this PR 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); + .format("%n Could not find user for pull request #%d. Skipping PR.%n", number); + // Don't throw exception - just skip this PR and continue scanning } catch (IOException e) { - throw new WrappedException(e); + // Other IO errors when getting PR user - log and skip this PR + request.listener() + .getLogger() + .format("%n IO error for pull request #%d: %s. Skipping PR.%n", number, e.getMessage()); + // Don't throw exception - just skip this PR and continue scanning } - - pullRequestMetadataCache.put( - number, - new ObjectMetadataAction( - pr.getTitle(), pr.getBody(), pr.getHtmlUrl().toExternalForm())); - pullRequestMetadataKeys.add(number); } @Override 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 77666a076..81e2d97c2 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 @@ -411,10 +411,10 @@ public boolean isHead(@NonNull Probe probe, @NonNull TaskListener listener) thro 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")); + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "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. + // PR-2 fails to find user metadata but is handled gracefully. + // The PR is included with default user values instead of being skipped. assertThat(byName.get("PR-3"), instanceOf(PullRequestSCMHead.class)); assertThat(((SCMHeadOrigin.Fork) byName.get("PR-3").getOrigin()).getName(), is("stephenc")); 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 d1c1031ad..e801c8ec3 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 @@ -153,15 +153,11 @@ public void testOpenSinglePRThrowsFileNotFoundOnObserve() throws Exception { .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 + // Expected: In the iterator will have one item and it will be successfully retrieved + // with default user values since user metadata is not available assertTrue(pullRequestIterator.hasNext()); - try { - pullRequestIterator.next(); - fail(); - } catch (Exception e) { - assertEquals("java.io.FileNotFoundException: User not found", e.getMessage()); - } + GHPullRequest pr = pullRequestIterator.next(); + assertNotNull(pr); } @Test @@ -197,15 +193,11 @@ public void testOpenSinglePRThrowsIOOnObserve() throws Exception { .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 + // Expected: In the iterator will have one item and it will be successfully retrieved + // with default user values since user metadata retrieval fails assertTrue(pullRequestIterator.hasNext()); - try { - pullRequestIterator.next(); - fail(); - } catch (Exception e) { - assertEquals("java.io.IOException: Failed to get user", e.getMessage()); - } + GHPullRequest pr = pullRequestIterator.next(); + assertNotNull(pr); } // Multiple PRs