diff options
11 files changed, 106 insertions, 110 deletions
@@ -25,6 +25,8 @@ Creating a Job - ex: * * * * * - In Bitbucket BasicAuth Username, write your bitbucket username like jenkins@densan-labs.net - In Bitbucket BasicAuth Password, write your password +- In CI Identifier, enter an unique identifier among your Jenkins jobs related to the repo +- In CI Name, enter a human readable name for your Jenkins server - Write RepositoryOwner - Write RepositoryName - Save to preserve your changes @@ -8,7 +8,7 @@ <artifactId>bitbucket-pullrequest-builder</artifactId> <name>Bitbucket Pullrequest Builder Plugin</name> - <version>1.4.8-SNAPSHOT</version> + <version>1.4.9-SNAPSHOT</version> <description>This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results.</description> <packaging>hpi</packaging> <url>https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin</url> diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java index 09802af..664c6d8 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java @@ -36,6 +36,8 @@ public class BitbucketBuildTrigger extends Trigger<AbstractProject<?, ?>> { private final String password; private final String repositoryOwner; private final String repositoryName; + private final String ciKey; + private final String ciName; private final String ciSkipPhrases; private final boolean checkDestinationCommit; private final boolean approveIfSuccess; @@ -54,6 +56,8 @@ public class BitbucketBuildTrigger extends Trigger<AbstractProject<?, ?>> { String password, String repositoryOwner, String repositoryName, + String ciKey, + String ciName, String ciSkipPhrases, boolean checkDestinationCommit, boolean approveIfSuccess @@ -66,6 +70,8 @@ public class BitbucketBuildTrigger extends Trigger<AbstractProject<?, ?>> { this.password = password; this.repositoryOwner = repositoryOwner; this.repositoryName = repositoryName; + this.ciKey = ciKey; + this.ciName = ciName; this.ciSkipPhrases = ciSkipPhrases; this.checkDestinationCommit = checkDestinationCommit; this.approveIfSuccess = approveIfSuccess; @@ -99,6 +105,14 @@ public class BitbucketBuildTrigger extends Trigger<AbstractProject<?, ?>> { return repositoryName; } + public String getCiKey() { + return ciKey; + } + + public String getCiName() { + return ciName; + } + public String getCiSkipPhrases() { return ciSkipPhrases; } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java index 45e1873..3e8e84b 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java @@ -1,5 +1,6 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import hudson.model.AbstractBuild; import hudson.model.Cause; import hudson.model.Result; @@ -51,13 +52,13 @@ public class BitbucketBuilds { String rootUrl = Jenkins.getInstance().getRootUrl(); String buildUrl = ""; if (rootUrl == null) { - buildUrl = " PLEASE SET JENKINS ROOT URL FROM GLOBAL CONFIGURATION " + build.getUrl(); - } - else { + logger.warning("PLEASE SET JENKINS ROOT URL IN GLOBAL CONFIGURATION FOR BUILD STATE REPORTING"); + } else { buildUrl = rootUrl + build.getUrl(); + BuildState state = result == Result.SUCCESS ? BuildState.SUCCESSFUL : BuildState.FAILED; + repository.setBuildStatus(cause, state, buildUrl); } - repository.deletePullRequestComment(cause.getPullRequestId(), cause.getBuildStartCommentId()); - repository.postFinishedComment(cause.getPullRequestId(), cause.getSourceCommitHash(), cause.getDestinationCommitHash(), result == Result.SUCCESS, buildUrl); + if ( this.trigger.getApproveIfSuccess() && result == Result.SUCCESS ) { this.repository.postPullRequestApproval(cause.getPullRequestId()); } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java index 01e6cd1..e233371 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java @@ -16,7 +16,6 @@ public class BitbucketCause extends Cause { private final String pullRequestTitle; private final String sourceCommitHash; private final String destinationCommitHash; - private final String buildStartCommentId; public static final String BITBUCKET_URL = "https://bitbucket.org/"; public BitbucketCause(String sourceBranch, @@ -28,8 +27,7 @@ public class BitbucketCause extends Cause { String destinationRepositoryName, String pullRequestTitle, String sourceCommitHash, - String destinationCommitHash, - String buildStartCommentId) { + String destinationCommitHash) { this.sourceBranch = sourceBranch; this.targetBranch = targetBranch; this.repositoryOwner = repositoryOwner; @@ -40,7 +38,6 @@ public class BitbucketCause extends Cause { this.pullRequestTitle = pullRequestTitle; this.sourceCommitHash = sourceCommitHash; this.destinationCommitHash = destinationCommitHash; - this.buildStartCommentId = buildStartCommentId; } public String getSourceBranch() { @@ -79,8 +76,6 @@ public class BitbucketCause extends Cause { public String getDestinationCommitHash() { return destinationCommitHash; } - public String getBuildStartCommentId() { return buildStartCommentId; } - @Override public String getShortDescription() { String description = "<a href=\"" + BITBUCKET_URL + this.getDestinationRepositoryOwner() + "/"; diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java index fb43f12..309ceec 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java @@ -5,16 +5,17 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; +import jenkins.model.Jenkins; + import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; /** @@ -22,17 +23,9 @@ import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; */ public class BitbucketRepository { private static final Logger logger = Logger.getLogger(BitbucketRepository.class.getName()); - public static final String BUILD_START_MARKER = "[*BuildStarted* **%s**] %s into %s"; - public static final String BUILD_FINISH_MARKER = "[*BuildFinished* **%s**] %s into %s"; - - public static final String BUILD_START_REGEX = "\\[\\*BuildStarted\\* \\*\\*%s\\*\\*\\] ([0-9a-fA-F]+) into ([0-9a-fA-F]+)"; - public static final String BUILD_FINISH_REGEX = "\\[\\*BuildFinished\\* \\*\\*%s\\*\\*\\] ([0-9a-fA-F]+) into ([0-9a-fA-F]+)"; - - public static final String BUILD_FINISH_SENTENCE = BUILD_FINISH_MARKER + " \n\n **%s** - %s"; - public static final String BUILD_REQUEST_MARKER = "test this please"; + private static final String BUILD_DESCRIPTION = "%s: %s into %s"; + private static final String BUILD_REQUEST_MARKER = "test this please"; - public static final String BUILD_SUCCESS_COMMENT = "SUCCESS"; - public static final String BUILD_FAILURE_COMMENT = "FAILURE"; private String projectPath; private BitbucketPullRequestsBuilder builder; private BitbucketBuildTrigger trigger; @@ -52,12 +45,14 @@ public class BitbucketRepository { username = credentials.getUsername(); password = credentials.getPassword().getPlainText(); } - client = new ApiClient( username, password, trigger.getRepositoryOwner(), - trigger.getRepositoryName()); + trigger.getRepositoryName(), + trigger.getCiKey(), + trigger.getCiName() + ); } public Collection<Pullrequest> getTargetPullRequests() { @@ -72,17 +67,8 @@ public class BitbucketRepository { return targetPullRequests; } - public String postBuildStartCommentTo(Pullrequest pullRequest) { - String sourceCommit = pullRequest.getSource().getCommit().getHash(); - String destinationCommit = pullRequest.getDestination().getCommit().getHash(); - String comment = String.format(BUILD_START_MARKER, builder.getProject().getDisplayName(), sourceCommit, destinationCommit); - Pullrequest.Comment commentResponse = this.client.postPullRequestComment(pullRequest.getId(), comment); - return commentResponse.getId().toString(); - } - public void addFutureBuildTasks(Collection<Pullrequest> pullRequests) { for(Pullrequest pullRequest : pullRequests) { - String commentId = postBuildStartCommentTo(pullRequest); if ( this.trigger.getApproveIfSuccess() ) { deletePullRequestApproval(pullRequest.getId()); } @@ -96,24 +82,26 @@ public class BitbucketRepository { pullRequest.getDestination().getRepository().getRepositoryName(), pullRequest.getTitle(), pullRequest.getSource().getCommit().getHash(), - pullRequest.getDestination().getCommit().getHash(), - commentId); + pullRequest.getDestination().getCommit().getHash()); + setBuildStatus(cause, BuildState.INPROGRESS, Jenkins.getInstance().getRootUrl()); this.builder.getTrigger().startJob(cause); } } - public void deletePullRequestComment(String pullRequestId, String commentId) { - this.client.deletePullRequestComment(pullRequestId,commentId); - } + public void setBuildStatus(BitbucketCause cause, BuildState state, String buildUrl) { + String comment = null; + String sourceCommit = cause.getSourceCommitHash(); + String owner = cause.getRepositoryOwner(); + String repository = cause.getRepositoryName(); + String destinationBranch = cause.getTargetBranch(); + + logger.info("setBuildStatus " + state + " for commit: " + sourceCommit + " with url " + buildUrl); - public void postFinishedComment(String pullRequestId, String sourceCommit, String destinationCommit, boolean success, String buildUrl) { - String message = BUILD_FAILURE_COMMENT; - if (success){ - message = BUILD_SUCCESS_COMMENT; + if (state == BuildState.FAILED || state == BuildState.SUCCESSFUL) { + comment = String.format(BUILD_DESCRIPTION, builder.getProject().getDisplayName(), sourceCommit, destinationBranch); } - String comment = String.format(BUILD_FINISH_SENTENCE, builder.getProject().getDisplayName(), sourceCommit, destinationCommit, message, buildUrl); - this.client.postPullRequestComment(pullRequestId, comment); + this.client.setBuildStatus(owner, repository, sourceCommit, state, buildUrl, comment); } public void deletePullRequestApproval(String pullRequestId) { @@ -125,19 +113,16 @@ public class BitbucketRepository { } private boolean isBuildTarget(Pullrequest pullRequest) { - - boolean shouldBuild = true; if (pullRequest.getState() != null && pullRequest.getState().equals("OPEN")) { if (isSkipBuild(pullRequest.getTitle())) { return false; } - String sourceCommit = pullRequest.getSource().getCommit().getHash(); - + Pullrequest.Revision source = pullRequest.getSource(); + String sourceCommit = source.getCommit().getHash(); Pullrequest.Revision destination = pullRequest.getDestination(); String owner = destination.getRepository().getOwnerName(); String repositoryName = destination.getRepository().getRepositoryName(); - String destinationCommit = destination.getCommit().getHash(); String id = pullRequest.getId(); List<Pullrequest.Comment> comments = client.getPullRequestComments(owner, repositoryName, id); @@ -151,47 +136,23 @@ public class BitbucketRepository { continue; } - //These will match any start or finish message -- need to check commits - String project_build_start = String.format(BUILD_START_REGEX, builder.getProject().getDisplayName()); - String project_build_finished = String.format(BUILD_FINISH_REGEX, builder.getProject().getDisplayName()); - Matcher startMatcher = Pattern.compile(project_build_start, Pattern.CASE_INSENSITIVE).matcher(content); - Matcher finishMatcher = Pattern.compile(project_build_finished, Pattern.CASE_INSENSITIVE).matcher(content); - - if (startMatcher.find() || - finishMatcher.find()) { - - String sourceCommitMatch; - String destinationCommitMatch; - - if (startMatcher.find(0)) { - sourceCommitMatch = startMatcher.group(1); - destinationCommitMatch = startMatcher.group(2); - } else { - sourceCommitMatch = finishMatcher.group(1); - destinationCommitMatch = finishMatcher.group(2); - } - - //first check source commit -- if it doesn't match, just move on. If it does, investigate further. - if (sourceCommitMatch.equalsIgnoreCase(sourceCommit)) { - // if we're checking destination commits, and if this doesn't match, then move on. - if (this.trigger.getCheckDestinationCommit() - && (!destinationCommitMatch.equalsIgnoreCase(destinationCommit))) { - continue; - } - - shouldBuild = false; - break; - } - } - if (content.contains(BUILD_REQUEST_MARKER.toLowerCase())) { - shouldBuild = true; - break; + return true; } } } + + Pullrequest.Repository sourceRepository = source.getRepository(); + + if (this.client.hasBuildStatus(sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit)) { + logger.info("Commit " + sourceCommit + " has already been processed"); + return false; + } + + return true; } - return shouldBuild; + + return false; } private boolean isSkipBuild(String pullRequestTitle) { diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java index 713a061..c8f1818 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java @@ -9,7 +9,6 @@ import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -23,17 +22,20 @@ import hudson.ProxyConfiguration; */ public class ApiClient { private static final Logger logger = Logger.getLogger(ApiClient.class.getName()); - private static final String BITBUCKET_HOST = "bitbucket.org"; private static final String V1_API_BASE_URL = "https://bitbucket.org/api/1.0/repositories/"; private static final String V2_API_BASE_URL = "https://bitbucket.org/api/2.0/repositories/"; private String owner; private String repositoryName; private Credentials credentials; + private String key; + private String name; - public ApiClient(String username, String password, String owner, String repositoryName) { + public ApiClient(String username, String password, String owner, String repositoryName, String key, String name) { this.credentials = new UsernamePasswordCredentials(username, password); this.owner = owner; this.repositoryName = repositoryName; + this.key = key; + this.name = name; } public List<Pullrequest> getPullRequests() { @@ -54,26 +56,22 @@ public class ApiClient { return Collections.EMPTY_LIST; } - public void deletePullRequestComment(String pullRequestId, String commentId) { - String path = V1_API_BASE_URL + this.owner + "/" + this.repositoryName + "/pullrequests/" + pullRequestId + "/comments/" + commentId; - //https://bitbucket.org/api/1.0/repositories/{accountname}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id} - delete(path); + public boolean hasBuildStatus(String owner, String repositoryName, String revision) { + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + this.key); + return get(url).contains("\"state\""); } - - public Pullrequest.Comment postPullRequestComment(String pullRequestId, String comment) { - try { - String response = post( - v1("/pullrequests/" + pullRequestId + "/comments"), - new NameValuePair[]{ new NameValuePair("content", comment) }); - return parse(response, Pullrequest.Comment.class); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - catch (IOException e) { - e.printStackTrace(); - } - return null; + public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment) { + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build"); + NameValuePair[] data = new NameValuePair[]{ + new NameValuePair("description", comment), + new NameValuePair("key", this.key), + new NameValuePair("name", this.name), + new NameValuePair("state", state.toString()), + new NameValuePair("url", buildUrl), + }; + logger.info("POST state " + state + " to " + url); + post(url, data); } public void deletePullRequestApproval(String pullRequestId) { @@ -115,8 +113,12 @@ public class ApiClient { return V1_API_BASE_URL + this.owner + "/" + this.repositoryName + url; } - private String v2(String url) { - return V2_API_BASE_URL + this.owner + "/" + this.repositoryName + url; + private String v2(String path) { + return v2(this.owner, this.repositoryName, path); + } + + private String v2(String owner, String repositoryName, String path) { + return V2_API_BASE_URL + owner + "/" + repositoryName + path; } private String get(String path) { diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java new file mode 100644 index 0000000..e5c15ef --- /dev/null +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java @@ -0,0 +1,10 @@ +package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket; + +/** + * Valid build states for a pull request + * + * @see https://confluence.atlassian.com/bitbucket/buildstatus-resource-779295267.html + */ +public enum BuildState { + FAILED, INPROGRESS, SUCCESSFUL +} diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly index db5ca0f..b324049 100644 --- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly @@ -17,6 +17,12 @@ <f:entry title="RepositoryName" field="repositoryName"> <f:textbox /> </f:entry> + <f:entry title="CI Identifier" field="ciKey"> + <f:textbox default="jenkins" /> + </f:entry> + <f:entry title="CI Name" field="ciName"> + <f:textbox default="Jenkins" /> + </f:entry> <f:entry title="CI Skip Phrases" field="ciSkipPhrases"> <f:textbox /> </f:entry> diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html new file mode 100644 index 0000000..97696b6 --- /dev/null +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html @@ -0,0 +1,4 @@ +The identifier needs to be unique among your Jenkins jobs related to this repo. +This identifier is used to decide whether a commit is already built by this job and to set status for a newly built commit. +If the value is changed rebuilds may occur and multiple statuses might show on an existing pull request. +The value is not shown for end users of Bitbucket. diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html new file mode 100644 index 0000000..32f248d --- /dev/null +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html @@ -0,0 +1 @@ +This value is the name of the current job when showing build statuses for a pull request. |