diff --git a/README.adoc b/README.adoc new file mode 100644 index 000000000..f26b16f4e --- /dev/null +++ b/README.adoc @@ -0,0 +1,6 @@ +== BitBucket Branch Source Plugin + +=== Notes + +* Unlike GitHub, in BitBucket, https://bitbucket.org/site/master/issues/4828/team-admins-dont-have-read-access-to-forks[team admins do not have access to forks]. +This means that when you have a private repository, or a private fork of a public repository, the team admin will not be able to see the PRs within the fork. diff --git a/pom.xml b/pom.xml index 644f93f38..41ddc52e2 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ cloudbees-bitbucket-branch-source - 1.10-SNAPSHOT + 2.0.0-beta-1-SNAPSHOT hpi Bitbucket Branch Source Plugin @@ -47,6 +47,7 @@ 1.642.3 + 2.0.1-beta-1 @@ -60,17 +61,12 @@ org.jenkins-ci.plugins scm-api - 1.2 - - - org.jenkins-ci.plugins - branch-api - 1.10.2 + ${scm-api.version} org.jenkins-ci.plugins git - 2.3.5 + 2.6.2-beta-1 org.apache.httpcomponents @@ -81,7 +77,7 @@ org.jenkins-ci.plugins mercurial - 1.54 + 1.58-beta-1-SNAPSHOT org.codehaus.jackson @@ -93,6 +89,19 @@ display-url-api 0.2 + + org.jenkins-ci.plugins + branch-api + 2.0.0-beta-1 + test + + + org.jenkins-ci.plugins + scm-api + ${scm-api.version} + tests + test + org.jenkins-ci.plugins.workflow workflow-multibranch diff --git a/src/images/bitbucket-branch.svg b/src/images/bitbucket-branch.svg new file mode 100644 index 000000000..438b2a7e0 --- /dev/null +++ b/src/images/bitbucket-branch.svg @@ -0,0 +1,2570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/bitbucket-logo.svg b/src/images/bitbucket-logo.svg new file mode 100644 index 000000000..2f23d6bef --- /dev/null +++ b/src/images/bitbucket-logo.svg @@ -0,0 +1,2419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/bitbucket-repository-git.svg b/src/images/bitbucket-repository-git.svg new file mode 100644 index 000000000..6b6122d70 --- /dev/null +++ b/src/images/bitbucket-repository-git.svg @@ -0,0 +1,2267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/bitbucket-repository-hg.svg b/src/images/bitbucket-repository-hg.svg new file mode 100644 index 000000000..f18bf08c1 --- /dev/null +++ b/src/images/bitbucket-repository-hg.svg @@ -0,0 +1,2363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/bitbucket-repository.svg b/src/images/bitbucket-repository.svg new file mode 100644 index 000000000..483c2da9d --- /dev/null +++ b/src/images/bitbucket-repository.svg @@ -0,0 +1,2245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + diff --git a/src/images/bitbucket-scmnavigator.svg b/src/images/bitbucket-scmnavigator.svg new file mode 100644 index 000000000..cc5461978 --- /dev/null +++ b/src/images/bitbucket-scmnavigator.svg @@ -0,0 +1,2674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + folder + directory + + + + + Andreas Nilsson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/make-inkscape.sh b/src/images/make-inkscape.sh new file mode 100755 index 000000000..4ee9868c2 --- /dev/null +++ b/src/images/make-inkscape.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +for src in "$dir"/*.svg +do + echo "Processing $(basename "$src")..." + file=$(basename "$src" | sed -e s/.svg/.png/ ) + for sz in 16 24 32 48 + do + mkdir -p "${dir}/../../src/main/webapp/images/${sz}x${sz}" + dst="${dir}/../../src/main/webapp/images/${sz}x${sz}/${file}" + if [ ! -e "$dst" -o "$src" -nt "$dst" ] + then + echo -n " generating ${sz}x${sz}..." + mkdir "${dir}/../../src/main/webapp/images/${sz}x${sz}" > /dev/null 2>&1 || true + inkscape -z -C -w ${sz} -h ${sz} -e "$dst" "$src" 2>&1 | grep "Bitmap saved as" + fi + done +done diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter.java deleted file mode 100644 index dc8bb2f0c..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cloudbees.jenkins.plugins.bitbucket; - -import java.util.List; - -import com.cloudbees.jenkins.plugins.bitbucket.CustomComputedFolderItemListener.Sniffer; -import com.cloudbees.jenkins.plugins.bitbucket.CustomComputedFolderItemListener.Sniffer.BranchMatch; - -import hudson.model.TopLevelItem; -import hudson.model.View; -import hudson.views.ViewJobFilter; -import jenkins.scm.api.SCMHead; - -abstract class AbstractBranchJobFilter extends ViewJobFilter { - public AbstractBranchJobFilter() {} - - @Override - public List filter(List added, List all, View filteringView) { - for (TopLevelItem i : all) { - if (added.contains(i)) continue; // already in there - - BranchMatch b = Sniffer.matchBranch(i); - if (b!=null) { - SCMHead head = b.getScmBranch().getHead(); - if (shouldShow(head)) { - added.add(i); - } - } - } - return added; - } - - protected abstract boolean shouldShow(SCMHead head); -} - diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketApiConnector.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketApiConnector.java index fb06b15a7..03c7a05a4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketApiConnector.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketApiConnector.java @@ -23,6 +23,8 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; +import hudson.model.Queue; +import hudson.model.queue.Tasks; import java.util.List; import javax.annotation.CheckForNull; @@ -44,6 +46,7 @@ import hudson.security.ACL; import hudson.util.ListBoxModel; import jenkins.scm.api.SCMSourceOwner; +import org.apache.commons.lang.StringUtils; public class BitbucketApiConnector { @@ -74,30 +77,48 @@ public BitbucketApi create(String owner, StandardUsernamePasswordCredentials cre @CheckForNull public T lookupCredentials(@CheckForNull SCMSourceOwner context, @CheckForNull String id, Class type) { - if (Util.fixEmpty(id) == null) { - return null; - } else { - if (id != null) { - return CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials(type, context, ACL.SYSTEM, - bitbucketDomainRequirements()), - CredentialsMatchers.allOf( - CredentialsMatchers.withId(id), - CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(type)))); - } - return null; + if (StringUtils.isNotBlank(id)) { + return CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + type, + context, + context instanceof Queue.Task + ? Tasks.getDefaultAuthenticationOf((Queue.Task) context) + : ACL.SYSTEM, + bitbucketDomainRequirements() + ), + CredentialsMatchers.allOf( + CredentialsMatchers.withId(id), + CredentialsMatchers.anyOf(CredentialsMatchers.instanceOf(type)) + ) + ); } + return null; } - public ListBoxModel fillCheckoutCredentials(StandardListBoxModel result, SCMSourceOwner context) { - result.withMatching(bitbucketCheckoutCredentialsMatcher(), CredentialsProvider.lookupCredentials( - StandardCredentials.class, context, ACL.SYSTEM, bitbucketDomainRequirements())); + public StandardListBoxModel fillCheckoutCredentials(StandardListBoxModel result, SCMSourceOwner context) { + result.includeMatchingAs( + context instanceof Queue.Task + ? Tasks.getDefaultAuthenticationOf((Queue.Task) context) + : ACL.SYSTEM, + context, + StandardCredentials.class, + bitbucketDomainRequirements(), + bitbucketCheckoutCredentialsMatcher() + ); return result; } - public ListBoxModel fillCredentials(StandardListBoxModel result, SCMSourceOwner context) { - result.withMatching(bitbucketCredentialsMatcher(), CredentialsProvider.lookupCredentials( - StandardUsernameCredentials.class, context, ACL.SYSTEM, bitbucketDomainRequirements())); + public StandardListBoxModel fillCredentials(StandardListBoxModel result, SCMSourceOwner context) { + result.includeMatchingAs( + context instanceof Queue.Task + ? Tasks.getDefaultAuthenticationOf((Queue.Task) context) + : ACL.SYSTEM, + context, + StandardUsernameCredentials.class, + bitbucketDomainRequirements(), + bitbucketCredentialsMatcher() + ); return result; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java index 9344974ba..320e41687 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java @@ -118,18 +118,6 @@ private static BitbucketNotifier getNotifier(BitbucketApi bitbucket) { return new BitbucketChangesetCommentNotifier(bitbucket); } - @CheckForNull - private static BitbucketApi buildBitbucketClientForBuild(Run build, BitbucketSCMSource source) { - Job job = build.getParent(); - StandardUsernamePasswordCredentials creds = source.getScanCredentials(); - SCMHead _head = SCMHead.HeadByItem.findHead(job); - if (_head instanceof SCMHeadWithOwnerAndRepo) { - SCMHeadWithOwnerAndRepo head = (SCMHeadWithOwnerAndRepo) _head; - return new BitbucketApiConnector(source.getBitbucketServerUrl()).create(head.getRepoOwner(), head.getRepoName(), creds); - } - return null; - } - @CheckForNull private static BitbucketSCMSource lookUpSCMSource(Run build) { ItemGroup multiBranchProject = build.getParent().getParent(); @@ -165,18 +153,13 @@ private static BitbucketSCMSource lookUpBitbucketSCMSource(final SCMSourceOwner private static void sendNotifications(Run build, TaskListener listener) { BitbucketSCMSource source = lookUpSCMSource(build); if (source != null && extractRevision(build) != null) { - BitbucketApi bitbucket = buildBitbucketClientForBuild(build, source); - if (bitbucket != null) { - if (source.getRepoOwner().equals(bitbucket.getOwner()) && - source.getRepository().equals(bitbucket.getRepositoryName())) { - listener.getLogger().println("[Bitbucket] Notifying commit build result"); - createBuildCommitStatus(build, listener, bitbucket); - } else { - listener.getLogger().println("[Bitbucket] Notifying pull request build result"); - createPullRequestCommitStatus(build, listener, bitbucket); - } + SCMHead head = SCMHead.HeadByItem.findHead(build.getParent()); + if (head instanceof PullRequestSCMHead) { + listener.getLogger().println("[Bitbucket] Notifying pull request build result"); + createPullRequestCommitStatus(build, listener, source.buildBitbucketClient((PullRequestSCMHead) head)); } else { - listener.getLogger().println("[Bitbucket] Can not get connection information from the source. Skipping notification..."); + listener.getLogger().println("[Bitbucket] Notifying commit build result"); + createBuildCommitStatus(build, listener, source.buildBitbucketClient()); } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketLink.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketLink.java new file mode 100644 index 000000000..da123bdd9 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketLink.java @@ -0,0 +1,100 @@ +package com.cloudbees.jenkins.plugins.bitbucket; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.Action; +import java.net.URL; +import jenkins.model.Jenkins; +import org.apache.commons.jelly.JellyContext; +import org.jenkins.ui.icon.Icon; +import org.jenkins.ui.icon.IconSet; +import org.jenkins.ui.icon.IconSpec; +import org.kohsuke.stapler.Stapler; + +/** + * @author Stephen Connolly + */ +public class BitbucketLink implements Action, IconSpec { + /** + * The icon class name to use. + */ + @NonNull + private final String iconClassName; + + /** + * Target of the hyperlink to take the user to. + */ + @NonNull + private final String url; + + public BitbucketLink(@NonNull String iconClassName, @NonNull String url) { + this.iconClassName = iconClassName; + this.url = url; + } + + @NonNull + public String getUrl() { + return url; + } + + @Override + public String getIconClassName() { + return iconClassName; + } + + @Override + public String getIconFileName() { + String iconClassName = getIconClassName(); + if (iconClassName != null) { + Icon icon = IconSet.icons.getIconByClassSpec(iconClassName + " icon-md"); + if (icon != null) { + JellyContext ctx = new JellyContext(); + ctx.setVariable("resURL", Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH); + return icon.getQualifiedUrl(ctx); + } + } + return null; + } + + @Override + public String getDisplayName() { + return Messages.BitbucketLink_DisplayName(); + } + + @Override + public String getUrlName() { + return url; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BitbucketLink that = (BitbucketLink) o; + + if (!iconClassName.equals(that.iconClassName)) { + return false; + } + return url.equals(that.url); + } + + @Override + public int hashCode() { + int result = iconClassName.hashCode(); + result = 31 * result + url.hashCode(); + return result; + } + + @Override + public String toString() { + return "BitbucketLink{" + + "iconClassName='" + iconClassName + '\'' + + ", url='" + url + '\'' + + '}'; + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java new file mode 100644 index 000000000..fda60eb19 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java @@ -0,0 +1,109 @@ +/* + * The MIT License + * + * Copyright 2016 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.cloudbees.jenkins.plugins.bitbucket; + +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.scm.api.metadata.AvatarMetadataAction; + +/** + * Invisible property that retains information about Bitbucket repository. + */ +public class BitbucketRepoMetadataAction extends AvatarMetadataAction{ + + private final String scm; + + public BitbucketRepoMetadataAction(@NonNull BitbucketRepository repo) { + this(repo.getScm()); + } + + public BitbucketRepoMetadataAction(String scm) { + this.scm = scm; + } + + /** + * {@inheritDoc} + */ + @Override + public String getAvatarIconClassName() { + if ("git".equals(scm)) { + return "icon-bitbucket-repo-git"; + } + if ("hg".equals(scm)) { + return "icon-bitbucket-repo-hg"; + } + return "icon-bitbucket-repo"; + } + + /** + * {@inheritDoc} + */ + @Override + public String getAvatarDescription() { + if ("git".equals(scm)) { + return Messages.BitbucketRepoMetadataAction_IconDescription_Git(); + } + if ("hg".equals(scm)) { + return Messages.BitbucketRepoMetadataAction_IconDescription_Hg(); + } + return Messages.BitbucketRepoMetadataAction_IconDescription(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BitbucketRepoMetadataAction that = (BitbucketRepoMetadataAction) o; + + return scm != null ? scm.equals(that.scm) : that.scm == null; + + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return scm != null ? scm.hashCode() : 0; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "BitbucketRepoMetadataAction{" + + "scm='" + scm + '\'' + + '}'; + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java index 20a560b4c..d759702bd 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -23,13 +23,25 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; +import hudson.console.HyperlinkNote; +import hudson.model.Action; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import javax.annotation.CheckForNull; +import jenkins.scm.api.SCMNavigatorEvent; +import jenkins.scm.api.SCMNavigatorOwner; +import jenkins.scm.api.SCMSourceCategory; +import jenkins.scm.api.metadata.ObjectMetadataAction; +import jenkins.scm.impl.UncategorizedSCMSourceCategory; import org.apache.commons.lang.StringUtils; +import org.jenkins.ui.icon.Icon; +import org.jenkins.ui.icon.IconSet; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; @@ -120,11 +132,15 @@ public void setSshPort(int sshPort) { @DataBoundSetter public void setBitbucketServerUrl(String url) { + if (StringUtils.equals(this.bitbucketServerUrl, url)) { + return; + } this.bitbucketServerUrl = Util.fixEmpty(url); if (this.bitbucketServerUrl != null) { // Remove a possible trailing slash this.bitbucketServerUrl = this.bitbucketServerUrl.replaceAll("/$", ""); } + resetId(); } @CheckForNull @@ -143,6 +159,12 @@ public void setBitbucketConnector(@NonNull BitbucketApiConnector bitbucketConnec return bitbucketConnector; } + @NonNull + @Override + protected String id() { + return bitbucketUrl() + "::" + repoOwner; + } + @Override public void visitSources(SCMSourceObserver observer) throws IOException, InterruptedException { TaskListener listener = observer.getListener(); @@ -155,9 +177,9 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru credentialsId, StandardUsernamePasswordCredentials.class); if (credentials == null) { - listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl); + listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", bitbucketUrl()); } else { - listener.getLogger().format("Connecting to %s using %s%n", bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, CredentialsNameProvider.name(credentials)); + listener.getLogger().format("Connecting to %s using %s%n", bitbucketUrl(), CredentialsNameProvider.name(credentials)); } List repositories; BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, credentials); @@ -172,6 +194,7 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru repositories = bitbucket.getRepositories(UserRoleInRepository.OWNER); } for (BitbucketRepository repo : repositories) { + checkInterrupt(); add(listener, observer, repo); } } @@ -183,11 +206,13 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep return; } listener.getLogger().format("Proposing %s%n", name); - if (Thread.interrupted()) { - throw new InterruptedException(); - } + checkInterrupt(); SCMSourceObserver.ProjectObserver projectObserver = observer.observe(name); - BitbucketSCMSource scmSource = new BitbucketSCMSource(null, repoOwner, name); + BitbucketSCMSource scmSource = new BitbucketSCMSource( + getId() + "::" + name, + repoOwner, + name + ); scmSource.setBitbucketConnector(getBitbucketConnector()); scmSource.setCredentialsId(credentialsId); scmSource.setCheckoutCredentialsId(checkoutCredentialsId); @@ -198,7 +223,68 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep projectObserver.complete(); } - @Extension + private String bitbucketUrl() { + return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); + } + + @NonNull + @Override + public List retrieveActions(@NonNull SCMNavigatorOwner owner, + @CheckForNull SCMNavigatorEvent event, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + // TODO when we have support for trusted events, use the details from event if event was from trusted source + listener.getLogger().printf("Looking up team details of %s...%n", getRepoOwner()); + List result = new ArrayList<>(); + StandardUsernamePasswordCredentials credentials = + getBitbucketConnector().lookupCredentials(owner, + credentialsId, StandardUsernamePasswordCredentials.class); + + String serverUrl = StringUtils.removeEnd(bitbucketUrl(), "/"); + if (credentials == null) { + listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", + serverUrl); + } else { + listener.getLogger().format("Connecting to %s using %s%n", + serverUrl, + CredentialsNameProvider.name(credentials)); + } + BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, credentials); + BitbucketTeam team = bitbucket.getTeam(); + if (team != null) { + String teamUrl = + StringUtils.defaultIfBlank(getLink(team.getLinks(), "html"), serverUrl + "/" + team.getName()); + String teamDisplayName = StringUtils.defaultIfBlank(team.getDisplayName(), team.getName()); + result.add(new ObjectMetadataAction( + teamDisplayName, + null, + teamUrl + )); + result.add(new BitbucketTeamMetadataAction(getLink(team.getLinks(), "avatar"))); + result.add(new BitbucketLink("icon-bitbucket-logo", teamUrl)); + listener.getLogger().printf("Team: %s%n", HyperlinkNote.encodeTo(teamUrl, teamDisplayName)); + } else { + String teamUrl = serverUrl + "/" + repoOwner; + result.add(new ObjectMetadataAction( + repoOwner, + null, + teamUrl + )); + result.add(new BitbucketLink("icon-bitbucket-logo", teamUrl)); + listener.getLogger().println("Could not resolve team details"); + } + return result; + } + + private static String getLink(Map links, String name) { + if (links == null) { + return null; + } + BitbucketHref href = links.get(name); + return href == null ? null : href.getHref(); + } + + @Extension public static class DescriptorImpl extends SCMNavigatorDescriptor { public static final String ANONYMOUS = BitbucketSCMSource.DescriptorImpl.ANONYMOUS; @@ -219,6 +305,11 @@ public String getIconFilePathPattern() { return "plugin/cloudbees-bitbucket-branch-source/images/:size/bitbucket-scmnavigator.png"; } + @Override + public String getIconClassName() { + return "icon-bitbucket-scmnavigator"; + } + @Override public SCMNavigator newInstance(String name) { return new BitbucketSCMNavigator(name, "", BitbucketSCMSource.DescriptorImpl.SAME); @@ -251,5 +342,116 @@ public ListBoxModel doFillCheckoutCredentialsIdItems(@AncestorInPath SCMSourceOw return result; } + @NonNull + @Override + protected SCMSourceCategory[] createCategories() { + return new SCMSourceCategory[]{ + new UncategorizedSCMSourceCategory(Messages._BitbucketSCMNavigator_UncategorizedSCMSourceCategory_DisplayName()) + }; + } + + static { + IconSet.icons.addIcon( + new Icon("icon-bitbucket-scm-navigator icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-scmnavigator.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-scm-navigator icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-scmnavigator.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-scm-navigator icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-scmnavigator.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-scm-navigator icon-xlg", + "plugin/cloudbees-bitbucket-branch-source/images/48x48/bitbucket-scmnavigator.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon("icon-bitbucket-logo icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-logo.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-logo icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-logo.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-logo icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-logo.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-logo icon-xlg", + "plugin/cloudbees-bitbucket-branch-source/images/48x48/bitbucket-logo.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-repository.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-repository.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-repository.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo icon-xlg", + "plugin/cloudbees-bitbucket-branch-source/images/48x48/bitbucket-repository.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-git icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-repository-git.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-git icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-repository-git.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-git icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-repository-git.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-git icon-xlg", + "plugin/cloudbees-bitbucket-branch-source/images/48x48/bitbucket-repository-git.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-hg icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-repository-hg.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-hg icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-repository-hg.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-hg icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-repository-hg.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-repo-hg icon-xlg", + "plugin/cloudbees-bitbucket-branch-source/images/48x48/bitbucket-repository-hg.png", + Icon.ICON_XLARGE_STYLE)); + + IconSet.icons.addIcon( + new Icon("icon-bitbucket-branch icon-sm", + "plugin/cloudbees-bitbucket-branch-source/images/16x16/bitbucket-branch.png", + Icon.ICON_SMALL_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-branch icon-md", + "plugin/cloudbees-bitbucket-branch-source/images/24x24/bitbucket-branch.png", + Icon.ICON_MEDIUM_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-branch icon-lg", + "plugin/cloudbees-bitbucket-branch-source/images/32x32/bitbucket-branch.png", + Icon.ICON_LARGE_STYLE)); + IconSet.icons.addIcon( + new Icon("icon-bitbucket-branch icon-xlg", + "plugin/cloudbees-bitbucket-branch-sourcee/images/48x48/bitbucket-branch.png", + Icon.ICON_XLARGE_STYLE)); + } } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java index 73754acba..60d782ac4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -23,37 +23,22 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -import com.cloudbees.plugins.credentials.CredentialsNameProvider; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.QueryParameter; - import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBranch; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRequestException; import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.plugins.credentials.CredentialsNameProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; - import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.Util; +import hudson.model.Action; import hudson.model.TaskListener; import hudson.plugins.git.BranchSpec; import hudson.plugins.git.GitSCM; @@ -68,15 +53,37 @@ import hudson.scm.SCM; import hudson.util.FormValidation; import hudson.util.ListBoxModel; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; import jenkins.plugins.git.AbstractGitSCMSource; import jenkins.plugins.git.AbstractGitSCMSource.SpecificRevisionBuildChooser; import jenkins.scm.api.SCMHead; +import jenkins.scm.api.SCMHeadCategory; +import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceCriteria; import jenkins.scm.api.SCMSourceDescriptor; +import jenkins.scm.api.SCMSourceEvent; import jenkins.scm.api.SCMSourceOwner; +import jenkins.scm.api.metadata.ObjectMetadataAction; +import jenkins.scm.impl.ChangeRequestSCMHeadCategory; +import jenkins.scm.impl.UncategorizedSCMHeadCategory; +import org.apache.commons.lang.StringUtils; +import org.eclipse.jgit.lib.Constants; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; /** * SCM source implementation for Bitbucket. @@ -234,6 +241,10 @@ public String getBitbucketServerUrl() { return bitbucketServerUrl; } + private String bitbucketUrl() { + return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); + } + public void setBitbucketConnector(@NonNull BitbucketApiConnector bitbucketConnector) { this.bitbucketConnector = bitbucketConnector; } @@ -264,24 +275,29 @@ public BitbucketApi buildBitbucketClient() { return getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); } - @Override - protected void retrieve(SCMHeadObserver observer, final TaskListener listener) throws IOException, - InterruptedException { + public BitbucketApi buildBitbucketClient(PullRequestSCMHead head) { + return getBitbucketConnector().create(head.getRepoOwner(), head.getRepository(), getScanCredentials()); + } + @Override + protected void retrieve(@CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer, + @CheckForNull SCMHeadEvent event, @NonNull TaskListener listener) + throws IOException, InterruptedException { StandardUsernamePasswordCredentials scanCredentials = getScanCredentials(); if (scanCredentials == null) { - listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl); + listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", bitbucketUrl()); } else { - listener.getLogger().format("Connecting to %s using %s%n", bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, CredentialsNameProvider.name(scanCredentials)); + listener.getLogger().format("Connecting to %s using %s%n", bitbucketUrl(), CredentialsNameProvider.name(scanCredentials)); } // Search branches - retrieveBranches(observer, listener); + retrieveBranches(criteria, observer, listener); // Search pull requests - retrievePullRequests(observer, listener); + retrievePullRequests(criteria, observer, listener); } - private void retrievePullRequests(SCMHeadObserver observer, final TaskListener listener) throws IOException { + private void retrievePullRequests(SCMSourceCriteria criteria, SCMHeadObserver observer, final TaskListener listener) + throws IOException, InterruptedException { String fullName = repoOwner + "/" + repository; listener.getLogger().println("Looking up " + fullName + " for pull requests"); @@ -289,14 +305,29 @@ private void retrievePullRequests(SCMHeadObserver observer, final TaskListener l if (bitbucket.isPrivate()) { List pulls = bitbucket.getPullRequests(); for (final BitbucketPullRequest pull : pulls) { + checkInterrupt(); listener.getLogger().println( "Checking PR from " + pull.getSource().getRepository().getFullName() + " and branch " + pull.getSource().getBranch().getName()); // Resolve full hash. See https://bitbucket.org/site/master/issues/11415/pull-request-api-should-return-full-commit - String hash = bitbucket.resolveSourceFullHash(pull); + + String hash = null; + try { + hash = bitbucket.resolveSourceFullHash(pull); + } catch (BitbucketRequestException e) { + if (e.getHttpCode() == 403) { + listener.getLogger().println( + "Do not have permission to view PR from " + pull.getSource().getRepository().getFullName() + " and branch " + + pull.getSource().getBranch().getName()); + } else { + e.printStackTrace( + listener.error("Cannot resolve hash: [%s]%n", pull.getSource().getCommit().getHash())); + } + continue; + } if (hash != null) { - observe(observer, listener, + observe(criteria, observer, listener, pull.getSource().getRepository().getOwnerName(), pull.getSource().getRepository().getRepositoryName(), pull.getSource().getBranch().getName(), @@ -314,7 +345,8 @@ private void retrievePullRequests(SCMHeadObserver observer, final TaskListener l } } - private void retrieveBranches(@NonNull final SCMHeadObserver observer, @NonNull TaskListener listener) + private void retrieveBranches(SCMSourceCriteria criteria, @NonNull final SCMHeadObserver observer, + @NonNull TaskListener listener) throws IOException, InterruptedException { String fullName = repoOwner + "/" + repository; listener.getLogger().println("Looking up " + fullName + " for branches"); @@ -322,20 +354,21 @@ private void retrieveBranches(@NonNull final SCMHeadObserver observer, @NonNull final BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); List branches = bitbucket.getBranches(); for (BitbucketBranch branch : branches) { + checkInterrupt(); listener.getLogger().println("Checking branch " + branch.getName() + " from " + fullName); - observe(observer, listener, repoOwner, repository, branch.getName(), + observe(criteria, observer, listener, repoOwner, repository, branch.getName(), branch.getRawNode(), null); } } - private void observe(SCMHeadObserver observer, final TaskListener listener, - final String owner, final String repositoryName, - final String branchName, final String hash, BitbucketPullRequest pr) throws IOException { + private void observe(SCMSourceCriteria criteria, SCMHeadObserver observer, final TaskListener listener, + final String owner, final String repositoryName, + final String branchName, final String hash, BitbucketPullRequest pr) throws IOException { if (isExcluded(branchName)) { return; } final BitbucketApi bitbucket = getBitbucketConnector().create(owner, repositoryName, getScanCredentials()); - SCMSourceCriteria branchCriteria = getCriteria(); + SCMSourceCriteria branchCriteria = criteria; if (branchCriteria != null) { SCMSourceCriteria.Probe probe = new SCMSourceCriteria.Probe() { @@ -368,7 +401,9 @@ public boolean exists(@NonNull String path) throws IOException { } } SCMRevision revision; - SCMHeadWithOwnerAndRepo head = new SCMHeadWithOwnerAndRepo(owner, repositoryName, branchName, pr); + SCMHead head = pr != null + ? new PullRequestSCMHead(owner, repositoryName, branchName, pr) + : new BranchSCMHead(branchName); if (getRepositoryType() == RepositoryType.MERCURIAL) { revision = new MercurialRevision(head, hash); } else { @@ -377,13 +412,21 @@ public boolean exists(@NonNull String path) throws IOException { observer.observe(head, revision); } + + @Override protected SCMRevision retrieve(SCMHead head, TaskListener listener) throws IOException, InterruptedException { - SCMHeadWithOwnerAndRepo bbHead = (SCMHeadWithOwnerAndRepo) head; - BitbucketApi bitbucket = getBitbucketConnector().create(bbHead.getRepoOwner(), bbHead.getRepoName(), getScanCredentials()); + BitbucketApi bitbucket = head instanceof PullRequestSCMHead + ? getBitbucketConnector().create( + ((PullRequestSCMHead) head).getRepoOwner(), + ((PullRequestSCMHead) head).getRepository(), + getScanCredentials() + ) + : getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); + String branchName = head instanceof PullRequestSCMHead ? ((PullRequestSCMHead) head).getBranchName() : head.getName(); List branches = bitbucket.getBranches(); for (BitbucketBranch b : branches) { - if (b.getName().equals(bbHead.getBranchName())) { + if (branchName.equals(b.getName())) { if (b.getRawNode() == null) { if (getBitbucketServerUrl() == null) { listener.getLogger().format("Cannot resolve the hash of the revision in branch %s", b.getName()); @@ -399,38 +442,75 @@ protected SCMRevision retrieve(SCMHead head, TaskListener listener) throws IOExc } } } - LOGGER.warning("No branch found in " + bbHead.getRepoOwner() + "/" + bbHead.getRepoName() + " with name [" + bbHead.getBranchName() + "]"); + LOGGER.log(Level.WARNING, "No branch found in {0}/{1} with name [{2}]", head instanceof PullRequestSCMHead + ? new Object[]{ + ((PullRequestSCMHead) head).getRepoOwner(), + ((PullRequestSCMHead) head).getRepository(), + ((PullRequestSCMHead) head).getBranchName()} + : new Object[]{repoOwner, repository, head.getName()}); return null; } @Override public SCM build(SCMHead head, SCMRevision revision) { - if (head instanceof SCMHeadWithOwnerAndRepo) { // Defensive, it must be always true - SCMHeadWithOwnerAndRepo h = (SCMHeadWithOwnerAndRepo) head; + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead h = (PullRequestSCMHead) head; if (getRepositoryType() == RepositoryType.MERCURIAL) { - MercurialSCM scm = new MercurialSCM(getRemote(h.getRepoOwner(), h.getRepoName())); + MercurialSCM scm = new MercurialSCM(getRemote(h.getRepoOwner(), h.getRepository())); // If no revision specified the branch name will be used as revision - scm.setRevision(revision instanceof MercurialRevision ? ((MercurialRevision) revision).getHash() : h.getBranchName()); + scm.setRevision(revision instanceof MercurialRevision + ? ((MercurialRevision) revision).getHash() + : h.getBranchName() + ); scm.setRevisionType(RevisionType.BRANCH); scm.setCredentialsId(getCheckoutEffectiveCredentials()); return scm; } else { // Defaults to Git - BuildChooser buildChooser = revision instanceof AbstractGitSCMSource.SCMRevisionImpl ? new SpecificRevisionBuildChooser( - (AbstractGitSCMSource.SCMRevisionImpl) revision) : new DefaultBuildChooser(); - return new GitSCM( - getGitRemoteConfigs(h), + BuildChooser buildChooser = revision instanceof AbstractGitSCMSource.SCMRevisionImpl + ? new SpecificRevisionBuildChooser((AbstractGitSCMSource.SCMRevisionImpl) revision) + : new DefaultBuildChooser(); + return new GitSCM(getGitRemoteConfigs(h), Collections.singletonList(new BranchSpec(h.getBranchName())), false, Collections.emptyList(), null, null, Collections.singletonList(new BuildChooserSetting(buildChooser))); } } - throw new IllegalArgumentException("An SCMHeadWithOwnerAndRepo required as parameter"); + if (head instanceof BranchSCMHead) { + if (getRepositoryType() == RepositoryType.MERCURIAL) { + MercurialSCM scm = new MercurialSCM(getRemote(repoOwner, repository)); + // If no revision specified the branch name will be used as revision + scm.setRevision(revision instanceof MercurialRevision + ? ((MercurialRevision) revision).getHash() + : head.getName() + ); + scm.setRevisionType(RevisionType.BRANCH); + scm.setCredentialsId(getCheckoutEffectiveCredentials()); + return scm; + } else { + // Defaults to Git + BuildChooser buildChooser = revision instanceof AbstractGitSCMSource.SCMRevisionImpl + ? new SpecificRevisionBuildChooser((AbstractGitSCMSource.SCMRevisionImpl) revision) + : new DefaultBuildChooser(); + return new GitSCM(getGitRemoteConfigs((BranchSCMHead)head), + Collections.singletonList(new BranchSpec(head.getName())), + false, Collections.emptyList(), + null, null, Collections.singletonList(new BuildChooserSetting(buildChooser))); + } + } + throw new IllegalArgumentException("Either PullRequestSCMHead or BranchSCMHead required as parameter"); + } + + protected List getGitRemoteConfigs(BranchSCMHead head) { + List result = new ArrayList(); + String remote = getRemote(repoOwner, repository); + result.add(new UserRemoteConfig(remote, getRemoteName(), "+refs/heads/" + head.getName(), getCheckoutEffectiveCredentials())); + return result; } - protected List getGitRemoteConfigs(SCMHeadWithOwnerAndRepo head) { + protected List getGitRemoteConfigs(PullRequestSCMHead head) { List result = new ArrayList(); - String remote = getRemote(head.getRepoOwner(), head.getRepoName()); + String remote = getRemote(head.getRepoOwner(), head.getRepository()); result.add(new UserRemoteConfig(remote, getRemoteName(), "+refs/heads/" + head.getBranchName(), getCheckoutEffectiveCredentials())); return result; } @@ -521,6 +601,66 @@ private String getCheckoutEffectiveCredentials() { } } + @NonNull + @Override + protected List retrieveActions(@CheckForNull SCMSourceEvent event, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + // TODO when we have support for trusted events, use the details from event if event was from trusted source + List result = new ArrayList<>(); + final BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); + BitbucketRepository r = bitbucket.getRepository(); + if (r != null) { + result.add(new BitbucketRepoMetadataAction(r)); + } + String serverUrl = StringUtils.removeEnd(bitbucketUrl(), "/"); + if (StringUtils.isNotEmpty(bitbucketServerUrl)) { + result.add(new BitbucketLink("icon-bitbucket-repo", + serverUrl + "/projects/" + repoOwner + "/repos/" + repository)); + result.add(new ObjectMetadataAction(r == null ? null : r.getFullName(), null, + serverUrl + "/projects/" + repoOwner + "/repos/" + repository)); + } else { + result.add(new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); + result.add(new ObjectMetadataAction(r == null ? null : r.getFullName(), null, + serverUrl + "/" + repoOwner + "/" + repository)); + } + return result; + } + + @NonNull + @Override + protected List retrieveActions(@NonNull SCMHead head, + @CheckForNull SCMHeadEvent event, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + // TODO when we have support for trusted events, use the details from event if event was from trusted source + List result = new ArrayList<>(); + String serverUrl = StringUtils.removeEnd(bitbucketUrl(), "/"); + if (StringUtils.isNotEmpty(bitbucketServerUrl)) { + String branchUrl; + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead pr = (PullRequestSCMHead) head; + branchUrl = "projects/" + repoOwner + "/repos/" + repository + "/pull-requests/"+pr.getId()+"/overview"; + } else { + branchUrl = "projects/" + repoOwner + "/repos/" + repository + "/compare/commits?sourceBranch=" + + URLEncoder.encode(Constants.R_HEADS + head.getName(), "UTF-8"); + } + result.add(new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); + result.add(new ObjectMetadataAction(null, null, serverUrl+"/"+branchUrl)); + } else { + String branchUrl; + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead pr = (PullRequestSCMHead) head; + branchUrl = repoOwner + "/" + repository + "/pull-requests/" + pr.getId(); + } else { + branchUrl = repoOwner + "/" + repository + "/branch/" + head.getName(); + } + result.add(new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); + result.add(new ObjectMetadataAction(null, null, serverUrl + "/" + branchUrl)); + } + return result; + } + @Extension public static class DescriptorImpl extends SCMSourceDescriptor { @@ -532,7 +672,8 @@ public String getDisplayName() { return "Bitbucket"; } - public FormValidation doCheckCredentialsId(@QueryParameter String value) { + public FormValidation doCheckCredentialsId(@QueryParameter String value, + @QueryParameter String bitbucketServerUrl) { if (!value.isEmpty()) { return FormValidation.ok(); } else { @@ -555,7 +696,7 @@ public static FormValidation doCheckBitbucketServerUrl(@QueryParameter String bi public ListBoxModel doFillCredentialsIdItems(@AncestorInPath SCMSourceOwner context, @QueryParameter String bitbucketServerUrl) { StandardListBoxModel result = new StandardListBoxModel(); - result.withEmptySelection(); + result.includeEmptyValue(); new BitbucketApiConnector(bitbucketServerUrl).fillCredentials(result, context); return result; } @@ -568,6 +709,15 @@ public ListBoxModel doFillCheckoutCredentialsIdItems(@AncestorInPath SCMSourceOw return result; } + @NonNull + @Override + protected SCMHeadCategory[] createCategories() { + return new SCMHeadCategory[]{ + new UncategorizedSCMHeadCategory(Messages._BitbucketSCMSource_UncategorizedSCMHeadCategory_DisplayName()), + new ChangeRequestSCMHeadCategory(Messages._BitbucketSCMSource_ChangeRequestSCMHeadCategory_DisplayName()) + // TODO add support for tags and maybe feature branch identification + }; + } } public static class MercurialRevision extends SCMRevision { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java new file mode 100644 index 000000000..722862a68 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java @@ -0,0 +1,119 @@ +/* + * The MIT License + * + * Copyright 2016 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.cloudbees.jenkins.plugins.bitbucket; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import jenkins.scm.api.metadata.AvatarMetadataAction; + +/** + * Invisible property that retains information about Bitbucket team. + */ +public class BitbucketTeamMetadataAction extends AvatarMetadataAction { + @CheckForNull + private final String avatarUrl; + + public BitbucketTeamMetadataAction(@CheckForNull String avatarUrl) { + this.avatarUrl = avatarUrl; + } + +// TODO when bitbucket supports serving avatars with a size request - currently only works if using gravatar or server +// /** +// * {@inheritDoc} +// */ +// @Override +// public String getAvatarImageOf(String size) { +// if (avatarUrl == null) { +// // fall back to the generic github org icon +// String image = avatarIconClassNameImageOf(getAvatarIconClassName(), size); +// return image != null +// ? image +// : (Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH +// + "/plugin/cloudbees-bitbucket-branch-source/images/" + size + "/bitbucket-logo.png"); +// } else { +// String[] xy = size.split("x"); +// if (xy.length == 0) return avatarUrl; +// if (avatarUrl.contains("?")) return avatarUrl + "&s=" + xy[0]; +// else return avatarUrl + "?s=" + xy[0]; +// } +// } + + + /** + * {@inheritDoc} + */ + @Override + public String getAvatarIconClassName() { + // TODO when bitbucket supports serving avatars with a size request + // return avatarUrl == null ? "icon-bitbucket-logo" : null; + return "icon-bitbucket-logo"; + } + + /** + * {@inheritDoc} + */ + @Override + public String getAvatarDescription() { + return Messages.BitbucketTeamMetadataAction_IconDescription(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BitbucketTeamMetadataAction that = (BitbucketTeamMetadataAction) o; + + if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) { + return false; + } + return true; + + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return (avatarUrl != null ? avatarUrl.hashCode() : 0); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "BitbucketTeamMetadataAction{" + + ", avatarUrl='" + avatarUrl + '\'' + + '}'; + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchJobFilter.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchJobFilter.java deleted file mode 100644 index 9a447423e..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchJobFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cloudbees.jenkins.plugins.bitbucket; - -import org.kohsuke.stapler.DataBoundConstructor; - -import hudson.Extension; -import hudson.model.Descriptor; -import hudson.views.ViewJobFilter; -import jenkins.scm.api.SCMHead; - -public class BranchJobFilter extends AbstractBranchJobFilter { - @DataBoundConstructor - public BranchJobFilter() {} - - @Override - protected boolean shouldShow(SCMHead head) { - return head instanceof SCMHeadWithOwnerAndRepo && ((SCMHeadWithOwnerAndRepo) head).getPullRequestId() == null; - } - - @Extension - public static class DescriptorImpl extends Descriptor { - @Override - public String getDisplayName() { - return "Bitbucket Branch Jobs Only"; - } - } - -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java new file mode 100644 index 000000000..a3214e8ac --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket; + +import jenkins.scm.api.SCMHead; + +/** + * {@link SCMHead} for a BitBucket branch. + * @since FIXME + */ +public class BranchSCMHead extends SCMHead { + + private static final long serialVersionUID = 1L; + + + public BranchSCMHead(String branchName) { + super(branchName); + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java deleted file mode 100644 index 181b9d1b5..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016, CloudBees, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.cloudbees.jenkins.plugins.bitbucket; - -import static java.util.Arrays.asList; - -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -import com.cloudbees.jenkins.plugins.bitbucket.CustomComputedFolderItemListener.Sniffer.OrgMatch; -import com.cloudbees.jenkins.plugins.bitbucket.CustomComputedFolderItemListener.Sniffer.RepoMatch; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; -import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; -import com.google.inject.Inject; - -import hudson.BulkChange; -import hudson.Extension; -import hudson.model.AbstractItem; -import hudson.model.AllView; -import hudson.model.Item; -import hudson.model.Job; -import hudson.model.ListView; -import hudson.model.listeners.ItemListener; -import hudson.views.StatusColumn; -import hudson.views.WeatherColumn; -import jenkins.branch.Branch; -import jenkins.branch.MultiBranchProject; -import jenkins.branch.OrganizationFolder; -import jenkins.scm.api.SCMNavigator; - -// TODO: this code should be mostly extracted to scm-api, and then the dependency to branch-api can be removed. -@Extension -public class CustomComputedFolderItemListener extends ItemListener { - - @Inject - private Applier applier; - - @Override - public void onUpdated(Item item) { - maybeApply(item); - } - - @Override - public void onCreated(Item item) { - maybeApply(item); - } - - private void maybeApply(Item item) { - OrgMatch f = Sniffer.matchOrg(item); - if (f != null && f.folder.getDisplayNameOrNull() == null) { - applier.applyOrg(f); - } - RepoMatch r = Sniffer.matchRepo(item); - if (r != null) { - applier.applyRepo(r); - } - } - - @Extension - @Restricted(NoExternalUse.class) - public static class Applier { - - public void applyOrg(OrgMatch f) { - if (UPDATING.get().add(f.folder)) { - BulkChange bc = new BulkChange(f.folder); - try { - StandardUsernamePasswordCredentials credentials = f.scm.getBitbucketConnector().lookupCredentials(f.folder, - f.scm.getCredentialsId(), StandardUsernamePasswordCredentials.class); - BitbucketTeam team = f.scm.getBitbucketConnector().create(f.scm.getRepoOwner(), credentials).getTeam(); - if (team != null) { - try { - f.folder.setDisplayName(team.getDisplayName()); - if (f.folder.getView("Repositories") == null && f.folder.getView("All") instanceof AllView) { - // need to set the default view - ListView lv = new ListView("Repositories"); - lv.getColumns().replaceBy(asList( - new StatusColumn(), - new WeatherColumn(), - new CustomNameJobColumn(Messages.class, Messages._ListViewColumn_Repository()) - )); - lv.setIncludeRegex(".*"); // show all - f.folder.addView(lv); - f.folder.deleteView(f.folder.getView("All")); - f.folder.setPrimaryView(lv); - } - bc.commit(); - } catch (IOException e) { - LOGGER.log(Level.INFO, "Can not set the Team/Project display name automatically. Skipping."); - LOGGER.log(Level.FINE, "StackTrace:", e); - } - } - } finally { - bc.abort(); - UPDATING.get().remove(f.folder); - } - } - } - - public void applyRepo(RepoMatch r) { - if (UPDATING.get().add(r.repo)) { - BulkChange bc = new BulkChange(r.repo); - try { - if (r.repo.getView("Branches") == null && r.repo.getView("All") instanceof AllView) { - // create initial views - ListView bv = new ListView("Branches"); - bv.getJobFilters().add(new BranchJobFilter()); - - ListView pv = new ListView("Pull Requests"); - pv.getJobFilters().add(new PullRequestJobFilter()); - - try { - r.repo.addView(bv); - r.repo.addView(pv); - r.repo.deleteView(r.repo.getView("All")); - r.repo.setPrimaryView(bv); - bc.commit(); - } catch (IOException e) { - LOGGER.log(Level.INFO, "Can not set the repo/PR views. Skipping."); - LOGGER.log(Level.FINE, "StackTrace:", e); - } - } - } finally { - bc.abort(); - UPDATING.get().remove(r.repo); - } - } - } - - /** - * Keeps track of what we are updating to avoid recursion, because {@link AbstractItem#save()} - * triggers {@link ItemListener}. - */ - private final ThreadLocal> UPDATING = new ThreadLocal>() { - @Override - protected Set initialValue() { - return new HashSet<>(); - } - }; - } - - public static class Sniffer { - - static class OrgMatch { - final OrganizationFolder folder; - final BitbucketSCMNavigator scm; - - public OrgMatch(OrganizationFolder folder, BitbucketSCMNavigator scm) { - this.folder = folder; - this.scm = scm; - } - } - - public static OrgMatch matchOrg(Object item) { - if (item instanceof OrganizationFolder) { - OrganizationFolder of = (OrganizationFolder) item; - List navigators = of.getNavigators(); - if (/* could be called from constructor */navigators != null && navigators.size() > 0) { - SCMNavigator n = navigators.get(0); - if (n instanceof BitbucketSCMNavigator) { - return new OrgMatch(of, (BitbucketSCMNavigator) n); - } - } - } - return null; - } - - static class RepoMatch extends OrgMatch { - final MultiBranchProject repo; - - public RepoMatch(OrgMatch x, MultiBranchProject repo) { - super(x.folder, x.scm); - this.repo = repo; - } - } - - public static RepoMatch matchRepo(Object item) { - if (item instanceof MultiBranchProject) { - MultiBranchProject repo = (MultiBranchProject) item; - OrgMatch org = matchOrg(repo.getParent()); - if (org != null) - return new RepoMatch(org, repo); - } - return null; - } - - static class BranchMatch extends RepoMatch { - final Job branch; - - public BranchMatch(RepoMatch x, Job branch) { - super(x,x.repo); - this.branch = branch; - } - - public Branch getScmBranch() { - return repo.getProjectFactory().getBranch(branch); - } - } - - public static BranchMatch matchBranch(Item item) { - if (item instanceof Job) { - Job branch = (Job) item; - RepoMatch x = matchRepo(item.getParent()); - if (x!=null) { - return new BranchMatch(x, branch); - } - } - return null; - } - - } - - private static final Logger LOGGER = Logger.getLogger(CustomComputedFolderItemListener.class.getName()); - -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn.java deleted file mode 100644 index ccb5af6d1..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.cloudbees.jenkins.plugins.bitbucket; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.jvnet.localizer.Localizable; -import org.jvnet.localizer.ResourceBundleHolder; -import org.kohsuke.stapler.DataBoundConstructor; - -import hudson.Extension; -import hudson.views.JobColumn; -import hudson.views.ListViewColumnDescriptor; -import jenkins.model.Jenkins; -import jenkins.util.NonLocalizable; - -public class CustomNameJobColumn extends JobColumn { - - /** - * Resource bundle name. - */ - private final String bundle; - private final String key; - - private transient Localizable loc; - - @DataBoundConstructor - public CustomNameJobColumn(String bundle, String key) { - this.bundle = bundle; - this.key = key; - readResolve(); - } - - public CustomNameJobColumn(Class bundle, Localizable loc) { - this.bundle = bundle.getName(); - this.key = loc.getKey(); - this.loc = loc; - } - - public String getBundle() { - return bundle; - } - - public String getKey() { - return loc.getKey(); - } - - public String getMessage() { - return loc.toString(); - } - - private Object readResolve() { - try { - loc = new Localizable( - ResourceBundleHolder.get(Jenkins.getActiveInstance().pluginManager.uberClassLoader.loadClass(bundle)), - key); - } catch (ClassNotFoundException e) { - LOGGER.log(Level.WARNING, "No such bundle: " + bundle); - loc = new NonLocalizable(bundle + ':' + key); - } - return this; - } - - @Extension - public static class DescriptorImpl extends ListViewColumnDescriptor { - @Override - public String getDisplayName() { - return "Repositories"; - } - - @Override - public boolean shownByDefault() { - return false; - } - } - - private static final Logger LOGGER = Logger.getLogger(CustomNameJobColumn.class.getName()); -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java index 1249d1ad7..1bf550e27 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java @@ -1,14 +1,42 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.cloudbees.jenkins.plugins.bitbucket; -import java.net.MalformedURLException; -import java.net.URL; - import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; - import edu.umd.cs.findbugs.annotations.NonNull; -import jenkins.scm.api.actions.ChangeRequestAction; +import java.net.MalformedURLException; +import java.net.URL; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; -public class PullRequestAction extends ChangeRequestAction { +/** + * Retained to help migrate legacy SCMHead instances + */ +@Deprecated +@Restricted(NoExternalUse.class) +class PullRequestAction { private final String number; private URL url; private final String title; @@ -25,23 +53,19 @@ public PullRequestAction(BitbucketPullRequest pr) { userLogin = pr.getAuthorLogin(); } - @Override @NonNull public String getId() { return number; } - @Override public URL getURL() { return url; } - @Override public String getTitle() { return title; } - @Override public String getAuthor() { return userLogin; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestJobFilter.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestJobFilter.java deleted file mode 100644 index 9a2527df7..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestJobFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cloudbees.jenkins.plugins.bitbucket; - -import org.kohsuke.stapler.DataBoundConstructor; - -import hudson.Extension; -import hudson.model.Descriptor; -import hudson.views.ViewJobFilter; -import jenkins.scm.api.SCMHead; - -public class PullRequestJobFilter extends AbstractBranchJobFilter { - @DataBoundConstructor - public PullRequestJobFilter() {} - - @Override - protected boolean shouldShow(SCMHead head) { - return head instanceof SCMHeadWithOwnerAndRepo && ((SCMHeadWithOwnerAndRepo) head).getPullRequestId() != null; - } - - @Extension - public static class DescriptorImpl extends Descriptor { - @Override - public String getDisplayName() { - return "Bitbucket Pull Request Jobs Only"; - } - } - -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java new file mode 100644 index 000000000..90f058f58 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java @@ -0,0 +1,94 @@ +/* + * The MIT License + * + * Copyright (c) 2016, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.cloudbees.jenkins.plugins.bitbucket; + +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; +import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.scm.api.mixin.ChangeRequestSCMHead; +import jenkins.scm.api.SCMHead; + +/** + * {@link SCMHead} for a BitBucket Pull request + * @since FIXME + */ +public class PullRequestSCMHead extends SCMHead implements ChangeRequestSCMHead { + + private static final String PR_BRANCH_PREFIX = "PR-"; + + private static final long serialVersionUID = 1L; + + private final String repoOwner; + + private final String repository; + + private final String branchName; + + private final String number; + + private final BranchSCMHead target; + + public PullRequestSCMHead(String repoOwner, String repository, String branchName, + String number, BranchSCMHead target) { + super(PR_BRANCH_PREFIX + number); + this.repoOwner = repoOwner; + this.repository = repository; + this.branchName = branchName; + this.number = number; + this.target = target; + } + + public PullRequestSCMHead(String repoOwner, String repository, String branchName, BitbucketPullRequest pr) { + super(PR_BRANCH_PREFIX + pr.getId()); + this.repoOwner = repoOwner; + this.repository = repository; + this.branchName = branchName; + this.number = pr.getId(); + this.target = new BranchSCMHead(pr.getDestination().getBranch().getName()); + } + + public String getRepoOwner() { + return repoOwner; + } + + public String getRepository() { + return repository; + } + + public String getBranchName() { + return branchName; + } + + @NonNull + @Override + public String getId() { + return number; + } + + @NonNull + @Override + public SCMHead getTarget() { + return target; + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java index ddad12ac6..f1b731ae9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java @@ -23,26 +23,27 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; -import java.util.LinkedList; -import java.util.List; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestDestination; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequest; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequestDestination; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequestSource; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ObjectStreamException; +import java.net.MalformedURLException; +import java.net.URL; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import hudson.model.Action; import jenkins.scm.api.SCMHead; +import jenkins.scm.api.actions.ChangeRequestAction; /** - * {@link SCMHead} extended with additional information: - *
    - *
  • {@link #repoOwner}: the repository owner
  • - *
  • {@link #repoName}: the repository name
  • - *
  • {@link #metadata}: metadata related to Pull Requests - null if this object is not representing a PR
  • - *
- * This information is required in this plugin since {@link BitbucketSCMSource} is processing pull requests - * and they are managed as separate repositories in Bitbucket without any reference to them in the destination - * repository. + * Legacy class retained to allow for graceful migration of serialized data. + * @deprecated use {@link PullRequestSCMHead} or {@link BranchSCMHead} */ +@Deprecated public class SCMHeadWithOwnerAndRepo extends SCMHead { private static final long serialVersionUID = 1L; @@ -51,62 +52,21 @@ public class SCMHeadWithOwnerAndRepo extends SCMHead { private final String repoName; - private PullRequestAction metadata = null; + private transient PullRequestAction metadata; - private static final String PR_BRANCH_PREFIX = "PR-"; - - public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName, BitbucketPullRequest pr) { + public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName) { super(branchName); this.repoOwner = repoOwner; this.repoName = repoName; - if (pr != null) { - this.metadata = new PullRequestAction(pr); - } } - public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName) { - this(repoOwner, repoName, branchName, null); - } - - public String getRepoOwner() { - return repoOwner; - } - - public String getRepoName() { - return repoName; - } - - /** - * @return the original branch name without the "PR-owner-" part. - */ - public String getBranchName() { - return super.getName(); - } - - /** - * Returns the prettified branch name by adding "PR-[ID]" if the branch is coming from a PR. - * Use {@link #getBranchName()} to get the real branch name. - */ - @Override - public String getName() { - return metadata != null ? PR_BRANCH_PREFIX + metadata.getId() : getBranchName(); - } - - @CheckForNull - public Integer getPullRequestId() { + private Object readResolve() throws ObjectStreamException { if (metadata != null) { - return Integer.parseInt(metadata.getId()); - } else { - return null; + // we just want to flag this as a PR, the legacy data did not contain the required information so + // we will end up triggering a rebuild on next index / event via take-over + return new PullRequestSCMHead(repoOwner, repoName, getName(), metadata.getId(), new BranchSCMHead("\u0000")); } + return new BranchSCMHead(getName()); } - @Override - public List getAllActions() { - List actions = new LinkedList(super.getAllActions()); - if (metadata != null) { - actions.add(metadata); - } - return actions; - } -} \ No newline at end of file +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketHref.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketHref.java new file mode 100644 index 000000000..787600e33 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketHref.java @@ -0,0 +1,31 @@ +package com.cloudbees.jenkins.plugins.bitbucket.api; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; + +/** + * A Href for something on bitbucket. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class BitbucketHref { + private String href; + + + // Used for marshalling/unmarshalling + @Restricted(DoNotUse.class) + public BitbucketHref() { + } + + public BitbucketHref(String href) { + this.href = href; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequest.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequest.java index c2b943b74..aafb91fbf 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequest.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequest.java @@ -36,6 +36,11 @@ public interface BitbucketPullRequest { */ BitbucketPullRequestSource getSource(); + /** + * @return the target repository of this pull request + */ + BitbucketPullRequestDestination getDestination(); + /** * @return pull request ID as provided by Bitbucket. It can be used for notifications. */ @@ -48,4 +53,4 @@ public interface BitbucketPullRequest { String getAuthorLogin(); -} \ No newline at end of file +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequestDestination.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequestDestination.java new file mode 100644 index 000000000..c6c7a167f --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequestDestination.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * + * Copyright (c) 2016 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +package com.cloudbees.jenkins.plugins.bitbucket.api; + +/** + * Represents a pull request destination, which is a repository and a branch in that repository. + */ +public interface BitbucketPullRequestDestination { + + /** + * @return source repository + */ + BitbucketRepository getRepository(); + + /** + * @return source branch to be merged in the pull request + */ + BitbucketBranch getBranch(); + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketTeam.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketTeam.java index 4e3c96b83..595c1396c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketTeam.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketTeam.java @@ -23,6 +23,8 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.api; +import java.util.Map; + /** * Represents a Bitbucket team (or a Project when working with Bitbucket Server). */ @@ -38,4 +40,10 @@ public interface BitbucketTeam { */ String getDisplayName(); -} \ No newline at end of file + /** + * Gets the links of the project. + * + * @return the links of the project. + */ + Map getLinks(); +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValue.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValue.java index de9fcb985..7c4127690 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValue.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValue.java @@ -23,6 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestDestination; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; @@ -30,6 +31,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketPullRequestValue implements BitbucketPullRequest { + private BitbucketPullRequestValueDestination destination; private BitbucketPullRequestValueRepository source; private String id; private String title; @@ -46,6 +48,15 @@ public void setSource(BitbucketPullRequestValueRepository source) { this.source = source; } + @Override + public BitbucketPullRequestValueDestination getDestination() { + return destination; + } + + public void setDestination(BitbucketPullRequestValueDestination destination) { + this.destination = destination; + } + public String getId() { return id; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValueDestination.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValueDestination.java new file mode 100644 index 000000000..30cbb300c --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValueDestination.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * + * Copyright (c) 2016 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest; + +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBranch; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestDestination; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudBranch; +import com.cloudbees.jenkins.plugins.bitbucket.client.branch.BitbucketCloudCommit; +import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudRepository; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.codehaus.jackson.annotate.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BitbucketPullRequestValueDestination implements BitbucketPullRequestDestination { + private BitbucketCloudRepository repository; + private BitbucketCloudBranch branch; + + @Override + @JsonProperty("repository") + public BitbucketRepository getRepository() { + return repository; + } + + @JsonProperty("repository") + public void setRepository(BitbucketCloudRepository repository) { + this.repository = repository; + } + + @Override + @JsonProperty("branch") + public BitbucketBranch getBranch() { + return branch; + } + + @JsonProperty("branch") + public void setBranch(BitbucketCloudBranch branch) { + this.branch = branch; + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudTeam.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudTeam.java index 1989f4656..de0edbf35 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudTeam.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/repository/BitbucketCloudTeam.java @@ -23,6 +23,8 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.client.repository; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; +import java.util.Map; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonProperty; @@ -37,6 +39,9 @@ public class BitbucketCloudTeam implements BitbucketTeam { @JsonProperty("display_name") private String displayName; + @JsonProperty("links") + private Map links; + @Override public String getName() { return name; @@ -55,4 +60,12 @@ public void setDisplayName(String displayName) { this.displayName = displayName; } + @Override + public Map getLinks() { + return links; + } + + public void setLinks(Map links) { + this.links = links; + } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequest.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequest.java index 5e24939e0..b9b0dd12f 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequest.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequest.java @@ -37,6 +37,9 @@ public class BitbucketServerPullRequest implements BitbucketPullRequest { @JsonProperty("fromRef") private BitbucketServerPullRequestSource source; + @JsonProperty("toRef") + private BitbucketServerPullRequestDestination destination; + private String title; private String link; @@ -52,6 +55,15 @@ public void setSource(BitbucketServerPullRequestSource source) { this.source = source; } + @Override + public BitbucketServerPullRequestDestination getDestination() { + return destination; + } + + public void setDestination(BitbucketServerPullRequestDestination destination) { + this.destination = destination; + } + @Override public String getId() { return id; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestDestination.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestDestination.java new file mode 100644 index 000000000..5d5fe03d0 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestDestination.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * + * Copyright (c) 2016 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest; + +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBranch; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestDestination; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepository; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.codehaus.jackson.annotate.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BitbucketServerPullRequestDestination implements BitbucketPullRequestDestination { + + @JsonProperty("displayId") + private String branchName; + + private BitbucketServerRepository repository; + + @Override + public BitbucketRepository getRepository() { + return repository; + } + + @Override + public BitbucketBranch getBranch() { + return new BitbucketServerBranch(branchName, null); + } + + public void setBranchName(String branchName) { + this.branchName = branchName; + } + + public void setRepository(BitbucketServerRepository repository) { + this.repository = repository; + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerProject.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerProject.java index 104f2e158..248651c47 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerProject.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerProject.java @@ -23,10 +23,16 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.server.client.repository; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonProperty; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; +import org.codehaus.jackson.map.annotate.JsonDeserialize; @JsonIgnoreProperties(ignoreUnknown = true) public class BitbucketServerProject implements BitbucketTeam { @@ -37,6 +43,10 @@ public class BitbucketServerProject implements BitbucketTeam { @JsonProperty("name") private String displayName; + @JsonProperty("links") + @JsonDeserialize(keyAs = String.class, contentAs = BitbucketHref[].class) + private Map links; + @Override public String getName() { return name; @@ -55,4 +65,25 @@ public void setDisplayName(String displayName) { this.displayName = displayName; } + @Override + public Map getLinks() { + Map result = new LinkedHashMap<>(links.size()); + for (Map.Entry entry: links.entrySet()) { + if (entry.getValue().length == 0) { + continue; + } + result.put(entry.getKey(), entry.getValue()[0]); + } + return result; + } + + // Do not call this setLinks or Jackson will have issues + public void links(Map links) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry: links.entrySet()) { + BitbucketHref[] value = entry.getValue() == null ? new BitbucketHref[0] : new BitbucketHref[]{entry.getValue()}; + result.put(entry.getKey(), value); + } + this.links = result; + } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter/config.jelly deleted file mode 100644 index 3637140da..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter/config.jelly +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/columnHeader.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/columnHeader.jelly deleted file mode 100644 index c6e4a9b5e..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/columnHeader.jelly +++ /dev/null @@ -1,4 +0,0 @@ - - - ${it.message} - \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/config.jelly deleted file mode 100644 index 0de73c50a..000000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/config.jelly +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties index d2d325f33..9e34b7aae 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties @@ -1,5 +1,13 @@ +BitbucketLink.DisplayName=Bitbucket +BitbucketSCMNavigator.UncategorizedSCMSourceCategory.DisplayName=Repositories +BitbucketSCMSource.UncategorizedSCMHeadCategory.DisplayName=Branches +BitbucketSCMSource.ChangeRequestSCMHeadCategory.DisplayName=Pull requests BitbucketSCMNavigator.DisplayName=Bitbucket Team/Project BitbucketSCMNavigator.Description=Scans a Bitbucket Cloud Team (or Bitbucket Server Project) for all repositories matching some defined markers. +BitbucketRepoMetadataAction.IconDescription=Bitbucket Repository +BitbucketRepoMetadataAction.IconDescription.Git=Bitbucket Git Repository +BitbucketRepoMetadataAction.IconDescription.Hg=Bitbucket Mercurial Repository +BitbucketTeamMetadataAction.IconDescription=Bitbucket Team/Project ListViewColumn.Repository=Repository ListViewColumn.Branch=Branch -ListViewColumn.PullRequest=Pull Request \ No newline at end of file +ListViewColumn.PullRequest=Pull Request diff --git a/src/main/webapp/images/16x16/bitbucket-branch.png b/src/main/webapp/images/16x16/bitbucket-branch.png new file mode 100644 index 000000000..b2ce6e4ab Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-branch.png differ diff --git a/src/main/webapp/images/16x16/bitbucket-logo.png b/src/main/webapp/images/16x16/bitbucket-logo.png new file mode 100644 index 000000000..c038daa48 Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-logo.png differ diff --git a/src/main/webapp/images/16x16/bitbucket-repository-git.png b/src/main/webapp/images/16x16/bitbucket-repository-git.png new file mode 100644 index 000000000..cd958f2ba Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-repository-git.png differ diff --git a/src/main/webapp/images/16x16/bitbucket-repository-hg.png b/src/main/webapp/images/16x16/bitbucket-repository-hg.png new file mode 100644 index 000000000..2bccb50cb Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-repository-hg.png differ diff --git a/src/main/webapp/images/16x16/bitbucket-repository.png b/src/main/webapp/images/16x16/bitbucket-repository.png new file mode 100644 index 000000000..455ade178 Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-repository.png differ diff --git a/src/main/webapp/images/16x16/bitbucket-scmnavigator.png b/src/main/webapp/images/16x16/bitbucket-scmnavigator.png new file mode 100644 index 000000000..29ac51d54 Binary files /dev/null and b/src/main/webapp/images/16x16/bitbucket-scmnavigator.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-branch.png b/src/main/webapp/images/24x24/bitbucket-branch.png new file mode 100644 index 000000000..7b116fca7 Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-branch.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-logo.png b/src/main/webapp/images/24x24/bitbucket-logo.png new file mode 100644 index 000000000..7b80f400c Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-logo.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-repository-git.png b/src/main/webapp/images/24x24/bitbucket-repository-git.png new file mode 100644 index 000000000..e0fab5b5e Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-repository-git.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-repository-hg.png b/src/main/webapp/images/24x24/bitbucket-repository-hg.png new file mode 100644 index 000000000..fe5381dca Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-repository-hg.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-repository.png b/src/main/webapp/images/24x24/bitbucket-repository.png new file mode 100644 index 000000000..743a839e5 Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-repository.png differ diff --git a/src/main/webapp/images/24x24/bitbucket-scmnavigator.png b/src/main/webapp/images/24x24/bitbucket-scmnavigator.png new file mode 100644 index 000000000..5342551b5 Binary files /dev/null and b/src/main/webapp/images/24x24/bitbucket-scmnavigator.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-branch.png b/src/main/webapp/images/32x32/bitbucket-branch.png new file mode 100644 index 000000000..00dc6d325 Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-branch.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-logo.png b/src/main/webapp/images/32x32/bitbucket-logo.png new file mode 100644 index 000000000..8cf8d459f Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-logo.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-repository-git.png b/src/main/webapp/images/32x32/bitbucket-repository-git.png new file mode 100644 index 000000000..4284cc882 Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-repository-git.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-repository-hg.png b/src/main/webapp/images/32x32/bitbucket-repository-hg.png new file mode 100644 index 000000000..24ae39804 Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-repository-hg.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-repository.png b/src/main/webapp/images/32x32/bitbucket-repository.png new file mode 100644 index 000000000..c0396917e Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-repository.png differ diff --git a/src/main/webapp/images/32x32/bitbucket-scmnavigator.png b/src/main/webapp/images/32x32/bitbucket-scmnavigator.png new file mode 100644 index 000000000..461b33fef Binary files /dev/null and b/src/main/webapp/images/32x32/bitbucket-scmnavigator.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-branch.png b/src/main/webapp/images/48x48/bitbucket-branch.png new file mode 100644 index 000000000..31628eb0d Binary files /dev/null and b/src/main/webapp/images/48x48/bitbucket-branch.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-logo.png b/src/main/webapp/images/48x48/bitbucket-logo.png new file mode 100644 index 000000000..790eb9005 Binary files /dev/null and b/src/main/webapp/images/48x48/bitbucket-logo.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-repository-git.png b/src/main/webapp/images/48x48/bitbucket-repository-git.png new file mode 100644 index 000000000..defab78d6 Binary files /dev/null and b/src/main/webapp/images/48x48/bitbucket-repository-git.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-repository-hg.png b/src/main/webapp/images/48x48/bitbucket-repository-hg.png new file mode 100644 index 000000000..4170107d8 Binary files /dev/null and b/src/main/webapp/images/48x48/bitbucket-repository-hg.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-repository.png b/src/main/webapp/images/48x48/bitbucket-repository.png new file mode 100644 index 000000000..41296e61f Binary files /dev/null and b/src/main/webapp/images/48x48/bitbucket-repository.png differ diff --git a/src/main/webapp/images/48x48/bitbucket-scmnavigator.png b/src/main/webapp/images/48x48/bitbucket-scmnavigator.png index 03fe00455..1b8a208ce 100644 Binary files a/src/main/webapp/images/48x48/bitbucket-scmnavigator.png and b/src/main/webapp/images/48x48/bitbucket-scmnavigator.png differ diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java index 311a19aae..d46b85c7a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.cloudbees.jenkins.plugins.bitbucket.client.pullrequest.BitbucketPullRequestValueDestination; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -159,6 +160,15 @@ private static BitbucketPullRequestValue getPullRequest() { pr.setSource(source); + BitbucketPullRequestValueDestination destination = new BitbucketPullRequestValueDestination(); + branch = new BitbucketCloudBranch(); + branch.setName("branch1"); + destination.setBranch(branch); + repository = new BitbucketCloudRepository(); + repository.setFullName("amuniz/test-repos"); + destination.setRepository(repository); + pr.setDestination(destination); + pr.setId("23"); pr.setAuthor(new BitbucketPullRequestValue.Author("amuniz")); pr.setLinks(new BitbucketPullRequestValue.Links("https://bitbucket.org/amuniz/test-repos/pull-requests/23")); diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java index a1df09864..01c73ce17 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningIntegrationTest.java @@ -113,6 +113,16 @@ public static class MultiBranchProjectImpl extends MultiBranchProject remoteConfigs = source.getGitRemoteConfigs(new SCMHeadWithOwnerAndRepo("amuniz", "test-repos", "branch1")); + List remoteConfigs = source.getGitRemoteConfigs(new BranchSCMHead("branch1")); assertEquals(1, remoteConfigs.size()); assertEquals("+refs/heads/branch1", remoteConfigs.get(0).getRefspec()); } @@ -84,7 +84,7 @@ public void remoteConfigsTest() { public void retrieveTest() throws IOException, InterruptedException { BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT); - SCMHeadWithOwnerAndRepo head = new SCMHeadWithOwnerAndRepo(repoOwner, repoName, branchName); + BranchSCMHead head = new BranchSCMHead(branchName); SCMRevision rev = source.retrieve(head, BitbucketClientMockUtils.getTaskListenerMock()); // Last revision on branch1 must be returned @@ -96,7 +96,7 @@ public void retrieveTest() throws IOException, InterruptedException { public void scanTest() throws IOException, InterruptedException { BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT); SCMHeadObserverImpl observer = new SCMHeadObserverImpl(); - source.retrieve(observer, BitbucketClientMockUtils.getTaskListenerMock()); + source.fetch(observer, BitbucketClientMockUtils.getTaskListenerMock()); // Only branch1 must be observed assertEquals(1, observer.getBranches().size()); @@ -107,7 +107,7 @@ public void scanTest() throws IOException, InterruptedException { public void scanTestPullRequests() throws IOException, InterruptedException { BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT, true); SCMHeadObserverImpl observer = new SCMHeadObserverImpl(); - source.retrieve(observer, BitbucketClientMockUtils.getTaskListenerMock()); + source.fetch(observer, BitbucketClientMockUtils.getTaskListenerMock()); // Only branch1 and my-feature-branch PR must be observed assertEquals(2, observer.getBranches().size()); @@ -129,7 +129,7 @@ public void mercurialSCMTest() { private SCM scmBuild(RepositoryType type) { BitbucketSCMSource source = getBitbucketSCMSourceMock(type); - return source.build(new SCMHeadWithOwnerAndRepo("amuniz", "test-repos", "branch1")); + return source.build(new BranchSCMHead("branch1")); } private BitbucketSCMSource getBitbucketSCMSourceMock(RepositoryType type, boolean includePullRequests) { @@ -155,6 +155,15 @@ public boolean isHead(Probe probe, TaskListener listener) throws IOException { return probe.exists("markerfile.txt"); } + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return getClass().isInstance(obj); + } }); return mocked; } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java index 24f744d94..80ba8b823 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -43,6 +44,7 @@ import jenkins.scm.api.SCMSourceObserver; import jenkins.scm.api.SCMSourceOwner; import jenkins.scm.api.SCMSourceObserver.ProjectObserver; +import org.mockito.Mockito; public class SCMNavigatorTest { @@ -51,7 +53,8 @@ public void teamRepositoriesDiscovering() throws IOException, InterruptedExcepti BitbucketSCMNavigator navigator = new BitbucketSCMNavigator("myteam", null, null); navigator.setPattern("repo(.*)"); navigator.setBitbucketConnector(getConnectorMock(RepositoryType.GIT, true)); - SCMSourceObserverImpl observer = new SCMSourceObserverImpl(BitbucketClientMockUtils.getTaskListenerMock()); + SCMSourceObserverImpl observer = new SCMSourceObserverImpl(BitbucketClientMockUtils.getTaskListenerMock(), + Mockito.mock(SCMSourceOwner.class)); navigator.visitSources(observer); assertEquals("myteam", navigator.getRepoOwner()); @@ -80,23 +83,28 @@ private class SCMSourceObserverImpl extends SCMSourceObserver { List observed = new ArrayList(); List projectObservers = new ArrayList(); TaskListener listener; + SCMSourceOwner owner; - public SCMSourceObserverImpl(TaskListener listener) { + public SCMSourceObserverImpl(TaskListener listener, SCMSourceOwner owner) { this.listener = listener; + this.owner = owner; } + @NonNull @Override public SCMSourceOwner getContext() { - return null; + return owner; } + @NonNull @Override public TaskListener getListener() { return listener; } + @NonNull @Override - public ProjectObserver observe(String projectName) throws IllegalArgumentException { + public ProjectObserver observe(@NonNull String projectName) throws IllegalArgumentException { observed.add(projectName); ProjectObserverImpl obs = new ProjectObserverImpl(); projectObservers.add(obs); @@ -104,7 +112,7 @@ public ProjectObserver observe(String projectName) throws IllegalArgumentExcepti } @Override - public void addAttribute(String key, Object value) throws IllegalArgumentException, ClassCastException { + public void addAttribute(@NonNull String key, Object value) throws IllegalArgumentException, ClassCastException { } public List getObserved() { @@ -120,12 +128,12 @@ public class ProjectObserverImpl extends ProjectObserver { private List sources = new ArrayList(); @Override - public void addSource(SCMSource source) { + public void addSource(@NonNull SCMSource source) { sources.add(source); } @Override - public void addAttribute(String key, Object value) throws IllegalArgumentException, ClassCastException { + public void addAttribute(@NonNull String key, Object value) throws IllegalArgumentException, ClassCastException { } @Override