Skip to content

Commit 830dab9

Browse files
committed
[JENKINS-47213] Opening a PR when the branch is still building with "Exclude branches that are also filed as PRs" leads to a never ending build in BitBucket
Bitbucket enhance its build status API with a refname (Cloud) and ref (Server) property to indicate the owner's git reference. This can be used as a sort of filter in case multiple build statuses have been associated with the same commit. For example, this means that when browsing commits from the webpage, you will see all the build statuses associated with a commit (no scope); but, you should only see one when you land on the pull request webpage (scoped). NOTE: At the time of implementation once a build status is created, it cannot be deleted via the API. If refname is set, it cannot be set to null. The property appears to participate in some way in the primary key of the build status, multiple build statuses with the same key but different refnames are allowed, especially in the use case the update APIs return an HTTP 500 error. Available documentations: * https://developer.atlassian.com/server/bitbucket/rest/v903/api-group-builds-and-deployments/#api-api-latest-projects-projectkey-repos-repositoryslug-commits-commitid-builds-post * https://developer.atlassian.com/cloud/bitbucket/rest/api-group-commit-statuses/#api-repositories-workspace-repo-slug-commit-commit-statuses-build-post does not explain precisly how this property should be valued. This force me to set the property to null in case of pull request, otherwise, the build status disappear from the webpage and branch restriction and merge conditions will not work.
1 parent 3331511 commit 830dab9

File tree

5 files changed

+160
-9
lines changed

5 files changed

+160
-9
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient;
3030
import edu.umd.cs.findbugs.annotations.CheckForNull;
3131
import edu.umd.cs.findbugs.annotations.NonNull;
32+
import edu.umd.cs.findbugs.annotations.Nullable;
3233
import hudson.Extension;
3334
import hudson.FilePath;
3435
import hudson.model.Result;
@@ -44,6 +45,7 @@
4445
import java.net.URL;
4546
import jenkins.model.JenkinsLocationConfiguration;
4647
import jenkins.plugins.git.AbstractGitSCMSource;
48+
import jenkins.scm.api.SCMHead;
4749
import jenkins.scm.api.SCMHeadObserver;
4850
import jenkins.scm.api.SCMRevision;
4951
import jenkins.scm.api.SCMRevisionAction;
@@ -96,9 +98,12 @@ static String checkURL(@NonNull String url, BitbucketApi bitbucket) {
9698
}
9799
}
98100

99-
private static void createStatus(@NonNull Run<?, ?> build, @NonNull TaskListener listener,
100-
@NonNull BitbucketApi bitbucket, @NonNull String key, @NonNull String hash)
101-
throws IOException, InterruptedException {
101+
private static void createStatus(@NonNull Run<?, ?> build,
102+
@NonNull TaskListener listener,
103+
@NonNull BitbucketApi bitbucket,
104+
@NonNull String key,
105+
@NonNull String hash,
106+
@Nullable String refName) throws IOException, InterruptedException {
102107

103108
final SCMSource source = SCMSource.SourceByItem.findSource(build.getParent());
104109
if (!(source instanceof BitbucketSCMSource)) {
@@ -161,7 +166,11 @@ private static void createStatus(@NonNull Run<?, ?> build, @NonNull TaskListener
161166

162167
if (state != null) {
163168
BitbucketChangesetCommentNotifier notifier = new BitbucketChangesetCommentNotifier(bitbucket);
164-
notifier.buildStatus(new BitbucketBuildStatus(hash, statusDescription, state, url, key, name));
169+
BitbucketBuildStatus buildStatus = new BitbucketBuildStatus(hash, statusDescription, state, url, key, name, refName);
170+
buildStatus.setBuildDuration(build.getDuration());
171+
buildStatus.setBuildNumber(build.getNumber());
172+
// TODO testResults should be provided by an extension point that integrates JUnit or anything else plugin
173+
notifier.buildStatus(buildStatus);
165174
if (result != null) {
166175
listener.getLogger().println("[Bitbucket] Build result notified");
167176
}
@@ -196,18 +205,32 @@ private static void sendNotifications(BitbucketSCMSource source, Run<?, ?> build
196205
.anyMatch(ExcludeOriginPRBranchesSCMHeadFilter.class::isInstance);
197206

198207
String key;
208+
String refName;
199209
BitbucketApi bitbucket;
200210
if (rev instanceof PullRequestSCMRevision) {
201211
listener.getLogger().println("[Bitbucket] Notifying pull request build result");
202212
PullRequestSCMHead head = (PullRequestSCMHead) rev.getHead();
203213
key = getBuildKey(build, head.getOriginName(), shareBuildKeyBetweenBranchAndPR);
214+
/*
215+
* in case of pull request it's not clear at all how to value refname. The
216+
* bitbucket documentation does not help and using values like
217+
* - refs/heads/PR-748;
218+
* - refs/pull-requests/748,
219+
* - refs/pull-requests/feature/test;
220+
* causes the build status disappear from the UI page.
221+
* The only working value is null. If commit is used for two different
222+
* pull requests than you will get double status in both PRs
223+
*/
224+
refName = null; // "refs/pull-requests/" + head.getBranchName();
204225
bitbucket = source.buildBitbucketClient(head);
205226
} else {
206227
listener.getLogger().println("[Bitbucket] Notifying commit build result");
207-
key = getBuildKey(build, rev.getHead().getName(), shareBuildKeyBetweenBranchAndPR);
228+
SCMHead head = rev.getHead();
229+
key = getBuildKey(build, head.getName(), shareBuildKeyBetweenBranchAndPR);
230+
refName = "refs/heads/" + head.getName();
208231
bitbucket = source.buildBitbucketClient();
209232
}
210-
createStatus(build, listener, bitbucket, key, hash);
233+
createStatus(build, listener, bitbucket, key, hash, refName);
211234
}
212235

213236
@CheckForNull

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.fasterxml.jackson.annotation.JsonIgnore;
2727
import edu.umd.cs.findbugs.annotations.NonNull;
28+
import edu.umd.cs.findbugs.annotations.Nullable;
2829
import org.apache.commons.codec.digest.DigestUtils;
2930
import org.kohsuke.accmod.Restricted;
3031
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -87,17 +88,39 @@ public String toString() {
8788
*/
8889
private String name;
8990

91+
/**
92+
* The fully qualified git reference e.g. refs/heads/master.
93+
*/
94+
private String refname;
95+
96+
/**
97+
* Duration of a completed build in milliseconds.
98+
*/
99+
private long buildDuration;
100+
101+
/**
102+
* A unique identifier of this particular run.
103+
*/
104+
private int buildNumber;
105+
90106
// Used for marshalling/unmarshalling
91107
@Restricted(DoNotUse.class)
92108
public BitbucketBuildStatus() {}
93109

94-
public BitbucketBuildStatus(String hash, String description, Status state, String url, String key, String name) {
110+
public BitbucketBuildStatus(String hash,
111+
String description,
112+
@NonNull Status state,
113+
String url,
114+
@NonNull String key,
115+
String name,
116+
@Nullable String refname) {
95117
this.hash = hash;
96118
this.description = description;
97119
this.state = state;
98120
this.url = url;
99121
this.key = DigestUtils.md5Hex(key);
100122
this.name = name;
123+
this.refname = refname;
101124
}
102125

103126
/**
@@ -112,6 +135,8 @@ public BitbucketBuildStatus(@NonNull BitbucketBuildStatus other) {
112135
this.url = other.url;
113136
this.key = other.key;
114137
this.name = other.name;
138+
this.refname = other.refname;
139+
this.buildDuration = other.buildDuration;
115140
}
116141

117142
public String getHash() {
@@ -162,4 +187,28 @@ public void setName(String name) {
162187
this.name = name;
163188
}
164189

190+
public String getRefname() {
191+
return refname;
192+
}
193+
194+
public void setRefname(String refname) {
195+
this.refname = refname;
196+
}
197+
198+
public long getBuildDuration() {
199+
return buildDuration;
200+
}
201+
202+
public void setBuildDuration(long buildDuration) {
203+
this.buildDuration = buildDuration;
204+
}
205+
206+
public int getBuildNumber() {
207+
return buildNumber;
208+
}
209+
210+
public void setBuildNumber(int buildNumber) {
211+
this.buildNumber = buildNumber;
212+
}
213+
165214
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.cloudbees.jenkins.plugins.bitbucket.server.BitbucketServerWebhookImplementation;
4848
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch;
4949
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranches;
50+
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBuildStatus;
5051
import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit;
5152
import com.cloudbees.jenkins.plugins.bitbucket.server.client.mirror.BitbucketMirrorServerDescriptors;
5253
import com.cloudbees.jenkins.plugins.bitbucket.server.client.mirror.BitbucketMirroredRepositoryDescriptors;
@@ -144,7 +145,7 @@ public class BitbucketServerAPIClient extends AbstractBitbucketApi implements Bi
144145
private static final String WEBHOOK_REPOSITORY_PATH = WEBHOOK_BASE_PATH + "/projects/{owner}/repos/{repo}/configurations";
145146
private static final String WEBHOOK_REPOSITORY_CONFIG_PATH = WEBHOOK_REPOSITORY_PATH + "/{id}";
146147

147-
private static final String API_COMMIT_STATUS_PATH = "/rest/build-status/1.0/commits{/hash}";
148+
private static final String API_COMMIT_STATUS_PATH = API_BASE_PATH + "/projects/{owner}/repos/{repo}/commits/{hash}/builds";
148149

149150
private static final String API_MIRRORS_FOR_REPO_PATH = "/rest/mirroring/1.0/repos/{id}/mirrors";
150151
private static final String API_MIRRORS_PATH = "/rest/mirroring/1.0/mirrorServers";
@@ -509,10 +510,12 @@ public void postCommitComment(@NonNull String hash, @NonNull String comment) thr
509510
*/
510511
@Override
511512
public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException, InterruptedException {
512-
BitbucketBuildStatus newStatus = new BitbucketBuildStatus(status);
513+
BitbucketServerBuildStatus newStatus = new BitbucketServerBuildStatus(status);
513514
newStatus.setName(truncateMiddle(newStatus.getName(), 255));
514515

515516
String url = UriTemplate.fromTemplate(API_COMMIT_STATUS_PATH)
517+
.set("owner", getUserCentricOwner())
518+
.set("repo", repositoryName)
516519
.set("hash", newStatus.getHash())
517520
.expand();
518521
postRequest(url, JsonParser.toJson(newStatus));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.cloudbees.jenkins.plugins.bitbucket.server.client.branch;
2+
3+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
public class BitbucketServerBuildStatus extends BitbucketBuildStatus {
7+
8+
/**
9+
* Copy constructor.
10+
*
11+
* @param other from copy to.
12+
*/
13+
public BitbucketServerBuildStatus(BitbucketBuildStatus other) {
14+
super(other);
15+
}
16+
17+
@JsonProperty("ref")
18+
@Override
19+
public String getRefname() {
20+
return super.getRefname();
21+
}
22+
23+
@JsonProperty("duration")
24+
@Override
25+
public long getBuildDuration() {
26+
return super.getBuildDuration();
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"key": "da005fd0512173736e591039b282053a",
3+
"type": "build",
4+
"state": "FAILED",
5+
"name": "test » BUILD test (#748) #1",
6+
"refname": null,
7+
"commit": {
8+
"hash": "046d9a3c1532acf4cf08fe93235c00e4d673c1d3",
9+
"links": {
10+
"self": {
11+
"href": "https://api.bitbucket.org/2.0/repositories/amuniz/test-repos/commit/046d9a3c1532acf4cf08fe93235c00e4d673c1d3"
12+
},
13+
"html": {
14+
"href": "https://bitbucket.org/amuniz/test-repos/commits/046d9a3c1532acf4cf08fe93235c00e4d673c1d3"
15+
}
16+
},
17+
"type": "commit"
18+
},
19+
"url": "https://localhost/job/test/job/PR-748/1/display/redirect",
20+
"repository": {
21+
"type": "repository",
22+
"full_name": "amuniz/test-repos",
23+
"links": {
24+
"self": {
25+
"href": "https://api.bitbucket.org/2.0/repositories/amuniz/test-repos"
26+
},
27+
"html": {
28+
"href": "https://bitbucket.org/amuniz/test-repos"
29+
},
30+
"avatar": {
31+
"href": "https://bytebucket.org/ravatar/%7Bf991126e-6825-40ea-a1c2-e72c336ae41e%7D?ts=java"
32+
}
33+
},
34+
"name": "test-repos",
35+
"uuid": "{f991126e-6825-40ea-a1c2-e72c336ae41e}"
36+
},
37+
"description": "There was a failure building this commit.",
38+
"created_on": "2024-11-30T19:04:49.002285+00:00",
39+
"updated_on": "2024-12-01T14:58:03.086428+00:00",
40+
"links": {
41+
"self": {
42+
"href": "https://api.bitbucket.org/2.0/repositories/amuniz/test-repos/commit/046d9a3c1532acf4cf08fe93235c00e4d673c1d3/statuses/build/da005fd0512173736e591039b282053a"
43+
},
44+
"commit": {
45+
"href": "https://api.bitbucket.org/2.0/repositories/amuniz/test-repos/commit/046d9a3c1532acf4cf08fe93235c00e4d673c1d3"
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)