diff options
author | Maxim Epishchev <epishev@garant.ru> | 2016-01-26 19:06:59 +0300 |
---|---|---|
committer | Maxim Epishchev <epishev@garant.ru> | 2016-01-26 19:06:59 +0300 |
commit | 96ab7a75f14d9990f3c8f1255f9790c496a64473 (patch) | |
tree | 439700ff0d73f9040723e7a5db43eb533c1237cd | |
parent | 91a85604177f7df2eb204c9e82564142dbe328f8 (diff) | |
download | bbprb-96ab7a75f14d9990f3c8f1255f9790c496a64473.tar.gz |
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.
5 files changed, 296 insertions, 37 deletions
@@ -8,7 +8,7 @@ <artifactId>bitbucket-pullrequest-builder</artifactId> <name>Bitbucket Pullrequest Builder Plugin</name> - <version>1.4.12</version> + <version>1.4.13</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/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<SCMSource> scmSources, String defaultFilter) { - logger.log(Level.FINE, "Filter instance by using SCM"); + public static BitbucketBuildFilter InstanceBySCM(Collection<SCMSource> 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<Pullrequest> 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<String> getAvailableBuildTagsFromTTPComment(String buildTags) { + logger.log(Level.INFO, "Parse {0}", new Object[]{ buildTags }); + List<String> availableBuildTags = new LinkedList<String>(); + 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<String> 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<Pullrequest.Comment> filterPullRequestComments(List<Pullrequest.Comment> comments) { + logger.info("Filter PullRequest Comments."); + Collections.sort(comments); + Collections.reverse(comments); + List<Pullrequest.Comment> filteredComments = new LinkedList<Pullrequest.Comment>(); + 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<Pullrequest.Comment> comments = client.getPullRequestComments(owner, repositoryName, id); boolean rebuildCommentAvailable = false; if (comments != null) { - Collections.sort(comments); - Collections.reverse(comments); - for (Pullrequest.Comment comment : comments) { + Collection<Pullrequest.Comment> 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<SCMSource> sources = new LinkedList<SCMSource>(); + 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<Pullrequest.Comment>() {}); + } 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<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>(); + 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<Pullrequest.Comment> 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<BitbucketRepository> 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<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>(); + 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<BitbucketRepository> 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<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>(); + 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<List<Pullrequest>> prs = new LinkedList<List<Pullrequest>>(); + + prs.add(Arrays.asList(new Pullrequest[] { + new Pullrequest() + })); + + for(List<Pullrequest> 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<Pullrequest> targetPRs = repo.getTargetPullRequests(); + } } |