Skip to content

Commit c218eef

Browse files
proskijakub-bochenski
authored andcommitted
Clean up exception handling in StashApiClient
Stop using RuntimeException, it's an unchecked exception. The callers may be unaware that they need to handle it. Stop using Exception. Always use most specific exception types. Always rethrow caught exceptions by wrapping them in StashApiClient. Let the callers handle them, as they are in a better position to chose what to do and emit meaningful logs. Don't print stack trace in StashApiClient. Print it when the exception is handled. Some callers log the stack trace to the build log, not to the global Jenkins log. Don't log error messages and throw StashApiException at the same time, unless the log message contains information that is not added to the exception. Adjust "throws" declarations. Catch errors where appropriate and log them. Document functions that throw StashApiException. Add comments explaining how StashApiException is handled. Adjust unit tests for the new behavior.
1 parent bfc9bb4 commit c218eef

File tree

6 files changed

+366
-119
lines changed

6 files changed

+366
-119
lines changed

src/main/java/stashpullrequestbuilder/stashpullrequestbuilder/StashBuildListener.java

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package stashpullrequestbuilder.stashpullrequestbuilder;
22

3+
import static java.lang.String.format;
4+
35
import hudson.Extension;
46
import hudson.Util;
57
import hudson.model.AbstractBuild;
@@ -10,11 +12,11 @@
1012
import java.io.IOException;
1113
import java.io.PrintStream;
1214
import java.lang.invoke.MethodHandles;
13-
import java.util.logging.Level;
1415
import java.util.logging.Logger;
1516
import javax.annotation.Nonnull;
1617
import jenkins.model.JenkinsLocationConfiguration;
1718
import jenkins.model.ParameterizedJobMixIn;
19+
import stashpullrequestbuilder.stashpullrequestbuilder.stash.StashApiClient.StashApiException;
1820

1921
/** Created by Nathan McCarthy */
2022
@Extension
@@ -42,6 +44,8 @@ public void onStarted(Run<?, ?> run, TaskListener listener) {
4244

4345
@Override
4446
public void onCompleted(Run<?, ?> run, @Nonnull TaskListener listener) {
47+
PrintStream buildLogger = listener.getLogger();
48+
4549
StashCause cause = run.getCause(StashCause.class);
4650
if (cause == null) {
4751
return;
@@ -65,7 +69,19 @@ public void onCompleted(Run<?, ?> run, @Nonnull TaskListener listener) {
6569
} else {
6670
buildUrl = rootUrl + run.getUrl();
6771
}
68-
repository.deletePullRequestComment(cause.getPullRequestId(), cause.getBuildStartCommentId());
72+
73+
// Delete the "Build Started" comment, as it gets replaced with a comment
74+
// about the build result. Failure to delete the comment is not fatal, it's
75+
// reported to the build log.
76+
try {
77+
repository.deletePullRequestComment(cause.getPullRequestId(), cause.getBuildStartCommentId());
78+
} catch (StashApiException e) {
79+
buildLogger.println(
80+
format(
81+
"%s: cannot delete Build Start comment for pull request %s",
82+
run.getParent().getDisplayName(), cause.getPullRequestId()));
83+
e.printStackTrace(buildLogger);
84+
}
6985

7086
String additionalComment = "";
7187
StashPostBuildComment comments = null;
@@ -103,31 +119,30 @@ public void onCompleted(Run<?, ?> run, @Nonnull TaskListener listener) {
103119
additionalComment,
104120
duration);
105121

106-
// Merge PR
122+
// Request the server to merge the pull request on success if that option is
123+
// enabled. Log the result to the build log. Handle exceptions here, report
124+
// them to the build log as well.
107125
if (trigger.getMergeOnSuccess() && run.getResult() == Result.SUCCESS) {
108-
boolean mergeStat =
109-
repository.mergePullRequest(cause.getPullRequestId(), cause.getPullRequestVersion());
110-
if (mergeStat == true) {
111-
String logmsg =
112-
"Merged pull request "
113-
+ cause.getPullRequestId()
114-
+ "("
115-
+ cause.getSourceBranch()
116-
+ ") to branch "
117-
+ cause.getTargetBranch();
118-
logger.log(Level.INFO, logmsg);
119-
listener.getLogger().println(logmsg);
120-
} else {
121-
String logmsg =
122-
"Failed to merge pull request "
123-
+ cause.getPullRequestId()
124-
+ "("
125-
+ cause.getSourceBranch()
126-
+ ") to branch "
127-
+ cause.getTargetBranch()
128-
+ " because it's out of date";
129-
logger.log(Level.INFO, logmsg);
130-
listener.getLogger().println(logmsg);
126+
try {
127+
boolean mergeStat =
128+
repository.mergePullRequest(cause.getPullRequestId(), cause.getPullRequestVersion());
129+
if (mergeStat == true) {
130+
buildLogger.println(
131+
format(
132+
"Successfully merged pull request %s (%s) to branch %s",
133+
cause.getPullRequestId(), cause.getSourceBranch(), cause.getTargetBranch()));
134+
} else {
135+
buildLogger.println(
136+
format(
137+
"Failed to merge pull request %s (%s) to branch %s, it may be out of date",
138+
cause.getPullRequestId(), cause.getSourceBranch(), cause.getTargetBranch()));
139+
}
140+
} catch (StashApiException e) {
141+
buildLogger.println(
142+
format(
143+
"Failed to merge pull request %s (%s) to branch %s",
144+
cause.getPullRequestId(), cause.getSourceBranch(), cause.getTargetBranch()));
145+
e.printStackTrace(buildLogger);
131146
}
132147
}
133148
}

src/main/java/stashpullrequestbuilder/stashpullrequestbuilder/StashRepository.java

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public Collection<StashPullRequestResponseValue> getTargetPullRequests() {
9494
List<StashPullRequestResponseValue> targetPullRequests =
9595
new ArrayList<StashPullRequestResponseValue>();
9696

97+
// Fetch "OPEN" pull requests from the server. Failure to get the list will
98+
// prevent builds from being scheduled. However, the call will be retried
99+
// during the next cycle, as determined by the cron settings.
97100
List<StashPullRequestResponseValue> pullRequests;
98101
try {
99102
pullRequests = client.getPullRequests();
@@ -110,7 +113,15 @@ public Collection<StashPullRequestResponseValue> getTargetPullRequests() {
110113
return targetPullRequests;
111114
}
112115

113-
public String postBuildStartCommentTo(StashPullRequestResponseValue pullRequest) {
116+
/**
117+
* Post "BuildStarted" comment to Bitbucket Server
118+
*
119+
* @param pullRequest pull request
120+
* @return comment ID
121+
* @throws StashApiException if posting the comment fails
122+
*/
123+
public String postBuildStartCommentTo(StashPullRequestResponseValue pullRequest)
124+
throws StashApiException {
114125
String sourceCommit = pullRequest.getFromRef().getLatestCommit();
115126
String destinationCommit = pullRequest.getToRef().getLatestCommit();
116127
String comment =
@@ -269,6 +280,9 @@ public void addFutureBuildTasks(Collection<StashPullRequestResponseValue> pullRe
269280
for (StashPullRequestResponseValue pullRequest : pullRequests) {
270281
Map<String, String> additionalParameters;
271282

283+
// Get parameter values for the build from the pull request comments.
284+
// Failure to do so causes the build to be skipped, as we would not run
285+
// the build with incorrect parameters.
272286
try {
273287
additionalParameters = getAdditionalParameters(pullRequest);
274288
} catch (StashApiException e) {
@@ -281,6 +295,8 @@ public void addFutureBuildTasks(Collection<StashPullRequestResponseValue> pullRe
281295
continue;
282296
}
283297

298+
// Delete comments about previous build results, if that option is
299+
// enabled. Run the build even if those comments cannot be deleted.
284300
if (trigger.getDeletePreviousBuildFinishComments()) {
285301
try {
286302
deletePreviousBuildFinishedComments(pullRequest);
@@ -294,7 +310,25 @@ public void addFutureBuildTasks(Collection<StashPullRequestResponseValue> pullRe
294310
}
295311
}
296312

297-
String commentId = postBuildStartCommentTo(pullRequest);
313+
// Post a comment indicating the build start. Strictly speaking, we are
314+
// just adding the build to the queue, it will start after the quiet time
315+
// expires and there are executors available. Failure to post the comment
316+
// prevents the build for safety reasons. If the plugin cannot post this
317+
// comment, chances are it won't be able to post the build results, which
318+
// would trigger the build again and again, wasting Jenkins resources.
319+
String commentId;
320+
try {
321+
commentId = postBuildStartCommentTo(pullRequest);
322+
} catch (StashApiException e) {
323+
logger.log(
324+
Level.INFO,
325+
format(
326+
"%s: cannot post Build Start comment for pull request %s, not building",
327+
job.getName(), pullRequest.getId()),
328+
e);
329+
continue;
330+
}
331+
298332
StashCause cause =
299333
new StashCause(
300334
trigger.getStashHost(),
@@ -315,7 +349,15 @@ public void addFutureBuildTasks(Collection<StashPullRequestResponseValue> pullRe
315349
}
316350
}
317351

318-
public void deletePullRequestComment(String pullRequestId, String commentId) {
352+
/**
353+
* Deletes pull request comment from Bitbucket Server
354+
*
355+
* @param pullRequestId pull request ID
356+
* @param commentId comment to be deleted
357+
* @throws StashApiException if deleting the comment fails
358+
*/
359+
public void deletePullRequestComment(String pullRequestId, String commentId)
360+
throws StashApiException {
319361
this.client.deletePullRequestComment(pullRequestId, commentId);
320362
}
321363

@@ -359,14 +401,41 @@ public void postFinishedComment(
359401

360402
comment = comment.concat(additionalComment);
361403

362-
this.client.postPullRequestComment(pullRequestId, comment);
404+
// Post the "Build Finished" comment. Failure to post it can lead to
405+
// scheduling another build for the pull request unnecessarily.
406+
try {
407+
this.client.postPullRequestComment(pullRequestId, comment);
408+
} catch (StashApiException e) {
409+
logger.log(
410+
Level.WARNING,
411+
format(
412+
"%s: cannot post Build Finished comment for pull request %s",
413+
job.getDisplayName(), pullRequestId),
414+
e);
415+
}
363416
}
364417

365-
public boolean mergePullRequest(String pullRequestId, String version) {
418+
/**
419+
* Instructs Bitbucket Server to merge pull request
420+
*
421+
* @param pullRequestId pull request ID
422+
* @param version pull request version
423+
* @return true if the merge succeeds, false if the server reports an error
424+
* @throws StashApiException if cannot communicate to the server
425+
*/
426+
public boolean mergePullRequest(String pullRequestId, String version) throws StashApiException {
366427
return this.client.mergePullRequest(pullRequestId, version);
367428
}
368429

369-
private boolean isPullRequestMergeable(StashPullRequestResponseValue pullRequest) {
430+
/**
431+
* Inquiries Bitbucket Server whether the pull request can be merged
432+
*
433+
* @param pullRequest pull request
434+
* @return true if the merge is allowed, false otherwise
435+
* @throws StashApiException if cannot communicate to the server
436+
*/
437+
private boolean isPullRequestMergeable(StashPullRequestResponseValue pullRequest)
438+
throws StashApiException {
370439
if (trigger.getCheckMergeable()
371440
|| trigger.getCheckNotConflicted()
372441
|| trigger.getCheckProbeMergeStatus()) {
@@ -454,8 +523,21 @@ private boolean isBuildTarget(StashPullRequestResponseValue pullRequest) {
454523
return false;
455524
}
456525

457-
if (!isPullRequestMergeable(pullRequest)) {
458-
logger.info("Skipping PR: " + pullRequest.getId() + " as cannot be merged");
526+
// Check whether the pull request can be merged and whether it's in the
527+
// "conflicted" state. If that information cannot be retrieved, don't build
528+
// the pull request in this cycle.
529+
try {
530+
if (!isPullRequestMergeable(pullRequest)) {
531+
logger.info("Skipping PR: " + pullRequest.getId() + " as cannot be merged");
532+
return false;
533+
}
534+
} catch (StashApiException e) {
535+
logger.log(
536+
Level.INFO,
537+
format(
538+
"%s: cannot determine if pull request %s can be merged, skipping",
539+
job.getDisplayName(), pullRequest.getId()),
540+
e);
459541
return false;
460542
}
461543

@@ -473,6 +555,10 @@ private boolean isBuildTarget(StashPullRequestResponseValue pullRequest) {
473555
String destinationCommit = destination.getLatestCommit();
474556

475557
String id = pullRequest.getId();
558+
559+
// Fetch all comments for the pull request. If it fails, don't build the
560+
// pull request in this cycle, as it cannot be determined if it should be
561+
// built without checking the comments.
476562
List<StashPullRequestComment> comments;
477563
try {
478564
comments = client.getPullRequestComments(owner, repositoryName, id);

0 commit comments

Comments
 (0)