From 523e5384fc123a554d9a0babc1ae3493d50499bb Mon Sep 17 00:00:00 2001 From: Maxim Epishchev Date: Wed, 13 Jan 2016 15:46:45 +0300 Subject: Add branches to build filter (similar issue:61) --- pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'pom.xml') diff --git a/pom.xml b/pom.xml index 8863269..2bb3a93 100644 --- a/pom.xml +++ b/pom.xml @@ -3,15 +3,15 @@ org.jenkins-ci.plugins plugin - 1.509.4 + 1.509.4 bitbucket-pullrequest-builder Bitbucket Pullrequest Builder Plugin - 1.4.9-SNAPSHOT + 1.4.10 This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results. hpi - https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin + https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin scm:git:ssh://git@github.com/jenkinsci/bitbucket-pullrequest-builder-plugin.git @@ -25,10 +25,10 @@ nishio_dens nishio@densan-labs.net - - + + - + repo.jenkins-ci.org http://repo.jenkins-ci.org/public/ @@ -61,6 +61,11 @@ git 2.2.4 + + com.google.guava + guava + 14.0-rc3 + -- cgit v1.2.3 From 31b3fbaa1926c751b651d58044a5216b64566029 Mon Sep 17 00:00:00 2001 From: Maxim Epishchev Date: Fri, 15 Jan 2016 19:34:49 +0300 Subject: Fix UTF-8 build statuses and fix issue:1; also minor fixes --- pom.xml | 2 +- .../BitbucketBuildFilter.java | 7 +++-- .../BitbucketPullRequestsBuilder.java | 12 ++++++++ .../BitbucketRepository.java | 33 ++++++++++++++-------- .../bitbucket/ApiClient.java | 20 +++++++++---- .../BitbucketBuildTrigger/help-branchesFilter.html | 2 +- 6 files changed, 54 insertions(+), 22 deletions(-) (limited to 'pom.xml') diff --git a/pom.xml b/pom.xml index 2bb3a93..6c9ed74 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ bitbucket-pullrequest-builder Bitbucket Pullrequest Builder Plugin - 1.4.10 + 1.4.11 This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results. hpi https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java index eb80d86..230432d 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java @@ -85,22 +85,23 @@ public class BitbucketBuildFilter { } public BitbucketBuildFilter(String f) { - this.filter = f; + this.filter = f != null ? f : ""; this.buildFilter(this.filter); } private void buildFilter(String filter) { + logger.log(Level.INFO, "Build filter by phrase: {0}", filter); for(Filter f : AvailableFilters) { if (f.check(filter)) { this.currFilter = f; logger.log(Level.INFO, "Using filter: {0}", f.getClass().getSimpleName()); break; } - } - logger.warning("No available filters to use ..."); + } } public boolean approved(BitbucketCause cause) { + logger.log(Level.INFO, "Approve cause: {0}", cause.toString()); return this.currFilter.apply(this.filter, cause); } } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java index 3671ea6..c5d4159 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java @@ -2,9 +2,11 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; import hudson.model.AbstractProject; +import java.security.MessageDigest; import java.util.Collection; import java.util.logging.Logger; +import org.apache.commons.codec.binary.Hex; /** * Created by nishio @@ -50,6 +52,16 @@ public class BitbucketPullRequestsBuilder { public AbstractProject getProject() { return this.project; + } + + public String getProjectId() { + try { + final MessageDigest MD5 = MessageDigest.getInstance("MD5"); + return new String(Hex.encodeHex(MD5.digest(this.project.getFullName().getBytes("UTF-8")))); + } catch (Exception exc) { + logger.severe(exc.toString()); + } + return this.project.getFullName(); } public BitbucketBuildTrigger getTrigger() { diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java index e0a4b19..2686ffd 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java @@ -9,6 +9,7 @@ import java.util.logging.Logger; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; +import java.util.logging.Level; import jenkins.model.Jenkins; /** @@ -87,7 +88,7 @@ public class BitbucketRepository { comment = String.format(BUILD_DESCRIPTION, builder.getProject().getDisplayName(), sourceCommit, destinationBranch); } - this.client.setBuildStatus(owner, repository, sourceCommit, state, buildUrl, comment); + this.client.setBuildStatus(owner, repository, sourceCommit, state, buildUrl, comment, this.builder.getProjectId()); } public void deletePullRequestApproval(String pullRequestId) { @@ -110,9 +111,20 @@ public class BitbucketRepository { String owner = destination.getRepository().getOwnerName(); String repositoryName = destination.getRepository().getRepositoryName(); + Pullrequest.Repository sourceRepository = source.getRepository(); + + boolean commitAlreadyBeenProcessed = this.client.hasBuildStatus( + sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit, this.builder.getProjectId() + ); + if (commitAlreadyBeenProcessed) logger.log(Level.INFO, + "Commit {0}#{1} has already been processed", + new Object[]{ sourceCommit, this.builder.getProjectId() } + ); + String id = pullRequest.getId(); List comments = client.getPullRequestComments(owner, repositoryName, id); + boolean rebuildCommentAvailable = false; if (comments != null) { Collections.sort(comments); Collections.reverse(comments); @@ -123,19 +135,18 @@ public class BitbucketRepository { } if (content.contains(BUILD_REQUEST_MARKER.toLowerCase())) { - return true; + rebuildCommentAvailable = true; + logger.log(Level.INFO, + "Rebuild comment available for commit {0} and comment #{1}", + new Object[]{ sourceCommit, comment.getId() } + ); + this.client.deleteComment(id, Integer.toString(comment.getId())); } } - } - - 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; + logger.log(Level.INFO, "Build target? {0}", rebuildCommentAvailable || !commitAlreadyBeenProcessed); + return rebuildCommentAvailable || !commitAlreadyBeenProcessed; } return false; diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java index c8f1818..18ca86f 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java @@ -24,6 +24,7 @@ public class ApiClient { private static final Logger logger = Logger.getLogger(ApiClient.class.getName()); 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 static final String COMPUTED_KEY_FORMAT = "%s-%s"; private String owner; private String repositoryName; private Credentials credentials; @@ -56,27 +57,33 @@ public class ApiClient { return Collections.EMPTY_LIST; } - public boolean hasBuildStatus(String owner, String repositoryName, String revision) { - String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + this.key); + public boolean hasBuildStatus(String owner, String repositoryName, String revision, String keyEx) { + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + String.format(COMPUTED_KEY_FORMAT, this.key, keyEx)); return get(url).contains("\"state\""); } - public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment) { + public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment, String keyEx) { String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build"); + String computedKey = String.format(COMPUTED_KEY_FORMAT, this.key, keyEx); NameValuePair[] data = new NameValuePair[]{ new NameValuePair("description", comment), - new NameValuePair("key", this.key), + new NameValuePair("key", computedKey), new NameValuePair("name", this.name), new NameValuePair("state", state.toString()), new NameValuePair("url", buildUrl), }; - logger.info("POST state " + state + " to " + url); - post(url, data); + logger.log(Level.INFO, "POST state {0} to {1} with key {2} with response {3}", new Object[]{ + state, url, computedKey, post(url, data)} + ); } public void deletePullRequestApproval(String pullRequestId) { delete(v2("/pullrequests/" + pullRequestId + "/approve")); } + + public void deleteComment(String pullRequestId, String commentId) { + delete(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId + "/")); + } public Pullrequest.Participant postPullRequestApproval(String pullRequestId) { try { @@ -128,6 +135,7 @@ public class ApiClient { private String post(String path, NameValuePair[] data) { PostMethod req = new PostMethod(path); req.setRequestBody(data); + req.getParams().setContentCharset("utf-8"); return send(req); } diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html index 8b801db..f49da40 100644 --- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html @@ -2,4 +2,4 @@ Filter option in custom format. Default value is empty or "any". Available formats: * any pull requests applied for this project: "any", "*" or empty string. * filtered by destination branch: "my-branch" or more complex reg-ex filter "r:^master" (must be started with "r:" and case insensitive match). -* filtered by source and destination branches: "s:source-branch d:dest-branch" \ No newline at end of file +* filtered by source and destination branches: "s:source-branch d:dest-branch" -- cgit v1.2.3 From 91a85604177f7df2eb204c9e82564142dbe328f8 Mon Sep 17 00:00:00 2001 From: Maxim Epishchev Date: Thu, 21 Jan 2016 19:52:23 +0300 Subject: Refactoring filters code and add tests Now we can using more complex filters with source/destination filtration. Also add experimental integration with Git SCM plugin. --- pom.xml | 8 +- .../BitbucketBuildFilter.java | 94 ++++++++++-- .../BitbucketBuildTrigger.java | 7 + .../BitbucketRepository.java | 15 +- .../BitbucketBuildTrigger/config.jelly | 3 + .../BitbucketBuildTrigger/help-branchesFilter.html | 7 + .../help-branchesFilterBySCMIncludes.html | 3 + src/test/java/BitbucketBuildFilterTest.java | 170 +++++++++++++++++++++ 8 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html create mode 100644 src/test/java/BitbucketBuildFilterTest.java (limited to 'pom.xml') diff --git a/pom.xml b/pom.xml index 6c9ed74..d161718 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ bitbucket-pullrequest-builder Bitbucket Pullrequest Builder Plugin - 1.4.11 + 1.4.12 This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results. hpi https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin @@ -66,6 +66,12 @@ guava 14.0-rc3 + + org.easymock + easymock + 3.4 + test + diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java index 230432d..1072337 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java @@ -1,11 +1,14 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; +import hudson.ExtensionList; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.logging.Level; +import java.util.regex.Matcher; +import jenkins.plugins.git.AbstractGitSCMSource; +import jenkins.scm.api.SCMSource; /** * Mutable wrapper @@ -24,8 +27,15 @@ abstract class Filter { public static final String RX_FILTER_FLAG = "r"; public static final String RX_FILTER_FLAG_SINGLE = RX_FILTER_FLAG + ":"; + public static final String SRC_RX = "s:(" + RX_FILTER_FLAG_SINGLE + ")?"; + public static final String DST_RX = "d:(" + RX_FILTER_FLAG_SINGLE + ")?"; + public static final String BRANCH_FILTER_RX_PART = "([^\\s$]*)"; + abstract public boolean apply(String filter, BitbucketCause cause); abstract public boolean check(String filter); + + static final Pattern RX_SRC_DST_PARTS = Pattern.compile("(s:)|(d:)"); + public static boolean HasSourceOrDestPartsPredicate(String filter) { return RX_SRC_DST_PARTS.matcher(filter).find(); } } class EmptyFilter extends Filter { @@ -42,27 +52,57 @@ class AnyFlag extends Filter { public boolean check(String filter) { return filter.isEmpty() || filter.contains("*") || filter.toLowerCase().contains("any"); } } +class OnlySourceFlag extends Filter { + @Override + public boolean apply(String filter, BitbucketCause cause) { + String selectedRx = filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter); + logger.log(Level.INFO, "OnlySourceFlag using filter: {0}", selectedRx); + Matcher matcher = Pattern.compile(selectedRx, Pattern.CASE_INSENSITIVE).matcher(cause.getSourceBranch()); + return filter.startsWith(RX_FILTER_FLAG_SINGLE) ? matcher.find() : matcher.matches(); + } + @Override + public boolean check(String filter) { + return false; + } +} + class OnlyDestFlag extends Filter { @Override public boolean apply(String filter, BitbucketCause cause) { - return Pattern.compile( - filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter), - Pattern.CASE_INSENSITIVE).matcher(cause.getTargetBranch()).find(); + String selectedRx = filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter); + logger.log(Level.INFO, "OnlyDestFlag using filter: {0}", selectedRx); + Matcher matcher = Pattern.compile(selectedRx, Pattern.CASE_INSENSITIVE).matcher(cause.getTargetBranch()); + return filter.startsWith(RX_FILTER_FLAG_SINGLE) ? matcher.find() : matcher.matches(); } @Override public boolean check(String filter) { - return !(Pattern.matches("(s:)|(d:)", filter)); + return !HasSourceOrDestPartsPredicate(filter); } } -class SourceDestFlag extends Filter { +class SourceDestFlag extends Filter { + static final Pattern SRC_MATCHER_RX = Pattern.compile(SRC_RX + BRANCH_FILTER_RX_PART, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ); + static final Pattern DST_MATCHER_RX = Pattern.compile(DST_RX + BRANCH_FILTER_RX_PART, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ); + + boolean applyByRx(Pattern rx, Filter usedFilter, String filter, BitbucketCause cause) { + Matcher srcMatch = rx.matcher(filter); + boolean apply = rx.matcher(filter).matches(); + while (srcMatch.find()) { + String computedFilter = ((srcMatch.group(1) == null ? "" : srcMatch.group(1)) + srcMatch.group(2)).trim(); + logger.log(Level.INFO, "Apply computed filter: {0}", computedFilter); + apply = apply || (computedFilter.isEmpty() ? true : usedFilter.apply(computedFilter, cause)); + } + return apply; + } + @Override public boolean apply(String filter, BitbucketCause cause) { - return false; + return this.applyByRx(SRC_MATCHER_RX, new OnlySourceFlag(), filter, cause) && + this.applyByRx(DST_MATCHER_RX, new OnlyDestFlag(), filter, cause); } @Override public boolean check(String filter) { - return Pattern.matches("(s:)|(d:)", filter); + return HasSourceOrDestPartsPredicate(filter); } } @@ -80,12 +120,13 @@ public class BitbucketBuildFilter { ArrayList filters = new ArrayList(); filters.add(new AnyFlag()); filters.add(new OnlyDestFlag()); - filters.add(new EmptyFilter()); + filters.add(new SourceDestFlag()); + filters.add(new EmptyFilter()); AvailableFilters = filters; } public BitbucketBuildFilter(String f) { - this.filter = f != null ? f : ""; + this.filter = (f != null ? f : "").trim(); this.buildFilter(this.filter); } @@ -104,4 +145,37 @@ public class BitbucketBuildFilter { logger.log(Level.INFO, "Approve cause: {0}", cause.toString()); return this.currFilter.apply(this.filter, cause); } + + public static BitbucketBuildFilter InstanceByString(String filter) { + logger.log(Level.INFO, "Filter instance by filter string"); + return new BitbucketBuildFilter(filter); + } + + static public String FilterFromGitSCMSource(AbstractGitSCMSource gitscm, String defaultFilter) { + if (gitscm == null) { + logger.log(Level.INFO, "Git SCMSource unavailable. Using default value: {0}", defaultFilter); + return defaultFilter; + } + + String filter = defaultFilter; + final String includes = gitscm.getIncludes(); + if (includes != null && !includes.isEmpty()) { + for(String part : includes.split("\\s+")) { + filter += String.format("%s ", part.replaceAll("\\*\\/", "d:")); + } + } + + logger.log(Level.INFO, "Git includes transformation to filter result: {1} -> {0}; default: {2}", new Object[]{ filter, includes, defaultFilter }); + return filter.trim(); + } + + public static BitbucketBuildFilter InstanceBySCM(ExtensionList scmSources, String defaultFilter) { + logger.log(Level.FINE, "Filter instance by using SCM"); + AbstractGitSCMSource gitscm = null; + for(SCMSource scm : scmSources) { + gitscm = (AbstractGitSCMSource)scm; + if (gitscm != null) break; + } + return new BitbucketBuildFilter(FilterFromGitSCMSource(gitscm, defaultFilter)); + } } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java index 254eec3..cd9345c 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java @@ -29,6 +29,7 @@ public class BitbucketBuildTrigger extends Trigger> { private final String repositoryOwner; private final String repositoryName; private final String branchesFilter; + private final boolean branchesFilterBySCMIncludes; private final String ciKey; private final String ciName; private final String ciSkipPhrases; @@ -49,6 +50,7 @@ public class BitbucketBuildTrigger extends Trigger> { String repositoryOwner, String repositoryName, String branchesFilter, + boolean branchesFilterBySCMIncludes, String ciKey, String ciName, String ciSkipPhrases, @@ -63,6 +65,7 @@ public class BitbucketBuildTrigger extends Trigger> { this.repositoryOwner = repositoryOwner; this.repositoryName = repositoryName; this.branchesFilter = branchesFilter; + this.branchesFilterBySCMIncludes = branchesFilterBySCMIncludes; this.ciKey = ciKey; this.ciName = ciName; this.ciSkipPhrases = ciSkipPhrases; @@ -97,6 +100,10 @@ public class BitbucketBuildTrigger extends Trigger> { public String getBranchesFilter() { return branchesFilter; } + + public boolean getBranchesFilterBySCMIncludes() { + return branchesFilterBySCMIncludes; + } public String getCiKey() { return ciKey; diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java index 2686ffd..c71cc99 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java @@ -10,7 +10,10 @@ import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClie import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import jenkins.model.Jenkins; +import jenkins.scm.api.SCMSource; /** * Created by nishio @@ -19,6 +22,7 @@ public class BitbucketRepository { private static final Logger logger = Logger.getLogger(BitbucketRepository.class.getName()); private static final String BUILD_DESCRIPTION = "%s: %s into %s"; private static final String BUILD_REQUEST_MARKER = "test this please"; + private static final String BUILD_REQUEST_MARKER_COUNTER_RX = "\\[(\\d+)]\\"; private String projectPath; private BitbucketPullRequestsBuilder builder; @@ -99,6 +103,11 @@ public class BitbucketRepository { this.client.postPullRequestApproval(pullRequestId); } + private Integer extractRebuildTimesFromComment(String content) { + Matcher matcher = Pattern.compile(BUILD_REQUEST_MARKER_COUNTER_RX).matcher(content); + return matcher.groupCount() >= 1 ? Integer.parseInt(matcher.group(1)) : 0; + } + private boolean isBuildTarget(Pullrequest pullRequest) { if (pullRequest.getState() != null && pullRequest.getState().equals("OPEN")) { if (isSkipBuild(pullRequest.getTitle()) || !isFilteredBuild(pullRequest)) { @@ -178,7 +187,11 @@ public class BitbucketRepository { pullRequest.getSource().getCommit().getHash(), pullRequest.getDestination().getCommit().getHash() ); - BitbucketBuildFilter filter = new BitbucketBuildFilter(this.trigger.getBranchesFilter()); + + BitbucketBuildFilter filter = !this.trigger.getBranchesFilterBySCMIncludes() ? + BitbucketBuildFilter.InstanceByString(this.trigger.getBranchesFilter()) : + BitbucketBuildFilter.InstanceBySCM(Jenkins.getInstance().getExtensionList(SCMSource.class), this.trigger.getBranchesFilter()); + return filter.approved(cause); } } diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly index 845926d..25acac0 100644 --- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly @@ -17,6 +17,9 @@ + + + diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html index f49da40..88af799 100644 --- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html @@ -3,3 +3,10 @@ Available formats: * any pull requests applied for this project: "any", "*" or empty string. * filtered by destination branch: "my-branch" or more complex reg-ex filter "r:^master" (must be started with "r:" and case insensitive match). * filtered by source and destination branches: "s:source-branch d:dest-branch" +* filtered by source and destination branches with regex: "s:r:^feature d:r:master$" +* filtered by many destination/source branches: "s:one s:two s:three d:master d:r:master$" +* filtered by many sources branches: "s:one s:two s:r:^three d:" +When you using format with source branch filter "s" or destination filter "d", you must cpecify great than one source and destination filter, eg "s:1 s:2 s:... d:". +Any sources and any destinations for pull request: +* filter string: "*" +* filter string: "s: d:" diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html new file mode 100644 index 0000000..3f24419 --- /dev/null +++ b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html @@ -0,0 +1,3 @@ +It is sugar-option for synchronize option "BranchesFilter" to Git SCM option "Branches to build" without manual editing. +Check this option suppose than your "BranchesFilter" field has logick equal value with Git SCM "Branches to build" option (original value from "BranchesFilter" field will e ignored). +If "Branches to build" option has values "*/master */feature-master */build-with-jenkins", then "BranchesFilter" field will have value "d:master d:feature-master d:build-with-jenkins". diff --git a/src/test/java/BitbucketBuildFilterTest.java b/src/test/java/BitbucketBuildFilterTest.java new file mode 100644 index 0000000..0765a4a --- /dev/null +++ b/src/test/java/BitbucketBuildFilterTest.java @@ -0,0 +1,170 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildFilter; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketCause; +import java.util.regex.Pattern; +import jenkins.plugins.git.AbstractGitSCMSource; +import org.easymock.*; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.WithoutJenkins; + +/** + * + * @author maxvodo + */ +public class BitbucketBuildFilterTest { + + @Rule + public JenkinsRule jRule = new JenkinsRule(); + + @Test + @WithoutJenkins + public void mockTest() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("mock").anyTimes(); + EasyMock.replay(cause); + for(Integer i : new Integer[] {1, 2, 3, 4, 5}) assertEquals("mock", cause.getTargetBranch()); + } + + @Test + @WithoutJenkins + public void anyFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes(); + EasyMock.replay(cause); + + for(String f : new String[] {"", "*", "any"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertTrue(filter.approved(cause)); + } + + for(String f : new String[] {"foo", "bar", " baz "}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertFalse(filter.approved(cause)); + } + } + + @Test + @WithoutJenkins + public void onlyDestinationFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master-branch").anyTimes(); + EasyMock.replay(cause); + + for(String f : new String[] {"master-branch", "r:^master", "r:branch$", " master-branch "}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertTrue(filter.approved(cause)); + } + + for(String f : new String[] {"develop", "feature-good-thing", "r:develop$"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertFalse(filter.approved(cause)); + } + } + + @Test + @WithoutJenkins + public void rxSourceDestCheck() { + for(String f : new String[] {"", "master", "r:master", "*"}) + assertFalse(Pattern.compile("(s:)|(d:)").matcher(f).find()); + + for(String f : new String[] {"s:master d:feature-master", "s:master d:r:^feature", "s:r:^master d:r:^feature"}) + assertTrue(Pattern.compile("(s:)|(d:)").matcher(f).find()); + } + + @Test + @WithoutJenkins + public void sourceAndDestFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes(); + EasyMock.expect(cause.getSourceBranch()).andReturn("feature-for-master").anyTimes(); + EasyMock.replay(cause); + + for(String f : new String[] {"s:feature-for-master d:master", "s:r:^feature d:master", "s:feature-for-master d:r:^m", "s:r:^feature d:r:^master"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertTrue(filter.approved(cause)); + } + + for(String f : new String[] {"s:feature-for-master d:foo", "s:bar d:master", "s:foo d:bar"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertFalse(filter.approved(cause)); + } + } + + @Test + @WithoutJenkins + public void multipleSrcDestFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes(); + EasyMock.expect(cause.getSourceBranch()).andReturn("feature-master").anyTimes(); + EasyMock.replay(cause); + + for(String f : new String[] {"s: d:", "s:r:^feature s:good-branch d:r:.*", "s:good-branch s:feature-master d:r:.*", "s: d:r:.*", "d:master d:foo d:bar s:"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertTrue(filter.approved(cause)); + } + + for(String f : new String[] {"d:ggg d:ooo d:333 s:feature-master", "s:111 s:222 s:333 d:master"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertFalse(filter.approved(cause)); + } + } + + @Test + @WithoutJenkins + public void sourceAndDestPartiallyFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes(); + EasyMock.expect(cause.getSourceBranch()).andReturn("feature-master").anyTimes(); + EasyMock.replay(cause); + + for(String f : new String[] {"s:feature-master d:", "d:master s:"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertTrue(filter.approved(cause)); + } + + for(String f : new String[] {"s:feature-master", "d:master"}) { + BitbucketBuildFilter filter = BitbucketBuildFilter.InstanceByString(f); + assertFalse(filter.approved(cause)); + } + } + + @Test + @WithoutJenkins + public void emptyGitSCMFilter() { + BitbucketCause cause = EasyMock.createMock(BitbucketCause.class); + EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes(); + EasyMock.replay(cause); + + assertTrue(BitbucketBuildFilter.FilterFromGitSCMSource(null, "").isEmpty()); + assertEquals("default", BitbucketBuildFilter.FilterFromGitSCMSource(null, "default")); + + assertTrue(BitbucketBuildFilter.InstanceByString( + BitbucketBuildFilter.FilterFromGitSCMSource(null, "")).approved(cause) + ); + } + + @Test + @WithoutJenkins + public void fromGitSCMFilter() { + AbstractGitSCMSource git = EasyMock.createMock(AbstractGitSCMSource.class); + EasyMock.expect(git.getIncludes()) + .andReturn("").times(1) + .andReturn("").times(1) + .andReturn("*/master */feature-branch").times(1) + .andReturn("*/master").anyTimes(); + EasyMock.replay(git); + + assertTrue(git.getIncludes().isEmpty()); + assertEquals("", BitbucketBuildFilter.FilterFromGitSCMSource(git, "")); + assertEquals("d:master d:feature-branch", BitbucketBuildFilter.FilterFromGitSCMSource(git, "")); + assertEquals("d:master", BitbucketBuildFilter.FilterFromGitSCMSource(git, "")); + } +} -- cgit v1.2.3 From 96ab7a75f14d9990f3c8f1255f9790c496a64473 Mon Sep 17 00:00:00 2001 From: Maxim Epishchev Date: Tue, 26 Jan 2016 19:06:59 +0300 Subject: Bugfixes issue for continuously rebuilding PR If available TTP (aka "test this please") comment Jenkins PR builder continuously rebuilding PR. Now Jenkins post specific build comment. If you want to rebuild already rebuilded PR, post new TTP comment. --- pom.xml | 2 +- .../BitbucketBuildFilter.java | 13 +- .../BitbucketRepository.java | 127 +++++++++++++++---- .../bitbucket/ApiClient.java | 50 +++++++- src/test/java/BitbucketBuildFilterTest.java | 141 +++++++++++++++++++++ 5 files changed, 296 insertions(+), 37 deletions(-) (limited to 'pom.xml') diff --git a/pom.xml b/pom.xml index d161718..4eda429 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ bitbucket-pullrequest-builder Bitbucket Pullrequest Builder Plugin - 1.4.12 + 1.4.13 This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results. hpi https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java index 1072337..c251930 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java @@ -1,9 +1,9 @@ package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder; -import hudson.ExtensionList; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.regex.Matcher; @@ -169,12 +169,15 @@ public class BitbucketBuildFilter { return filter.trim(); } - public static BitbucketBuildFilter InstanceBySCM(ExtensionList scmSources, String defaultFilter) { - logger.log(Level.FINE, "Filter instance by using SCM"); + public static BitbucketBuildFilter InstanceBySCM(Collection scmSources, String defaultFilter) { + logger.log(Level.INFO, "Filter instance by using SCMSources list with {0} items", scmSources.size()); AbstractGitSCMSource gitscm = null; for(SCMSource scm : scmSources) { - gitscm = (AbstractGitSCMSource)scm; - if (gitscm != null) break; + logger.log(Level.INFO, "Check {0} SCMSource ", scm.getClass()); + if (scm instanceof AbstractGitSCMSource) { + gitscm = (AbstractGitSCMSource)scm; + break; + } } return new BitbucketBuildFilter(FilterFromGitSCMSource(gitscm, defaultFilter)); } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java index c71cc99..6736ee9 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java @@ -9,11 +9,15 @@ import java.util.logging.Logger; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; +import java.util.LinkedList; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import jenkins.model.Jenkins; import jenkins.scm.api.SCMSource; +import jenkins.scm.api.SCMSourceOwner; +import jenkins.scm.api.SCMSourceOwners; +import org.apache.commons.lang.StringUtils; /** * Created by nishio @@ -22,7 +26,9 @@ public class BitbucketRepository { private static final Logger logger = Logger.getLogger(BitbucketRepository.class.getName()); private static final String BUILD_DESCRIPTION = "%s: %s into %s"; private static final String BUILD_REQUEST_MARKER = "test this please"; - private static final String BUILD_REQUEST_MARKER_COUNTER_RX = "\\[(\\d+)]\\"; + private static final String BUILD_REQUEST_DONE_MARKER = "ttp build flag"; + private static final String BUILD_REQUEST_MARKER_TAG_SINGLE_RX = "\\#[\\w\\-\\d]+"; + private static final String BUILD_REQUEST_MARKER_TAGS_RX = "\\[bid\\:\\s?(.*)\\]"; private String projectPath; private BitbucketPullRequestsBuilder builder; @@ -35,15 +41,19 @@ public class BitbucketRepository { } public void init() { - trigger = this.builder.getTrigger(); - client = new ApiClient( + this.init(null); + } + + public void init(ApiClient client) { + this.trigger = this.builder.getTrigger(); + this.client = (client == null) ? new ApiClient( trigger.getUsername(), trigger.getPassword(), trigger.getRepositoryOwner(), trigger.getRepositoryName(), trigger.getCiKey(), trigger.getCiName() - ); + ) : client; } public Collection getTargetPullRequests() { @@ -102,10 +112,68 @@ public class BitbucketRepository { public void postPullRequestApproval(String pullRequestId) { this.client.postPullRequestApproval(pullRequestId); } - - private Integer extractRebuildTimesFromComment(String content) { - Matcher matcher = Pattern.compile(BUILD_REQUEST_MARKER_COUNTER_RX).matcher(content); - return matcher.groupCount() >= 1 ? Integer.parseInt(matcher.group(1)) : 0; + + public String getMyBuildTag(String buildKey) { + return "#" + this.client.buildStatusKey(buildKey); + } + + final static Pattern BUILD_TAGS_RX = Pattern.compile(BUILD_REQUEST_MARKER_TAGS_RX, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ); + final static Pattern SINGLE_BUILD_TAG_RX = Pattern.compile(BUILD_REQUEST_MARKER_TAG_SINGLE_RX, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ); + final static String CONTENT_PART_TEMPLATE = "```[bid: %s]```"; + + private List getAvailableBuildTagsFromTTPComment(String buildTags) { + logger.log(Level.INFO, "Parse {0}", new Object[]{ buildTags }); + List availableBuildTags = new LinkedList(); + Matcher subBuildTagMatcher = SINGLE_BUILD_TAG_RX.matcher(buildTags); + while(subBuildTagMatcher.find()) availableBuildTags.add(subBuildTagMatcher.group(0).trim()); + return availableBuildTags; + } + + public boolean hasMyBuildTagInTTPComment(String content, String buildKey) { + Matcher tagsMatcher = BUILD_TAGS_RX.matcher(content); + if (tagsMatcher.find()) { + logger.log(Level.INFO, "Content {0} g[1]:{1} mykey:{2}", new Object[] { content, tagsMatcher.group(1).trim(), this.getMyBuildTag(buildKey) }); + return this.getAvailableBuildTagsFromTTPComment(tagsMatcher.group(1).trim()).contains(this.getMyBuildTag(buildKey)); + } + else return false; + } + + private void postBuildTagInTTPComment(String pullRequestId, String content, String buildKey) { + logger.log(Level.INFO, "Update build tag for {0} build key", buildKey); + List builds = this.getAvailableBuildTagsFromTTPComment(content); + builds.add(this.getMyBuildTag(buildKey)); + content += " " + String.format(CONTENT_PART_TEMPLATE, StringUtils.join(builds, " ")); + logger.log(Level.INFO, "Post comment: {0} with original content {1}", new Object[]{ content, this.client.postPullRequestComment(pullRequestId, content).getId() }); + } + + private boolean processTTPCommentBuildTags(String content, String buildKey) { + if (!this.isTTPCommentBuildTags(content)) return true; + logger.log(Level.INFO, "Processing ttp with build comment: {0}", content); + return !this.hasMyBuildTagInTTPComment(content, buildKey); + } + + private boolean isTTPComment(String content) { + return content.toLowerCase().contains(BUILD_REQUEST_MARKER.toLowerCase()); + } + + private boolean isTTPCommentBuildTags(String content) { + return content.toLowerCase().contains(BUILD_REQUEST_DONE_MARKER.toLowerCase()); + } + + public List filterPullRequestComments(List comments) { + logger.info("Filter PullRequest Comments."); + Collections.sort(comments); + Collections.reverse(comments); + List filteredComments = new LinkedList(); + for(Pullrequest.Comment comment : comments) { + String content = comment.getContent(); + if (content == null || content.isEmpty()) continue; + boolean isTTP = this.isTTPComment(content); + boolean isTTPBuild = this.isTTPCommentBuildTags(content); + if (isTTP || isTTPBuild) filteredComments.add(comment); + if (isTTP) break; + } + return filteredComments; } private boolean isBuildTarget(Pullrequest pullRequest) { @@ -121,41 +189,40 @@ public class BitbucketRepository { String repositoryName = destination.getRepository().getRepositoryName(); Pullrequest.Repository sourceRepository = source.getRepository(); + String buildKeyPart = this.builder.getProjectId(); - boolean commitAlreadyBeenProcessed = this.client.hasBuildStatus( - sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit, this.builder.getProjectId() + final boolean commitAlreadyBeenProcessed = this.client.hasBuildStatus( + sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit, buildKeyPart ); if (commitAlreadyBeenProcessed) logger.log(Level.INFO, "Commit {0}#{1} has already been processed", - new Object[]{ sourceCommit, this.builder.getProjectId() } + new Object[]{ sourceCommit, buildKeyPart } ); - String id = pullRequest.getId(); + final String id = pullRequest.getId(); List comments = client.getPullRequestComments(owner, repositoryName, id); boolean rebuildCommentAvailable = false; if (comments != null) { - Collections.sort(comments); - Collections.reverse(comments); - for (Pullrequest.Comment comment : comments) { + Collection filteredComments = this.filterPullRequestComments(comments); + for (Pullrequest.Comment comment : filteredComments) { String content = comment.getContent(); - if (content == null || content.isEmpty()) { - continue; - } - - if (content.contains(BUILD_REQUEST_MARKER.toLowerCase())) { + if (this.isTTPComment(content)) { rebuildCommentAvailable = true; logger.log(Level.INFO, "Rebuild comment available for commit {0} and comment #{1}", new Object[]{ sourceCommit, comment.getId() } - ); - this.client.deleteComment(id, Integer.toString(comment.getId())); - } + ); + } + rebuildCommentAvailable &= this.processTTPCommentBuildTags(content, buildKeyPart); + if (!rebuildCommentAvailable) break; } - } + } + if (rebuildCommentAvailable) this.postBuildTagInTTPComment(id, "TTP build flag", buildKeyPart); - logger.log(Level.INFO, "Build target? {0}", rebuildCommentAvailable || !commitAlreadyBeenProcessed); - return rebuildCommentAvailable || !commitAlreadyBeenProcessed; + final boolean canBuildTarget = rebuildCommentAvailable || !commitAlreadyBeenProcessed; + logger.log(Level.INFO, "Build target? {0} [rebuild:{1} processed:{2}]", new Object[]{ canBuildTarget, rebuildCommentAvailable, commitAlreadyBeenProcessed}); + return canBuildTarget; } return false; @@ -188,9 +255,15 @@ public class BitbucketRepository { pullRequest.getDestination().getCommit().getHash() ); + //@FIXME: Way to iterate over all available SCMSources + List sources = new LinkedList(); + for(SCMSourceOwner owner : SCMSourceOwners.all()) + for(SCMSource src : owner.getSCMSources()) + sources.add(src); + BitbucketBuildFilter filter = !this.trigger.getBranchesFilterBySCMIncludes() ? BitbucketBuildFilter.InstanceByString(this.trigger.getBranchesFilter()) : - BitbucketBuildFilter.InstanceBySCM(Jenkins.getInstance().getExtensionList(SCMSource.class), this.trigger.getBranchesFilter()); + BitbucketBuildFilter.InstanceBySCM(sources, this.trigger.getBranchesFilter()); return filter.approved(cause); } diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java index 18ca86f..e635f65 100644 --- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java +++ b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java @@ -16,6 +16,9 @@ import java.util.logging.Logger; import jenkins.model.Jenkins; import hudson.ProxyConfiguration; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.httpclient.util.EncodingUtil; /** * Created by nishio @@ -56,15 +59,27 @@ public class ApiClient { } return Collections.EMPTY_LIST; } + + public String getName() { + return this.name; + } + + private String computeAPIKey(String keyExPart) { + return String.format(COMPUTED_KEY_FORMAT, this.key, keyExPart); + } + + public String buildStatusKey(String bsKey) { + return this.computeAPIKey(bsKey); + } public boolean hasBuildStatus(String owner, String repositoryName, String revision, String keyEx) { - String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + String.format(COMPUTED_KEY_FORMAT, this.key, keyEx)); + String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + this.computeAPIKey(keyEx)); return get(url).contains("\"state\""); } public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment, String keyEx) { String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build"); - String computedKey = String.format(COMPUTED_KEY_FORMAT, this.key, keyEx); + String computedKey = this.computeAPIKey(keyEx); NameValuePair[] data = new NameValuePair[]{ new NameValuePair("description", comment), new NameValuePair("key", computedKey), @@ -81,8 +96,15 @@ public class ApiClient { delete(v2("/pullrequests/" + pullRequestId + "/approve")); } - public void deleteComment(String pullRequestId, String commentId) { - delete(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId + "/")); + public void deletePullRequestComment(String pullRequestId, String commentId) { + delete(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId)); + } + + public void updatePullRequestComment(String pullRequestId, String content, String commentId) { + NameValuePair[] data = new NameValuePair[] { + new NameValuePair("content", content), + }; + put(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId), data); } public Pullrequest.Participant postPullRequestApproval(String pullRequestId) { @@ -94,6 +116,19 @@ public class ApiClient { } return null; } + + public Pullrequest.Comment postPullRequestComment(String pullRequestId, String content) { + NameValuePair[] data = new NameValuePair[] { + new NameValuePair("content", content), + }; + try { + return parse(post(v1("/pullrequests/" + pullRequestId + "/comments"), data), new TypeReference() {}); + } catch(Exception e) { + logger.log(Level.WARNING, "Invalid pull request comment response.", e); + e.printStackTrace(); + } + return null; + } private HttpClient getHttpClient() { HttpClient client = new HttpClient(); @@ -142,6 +177,13 @@ public class ApiClient { private void delete(String path) { send(new DeleteMethod(path)); } + + private void put(String path, NameValuePair[] data) { + PutMethod req = new PutMethod(path); + req.setRequestBody(EncodingUtil.formUrlEncode(data, "utf-8")); + req.getParams().setContentCharset("utf-8"); + send(req); + } private String send(HttpMethodBase req) { HttpClient client = getHttpClient(); diff --git a/src/test/java/BitbucketBuildFilterTest.java b/src/test/java/BitbucketBuildFilterTest.java index 0765a4a..0fabfe4 100644 --- a/src/test/java/BitbucketBuildFilterTest.java +++ b/src/test/java/BitbucketBuildFilterTest.java @@ -6,6 +6,15 @@ import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildFilter; import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketCause; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketPullRequestsBuilder; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketRepository; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient; +import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.regex.Pattern; import jenkins.plugins.git.AbstractGitSCMSource; import org.easymock.*; @@ -167,4 +176,136 @@ public class BitbucketBuildFilterTest { assertEquals("d:master d:feature-branch", BitbucketBuildFilter.FilterFromGitSCMSource(git, "")); assertEquals("d:master", BitbucketBuildFilter.FilterFromGitSCMSource(git, "")); } + + @Test + @WithoutJenkins + public void filterPRComments() { + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes(); + EasyMock.replay(builder); + + List comments = new LinkedList(); + for(String commentContent : new String[] { + "check", + "", + "Hello from mock", + "Jenkins: test this please", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]", + "check", + "", + "Hello from mock", + "Jenkins: test this please", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo]", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]", + }) { + Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class); + EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes(); + EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes(); + EasyMock.replay(comment); + comments.add(comment); + } + + // Check twice + assertEquals("check", comments.get(0).getContent()); + assertEquals("check", comments.get(0).getContent()); + + assertEquals("Hello from mock", comments.get(2).getContent()); + + BitbucketRepository repo = new BitbucketRepository("", builder); + repo.init(EasyMock.createNiceMock(ApiClient.class)); + + List filteredComments = repo.filterPullRequestComments(comments); + + assertTrue(filteredComments.size() == 4); + assertEquals("Jenkins: test this please", filteredComments.get(filteredComments.size() - 1).getContent()); + } + + @Test + @WithoutJenkins + public void checkHashMyBuildTagTrue() { + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes(); + EasyMock.replay(builder); + + IMockBuilder repoBuilder = EasyMock.partialMockBuilder(BitbucketRepository.class); + repoBuilder.addMockedMethod("getMyBuildTag"); + BitbucketRepository repo = repoBuilder.createMock(); + EasyMock.expect(repo.getMyBuildTag(EasyMock.anyString())).andReturn("#jenkins-902f259e962ff16100843123480a0970").anyTimes(); + EasyMock.replay(repo); + + List comments = new LinkedList(); + for(String commentContent : new String[] { + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo]", + "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]", + "TTP build flag ```[bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]```", + }) { + Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class); + EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes(); + EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes(); + EasyMock.replay(comment); + comments.add(comment); + } + + String myBuildKey = "902f259e962ff16100843123480a0970"; + for(Pullrequest.Comment comment : comments) + assertTrue(repo.hasMyBuildTagInTTPComment(comment.getContent(), myBuildKey)); + } + + @Test + @WithoutJenkins + public void checkHashMyBuildTagFalse() { + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes(); + EasyMock.replay(builder); + + IMockBuilder repoBuilder = EasyMock.partialMockBuilder(BitbucketRepository.class); + repoBuilder.addMockedMethod("getMyBuildTag"); + BitbucketRepository repo = repoBuilder.createMock(); + EasyMock.expect(repo.getMyBuildTag(EasyMock.anyString())).andReturn("#jenkins-902f259e962ff16100843123480a0970").anyTimes(); + EasyMock.replay(repo); + + List comments = new LinkedList(); + for(String commentContent : new String[] { + "check", + "", + "Hello from mock", + "Jenkins: test this please", + "TTP build flag [bid: #jenkins]", + "TTP build flag [bid: #jenkins-foo]", + "TTP build flag [bid: #jenkins-foo #jenkins-bar]", + "TTP build flag ```[bid: #jenkins-foo #jenkins-bar]```", + }) { + Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class); + EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes(); + EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes(); + EasyMock.replay(comment); + comments.add(comment); + } + + String myBuildKey = "902f259e962ff16100843123480a0970"; + for(Pullrequest.Comment comment : comments) + assertFalse(repo.hasMyBuildTagInTTPComment(comment.getContent(), myBuildKey)); + } + + //@Test + @WithoutJenkins + public void ttpCommentTest() { + ApiClient client = EasyMock.createNiceMock(ApiClient.class); + Collection> prs = new LinkedList>(); + + prs.add(Arrays.asList(new Pullrequest[] { + new Pullrequest() + })); + + for(List pr : prs) EasyMock.expect(client.getPullRequests()).andReturn(pr).times(1); + BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class); + EasyMock.replay(client, builder); + + BitbucketRepository repo = new BitbucketRepository("", builder); + repo.init(client); + + Collection targetPRs = repo.getTargetPullRequests(); + } } -- cgit v1.2.3