diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java index 883c95178..1eb72f804 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java @@ -26,6 +26,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait.ExcludeOriginPRBranchesSCMHeadFilter; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses; import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; @@ -42,12 +43,15 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.List; +import java.util.regex.Pattern; import jenkins.model.JenkinsLocationConfiguration; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMRevisionAction; import jenkins.scm.api.SCMSource; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; @@ -62,6 +66,7 @@ public class BitbucketBuildStatusNotifications { private static final String FAILED_STATE = "FAILED"; private static final String STOPPED_STATE = "STOPPED"; private static final String INPROGRESS_STATE = "INPROGRESS"; + private static final Pattern PR_BUILD_PATTERN = Pattern.compile("^.*/job/PR-(\\d+)/(\\d+)/.*$"); private static String getRootURL(@NonNull Run build) { JenkinsLocationConfiguration cfg = JenkinsLocationConfiguration.get(); @@ -157,7 +162,7 @@ private static void createStatus(@NonNull Run build, @NonNull TaskListener statusDescription = StringUtils.defaultIfBlank(buildDescription, "The build is in progress..."); state = INPROGRESS_STATE; } - status = new BitbucketBuildStatus(hash, statusDescription, state, url, key, name); + status = new BitbucketBuildStatus(hash, statusDescription, state, url, DigestUtils.md5Hex(key), name); new BitbucketChangesetCommentNotifier(bitbucket).buildStatus(status); if (result != null) { listener.getLogger().println("[Bitbucket] Build result notified"); @@ -184,6 +189,7 @@ private static void sendNotifications(BitbucketSCMSource source, Run build if (hash == null) { return; } + boolean shareBuildKeyBetweenBranchAndPR = sourceContext .filters().stream() .anyMatch(filter -> filter instanceof ExcludeOriginPRBranchesSCMHeadFilter); @@ -199,6 +205,15 @@ private static void sendNotifications(BitbucketSCMSource source, Run build listener.getLogger().println("[Bitbucket] Notifying commit build result"); key = getBuildKey(build, r.getHead().getName(), shareBuildKeyBetweenBranchAndPR); bitbucket = source.buildBitbucketClient(); + if (sourceContext.doNotOverridePrBuildStatuses()) { + List buildStatuses = bitbucket.getBuildStatus(hash).getValues(); + for (final BitbucketBuildStatus bs : buildStatuses) { + if (DigestUtils.md5Hex(key).equals(bs.getKey()) && PR_BUILD_PATTERN.matcher(bs.getUrl()).matches()) { + listener.getLogger().println("[Bitbucket] Skip branch notification as this revision has PR build status"); + return; + } + } + } } createStatus(build, listener, bitbucket, key, hash); } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait.java index 0f93f3b1d..e7004e278 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait.java @@ -45,6 +45,9 @@ public class BitbucketBuildStatusNotificationsTrait extends SCMSourceTrait { */ private boolean sendSuccessNotificationForUnstableBuild; + + private boolean doNotOverridePrBuildStatuses; + /** * Constructor. * @@ -61,6 +64,11 @@ public void setSendSuccessNotificationForUnstableBuild(boolean isSendSuccess) { sendSuccessNotificationForUnstableBuild = isSendSuccess; } + @DataBoundSetter + public void setDoNotOverridePrBuildStatuses(boolean isDoNotOverridePrBuildStatuses) { + doNotOverridePrBuildStatuses = isDoNotOverridePrBuildStatuses; + } + /** * @return if unstable builds will be communicated as successful */ @@ -68,12 +76,17 @@ public boolean getSendSuccessNotificationForUnstableBuild() { return this.sendSuccessNotificationForUnstableBuild; } + public boolean getDoNotOverridePrBuildStatuses() { + return this.doNotOverridePrBuildStatuses; + } + /** * {@inheritDoc} */ @Override protected void decorateContext(SCMSourceContext context) { ((BitbucketSCMSourceContext) context).withSendSuccessNotificationForUnstableBuild(getSendSuccessNotificationForUnstableBuild()); + ((BitbucketSCMSourceContext) context).withDoNotOverridePrBuildStatuses(getDoNotOverridePrBuildStatuses()); } /** diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceContext.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceContext.java index c0985d26a..0a56aee96 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceContext.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSourceContext.java @@ -91,6 +91,8 @@ public class BitbucketSCMSourceContext extends SCMSourceContext getRepositories(@CheckForNull UserRoleInRepo */ void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOException, InterruptedException; + /** + * Set the build status for the given commit hash. + * + * @param hash get the status object for specified key + * @throws IOException if there was a network communications error. + * @throws InterruptedException if interrupted while waiting on remote communications. + */ + BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException, InterruptedException; + /** * Returns {@code true} if and only if the repository is private. * diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java index 8b30ae923..69b5bc56b 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatus.java @@ -24,7 +24,6 @@ package com.cloudbees.jenkins.plugins.bitbucket.api; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.apache.commons.codec.digest.DigestUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; @@ -71,7 +70,7 @@ public BitbucketBuildStatus(String hash, String description, String state, Strin this.description = description; this.state = state; this.url = url; - this.key = DigestUtils.md5Hex(key); + this.key = key; this.name = name; } @@ -112,7 +111,7 @@ public String getKey() { } public void setKey(String key) { - this.key = DigestUtils.md5Hex(key); + this.key = key; } public String getName() { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatuses.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatuses.java new file mode 100644 index 000000000..fb3797dc1 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketBuildStatuses.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket.api; + +import java.util.Collections; +import java.util.List; + +public class BitbucketBuildStatuses { + private List values; + + public List getValues() { + return values == null ? Collections.emptyList() : Collections.unmodifiableList(values); + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java index b16860d15..ed82c8c22 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java @@ -28,6 +28,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCloudWorkspace; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketException; @@ -648,6 +649,21 @@ public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOExcep postRequest(url, JsonParser.toJson(status)); } + @Override + public BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException, InterruptedException { + String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/commit/{hash}/statuses/build") + .set("owner", owner) + .set("repo", repositoryName) + .set("hash", hash) + .expand(); + String response = getRequest(url); + try { + return JsonParser.toJava(response, BitbucketBuildStatuses.class); + } catch (IOException e) { + throw new IOException("I/O error when accessing URL: " + url, e); + } + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java index 07ce17aa3..cb27ed1d0 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java @@ -27,6 +27,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketAuthenticator; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatuses; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; @@ -510,6 +511,22 @@ public void postBuildStatus(@NonNull BitbucketBuildStatus status) throws IOExcep ); } + /** + * {@inheritDoc} + */ + public BitbucketBuildStatuses getBuildStatus(@NonNull String hash) throws IOException { + String url = UriTemplate + .fromTemplate(API_COMMIT_STATUS_PATH) + .set("hash", hash) + .expand(); + String response = getRequest(url); + try { + return JsonParser.toJava(response, BitbucketBuildStatuses.class); + } catch (IOException e) { + throw new IOException("I/O error when accessing URL: " + url, e); + } + } + /** * {@inheritDoc} */ diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait/config.jelly index 4b2283124..beb44eca0 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotificationsTrait/config.jelly @@ -3,4 +3,7 @@ - \ No newline at end of file + + + +