Skip to content

Commit 155cd5f

Browse files
committed
refactor(retest): acknowledge successful reruns and simplify head matching and test fixtures
1 parent 6e1dfd5 commit 155cd5f

26 files changed

Lines changed: 526 additions & 1546 deletions

README.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ features: [ RETEST_PULL_REQUEST_WORKFLOWS ]
252252
----
253253

254254
Only failed jobs from the latest run of each workflow are retriggered. If the latest run is still in progress, the bot asks you to retry once it completes.
255+
When reruns are started, the bot posts a confirmation comment linking to the workflow runs.
255256

256257
This feature requires:
257258

src/main/java/io/quarkus/bot/ApproveWorkflow.java

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.quarkus.bot.config.Feature;
2222
import io.quarkus.bot.config.QuarkusGitHubBotConfig;
2323
import io.quarkus.bot.config.QuarkusGitHubBotConfigFile;
24+
import io.quarkus.bot.util.GHPullRequests;
2425
import io.quarkus.bot.util.PullRequestFilesMatcher;
2526
import io.quarkus.cache.CacheKey;
2627
import io.quarkus.cache.CacheResult;
@@ -90,36 +91,19 @@ private void checkUser(GHEventPayload.WorkflowRun workflowPayload, QuarkusGitHub
9091
}
9192

9293
private void checkFiles(QuarkusGitHubBotConfigFile quarkusBotConfigFile, GHWorkflowRun workflowRun,
93-
ApprovalStatus approval) {
94+
ApprovalStatus approval) throws IOException {
9495
String sha = workflowRun.getHeadSha();
9596

96-
// Now we want to get the pull request we're supposed to be checking.
97-
// It would be nice to use commit.listPullRequests() but that only returns something if the
98-
// base and head of the PR are from the same repository, which rules out most scenarios where we would want to do an approval
99-
100-
String fullyQualifiedBranchName = workflowRun.getHeadRepository().getOwnerName() + ":" + workflowRun.getHeadBranch();
101-
102-
PagedIterable<GHPullRequest> pullRequestsForThisBranch = workflowRun.getRepository().queryPullRequests()
103-
.head(fullyQualifiedBranchName)
104-
.list();
105-
106-
// The number of PRs with matching branch name should be exactly one, but if the PR
107-
// has been closed it sometimes disappears from the list; also, if two branch names
108-
// start with the same string, both will turn up in the query.
109-
for (GHPullRequest pullRequest : pullRequestsForThisBranch) {
110-
111-
// Only look at PRs whose commit sha matches
112-
if (sha.equals(pullRequest.getHead().getSha())) {
113-
114-
for (QuarkusGitHubBotConfigFile.WorkflowApprovalRule rule : quarkusBotConfigFile.workflows.rules) {
115-
// We allow if the files or directories match the allow rule ...
116-
if (matchRuleFromChangedFiles(pullRequest, rule.allow)) {
117-
approval.shouldApprove = true;
118-
}
119-
// ... unless we also match the unless rule
120-
if (matchRuleFromChangedFiles(pullRequest, rule.unless)) {
121-
approval.shouldNotApprove = true;
122-
}
97+
for (GHPullRequest pullRequest : GHPullRequests.matchingHeadPullRequests(workflowRun.getRepository(),
98+
workflowRun.getHeadRepository(), workflowRun.getHeadBranch(), sha)) {
99+
for (QuarkusGitHubBotConfigFile.WorkflowApprovalRule rule : quarkusBotConfigFile.workflows.rules) {
100+
// We allow if the files or directories match the allow rule ...
101+
if (matchRuleFromChangedFiles(pullRequest, rule.allow)) {
102+
approval.shouldApprove = true;
103+
}
104+
// ... unless we also match the unless rule
105+
if (matchRuleFromChangedFiles(pullRequest, rule.unless)) {
106+
approval.shouldNotApprove = true;
123107
}
124108
}
125109
}

src/main/java/io/quarkus/bot/retest/GitHubFailedJobsRerunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void rerunFailedJobs(GHEventPayload.IssueComment issueCommentPayload, GHW
6666
}
6767
}
6868

69-
URI rerunFailedJobsUri(GHEventPayload.IssueComment issueCommentPayload, GHWorkflowRun workflowRun) {
69+
private URI rerunFailedJobsUri(GHEventPayload.IssueComment issueCommentPayload, GHWorkflowRun workflowRun) {
7070
String normalizedApiUrl = normalizeApiUrl(gitHubApiUrl(issueCommentPayload));
7171

7272
// TODO switch to GHWorkflowRun.rerunFailedJobs() when Hub4j adds native support for rerunning failed jobs.

src/main/java/io/quarkus/bot/retest/RetestCli.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66

77
import io.quarkiverse.githubapp.ConfigFile;
88
import io.quarkiverse.githubapp.command.airline.CliOptions;
9-
import io.quarkiverse.githubapp.command.airline.CommandOptions;
10-
import io.quarkiverse.githubapp.command.airline.CommandOptions.ReactionStrategy;
9+
import io.quarkiverse.githubapp.command.airline.CliOptions.ParseErrorStrategy;
1110
import io.quarkus.bot.config.QuarkusGitHubBotConfigFile;
1211

1312
/**
1413
* Command-airline entry point for comment commands handled by the bot.
1514
*/
1615
@Cli(name = "@quarkusbot", commands = RetestCommand.class)
1716
@CliOptions(aliases = {
18-
"@quarkus-bot" }, parseErrorHandler = RetestParseErrorHandler.class, defaultCommandOptions = @CommandOptions(reactionStrategy = ReactionStrategy.NONE))
17+
"@quarkus-bot" }, parseErrorStrategy = ParseErrorStrategy.NONE)
1918
class RetestCli {
2019
}
2120

src/main/java/io/quarkus/bot/retest/RetestCommand.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.quarkus.bot.retest;
22

33
import java.io.IOException;
4+
import java.net.URL;
45
import java.util.ArrayList;
56
import java.util.List;
7+
import java.util.stream.Collectors;
68

79
import jakarta.inject.Inject;
810

@@ -23,6 +25,7 @@
2325
import io.quarkus.bot.config.Feature;
2426
import io.quarkus.bot.config.QuarkusGitHubBotConfig;
2527
import io.quarkus.bot.config.QuarkusGitHubBotConfigFile;
28+
import io.quarkus.bot.service.GHIssueCommentService;
2629

2730
/**
2831
* Handles {@code @quarkusbot retest} comments on pull requests.
@@ -43,6 +46,9 @@ class RetestCommand implements RetestCommandHandler {
4346
@Inject
4447
FailedJobsRerunner failedJobsRerunner;
4548

49+
@Inject
50+
GHIssueCommentService issueCommentService;
51+
4652
@Override
4753
public void run(QuarkusGitHubBotConfigFile quarkusBotConfigFile, GHEventPayload.IssueComment issueCommentPayload) {
4854
if (!Feature.RETEST_PULL_REQUEST_WORKFLOWS.isEnabled(quarkusBotConfigFile)) {
@@ -56,11 +62,11 @@ public void run(QuarkusGitHubBotConfigFile quarkusBotConfigFile, GHEventPayload.
5662

5763
RetestWorkflowSelection workflowSelection = getWorkflowSelection(pullRequest);
5864

59-
if (!workflowSelection.hasEligibleRuns()) {
65+
if (workflowSelection.eligibleRuns().isEmpty()) {
6066
throw RetestCommandException.noEligibleWorkflowRuns(workflowSelection.noEligibleReason());
6167
}
6268

63-
List<Long> startedWorkflowRunIds = new ArrayList<>();
69+
List<GHWorkflowRun> startedWorkflowRuns = new ArrayList<>();
6470
for (GHWorkflowRun workflowRun : workflowSelection.eligibleRuns()) {
6571
if (quarkusBotConfig.isDryRun()) {
6672
LOG.infof("Pull request #%d - Retest failed jobs for workflow run #%d (dry-run)",
@@ -70,15 +76,21 @@ public void run(QuarkusGitHubBotConfigFile quarkusBotConfigFile, GHEventPayload.
7076

7177
try {
7278
failedJobsRerunner.rerunFailedJobs(issueCommentPayload, workflowRun);
73-
startedWorkflowRunIds.add(workflowRun.getId());
79+
startedWorkflowRuns.add(workflowRun);
7480
} catch (RuntimeException e) {
75-
if (startedWorkflowRunIds.isEmpty()) {
81+
if (startedWorkflowRuns.isEmpty()) {
7682
throw e;
7783
}
7884

79-
throw RetestCommandException.partialRerunFailure(startedWorkflowRunIds, workflowRun.getId(), e);
85+
throw RetestCommandException.partialRerunFailure(
86+
startedWorkflowRuns.stream().map(GHWorkflowRun::getId).toList(), workflowRun.getId(), e);
8087
}
8188
}
89+
90+
if (!startedWorkflowRuns.isEmpty()) {
91+
issueCommentService.addComment(issueCommentPayload.getIssue(), successMessage(startedWorkflowRuns), false,
92+
quarkusBotConfig.isDryRun());
93+
}
8294
}
8395

8496
private static GHPullRequest getPullRequest(GHEventPayload.IssueComment issueCommentPayload) {
@@ -97,4 +109,27 @@ private RetestWorkflowSelection getWorkflowSelection(GHPullRequest pullRequest)
97109
throw RetestCommandException.unableToInspectWorkflowRuns(e);
98110
}
99111
}
112+
113+
private static String successMessage(List<GHWorkflowRun> startedWorkflowRuns) {
114+
String workflowRunLinks = startedWorkflowRuns.stream()
115+
.map(RetestCommand::workflowRunReference)
116+
.collect(Collectors.joining(", "));
117+
String label = startedWorkflowRuns.size() == 1 ? "workflow run " : "workflow runs ";
118+
return ":arrows_counterclockwise: Retest started for failed jobs in " + label + workflowRunLinks + ".";
119+
}
120+
121+
private static String workflowRunReference(GHWorkflowRun workflowRun) {
122+
String label = "#" + workflowRun.getId();
123+
URL htmlUrl;
124+
try {
125+
htmlUrl = workflowRun.getHtmlUrl();
126+
} catch (IOException e) {
127+
return label;
128+
}
129+
if (htmlUrl == null) {
130+
return label;
131+
}
132+
133+
return "[" + label + "](" + htmlUrl + ")";
134+
}
100135
}

src/main/java/io/quarkus/bot/retest/RetestCommandException.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ static RetestCommandException featureDisabled() {
2828
return new RetestCommandException(":warning: Pull request workflow retest is disabled for this repository.");
2929
}
3030

31-
static RetestCommandException ambiguousPullRequestHead() {
32-
return new RetestCommandException(
33-
":warning: Multiple pull requests share the same head branch and commit, so retest was skipped.");
34-
}
35-
3631
static RetestCommandException pullRequestNotOpen() {
3732
return new RetestCommandException(":warning: Retest is only available on open pull requests.");
3833
}

src/main/java/io/quarkus/bot/retest/RetestCommentFormatter.java

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/main/java/io/quarkus/bot/retest/RetestExecutionErrorHandler.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,18 @@
33
import jakarta.inject.Inject;
44
import jakarta.inject.Singleton;
55

6-
import org.jboss.logging.Logger;
76
import org.kohsuke.github.GHEventPayload;
87

98
import io.quarkiverse.githubapp.command.airline.ExecutionErrorHandler;
10-
import io.quarkus.bot.GHIssueCommentService;
119
import io.quarkus.bot.config.QuarkusGitHubBotConfig;
10+
import io.quarkus.bot.service.GHIssueCommentService;
1211

1312
/**
1413
* Formats command failures as pull-request comments.
1514
*/
1615
@Singleton
1716
class RetestExecutionErrorHandler implements ExecutionErrorHandler {
1817

19-
private static final Logger LOG = Logger.getLogger(RetestExecutionErrorHandler.class);
20-
2118
@Inject
2219
QuarkusGitHubBotConfig quarkusBotConfig;
2320

@@ -34,20 +31,14 @@ public void handleExecutionError(GHEventPayload.IssueComment issueCommentPayload
3431

3532
String commandLine = executionErrorContext.commandExecutionContext().getCommandLine();
3633
String message = formatMessage(commandLine, executionErrorContext.exception());
37-
String location = issueCommentPayload.getRepository().getFullName() + "#" + issueCommentPayload.getIssue().getNumber();
38-
39-
try {
40-
issueCommentService.addCommentOrThrow(issueCommentPayload.getIssue(), message, false, quarkusBotConfig.isDryRun());
41-
} catch (Exception e) {
42-
LOG.warn("Error trying to add retest execution error comment for command in " + location, e);
43-
}
34+
issueCommentService.addComment(issueCommentPayload.getIssue(), message, false, quarkusBotConfig.isDryRun());
4435
}
4536

46-
static String formatMessage(String commandLine, Exception exception) {
47-
String userMessage = exception instanceof RetestCommandException retestCommandException
48-
? retestCommandException.userMessage()
49-
: ":rotating_light: An error occurred while executing the command.";
37+
private static String formatMessage(String commandLine, Exception exception) {
38+
if (exception instanceof RetestCommandException retestCommandException) {
39+
return retestCommandException.userMessage();
40+
}
5041

51-
return RetestCommentFormatter.formatCommandMessage(commandLine, userMessage);
42+
return ":rotating_light: An error occurred while executing `" + commandLine + "`.";
5243
}
5344
}

0 commit comments

Comments
 (0)