Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import hudson.scm.SCM;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
Expand All @@ -53,11 +54,14 @@
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy;
import jenkins.scm.api.trait.SCMHeadPrefilter;
import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
import org.jenkinsci.plugins.github.extension.GHSubscriberEvent;
import org.kohsuke.github.GHEvent;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;

Expand Down Expand Up @@ -175,6 +179,11 @@
&& repoOwner.equalsIgnoreCase(((GitHubSCMNavigator) navigator).getRepoOwner());
}

@Override
public boolean isMatch(@NonNull SCM scm) {
return false;

Check warning on line 184 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 184 is not covered by tests
}

/** {@inheritDoc} */
@Override
public String descriptionFor(@NonNull SCMNavigator navigator) {
Expand Down Expand Up @@ -253,18 +262,22 @@
}

/*
* What we are looking for is to return the BranchSCMHead for this push
* What we are looking for is to return the BranchSCMHead for this push and also any
* PullRequestSCMHead instances that target this branch with MERGE strategy.
*
* 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
* For source branch changes, the PullRequest:synchronize event will handle those updates.
* However, for target branch changes with MERGE strategy, we need to trigger PR builds here
* because the merge result has changed even though the source branch hasn't.
*/

GitHubSCMSourceContext context =
new GitHubSCMSourceContext(null, SCMHeadObserver.none()).withTraits(src.getTraits());
String ref = push.getRef();
Map<SCMHead, SCMRevision> result = new HashMap<>();

if (context.wantBranches() && !ref.startsWith(R_TAGS)) {
// we only want the branch details if the branch is actually built!
BranchSCMHead head;
Expand All @@ -282,8 +295,13 @@
}
}
if (!excluded) {
return Collections.singletonMap(
head, new AbstractGitSCMSource.SCMRevisionImpl(head, push.getHead()));
result.put(head, new AbstractGitSCMSource.SCMRevisionImpl(head, push.getHead()));
}

// Query for PRs targeting this branch with MERGE strategy
// Only query for PRs on UPDATED events, not CREATED or REMOVED
if (getType() == Type.UPDATED) {
addPullRequestsTargetingBranch(result, source, context, head.getName(), push.getHead());
}
}
if (context.wantTags() && ref.startsWith(R_TAGS)) {
Expand Down Expand Up @@ -321,16 +339,173 @@
}
}
if (!excluded) {
return Collections.singletonMap(head, new GitTagSCMRevision(head, push.getHead()));
result.put(head, new GitTagSCMRevision(head, push.getHead()));
}
}
return Collections.emptyMap();
return result;
}

/** {@inheritDoc} */
@Override
public boolean isMatch(@NonNull SCM scm) {
return false;
/**
* Query GitHub API for open PRs targeting the specified branch and add them to the result
* if they use MERGE strategy.
*
* @param result the map to add PR heads to
* @param source the SCM source
* @param context the context with trait configuration
* @param branchName the target branch name
* @param branchHash the current hash of the target branch
*/
private void addPullRequestsTargetingBranch(
Map<SCMHead, SCMRevision> result,
SCMSource source,
GitHubSCMSourceContext context,
String branchName,
String branchHash) {

// Only query for PRs if PR discovery is enabled
if (!context.wantPRs()) {
return;
}

// Check if MERGE strategy is enabled for either origin or fork PRs
boolean wantOriginMerge = context.wantOriginPRs()

Check warning on line 371 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 371 is only partially covered, one branch is missing
&& context.originPRStrategies().contains(ChangeRequestCheckoutStrategy.MERGE);
boolean wantForkMerge =
context.wantForkPRs() && context.forkPRStrategies().contains(ChangeRequestCheckoutStrategy.MERGE);

Check warning on line 374 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 374 is only partially covered, one branch is missing

if (!wantOriginMerge && !wantForkMerge) {

Check warning on line 376 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 376 is only partially covered, one branch is missing
// No MERGE strategies enabled, nothing to do
return;
}

GitHubSCMSource src = (GitHubSCMSource) source;
GitHub github = null;
try {
LOGGER.log(Level.FINE, "Querying for open PRs targeting branch {0} in {1}/{2}", new Object[] {
branchName, repoOwner, repository
});

// Get a fresh GitHub connection using the source's credentials and API URI
// This ensures tests using WireMock work correctly
com.cloudbees.plugins.credentials.common.StandardCredentials credentials =
Connector.lookupScanCredentials(
(Item) src.getOwner(), src.getApiUri(), src.getCredentialsId(), repoOwner);
github = Connector.connect(src.getApiUri(), credentials);

// Get the repository using the proper connection
GHRepository ghRepo = github.getRepository(repoOwner + "/" + this.repository);

// Query GitHub for open PRs targeting this branch
Iterable<GHPullRequest> pullRequests = ghRepo.queryPullRequests()
.state(GHIssueState.OPEN)
.base(branchName)
.list();

int prCount = 0;
for (GHPullRequest pr : pullRequests) {
prCount++;
try {
// Validate the PR data
if (!pr.getBase().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) {

Check warning on line 409 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 409 is only partially covered, one branch is missing
LOGGER.log(Level.WARNING, "Skipping PR #{0} with invalid base SHA", pr.getNumber());
continue;

Check warning on line 411 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 410-411 are not covered by tests
}
if (!pr.getHead().getSha().matches(GitHubSCMSource.VALID_GIT_SHA1)) {

Check warning on line 413 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 413 is only partially covered, one branch is missing
LOGGER.log(Level.WARNING, "Skipping PR #{0} with invalid head SHA", pr.getNumber());
continue;

Check warning on line 415 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 414-415 are not covered by tests
}

// Determine if this is a fork PR
GHRepository headRepo = pr.getHead().getRepository();
if (headRepo == null) {

Check warning on line 420 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 420 is only partially covered, one branch is missing
LOGGER.log(Level.FINE, "Skipping PR #{0} with deleted fork", pr.getNumber());
continue;

Check warning on line 422 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 421-422 are not covered by tests
}

String prHeadOwner = headRepo.getOwnerName();
boolean fork = !repoOwner.equalsIgnoreCase(prHeadOwner);

// Check if MERGE strategy is wanted for this PR type
Set<ChangeRequestCheckoutStrategy> strategies =
fork ? context.forkPRStrategies() : context.originPRStrategies();

if (!strategies.contains(ChangeRequestCheckoutStrategy.MERGE)) {

Check warning on line 432 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 432 is only partially covered, one branch is missing
// MERGE strategy not enabled for this PR type
continue;

Check warning on line 434 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 434 is not covered by tests
}

// Determine the branch name for the PR head
final String prBranchName;
if (strategies.size() == 1) {

Check warning on line 439 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 439 is only partially covered, one branch is missing
prBranchName = "PR-" + pr.getNumber();

Check warning on line 440 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 440 is not covered by tests
} else {
prBranchName = "PR-" + pr.getNumber() + "-merge";
}

// Create the PullRequestSCMHead for MERGE strategy
PullRequestSCMHead head = new PullRequestSCMHead(
prBranchName,
prHeadOwner,
headRepo.getName(),
pr.getHead().getRef(),
pr.getNumber(),
new BranchSCMHead(pr.getBase().getRef()),
fork
? new jenkins.scm.api.SCMHeadOrigin.Fork(prHeadOwner)
: jenkins.scm.api.SCMHeadOrigin.DEFAULT,
ChangeRequestCheckoutStrategy.MERGE);

// Check if the head is excluded by pre-filters
boolean excluded = false;
for (SCMHeadPrefilter prefilter : context.prefilters()) {

Check warning on line 460 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 460 is only partially covered, one branch is missing
if (prefilter.isExcluded(source, head)) {
excluded = true;
break;
}
}

Check warning on line 465 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 461-465 are not covered by tests

if (!excluded) {

Check warning on line 467 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 467 is only partially covered, one branch is missing
// Create revision with current base and head hashes
// For MERGE strategy, we don't provide the merge hash in the event
// (it will be fetched later during the actual build)
PullRequestSCMRevision revision = new PullRequestSCMRevision(
head,
branchHash, // Use the updated target branch hash
pr.getHead().getSha());
result.put(head, revision);

LOGGER.log(
Level.FINE,
"Added PR #{0} ({1}) targeting {2} for rebuild due to target branch update",
new Object[] {pr.getNumber(), prBranchName, branchName});
}
} catch (Exception e) {
// Log warning but continue processing other PRs
LOGGER.log(
Level.WARNING,
"Failed to process PR #" + pr.getNumber() + " targeting branch " + branchName,

Check warning on line 486 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 482-486 are not covered by tests
e);
}
}

if (prCount > 0) {

Check warning on line 491 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 491 is only partially covered, one branch is missing
LOGGER.log(
Level.FINE, "Found {0} open PR(s) targeting branch {1}", new Object[] {prCount, branchName
});
}
} catch (Exception e) {
// Log warning but don't fail the entire event
LOGGER.log(
Level.WARNING,
"Failed to query PRs targeting branch " + branchName
+ " in repository " + repoOwner + "/" + repository
+ ". PR builds may not be triggered for target branch updates.",
e);
} finally {
if (github != null) {

Check warning on line 505 in src/main/java/org/jenkinsci/plugins/github_branch_source/PushGHEventSubscriber.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 505 is only partially covered, one branch is missing
Connector.release(github);
}
}
}
}
}
Loading
Loading