From 0d5b3a49f770daae266a73c41a6cb933df7a25cc Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 28 Oct 2016 17:47:14 +0100 Subject: [PATCH 01/17] [JENKINS-39355 Follow-up] Unpicking some of the replicated hacks from the GitHub Org Folders plugin --- pom.xml | 4 +- .../bitbucket/AbstractBranchJobFilter.java | 34 ------- .../BitbucketBuildStatusNotifications.java | 4 +- .../plugins/bitbucket/BitbucketSCMHead.java | 12 +++ .../bitbucket/BitbucketSCMNavigator.java | 9 ++ .../plugins/bitbucket/BitbucketSCMSource.java | 49 +++++++---- .../plugins/bitbucket/BranchJobFilter.java | 27 ------ .../plugins/bitbucket/BranchSCMHead.java | 66 ++++++++++++++ .../CustomComputedFolderItemListener.java | 31 ------- .../bitbucket/PullRequestJobFilter.java | 27 ------ .../plugins/bitbucket/PullRequestSCMHead.java | 87 ++++++++++++++++++ .../bitbucket/SCMHeadWithOwnerAndRepo.java | 88 ++++++++----------- .../plugins/bitbucket/Messages.properties | 5 +- .../plugins/bitbucket/BranchScanningTest.java | 10 +-- 14 files changed, 259 insertions(+), 194 deletions(-) delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchJobFilter.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestJobFilter.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java diff --git a/pom.xml b/pom.xml index 55a61e601..bf2b29ca3 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,12 @@ org.jenkins-ci.plugins scm-api - 1.2 + 1.4-SNAPSHOT org.jenkins-ci.plugins branch-api - 1.10.2 + 1.12-SNAPSHOT org.jenkins-ci.plugins 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/BitbucketBuildStatusNotifications.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java index 9344974ba..5ab2302b5 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketBuildStatusNotifications.java @@ -123,8 +123,8 @@ private static BitbucketApi buildBitbucketClientForBuild(Run build, Bitbuck Job job = build.getParent(); StandardUsernamePasswordCredentials creds = source.getScanCredentials(); SCMHead _head = SCMHead.HeadByItem.findHead(job); - if (_head instanceof SCMHeadWithOwnerAndRepo) { - SCMHeadWithOwnerAndRepo head = (SCMHeadWithOwnerAndRepo) _head; + if (_head instanceof BitbucketSCMHead) { + BitbucketSCMHead head = (BitbucketSCMHead) _head; return new BitbucketApiConnector(source.getBitbucketServerUrl()).create(head.getRepoOwner(), head.getRepoName(), creds); } return null; diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java new file mode 100644 index 000000000..846a23926 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java @@ -0,0 +1,12 @@ +package com.cloudbees.jenkins.plugins.bitbucket; + +/** + * @author Stephen Connolly + */ +public interface BitbucketSCMHead { + String getRepoOwner(); + + String getRepoName(); + + String getBranchName(); +} 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..90de050b5 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -29,6 +29,8 @@ import javax.annotation.CheckForNull; +import jenkins.scm.api.SCMSourceCategory; +import jenkins.scm.impl.UncategorizedSCMSourceCategory; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; @@ -251,5 +253,12 @@ public ListBoxModel doFillCheckoutCredentialsIdItems(@AncestorInPath SCMSourceOw return result; } + @NonNull + @Override + protected SCMSourceCategory[] createCategories() { + return new SCMSourceCategory[]{ + new UncategorizedSCMSourceCategory(Messages._BitbucketSCMNavigator_UncategorizedSCMSourceCategory_DisplayName()) + }; + } } } 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..939f19ce6 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -34,6 +34,9 @@ import java.util.regex.Pattern; import com.cloudbees.plugins.credentials.CredentialsNameProvider; +import jenkins.scm.api.SCMHeadCategory; +import jenkins.scm.impl.ChangeRequestSCMHeadCategory; +import jenkins.scm.impl.UncategorizedSCMHeadCategory; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; @@ -265,7 +268,7 @@ public BitbucketApi buildBitbucketClient() { } @Override - protected void retrieve(SCMHeadObserver observer, final TaskListener listener) throws IOException, + protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, final TaskListener listener) throws IOException, InterruptedException { StandardUsernamePasswordCredentials scanCredentials = getScanCredentials(); @@ -276,12 +279,12 @@ protected void retrieve(SCMHeadObserver observer, final TaskListener listener) t } // 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 { String fullName = repoOwner + "/" + repository; listener.getLogger().println("Looking up " + fullName + " for pull requests"); @@ -296,7 +299,7 @@ private void retrievePullRequests(SCMHeadObserver observer, final TaskListener l // Resolve full hash. See https://bitbucket.org/site/master/issues/11415/pull-request-api-should-return-full-commit String hash = bitbucket.resolveSourceFullHash(pull); if (hash != null) { - observe(observer, listener, + observe(criteria, observer, listener, pull.getSource().getRepository().getOwnerName(), pull.getSource().getRepository().getRepositoryName(), pull.getSource().getBranch().getName(), @@ -314,7 +317,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"); @@ -323,19 +327,19 @@ private void retrieveBranches(@NonNull final SCMHeadObserver observer, @NonNull List branches = bitbucket.getBranches(); for (BitbucketBranch branch : branches) { 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 +372,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(owner, repositoryName, branchName); if (getRepositoryType() == RepositoryType.MERCURIAL) { revision = new MercurialRevision(head, hash); } else { @@ -377,9 +383,11 @@ 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; + BitbucketSCMHead bbHead = (BitbucketSCMHead) head; BitbucketApi bitbucket = getBitbucketConnector().create(bbHead.getRepoOwner(), bbHead.getRepoName(), getScanCredentials()); List branches = bitbucket.getBranches(); for (BitbucketBranch b : branches) { @@ -405,8 +413,8 @@ protected SCMRevision retrieve(SCMHead head, TaskListener listener) throws IOExc @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 BitbucketSCMHead) { // Defensive, it must be always true + BitbucketSCMHead h = (BitbucketSCMHead) head; if (getRepositoryType() == RepositoryType.MERCURIAL) { MercurialSCM scm = new MercurialSCM(getRemote(h.getRepoOwner(), h.getRepoName())); // If no revision specified the branch name will be used as revision @@ -428,7 +436,7 @@ public SCM build(SCMHead head, SCMRevision revision) { throw new IllegalArgumentException("An SCMHeadWithOwnerAndRepo required as parameter"); } - protected List getGitRemoteConfigs(SCMHeadWithOwnerAndRepo head) { + protected List getGitRemoteConfigs(BitbucketSCMHead head) { List result = new ArrayList(); String remote = getRemote(head.getRepoOwner(), head.getRepoName()); result.add(new UserRemoteConfig(remote, getRemoteName(), "+refs/heads/" + head.getBranchName(), getCheckoutEffectiveCredentials())); @@ -568,6 +576,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/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..8068f6367 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.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; + +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ObjectStreamException; +import java.net.URL; +import jenkins.scm.api.SCMHead; + +/** + * {@link SCMHead} for a BitBucket branch. + * @since FIXME + */ +public class BranchSCMHead extends SCMHead implements BitbucketSCMHead { + + private static final long serialVersionUID = 1L; + + private final String repoOwner; + + private final String repoName; + + public BranchSCMHead(String repoOwner, String repoName, String branchName) { + super(branchName); + this.repoOwner = repoOwner; + this.repoName = repoName; + } + + @Override + public String getRepoOwner() { + return repoOwner; + } + + @Override + public String getRepoName() { + return repoName; + } + + @Override + public String getBranchName() { + return getName(); + } + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java index 181b9d1b5..d4c7926e1 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java @@ -80,7 +80,6 @@ private void maybeApply(Item item) { } RepoMatch r = Sniffer.matchRepo(item); if (r != null) { - applier.applyRepo(r); } } @@ -124,36 +123,6 @@ public void applyOrg(OrgMatch f) { } } - 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}. 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..a01edc11e --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java @@ -0,0 +1,87 @@ +/* + * 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.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.Action; +import java.util.LinkedList; +import java.util.List; +import jenkins.scm.api.ChangeRequestSCMHead; +import jenkins.scm.api.SCMHead; +import jenkins.scm.api.actions.ChangeRequestAction; + +/** + * {@link SCMHead} for a BitBucket Pull request + * @since FIXME + */ +public class PullRequestSCMHead extends ChangeRequestSCMHead implements BitbucketSCMHead { + + private static final String PR_BRANCH_PREFIX = "PR-"; + + private static final long serialVersionUID = 1L; + + private final String repoOwner; + + private final String repoName; + + private final String branchName; + + @NonNull + private PullRequestAction metadata; + + public PullRequestSCMHead(String repoOwner, String repoName, String branchName, BitbucketPullRequest pr) { + super(PR_BRANCH_PREFIX + pr.getId()); + this.repoOwner = repoOwner; + this.repoName = repoName; + this.branchName = branchName; + this.metadata = new PullRequestAction(pr); + } + + public String getRepoOwner() { + return repoOwner; + } + + public String getRepoName() { + return repoName; + } + + @Override + public String getBranchName() { + return branchName; + } + + @CheckForNull + public Integer getPullRequestId() { + return Integer.parseInt(metadata.getId()); + } + + @NonNull + @Override + public ChangeRequestAction getChangeRequestAction() { + return metadata; + } + +} 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..11b3a9b1c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java @@ -23,6 +23,10 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ObjectStreamException; +import java.net.URL; import java.util.LinkedList; import java.util.List; @@ -42,7 +46,9 @@ * 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. + * @deprecated use {@link PullRequestSCMHead} or {@link BranchSCMHead} */ +@Deprecated public class SCMHeadWithOwnerAndRepo extends SCMHead { private static final long serialVersionUID = 1L; @@ -51,62 +57,46 @@ 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 SCMHead readResolve() throws ObjectStreamException { if (metadata != null) { - return Integer.parseInt(metadata.getId()); - } else { - return null; + return new PullRequestSCMHead(repoOwner, repoName, super.getName(), new BitbucketPullRequest() { + @Override + public BitbucketPullRequestSource getSource() { + return null; + } + + @NonNull + @Override + public String getId() { + return metadata.getId(); + } + + @Override + public String getTitle() { + return metadata.getTitle(); + } + + @Override + public String getLink() { + URL url = metadata.getURL(); + return url == null ? null : url.toString(); + } + + @Override + public String getAuthorLogin() { + return metadata.getAuthor(); + } + }); } + return new BranchSCMHead(repoOwner, repoName, 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/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties index d2d325f33..88a6d2666 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,8 @@ +BitbucketSCMNavigator.UncategorizedSCMSourceCategory.DisplayName=Repositories +BitbucketSCMSource.UncategorizedSCMHeadCategory.DisplayName=Repositories +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. ListViewColumn.Repository=Repository ListViewColumn.Branch=Branch -ListViewColumn.PullRequest=Pull Request \ No newline at end of file +ListViewColumn.PullRequest=Pull Request diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java index 731b5b67f..7c7ee096a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java @@ -75,7 +75,7 @@ public void uriResolverTest() { @Test public void remoteConfigsTest() { BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT); - List remoteConfigs = source.getGitRemoteConfigs(new SCMHeadWithOwnerAndRepo("amuniz", "test-repos", "branch1")); + List remoteConfigs = source.getGitRemoteConfigs(new BranchSCMHead("amuniz", "test-repos", "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(repoOwner, repoName, 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.retrieve(null, 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.retrieve(null, 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("amuniz", "test-repos", "branch1")); } private BitbucketSCMSource getBitbucketSCMSourceMock(RepositoryType type, boolean includePullRequests) { From a2da353590b752459c3a123b0d9c42e2150a5093 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 28 Oct 2016 17:49:57 +0100 Subject: [PATCH 02/17] [JENKINS-39355 Follow-up] More dead code removed --- .../CustomComputedFolderItemListener.java | 13 ---- .../bitbucket/CustomNameJobColumn.java | 77 ------------------- .../AbstractBranchJobFilter/config.jelly | 3 - .../CustomNameJobColumn/columnHeader.jelly | 4 - .../CustomNameJobColumn/config.jelly | 9 --- 5 files changed, 106 deletions(-) delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn.java delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/AbstractBranchJobFilter/config.jelly delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/columnHeader.jelly delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/CustomNameJobColumn/config.jelly diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java index d4c7926e1..2b4fe4d85 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java @@ -97,19 +97,6 @@ public void applyOrg(OrgMatch f) { 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."); 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/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 @@ - - - - - - - - - From 76c087fe5bd62e61bfcbbe61b9731209a720ea11 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Tue, 1 Nov 2016 12:30:11 +0000 Subject: [PATCH 03/17] [JENKINS-39355 Follow-up] Remove data duplication from BranchSCMHead --- .../BitbucketBuildStatusNotifications.java | 29 +---- .../plugins/bitbucket/BitbucketSCMHead.java | 12 -- .../plugins/bitbucket/BitbucketSCMSource.java | 118 ++++++++++++------ .../plugins/bitbucket/BranchSCMHead.java | 29 +---- .../CustomComputedFolderItemListener.java | 3 - .../plugins/bitbucket/PullRequestSCMHead.java | 16 +-- .../bitbucket/SCMHeadWithOwnerAndRepo.java | 8 +- .../plugins/bitbucket/BranchScanningTest.java | 10 +- 8 files changed, 102 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java 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 5ab2302b5..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 BitbucketSCMHead) { - BitbucketSCMHead head = (BitbucketSCMHead) _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/BitbucketSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java deleted file mode 100644 index 846a23926..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMHead.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.cloudbees.jenkins.plugins.bitbucket; - -/** - * @author Stephen Connolly - */ -public interface BitbucketSCMHead { - String getRepoOwner(); - - String getRepoName(); - - String getBranchName(); -} 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 939f19ce6..2dc9c6ba2 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -23,36 +23,16 @@ */ 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 jenkins.scm.api.SCMHeadCategory; -import jenkins.scm.impl.ChangeRequestSCMHeadCategory; -import jenkins.scm.impl.UncategorizedSCMHeadCategory; -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.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; @@ -71,15 +51,32 @@ 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.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.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.SCMSourceOwner; +import jenkins.scm.impl.ChangeRequestSCMHeadCategory; +import jenkins.scm.impl.UncategorizedSCMHeadCategory; +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; /** * SCM source implementation for Bitbucket. @@ -267,6 +264,10 @@ public BitbucketApi buildBitbucketClient() { return getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); } + public BitbucketApi buildBitbucketClient(PullRequestSCMHead head) { + return getBitbucketConnector().create(head.getRepoOwner(), head.getRepository(), getScanCredentials()); + } + @Override protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, final TaskListener listener) throws IOException, InterruptedException { @@ -374,7 +375,7 @@ public boolean exists(@NonNull String path) throws IOException { SCMRevision revision; SCMHead head = pr != null ? new PullRequestSCMHead(owner, repositoryName, branchName, pr) - : new BranchSCMHead(owner, repositoryName, branchName); + : new BranchSCMHead(branchName); if (getRepositoryType() == RepositoryType.MERCURIAL) { revision = new MercurialRevision(head, hash); } else { @@ -387,11 +388,17 @@ public boolean exists(@NonNull String path) throws IOException { @Override protected SCMRevision retrieve(SCMHead head, TaskListener listener) throws IOException, InterruptedException { - BitbucketSCMHead bbHead = (BitbucketSCMHead) 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()); @@ -407,38 +414,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 BitbucketSCMHead) { // Defensive, it must be always true - BitbucketSCMHead h = (BitbucketSCMHead) 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(BitbucketSCMHead 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; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java index 8068f6367..a3214e8ac 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BranchSCMHead.java @@ -23,44 +23,19 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.ObjectStreamException; -import java.net.URL; import jenkins.scm.api.SCMHead; /** * {@link SCMHead} for a BitBucket branch. * @since FIXME */ -public class BranchSCMHead extends SCMHead implements BitbucketSCMHead { +public class BranchSCMHead extends SCMHead { private static final long serialVersionUID = 1L; - private final String repoOwner; - private final String repoName; - - public BranchSCMHead(String repoOwner, String repoName, String branchName) { + public BranchSCMHead(String branchName) { super(branchName); - this.repoOwner = repoOwner; - this.repoName = repoName; - } - - @Override - public String getRepoOwner() { - return repoOwner; - } - - @Override - public String getRepoName() { - return repoName; - } - - @Override - public String getBranchName() { - return getName(); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java index 2b4fe4d85..e39fcd130 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java @@ -78,9 +78,6 @@ private void maybeApply(Item item) { if (f != null && f.folder.getDisplayNameOrNull() == null) { applier.applyOrg(f); } - RepoMatch r = Sniffer.matchRepo(item); - if (r != null) { - } } @Extension diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java index a01edc11e..5a2e0b110 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java @@ -26,9 +26,6 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import hudson.model.Action; -import java.util.LinkedList; -import java.util.List; import jenkins.scm.api.ChangeRequestSCMHead; import jenkins.scm.api.SCMHead; import jenkins.scm.api.actions.ChangeRequestAction; @@ -37,7 +34,7 @@ * {@link SCMHead} for a BitBucket Pull request * @since FIXME */ -public class PullRequestSCMHead extends ChangeRequestSCMHead implements BitbucketSCMHead { +public class PullRequestSCMHead extends ChangeRequestSCMHead { private static final String PR_BRANCH_PREFIX = "PR-"; @@ -45,17 +42,17 @@ public class PullRequestSCMHead extends ChangeRequestSCMHead implements Bitbucke private final String repoOwner; - private final String repoName; + private final String repository; private final String branchName; @NonNull private PullRequestAction metadata; - public PullRequestSCMHead(String repoOwner, String repoName, String branchName, BitbucketPullRequest pr) { + public PullRequestSCMHead(String repoOwner, String repository, String branchName, BitbucketPullRequest pr) { super(PR_BRANCH_PREFIX + pr.getId()); this.repoOwner = repoOwner; - this.repoName = repoName; + this.repository = repository; this.branchName = branchName; this.metadata = new PullRequestAction(pr); } @@ -64,11 +61,10 @@ public String getRepoOwner() { return repoOwner; } - public String getRepoName() { - return repoName; + public String getRepository() { + return repository; } - @Override public String getBranchName() { return branchName; } 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 11b3a9b1c..7422ccb57 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java @@ -27,13 +27,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ObjectStreamException; import java.net.URL; -import java.util.LinkedList; -import java.util.List; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import hudson.model.Action; import jenkins.scm.api.SCMHead; /** @@ -65,7 +61,7 @@ public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchN this.repoName = repoName; } - private SCMHead readResolve() throws ObjectStreamException { + private Object readResolve() throws ObjectStreamException { if (metadata != null) { return new PullRequestSCMHead(repoOwner, repoName, super.getName(), new BitbucketPullRequest() { @Override @@ -96,7 +92,7 @@ public String getAuthorLogin() { } }); } - return new BranchSCMHead(repoOwner, repoName, getName()); + return new BranchSCMHead(getName()); } } diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java index 7c7ee096a..2d7fa75a4 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BranchScanningTest.java @@ -75,7 +75,7 @@ public void uriResolverTest() { @Test public void remoteConfigsTest() { BitbucketSCMSource source = getBitbucketSCMSourceMock(RepositoryType.GIT); - List remoteConfigs = source.getGitRemoteConfigs(new BranchSCMHead("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); - BranchSCMHead head = new BranchSCMHead(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(null, 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(null, 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 BranchSCMHead("amuniz", "test-repos", "branch1")); + return source.build(new BranchSCMHead("branch1")); } private BitbucketSCMSource getBitbucketSCMSourceMock(RepositoryType type, boolean includePullRequests) { From b932be3ed6e9f65bb38a74a64f0f0ba58dd583f7 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 3 Nov 2016 16:24:36 +0000 Subject: [PATCH 04/17] [JENKINS-39355 Follow-up] Sync with latest scm-api improvements - Also add bitbucket links and icons --- src/images/bitbucket-branch.svg | 2570 ++++++++++++++++ src/images/bitbucket-logo.svg | 2419 +++++++++++++++ src/images/bitbucket-repository-git.svg | 2267 ++++++++++++++ src/images/bitbucket-repository-hg.svg | 2363 +++++++++++++++ src/images/bitbucket-repository.svg | 2245 ++++++++++++++ src/images/bitbucket-scmnavigator.svg | 2674 +++++++++++++++++ src/images/make-inkscape.sh | 19 + .../plugins/bitbucket/BitbucketLink.java | 100 + .../BitbucketRepoMetadataAction.java | 131 + .../bitbucket/BitbucketSCMNavigator.java | 155 +- .../plugins/bitbucket/BitbucketSCMSource.java | 41 + .../BitbucketTeamMetadataAction.java | 179 ++ .../plugins/bitbucket/api/BitbucketHref.java | 31 + .../plugins/bitbucket/api/BitbucketTeam.java | 10 +- .../client/repository/BitbucketCloudTeam.java | 13 + .../repository/BitbucketServerProject.java | 13 + .../plugins/bitbucket/Messages.properties | 5 + .../webapp/images/16x16/bitbucket-branch.png | Bin 0 -> 711 bytes .../webapp/images/16x16/bitbucket-logo.png | Bin 0 -> 790 bytes .../images/16x16/bitbucket-repository-git.png | Bin 0 -> 990 bytes .../images/16x16/bitbucket-repository-hg.png | Bin 0 -> 1024 bytes .../images/16x16/bitbucket-repository.png | Bin 0 -> 817 bytes .../images/16x16/bitbucket-scmnavigator.png | Bin 0 -> 1074 bytes .../webapp/images/24x24/bitbucket-branch.png | Bin 0 -> 902 bytes .../webapp/images/24x24/bitbucket-logo.png | Bin 0 -> 1114 bytes .../images/24x24/bitbucket-repository-git.png | Bin 0 -> 1461 bytes .../images/24x24/bitbucket-repository-hg.png | Bin 0 -> 1559 bytes .../images/24x24/bitbucket-repository.png | Bin 0 -> 1144 bytes .../images/24x24/bitbucket-scmnavigator.png | Bin 0 -> 1689 bytes .../webapp/images/32x32/bitbucket-branch.png | Bin 0 -> 1095 bytes .../webapp/images/32x32/bitbucket-logo.png | Bin 0 -> 1420 bytes .../images/32x32/bitbucket-repository-git.png | Bin 0 -> 1861 bytes .../images/32x32/bitbucket-repository-hg.png | Bin 0 -> 2052 bytes .../images/32x32/bitbucket-repository.png | Bin 0 -> 1429 bytes .../images/32x32/bitbucket-scmnavigator.png | Bin 0 -> 2345 bytes .../webapp/images/48x48/bitbucket-branch.png | Bin 0 -> 1489 bytes .../webapp/images/48x48/bitbucket-logo.png | Bin 0 -> 2080 bytes .../images/48x48/bitbucket-repository-git.png | Bin 0 -> 2881 bytes .../images/48x48/bitbucket-repository-hg.png | Bin 0 -> 3483 bytes .../images/48x48/bitbucket-repository.png | Bin 0 -> 2323 bytes .../images/48x48/bitbucket-scmnavigator.png | Bin 4448 -> 3793 bytes 41 files changed, 15233 insertions(+), 2 deletions(-) create mode 100644 src/images/bitbucket-branch.svg create mode 100644 src/images/bitbucket-logo.svg create mode 100644 src/images/bitbucket-repository-git.svg create mode 100644 src/images/bitbucket-repository-hg.svg create mode 100644 src/images/bitbucket-repository.svg create mode 100644 src/images/bitbucket-scmnavigator.svg create mode 100755 src/images/make-inkscape.sh create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketLink.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketHref.java create mode 100644 src/main/webapp/images/16x16/bitbucket-branch.png create mode 100644 src/main/webapp/images/16x16/bitbucket-logo.png create mode 100644 src/main/webapp/images/16x16/bitbucket-repository-git.png create mode 100644 src/main/webapp/images/16x16/bitbucket-repository-hg.png create mode 100644 src/main/webapp/images/16x16/bitbucket-repository.png create mode 100644 src/main/webapp/images/16x16/bitbucket-scmnavigator.png create mode 100644 src/main/webapp/images/24x24/bitbucket-branch.png create mode 100644 src/main/webapp/images/24x24/bitbucket-logo.png create mode 100644 src/main/webapp/images/24x24/bitbucket-repository-git.png create mode 100644 src/main/webapp/images/24x24/bitbucket-repository-hg.png create mode 100644 src/main/webapp/images/24x24/bitbucket-repository.png create mode 100644 src/main/webapp/images/24x24/bitbucket-scmnavigator.png create mode 100644 src/main/webapp/images/32x32/bitbucket-branch.png create mode 100644 src/main/webapp/images/32x32/bitbucket-logo.png create mode 100644 src/main/webapp/images/32x32/bitbucket-repository-git.png create mode 100644 src/main/webapp/images/32x32/bitbucket-repository-hg.png create mode 100644 src/main/webapp/images/32x32/bitbucket-repository.png create mode 100644 src/main/webapp/images/32x32/bitbucket-scmnavigator.png create mode 100644 src/main/webapp/images/48x48/bitbucket-branch.png create mode 100644 src/main/webapp/images/48x48/bitbucket-logo.png create mode 100644 src/main/webapp/images/48x48/bitbucket-repository-git.png create mode 100644 src/main/webapp/images/48x48/bitbucket-repository-hg.png create mode 100644 src/main/webapp/images/48x48/bitbucket-repository.png 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/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..070768099 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java @@ -0,0 +1,131 @@ +/* + * 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.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.branch.MetadataAction; +import jenkins.branch.MultiBranchProject; + +/** + * Invisible property on {@link MultiBranchProject} + * that retains information about GitHub repository. + * + * @author Kohsuke Kawaguchi + */ +public class BitbucketRepoMetadataAction extends MetadataAction { + + private final String scm; + + public BitbucketRepoMetadataAction(@NonNull BitbucketRepository repo) { + this(repo.getScm()); + } + + public BitbucketRepoMetadataAction(String scm) { + this.scm = scm; + } + + /** + * {@inheritDoc} + */ + @CheckForNull + @Override + public String getObjectDescription() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getObjectUrl() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getFolderIconClassName() { + 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 getFolderIconDescription() { + 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 90de050b5..adbd64ab3 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -23,15 +23,23 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; +import com.cloudbees.plugins.credentials.common.StandardCredentials; +import hudson.console.HyperlinkNote; +import hudson.model.Action; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import javax.annotation.CheckForNull; +import jenkins.scm.api.SCMNavigatorOwner; import jenkins.scm.api.SCMSourceCategory; 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; @@ -200,7 +208,43 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep projectObserver.complete(); } - @Extension + @NonNull + @Override + public Map, Action> retrieveActions(@NonNull SCMNavigatorOwner owner, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + listener.getLogger().printf("Looking up team details of %s...%n", getRepoOwner()); + Map, Action> result = new HashMap<>(); + StandardUsernamePasswordCredentials credentials = + getBitbucketConnector().lookupCredentials(owner, + credentialsId, StandardUsernamePasswordCredentials.class); + + String serverUrl = StringUtils.removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); + 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) { + BitbucketTeamMetadataAction metadataAction = new BitbucketTeamMetadataAction(team); + result.put(BitbucketTeamMetadataAction.class, metadataAction); + String teamUrl = StringUtils.defaultIfBlank(metadataAction.getTeamUrl(), serverUrl + "/" + team.getName()); + result.put(BitbucketLink.class, new BitbucketLink("icon-bitbucket-logo", teamUrl)); + listener.getLogger().printf("Team URL: %s%n", HyperlinkNote.encodeTo(teamUrl, team.getName())); + } else { + String teamUrl = serverUrl + "/" + repoOwner; + result.put(BitbucketLink.class, new BitbucketLink("icon-bitbucket-logo", teamUrl)); + listener.getLogger().println("Could not resolve team details"); + } + return result; + } + + @Extension public static class DescriptorImpl extends SCMNavigatorDescriptor { public static final String ANONYMOUS = BitbucketSCMSource.DescriptorImpl.ANONYMOUS; @@ -221,6 +265,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); @@ -260,5 +309,109 @@ protected SCMSourceCategory[] createCategories() { 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 2dc9c6ba2..005f65638 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -37,6 +37,7 @@ 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; @@ -56,7 +57,9 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -573,6 +576,44 @@ private String getCheckoutEffectiveCredentials() { } } + @NonNull + @Override + protected Map, Action> retrieveActions(@NonNull TaskListener listener) + throws IOException, InterruptedException { + Map, Action> result = new HashMap<>(); + final BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, repository, getScanCredentials()); + BitbucketRepository r = bitbucket.getRepository(); + if (r != null) { + result.put(BitbucketRepoMetadataAction.class, new BitbucketRepoMetadataAction(r)); + } + String serverUrl = + StringUtils + .removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); + result.put(BitbucketLink.class, + new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); + return result; + } + + @NonNull + @Override + protected Map, Action> retrieveActions(@NonNull SCMHead head, + @NonNull TaskListener listener) + throws IOException, InterruptedException { + Map, Action> result = new HashMap<>(); + String serverUrl = + StringUtils.removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); + String branchUrl; + if (head instanceof PullRequestSCMHead) { + PullRequestSCMHead pr = (PullRequestSCMHead) head; + branchUrl = pr.getRepoOwner() + "/" + pr.getRepository() + "/branch/" + pr.getBranchName(); + } else { + branchUrl = repoOwner + "/" + repository + "/branch/" + head.getName(); + } + result.put(BitbucketLink.class, + new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); + return result; + } + @Extension public static class DescriptorImpl extends SCMSourceDescriptor { 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..5e0b79e9d --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java @@ -0,0 +1,179 @@ +/* + * 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.BitbucketHref; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Util; +import java.io.IOException; +import java.util.Map; +import jenkins.branch.MetadataAction; +import jenkins.branch.OrganizationFolder; + +/** + * Invisible {@link OrganizationFolder} property that + * retains information about Bitbucket team. + */ +public class BitbucketTeamMetadataAction extends MetadataAction { + @NonNull + private final String name; + @CheckForNull + private final String displayName; + @CheckForNull + private final String avatarUrl; + @CheckForNull + private final String teamUrl; + + public BitbucketTeamMetadataAction(@NonNull BitbucketTeam team) throws IOException { + this.name = team.getName(); + this.displayName = team.getDisplayName(); + Map links = team.getLinks(); + this.avatarUrl = getLink(links, "avatar"); + this.teamUrl = getLink(links, "html"); + } + + private static String getLink(Map links, String name) { + if (links == null) { + return null; + } + BitbucketHref href = links.get(name); + return href == null ? null : href.getHref(); + } + + public BitbucketTeamMetadataAction(@NonNull String name, @CheckForNull String displayName, + @CheckForNull String avatarUrl, @CheckForNull String teamUrl) { + this.name = name; + this.displayName = displayName; + this.avatarUrl = avatarUrl; + this.teamUrl = teamUrl; + } + + /** + * {@inheritDoc} + */ + @CheckForNull + @Override + public String getObjectDisplayName() { + return Util.fixEmpty(displayName); + } + +// TODO when bitbucket supports serving avatars with a size request - currently only works if using gravatar or server +// /** +// * {@inheritDoc} +// */ +// @Override +// public String getFolderIconImageOf(String size) { +// if (avatarUrl == null) { +// // fall back to the generic github org icon +// String image = folderIconClassNameImageOf(getFolderIconClassName(), size); +// return image != null +// ? image +// : (Stapler.getCurrentRequest().getContextPath() + Hudson.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 getFolderIconClassName() { + // 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 getFolderIconDescription() { + 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 (!name.equals(that.name)) { + return false; + } + if (displayName != null ? !displayName.equals(that.displayName) : that.displayName != null) { + return false; + } + if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) { + return false; + } + return teamUrl != null ? teamUrl.equals(that.teamUrl) : that.teamUrl == null; + + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (displayName != null ? displayName.hashCode() : 0); + result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0); + result = 31 * result + (teamUrl != null ? teamUrl.hashCode() : 0); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "BitbucketTeamMetadataAction{" + + "name='" + name + '\'' + + ", displayName='" + displayName + '\'' + + ", avatarUrl='" + avatarUrl + '\'' + + ", teamUrl='" + teamUrl + '\'' + + '}'; + } + + public String getTeamUrl() { + return teamUrl; + } +} 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/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/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/repository/BitbucketServerProject.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerProject.java index 104f2e158..9f1a81533 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,6 +23,8 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.server.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 BitbucketServerProject implements BitbucketTeam { @JsonProperty("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/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties index 88a6d2666..ad1658ad4 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/Messages.properties @@ -1,8 +1,13 @@ +BitbucketLink.DisplayName=Bitbucket BitbucketSCMNavigator.UncategorizedSCMSourceCategory.DisplayName=Repositories BitbucketSCMSource.UncategorizedSCMHeadCategory.DisplayName=Repositories 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 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 0000000000000000000000000000000000000000..b2ce6e4ab57cb600342eb69c7d6bb8d31dfe19b0 GIT binary patch literal 711 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2>u z=YlX}jY;-GprB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH< z8TmyFUWwVINeaOwshN4HMbCbJ*9XcgLJTP_N={|SC@Cqh($~+*%*{io z%*oM9uF6b%90yb!0aEOoUr<4p> zCiXy)-p!jYW_;nOn6XVs>37uY1V4qNEFNMx3u0b`PEvX5%EWCr)5ngfzG(6F=7SG- zF8Rl>-T%J5A^HISgOyvqo!&Y%^@-o|VsVQH@!!%bKh-mBzIle->zD*<$F*OclT_Ld zv~|333;eS3M?*^MwO!mc{SP1ZDaDhQXWm%=lK_LKtDnm{r-UW|aE2^_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c038daa489dccc61e9a364092ac9f6a6755af17c GIT binary patch literal 790 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2>u z=YlX}jY;-GprB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH< z8TmyFUWwVINeaOwshN4HMbCbJ*9XcgLJTP_N={|SC@Cqh($~+*%*{io z%*oM9uF6b%90yb!0aEOoUrd=LsvlE+2Wb?W&KeC=kHSqv?O@9Wv5y}my_u;bUHSvj_=Pr7DY6Q6hTP2&;$zH9rA%uU<- z$%tF-cewY`RrzxZ#C28D15Cv4ZeRQGB)`pG$zMC`8FL$UCZFc~dkL7L7(8A5T-G@y GGywpFyHDi+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cd958f2ba503bb05490609dcabc515d133997b91 GIT binary patch literal 990 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2>u z=YlX}jY;-GprB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH< z8TmyFUWwVINeaOwshN4HMbCbJ*9XcgLJTP_N={|SC@Cqh($~+*%*{io z%*oM9uF6b%90yb!0aEOoUrQf5Z{QIL@r;9)v~{1bdyAI&tWOeAcMvR6oBD8aJCpQ>7TxUZ_&=?+O67dLvyS(cKF@nyaw{!Rq(A7T ziF%ikJHvvET~0?ev^^#Txhmbs-+p`ZhqS6+mJE^%3H$87-Pp8AMK@Y(>AZdWJSU%A zqIvgBicx0Ouixs^?e)A)PdmEi=e--s3=0?=CTO%JN-#0Fy}xH@9sBO}Uh`_pK#|7` zn%Wsr&KFabUW^D24qK+gAYkH{^{FYNr+8UVra_NT)!eX!*E7#g&kLB*qoH)__+ADF zriSwJ^I}}B!Ap<6YLJ)ee?6mtceCU-g-@pXKep!mmA-NNyVyY=OCIjx9R|j&UdPKO z9(eq@di9oDXZJ8P%z40?zAoX*&w$TwI79f4FMPA3?byznN}Tfr=6wGA`KA-2(8n3d z?5~%rDHI!aNZ&ue)7uoXx?81_W&7mEpPCXhwjE{pzHt65hE0(S4NuCHz9x#jn!bKv zMy~te-I4)05(g7t`?}zQ;17$NKRnceQ?6--B)M**WdNQOi|N9HR@uL zyo}rD-`f?`;vFZc6r2@*C?V145-2cf&*BM+-0dDJyHrmbsYEgUeqgYuE$T?m*UE@# zsT*?6#GMFeZFZ|In(M6+E$LPnR=g@mGBex#NszQkQ^V=EkHf=r=WjEA%YP@Y-0HZ% z5C2`ec1y4Jo4<2u*!0&aPSaTDr8CcKR$cvd^X4tT`PI2h!sd&6$^ml*gQu&X%Q~lo FCIBavrD6a8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2bccb50cb883a14941385fc37a68f710f859cd26 GIT binary patch literal 1024 zcmV+b1poVqP)bY*F7Wpd{G_b&hd03mcmSaefw zb#h~60BCe{a5^t9YH4k4VPh_Bb#rNBXklz@E@OIWX5(G}003BYMObu0Z*X~XX=iA3 z07FACAW(H;Y-wX4L~m_jX>K4yWn^h%VRUJ4ZXjrMbZ|N^FJp3LVRUJBWn*t`ZEtRK zE^l&YFK~5YY-wX;Z*5^|ZZCRea&Ip&E-){~AE8pDvA-Va`-AJyxH*ep*pGYLaXE#eE65*T6%l9{aS$8** z>(0v8_r3#=0R&f46h#pL?tb;fO72koXz7!smTlW@5WNvu4okB5((G*faxfSW06`YS zun%Z7Te)7(LQzJUh($*xV$nAM8uccoVG)jub74H_Z_~GJ8hSeqAOhr%i|KN?+8YQQ z$KhBzrQPgenmwwOI>%ZO%d!BdRcrlPrJnHtJ5N*poD@&<1Av;-5JeH!bpdc)mww+S z`|JP!?WmBSj7E2T0N{1>ZqD;OiiHxUY2rA}**g*rM=%T%z0=LDt*tr306OjF!BM_I z^yLX`+hzODZ7P)tvMl?6P$v$<6~hK78b~4GF+aSrdTQ+Zl_YYa{z#@(alZI zOp(p*@hJI-_dj@#oxgWjU0tQsZZd!U8eKy_81~4+O}*a80=I4~e(29WUb^Aep65}k zHTQ-+(tqAGHvY8LG2T{`(WyuzJSqr+Z#-0w^v93WzwYnueXFXfHSh@hPh4MIygK>H u#ZSk^LNQP9ysoa-PPEFyRO+|fS?#}IrBNmLhi}FJ00002>u z=YlX}jY;-GprB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH< z8TmyFUWwVINeaOwshN4HMbCbJ*9XcgLJTP_N={|SC@Cqh($~+*%*{io z%*oM9uF6b%90yb!0aEOoUr3AZoHTyr&$JxA&<^BkEk{vYyRTsYY2 z;cRf^;EpK)!H*v_WL`aV_hV6O(SfggA{D1Dcb*mv`JsFM_4mooe+u?oncLc$9sc~; zyYn6YI{d%PVfK&Me|x+2{@ZS@YnPXm*-gFs{bWJSCb4SKx%>ZV$nf1=H09sRZ&hkb zuc$CoICA`Q$)E7JB7SjVUAV~CySLAtHD{1uQ218!w^VDdsmC&*=A~1o@7CIlxBC`ZHDwL?yJyZ9{Tnb_ETB6z*G%M;B?Cj+&9{HHG~{mm{ITNC5#9q< zd4Fe#ZP59gU{T(XwRY?M7>QqM|axao_pf9&HCHSxVel{z0d5D-^@=uCc(3K>+^e$?{aGv)Lq|V zwcYh%wWY+AJuL$25dmG_zLkAD+<)gDw`*Y4VV;BWuiia+boOTVbU8LA<3u-!?AzM{ j1N*lb&FoK(w_$b3dbV6<|A#-o#Kz$1>gTe~DWM4f4?Jfc literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..29ac51d54f5c1a73e55cd04fe0bed0100ffcc08b GIT binary patch literal 1074 zcmV-21kL-2P)bY*F7Wpd{G_b&hd03mcmSaefw zb#h~60BCe{a5^t9YH4k4VPh_Bb#rNBXklz@E@OIWX5(G}003BYMObu0Z*X~XX=iA3 z07FACAW(H;Y-wX4L~m_jX>K4yWn^h%VRUJ4ZXjrMbZ|N^FJp3LVRUJBWn*t`ZEtRK zE^l&YFK~5YY-wX;Z*5^|ZZCRea&Ip&E-){~; z&PkglO_OCK%`q->$!?6o#5A@m{z<7f18)RxyzHeHGS+%+t^`q7F7(3d3EPcDnBq_> zXx-X@Lx+EG9d(S>bxqrKO_TKGob!F37hNia`CPs5^8ViEd3gnowY0SKc|0C}ISfO> zFhnVZ5W=jluTPJUkAK1c$nF)!m}n3L$34#@2m+K+D5WsQOwG;BWwq8wDUniQjQMV2 zV&e88KoA7J<2dvj^dPovpN&SNXQh-lj)QI6Y;SLW1-x+x;QM}8DaGAQO*A6V+5#gH z2($-67^Db5D6|=hqtqdGZ4WKLK4XoL#gzWqNfd1Hy;Im z9{?JSM!8rl^6JFRyHIbN(+n zUO*&baeO$*vnQWsV{4OEyG1M(2O&^O?^A?m%X+>3*2u`nn-?x#7oS|dK_)%OnbUa+ zxinX=-Qk@NuHX(2BZCfFoBxIZrA*Tv9UVPySyrUA=kv#MgZD06J!JJ*Du&jYY&OH) z`&%T2QU^V+t9N97e}7I0!3lT2kx9pS_w6%0e<}k&-c2Hh($07*qoM6N<$f?_P+m;e9( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7b116fca78b9623f9b748fc36cc0782ec4a69506 GIT binary patch literal 902 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf4nJ zu)P6cM!8Z8Q=p(^iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0u_AVrR) zB^mie3|@)ZrAZ3GC8?QtsYTCzf7b`fD?$t@ElN&h$S5f(u+rDh%FNA8OxDXSEzV5N zNX*I6ORmaHdmIN;905}7oL^8`l$oAU!r<&|pb$`+l#`jP;F6!4n3<>GlA4m4oLG{X zpQivdEV(EZ$S+Gx&d<%w&npJGOuqoECk3oUzbdsTU*AyAK>yg|y+0Wkn1VcA978-h zw@%%kE#fG0e1HCPy)4ByD;FdzWnooy+TfF_xMJ?y1qq6b+~4)KKNnRxaYOTDk-47O zEPDrSQCAVxjT{{cT`n#M7hx3 zQp3+_rTtGlB--M4PyW4czrTwf(+TxAbG=q(d&qocYPfdm+Uu_Z2mBcnD(^}dPu^CY z{WfvQ_ZT9@DO*n;0^SsGXCq1XEuw?@5HJ1HJp#`0jtwOw}) z-(%eOp1=Dd24UOjAxjy&EWED>TtrH#^Fuk0IgOtatafUd}M_>-1nyaR0P#YnjRP z)4UU8mp1ceCBx^zopr01SG3jQ{`u literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7b80f400c0b96cd67473a25867d7515e576fcdec GIT binary patch literal 1114 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 JPDHLkV1jT`z{vmr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..743a839e5389ef4913cfb5e33afa590df490da73 GIT binary patch literal 1144 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..24ae398043cbc3a688de148af132a6a99f5c22ad GIT binary patch literal 2052 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..790eb9005f0e2b68cadb63fd9bdb2f95e8958c05 GIT binary patch literal 2080 zcmV+*2;cXKP)bY*F7Wpd{G_b&hd03mcmSaefw zb#h~60BCe{a5^t9YH4k4VPh_Bb#rNBXklz@E@OIWX5(G}003BYMObu0Z*X~XX=iA3 z07FACAW(H;Y-wX4L~m_jX>K4yWn^h%VRUJ4ZXjrMbZ|N^FJp3LVRUJBWn*t`ZEtRK zE^l&YFK~5YY-wX;Z*5^|ZZCRea&Ip&E-){~p5Gg8D zYSJ_>#cD{f2_dN{Rf$B@ptRt%@g>ATG#6|x#&&G%+3)mWy|KNXnVmJJNu~Uyk;mV; z{%7W#IrA-ihEEnUH#3zr_021j5&5D~Sz|?S2UZB&Ca_px$)w^{ko(p3XOknf= zNYz;P!&Z4z;Qp-o^9KtY5!K%%4((_KQ9niBgeo@v`YTx7D?+u8$}n(3lx~dn3iN2w zoHt^wx~?6KM$OpiuU;DjSo_a~i(S{|MWZG^8JSpYRBlx$!%C?NtAMWoi+nQtTT=8# zT?ZSxLK9fsu3l$`cj>Z()wM?KP-&>2JhqnR+ZK}#hHtqk5FYp^8m0kAl^0L zb(G^`qzjTg#>RS2AKH33OtMw$ozD@AmLgKBR?9KmhjK5lAi(TmN865NnTt^`O@%Ij zuLn3=2-G3esVZPdN|MTk<{@Ad7*-e-m~3ckZ5tnfjLcwHwb4ZbD9OuE|h{KK+EPB@Lk|9f}R1eYAQtR zcR@Zs7iR(qKbbE9LE9A`$}v#V!nT61dluzG_4B4^{X9 zpo%=#*|z;TfJ9s4@2fU6Z&qPlXxCzW?|^p~0t!A@)&+PLj%N_%>GXL;L(}6bJPXtT z4+{Fn%8kuGOUtd%r#)QwIA%ifY>%JVm%!vL?+zO$5_8{}aw?B|sC-PoB$Fq?`bFvT z>p-bk0SK!8Ok9c<4^74N_+C~Pr%wk9iwDE{75obrvJkKkXgnQDhDUCh_HXN~M3k4( z=Y`isLbp?ke*r@l0tB?LOeCYSK7GC*>OKMTBB_saD{vqdbGM}B5tWT${btJ{uv1nv zG?fVwwhqo-%`aGUzU7J0^g{Xzs`s#SHNAHCL(Y8Tt3W}RV5siT|DfcN%$Hai@r_Qws77A^)_ci4Fw#-&7UMY9Pa*h?*wLcPSJjmQ zUk(*cq{>}1QnzgNgB=6iN6$|QRH`;Ccwhq40(k8@_+elFzMU@zhRo)neAAwys2x9| z$UQj(2Bi<7BNao|g5H*sz3|FML+-cxjyYpNYFTHJi(P*mFZt5_f079?h|~}`0M1BY z2~ak9%-T%|z_21aI;-8ETzS^$WLctezDu359S_wcs<>GJYTdy0&@Wn%R%6}c0!OGTwnlzadcMpbk~kO5KcQ=@Oo zLfeyQafYY+iO2WG##Zz{rm$PE-$=CW{P(;NsM@r$xQa0Ogk5FpAbdZ@l$YX-OXoWvdxXGi_Usj=$E?!Wh z)_w>09y4BSdak3b@s2QlLf1e=L-XqayLB!iCq?zNs+)(wM0PR6#fLFsMaXdAe!1S6hT!m0*dnS9-?NyY6kul zHm~4eZYHu8)ciq1#|llrV*Y0YYz`MIG=WI|`014I8;+6A>g3yDl|vIqv^c{Eug)!} z93XliX!^`VE;T?B{V~XUxn~s`Jx0uy>}pfUi9lCt<2zy};!OKIoF`aBpO0vyrv2dd z4|6e`^KTEUH~cCt#{5*2O-%hFpA!W7RpkXt@~Mv2ouM}nGJISg;_Sc9f6t9;mGV0000< KMNUMnLSTZbLdrq_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..defab78d6a44b7c638a6444188d9a372ffb290d8 GIT binary patch literal 2881 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 literal 0 HcmV?d00001 diff --git a/src/main/webapp/images/48x48/bitbucket-scmnavigator.png b/src/main/webapp/images/48x48/bitbucket-scmnavigator.png index 03fe00455f0e657b83ebf6a48b9a61070628e71e..1b8a208ce19052e3ca10d888d5ca1b8f53058cef 100644 GIT binary patch literal 3793 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 Hu0mjfAbkcU literal 4448 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 From a0adbe01fa61e8268e2e2c2346baecaf8793b0b9 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 3 Nov 2016 19:31:25 +0000 Subject: [PATCH 05/17] [JENKINS-39355] Switch to the utility checkInterrupt() method for interrupt checking --- .../jenkins/plugins/bitbucket/BitbucketSCMNavigator.java | 5 ++--- .../jenkins/plugins/bitbucket/BitbucketSCMSource.java | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) 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 adbd64ab3..9e550ee81 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -182,6 +182,7 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru repositories = bitbucket.getRepositories(UserRoleInRepository.OWNER); } for (BitbucketRepository repo : repositories) { + checkInterrupt(); add(listener, observer, repo); } } @@ -193,9 +194,7 @@ 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); scmSource.setBitbucketConnector(getBitbucketConnector()); 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 005f65638..87be9c9c7 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -288,7 +288,8 @@ protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, fi retrievePullRequests(criteria, observer, listener); } - private void retrievePullRequests(SCMSourceCriteria criteria, 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"); @@ -296,6 +297,7 @@ private void retrievePullRequests(SCMSourceCriteria criteria, SCMHeadObserver ob 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()); @@ -330,6 +332,7 @@ private void retrieveBranches(SCMSourceCriteria criteria, @NonNull final SCMHead 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(criteria, observer, listener, repoOwner, repository, branch.getName(), branch.getRawNode(), null); From 6373fb65365e7cebfdfe41e9dacc8d5838d93cac Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 5 Dec 2016 18:45:10 +0000 Subject: [PATCH 06/17] Pick up cloudbees-folders 5.14 --- pom.xml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index bf2b29ca3..4e7d6b622 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ cloudbees-bitbucket-branch-source - 1.9-SNAPSHOT + 2.0-SNAPSHOT hpi Bitbucket Branch Source Plugin @@ -60,12 +60,7 @@ org.jenkins-ci.plugins scm-api - 1.4-SNAPSHOT - - - org.jenkins-ci.plugins - branch-api - 1.12-SNAPSHOT + 2.0-SNAPSHOT org.jenkins-ci.plugins @@ -93,6 +88,12 @@ display-url-api 0.2 + + org.jenkins-ci.plugins + branch-api + 2.0-SNAPSHOT + test + org.jenkins-ci.plugins.workflow workflow-multibranch From 6899192dd56dafd2b350231822dcb6664178eee3 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 16:16:35 +0000 Subject: [PATCH 07/17] [JENKINS-39355 Follow-up] Lots of changes to make this work with the SCM API 2.0+ --- README.adoc | 6 + pom.xml | 15 +- .../bitbucket/BitbucketApiConnector.java | 57 +++-- .../BitbucketRepoMetadataAction.java | 30 +-- .../bitbucket/BitbucketSCMNavigator.java | 69 +++++-- .../plugins/bitbucket/BitbucketSCMSource.java | 69 ++++--- .../BitbucketTeamMetadataAction.java | 84 ++------ .../CustomComputedFolderItemListener.java | 195 ------------------ .../plugins/bitbucket/PullRequestAction.java | 51 ----- .../plugins/bitbucket/PullRequestSCMHead.java | 25 ++- .../bitbucket/SCMHeadWithOwnerAndRepo.java | 19 +- .../bitbucket/api/BitbucketPullRequest.java | 7 +- .../api/BitbucketPullRequestDestination.java | 42 ++++ .../BitbucketPullRequestValue.java | 11 + .../BitbucketPullRequestValueDestination.java | 66 ++++++ .../BitbucketServerPullRequest.java | 12 ++ ...BitbucketServerPullRequestDestination.java | 65 ++++++ .../repository/BitbucketServerProject.java | 26 ++- .../BranchScanningIntegrationTest.java | 10 + .../plugins/bitbucket/BranchScanningTest.java | 9 + .../plugins/bitbucket/SCMNavigatorTest.java | 12 +- 21 files changed, 447 insertions(+), 433 deletions(-) create mode 100644 README.adoc delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java delete mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/BitbucketPullRequestDestination.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketPullRequestValueDestination.java create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestDestination.java 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 4e7d6b622..c5b399138 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ cloudbees-bitbucket-branch-source - 2.0-SNAPSHOT + 2.0.0-SNAPSHOT hpi Bitbucket Branch Source Plugin @@ -60,12 +60,12 @@ org.jenkins-ci.plugins scm-api - 2.0-SNAPSHOT + 2.0.1-SNAPSHOT org.jenkins-ci.plugins git - 2.3.5 + 2.6.1-SNAPSHOT org.apache.httpcomponents @@ -91,7 +91,14 @@ org.jenkins-ci.plugins branch-api - 2.0-SNAPSHOT + 2.0.0-SNAPSHOT + test + + + org.jenkins-ci.plugins + scm-api + 2.0.1-SNAPSHOT + tests test 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/BitbucketRepoMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java index 070768099..e39ce8f5a 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java @@ -25,18 +25,15 @@ package com.cloudbees.jenkins.plugins.bitbucket; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository; -import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import jenkins.branch.MetadataAction; -import jenkins.branch.MultiBranchProject; +import jenkins.scm.api.metadata.AvatarMetadataAction; /** - * Invisible property on {@link MultiBranchProject} - * that retains information about GitHub repository. + * Invisible property that retains information about GitHub repository. * * @author Kohsuke Kawaguchi */ -public class BitbucketRepoMetadataAction extends MetadataAction { +public class BitbucketRepoMetadataAction extends AvatarMetadataAction{ private final String scm; @@ -51,25 +48,8 @@ public BitbucketRepoMetadataAction(String scm) { /** * {@inheritDoc} */ - @CheckForNull @Override - public String getObjectDescription() { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public String getObjectUrl() { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public String getFolderIconClassName() { + public String getAvatarIconClassName() { if ("git".equals(scm)) { return "icon-bitbucket-repo-git"; } @@ -83,7 +63,7 @@ public String getFolderIconClassName() { * {@inheritDoc} */ @Override - public String getFolderIconDescription() { + public String getAvatarDescription() { if ("git".equals(scm)) { return Messages.BitbucketRepoMetadataAction_IconDescription_Git(); } 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 9e550ee81..3a1144d50 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -23,19 +23,21 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; -import com.cloudbees.plugins.credentials.common.StandardCredentials; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; import hudson.console.HyperlinkNote; import hudson.model.Action; import java.io.IOException; -import java.util.HashMap; +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; @@ -130,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 @@ -153,6 +159,12 @@ public void setBitbucketConnector(@NonNull BitbucketApiConnector bitbucketConnec return bitbucketConnector; } + @NonNull + @Override + protected String id() { + return bitbucketServerUrl() + "::" + repoOwner; + } + @Override public void visitSources(SCMSourceObserver observer) throws IOException, InterruptedException { TaskListener listener = observer.getListener(); @@ -165,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", bitbucketServerUrl()); } 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", bitbucketServerUrl(), CredentialsNameProvider.name(credentials)); } List repositories; BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, credentials); @@ -196,7 +208,11 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep listener.getLogger().format("Proposing %s%n", name); 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); @@ -207,18 +223,24 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep projectObserver.complete(); } + private String bitbucketServerUrl() { + return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); + } + @NonNull @Override - public Map, Action> retrieveActions(@NonNull SCMNavigatorOwner owner, + 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()); - Map, Action> result = new HashMap<>(); + List result = new ArrayList<>(); StandardUsernamePasswordCredentials credentials = getBitbucketConnector().lookupCredentials(owner, credentialsId, StandardUsernamePasswordCredentials.class); - String serverUrl = StringUtils.removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); + String serverUrl = StringUtils.removeEnd(bitbucketServerUrl(), "/"); if (credentials == null) { listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", serverUrl); @@ -230,19 +252,38 @@ public Map, Action> retrieveActions(@NonNull SCMNavigato BitbucketApi bitbucket = getBitbucketConnector().create(repoOwner, credentials); BitbucketTeam team = bitbucket.getTeam(); if (team != null) { - BitbucketTeamMetadataAction metadataAction = new BitbucketTeamMetadataAction(team); - result.put(BitbucketTeamMetadataAction.class, metadataAction); - String teamUrl = StringUtils.defaultIfBlank(metadataAction.getTeamUrl(), serverUrl + "/" + team.getName()); - result.put(BitbucketLink.class, new BitbucketLink("icon-bitbucket-logo", teamUrl)); - listener.getLogger().printf("Team URL: %s%n", HyperlinkNote.encodeTo(teamUrl, team.getName())); + 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.put(BitbucketLink.class, new BitbucketLink("icon-bitbucket-logo", teamUrl)); + 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 { 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 87be9c9c7..a54d69102 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -28,6 +28,7 @@ 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; @@ -57,9 +58,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -67,11 +66,13 @@ 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.impl.ChangeRequestSCMHeadCategory; import jenkins.scm.impl.UncategorizedSCMHeadCategory; @@ -237,6 +238,10 @@ public String getBitbucketServerUrl() { return bitbucketServerUrl; } + private String bitbucketServerUrl() { + return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); + } + public void setBitbucketConnector(@NonNull BitbucketApiConnector bitbucketConnector) { this.bitbucketConnector = bitbucketConnector; } @@ -272,14 +277,14 @@ public BitbucketApi buildBitbucketClient(PullRequestSCMHead head) { } @Override - protected void retrieve(SCMSourceCriteria criteria, SCMHeadObserver observer, final TaskListener listener) throws IOException, - InterruptedException { - + 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", bitbucketServerUrl()); } 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", bitbucketServerUrl(), CredentialsNameProvider.name(scanCredentials)); } // Search branches @@ -303,7 +308,21 @@ private void retrievePullRequests(SCMSourceCriteria criteria, SCMHeadObserver ob + 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("Can not resolve hash: [%s]%n", pull.getSource().getCommit().getHash())); + } + continue; + } if (hash != null) { observe(criteria, observer, listener, pull.getSource().getRepository().getOwnerName(), @@ -581,30 +600,30 @@ private String getCheckoutEffectiveCredentials() { @NonNull @Override - protected Map, Action> retrieveActions(@NonNull TaskListener listener) + protected List retrieveActions(@CheckForNull SCMSourceEvent event, + @NonNull TaskListener listener) throws IOException, InterruptedException { - Map, Action> result = new HashMap<>(); + // 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.put(BitbucketRepoMetadataAction.class, new BitbucketRepoMetadataAction(r)); + result.add(new BitbucketRepoMetadataAction(r)); } - String serverUrl = - StringUtils - .removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); - result.put(BitbucketLink.class, - new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); + String serverUrl = StringUtils.removeEnd(bitbucketServerUrl(), "/"); + result.add(new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); return result; } @NonNull @Override - protected Map, Action> retrieveActions(@NonNull SCMHead head, - @NonNull TaskListener listener) + protected List retrieveActions(@NonNull SCMHead head, + @CheckForNull SCMHeadEvent event, + @NonNull TaskListener listener) throws IOException, InterruptedException { - Map, Action> result = new HashMap<>(); - String serverUrl = - StringUtils.removeEnd(bitbucketServerUrl == null ? "https://bitbucket.org" : bitbucketServerUrl, "/"); + // 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(bitbucketServerUrl(), "/"); String branchUrl; if (head instanceof PullRequestSCMHead) { PullRequestSCMHead pr = (PullRequestSCMHead) head; @@ -612,8 +631,7 @@ protected Map, Action> retrieveActions(@NonNull SCMHead } else { branchUrl = repoOwner + "/" + repository + "/branch/" + head.getName(); } - result.put(BitbucketLink.class, - new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); + result.add(new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); return result; } @@ -628,7 +646,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 { @@ -651,7 +670,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; } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java index 5e0b79e9d..722862a68 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketTeamMetadataAction.java @@ -24,61 +24,18 @@ package com.cloudbees.jenkins.plugins.bitbucket; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; -import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import hudson.Util; -import java.io.IOException; -import java.util.Map; -import jenkins.branch.MetadataAction; -import jenkins.branch.OrganizationFolder; +import jenkins.scm.api.metadata.AvatarMetadataAction; /** - * Invisible {@link OrganizationFolder} property that - * retains information about Bitbucket team. + * Invisible property that retains information about Bitbucket team. */ -public class BitbucketTeamMetadataAction extends MetadataAction { - @NonNull - private final String name; - @CheckForNull - private final String displayName; +public class BitbucketTeamMetadataAction extends AvatarMetadataAction { @CheckForNull private final String avatarUrl; - @CheckForNull - private final String teamUrl; - - public BitbucketTeamMetadataAction(@NonNull BitbucketTeam team) throws IOException { - this.name = team.getName(); - this.displayName = team.getDisplayName(); - Map links = team.getLinks(); - this.avatarUrl = getLink(links, "avatar"); - this.teamUrl = getLink(links, "html"); - } - private static String getLink(Map links, String name) { - if (links == null) { - return null; - } - BitbucketHref href = links.get(name); - return href == null ? null : href.getHref(); - } - - public BitbucketTeamMetadataAction(@NonNull String name, @CheckForNull String displayName, - @CheckForNull String avatarUrl, @CheckForNull String teamUrl) { - this.name = name; - this.displayName = displayName; + public BitbucketTeamMetadataAction(@CheckForNull String avatarUrl) { this.avatarUrl = avatarUrl; - this.teamUrl = teamUrl; - } - - /** - * {@inheritDoc} - */ - @CheckForNull - @Override - public String getObjectDisplayName() { - return Util.fixEmpty(displayName); } // TODO when bitbucket supports serving avatars with a size request - currently only works if using gravatar or server @@ -86,13 +43,13 @@ public String getObjectDisplayName() { // * {@inheritDoc} // */ // @Override -// public String getFolderIconImageOf(String size) { +// public String getAvatarImageOf(String size) { // if (avatarUrl == null) { // // fall back to the generic github org icon -// String image = folderIconClassNameImageOf(getFolderIconClassName(), size); +// String image = avatarIconClassNameImageOf(getAvatarIconClassName(), size); // return image != null // ? image -// : (Stapler.getCurrentRequest().getContextPath() + Hudson.RESOURCE_PATH +// : (Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH // + "/plugin/cloudbees-bitbucket-branch-source/images/" + size + "/bitbucket-logo.png"); // } else { // String[] xy = size.split("x"); @@ -101,13 +58,13 @@ public String getObjectDisplayName() { // else return avatarUrl + "?s=" + xy[0]; // } // } -// + /** * {@inheritDoc} */ @Override - public String getFolderIconClassName() { + public String getAvatarIconClassName() { // TODO when bitbucket supports serving avatars with a size request // return avatarUrl == null ? "icon-bitbucket-logo" : null; return "icon-bitbucket-logo"; @@ -117,7 +74,7 @@ public String getFolderIconClassName() { * {@inheritDoc} */ @Override - public String getFolderIconDescription() { + public String getAvatarDescription() { return Messages.BitbucketTeamMetadataAction_IconDescription(); } @@ -135,16 +92,10 @@ public boolean equals(Object o) { BitbucketTeamMetadataAction that = (BitbucketTeamMetadataAction) o; - if (!name.equals(that.name)) { - return false; - } - if (displayName != null ? !displayName.equals(that.displayName) : that.displayName != null) { - return false; - } if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) { return false; } - return teamUrl != null ? teamUrl.equals(that.teamUrl) : that.teamUrl == null; + return true; } @@ -153,11 +104,7 @@ public boolean equals(Object o) { */ @Override public int hashCode() { - int result = name.hashCode(); - result = 31 * result + (displayName != null ? displayName.hashCode() : 0); - result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0); - result = 31 * result + (teamUrl != null ? teamUrl.hashCode() : 0); - return result; + return (avatarUrl != null ? avatarUrl.hashCode() : 0); } /** @@ -166,14 +113,7 @@ public int hashCode() { @Override public String toString() { return "BitbucketTeamMetadataAction{" + - "name='" + name + '\'' + - ", displayName='" + displayName + '\'' + ", avatarUrl='" + avatarUrl + '\'' + - ", teamUrl='" + teamUrl + '\'' + '}'; } - - public String getTeamUrl() { - return teamUrl; - } } 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 e39fcd130..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/CustomComputedFolderItemListener.java +++ /dev/null @@ -1,195 +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); - } - } - - @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()); - 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); - } - } - } - - /** - * 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/PullRequestAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java deleted file mode 100644 index 1249d1ad7..000000000 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java +++ /dev/null @@ -1,51 +0,0 @@ -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; - -public class PullRequestAction extends ChangeRequestAction { - private final String number; - private URL url; - private final String title; - private final String userLogin; - - public PullRequestAction(BitbucketPullRequest pr) { - number = pr.getId(); - try { - url = new URL(pr.getLink()); - } catch (MalformedURLException e) { - url = null; - } - title = pr.getTitle(); - 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; - } - - private static final long serialVersionUID = 1L; - -} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java index 5a2e0b110..b7b5a021c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java @@ -26,7 +26,9 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import jenkins.scm.api.ChangeRequestSCMHead; +import java.net.MalformedURLException; +import java.net.URL; +import jenkins.scm.api.mixin.ChangeRequestSCMHead; import jenkins.scm.api.SCMHead; import jenkins.scm.api.actions.ChangeRequestAction; @@ -34,7 +36,7 @@ * {@link SCMHead} for a BitBucket Pull request * @since FIXME */ -public class PullRequestSCMHead extends ChangeRequestSCMHead { +public class PullRequestSCMHead extends SCMHead implements ChangeRequestSCMHead { private static final String PR_BRANCH_PREFIX = "PR-"; @@ -46,15 +48,17 @@ public class PullRequestSCMHead extends ChangeRequestSCMHead { private final String branchName; - @NonNull - private PullRequestAction metadata; + private final String number; + + private final BranchSCMHead 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.metadata = new PullRequestAction(pr); + this.number = pr.getId(); + this.target = new BranchSCMHead(pr.getDestination().getBranch().getName()); } public String getRepoOwner() { @@ -69,15 +73,16 @@ public String getBranchName() { return branchName; } - @CheckForNull - public Integer getPullRequestId() { - return Integer.parseInt(metadata.getId()); + @NonNull + @Override + public String getId() { + return number; } @NonNull @Override - public ChangeRequestAction getChangeRequestAction() { - return metadata; + 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 7422ccb57..c98be8bbd 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java @@ -23,6 +23,7 @@ */ package com.cloudbees.jenkins.plugins.bitbucket; +import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestDestination; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.ObjectStreamException; @@ -31,17 +32,10 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; 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 @@ -53,7 +47,7 @@ public class SCMHeadWithOwnerAndRepo extends SCMHead { private final String repoName; - private transient PullRequestAction metadata; + private transient ChangeRequestAction metadata; public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName) { super(branchName); @@ -69,6 +63,11 @@ public BitbucketPullRequestSource getSource() { return null; } + @Override + public BitbucketPullRequestDestination getDestination() { + return null; + } + @NonNull @Override public String getId() { 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/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/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 9f1a81533..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 @@ -24,11 +24,15 @@ 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 { @@ -40,7 +44,8 @@ public class BitbucketServerProject implements BitbucketTeam { private String displayName; @JsonProperty("links") - private Map links; + @JsonDeserialize(keyAs = String.class, contentAs = BitbucketHref[].class) + private Map links; @Override public String getName() { @@ -62,10 +67,23 @@ public void setDisplayName(String displayName) { @Override public Map getLinks() { - return links; + 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; } - public void setLinks(Map links) { - this.links = links; + // 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/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 getObserved() { @@ -120,12 +124,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 From e1f33de62fba055fc9a5f2a52a86391ef308d9bd Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 22:09:30 +0000 Subject: [PATCH 08/17] Fix mock --- .../plugins/bitbucket/BitbucketClientMockUtils.java | 7 +++++++ 1 file changed, 7 insertions(+) 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..240bd369f 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,12 @@ private static BitbucketPullRequestValue getPullRequest() { pr.setSource(source); + BitbucketPullRequestValueDestination destination = new BitbucketPullRequestValueDestination(); + branch = new BitbucketCloudBranch(); + branch.setName("branch1"); + destination.setBranch(branch); + 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")); From 74f9d75dd15c6c401fdbac325f5d2275920a4ff0 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 22:49:16 +0000 Subject: [PATCH 09/17] Tidy up data migration --- .../plugins/bitbucket/PullRequestAction.java | 75 +++++++++++++++++++ .../plugins/bitbucket/PullRequestSCMHead.java | 14 +++- .../bitbucket/SCMHeadWithOwnerAndRepo.java | 43 +++-------- 3 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java new file mode 100644 index 000000000..1bf550e27 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestAction.java @@ -0,0 +1,75 @@ +/* + * 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 java.net.MalformedURLException; +import java.net.URL; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * Retained to help migrate legacy SCMHead instances + */ +@Deprecated +@Restricted(NoExternalUse.class) +class PullRequestAction { + private final String number; + private URL url; + private final String title; + private final String userLogin; + + public PullRequestAction(BitbucketPullRequest pr) { + number = pr.getId(); + try { + url = new URL(pr.getLink()); + } catch (MalformedURLException e) { + url = null; + } + title = pr.getTitle(); + userLogin = pr.getAuthorLogin(); + } + + @NonNull + public String getId() { + return number; + } + + public URL getURL() { + return url; + } + + public String getTitle() { + return title; + } + + public String getAuthor() { + return userLogin; + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java index b7b5a021c..90f058f58 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMHead.java @@ -24,13 +24,9 @@ package com.cloudbees.jenkins.plugins.bitbucket; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; -import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import java.net.MalformedURLException; -import java.net.URL; import jenkins.scm.api.mixin.ChangeRequestSCMHead; import jenkins.scm.api.SCMHead; -import jenkins.scm.api.actions.ChangeRequestAction; /** * {@link SCMHead} for a BitBucket Pull request @@ -52,6 +48,16 @@ public class PullRequestSCMHead extends SCMHead implements ChangeRequestSCMHead 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; 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 c98be8bbd..f1b731ae9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/SCMHeadWithOwnerAndRepo.java @@ -25,8 +25,13 @@ 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; @@ -47,7 +52,7 @@ public class SCMHeadWithOwnerAndRepo extends SCMHead { private final String repoName; - private transient ChangeRequestAction metadata; + private transient PullRequestAction metadata; public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchName) { super(branchName); @@ -57,39 +62,9 @@ public SCMHeadWithOwnerAndRepo(String repoOwner, String repoName, String branchN private Object readResolve() throws ObjectStreamException { if (metadata != null) { - return new PullRequestSCMHead(repoOwner, repoName, super.getName(), new BitbucketPullRequest() { - @Override - public BitbucketPullRequestSource getSource() { - return null; - } - - @Override - public BitbucketPullRequestDestination getDestination() { - return null; - } - - @NonNull - @Override - public String getId() { - return metadata.getId(); - } - - @Override - public String getTitle() { - return metadata.getTitle(); - } - - @Override - public String getLink() { - URL url = metadata.getURL(); - return url == null ? null : url.toString(); - } - - @Override - public String getAuthorLogin() { - return metadata.getAuthor(); - } - }); + // 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()); } From 8d8333382a59a75e432639c97aa5a3d54baf03d0 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 22:53:10 +0000 Subject: [PATCH 10/17] No need to retain author tag --- .../jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java index e39ce8f5a..2ea29caf6 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java @@ -30,8 +30,6 @@ /** * Invisible property that retains information about GitHub repository. - * - * @author Kohsuke Kawaguchi */ public class BitbucketRepoMetadataAction extends AvatarMetadataAction{ From 7a902d4b5339f3d6a5c8d2ba9580c9e15a522542 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 22:53:41 +0000 Subject: [PATCH 11/17] Typo --- .../cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a54d69102..a6b4fbe1c 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -319,7 +319,7 @@ private void retrievePullRequests(SCMSourceCriteria criteria, SCMHeadObserver ob + pull.getSource().getBranch().getName()); } else { e.printStackTrace( - listener.error("Can not resolve hash: [%s]%n", pull.getSource().getCommit().getHash())); + listener.error("Cannot resolve hash: [%s]%n", pull.getSource().getCommit().getHash())); } continue; } From 99c2d3c59c450f1c5abc110bdd20c0d96a475c84 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 15 Dec 2016 22:56:38 +0000 Subject: [PATCH 12/17] We need the mercurial plugin update too --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c5b399138..08c987fec 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ org.jenkins-ci.plugins mercurial - 1.54 + 1.58-SNAPSHOT org.codehaus.jackson From 80adc944fee1449824dcd9d1fdc2d060818c9a01 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 16 Dec 2016 11:50:36 +0000 Subject: [PATCH 13/17] Prepare for beta-1 --- pom.xml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 08c987fec..95408ef73 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ cloudbees-bitbucket-branch-source - 2.0.0-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,12 +61,12 @@ org.jenkins-ci.plugins scm-api - 2.0.1-SNAPSHOT + ${scm-api.version} org.jenkins-ci.plugins git - 2.6.1-SNAPSHOT + 2.6.2-beta-1-SNAPSHOT org.apache.httpcomponents @@ -76,7 +77,7 @@ org.jenkins-ci.plugins mercurial - 1.58-SNAPSHOT + 1.58-beta-1-SNAPSHOT org.codehaus.jackson @@ -91,13 +92,13 @@ org.jenkins-ci.plugins branch-api - 2.0.0-SNAPSHOT + 2.0.0-beta-1 test org.jenkins-ci.plugins scm-api - 2.0.1-SNAPSHOT + ${scm-api.version} tests test From 93141b33ad207f53d8555eb7b4ed17b3051f6dd1 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Fri, 16 Dec 2016 17:48:31 +0000 Subject: [PATCH 14/17] Pick up 2.6.2-beta-1 of git plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 95408ef73..41ddc52e2 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ org.jenkins-ci.plugins git - 2.6.2-beta-1-SNAPSHOT + 2.6.2-beta-1 org.apache.httpcomponents From 991057b60af4ec2c9093d37b1f21e56144ba4a44 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 12 Jan 2017 21:43:40 +0000 Subject: [PATCH 15/17] [JENKINS-39355 Follow-up] Address code review comments --- .../BitbucketRepoMetadataAction.java | 2 +- .../bitbucket/BitbucketSCMNavigator.java | 12 ++--- .../plugins/bitbucket/BitbucketSCMSource.java | 52 ++++++++++++++----- .../plugins/bitbucket/Messages.properties | 2 +- .../bitbucket/BitbucketClientMockUtils.java | 3 ++ .../plugins/bitbucket/SCMNavigatorTest.java | 10 ++-- 6 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java index 2ea29caf6..fda60eb19 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketRepoMetadataAction.java @@ -29,7 +29,7 @@ import jenkins.scm.api.metadata.AvatarMetadataAction; /** - * Invisible property that retains information about GitHub repository. + * Invisible property that retains information about Bitbucket repository. */ public class BitbucketRepoMetadataAction extends AvatarMetadataAction{ 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 3a1144d50..d759702bd 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMNavigator.java @@ -162,7 +162,7 @@ public void setBitbucketConnector(@NonNull BitbucketApiConnector bitbucketConnec @NonNull @Override protected String id() { - return bitbucketServerUrl() + "::" + repoOwner; + return bitbucketUrl() + "::" + repoOwner; } @Override @@ -177,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()); + listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", bitbucketUrl()); } else { - listener.getLogger().format("Connecting to %s using %s%n", 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); @@ -223,7 +223,7 @@ private void add(TaskListener listener, SCMSourceObserver observer, BitbucketRep projectObserver.complete(); } - private String bitbucketServerUrl() { + private String bitbucketUrl() { return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); } @@ -231,7 +231,7 @@ private String bitbucketServerUrl() { @Override public List retrieveActions(@NonNull SCMNavigatorOwner owner, @CheckForNull SCMNavigatorEvent event, - @NonNull TaskListener listener) + @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()); @@ -240,7 +240,7 @@ public List retrieveActions(@NonNull SCMNavigatorOwner owner, getBitbucketConnector().lookupCredentials(owner, credentialsId, StandardUsernamePasswordCredentials.class); - String serverUrl = StringUtils.removeEnd(bitbucketServerUrl(), "/"); + String serverUrl = StringUtils.removeEnd(bitbucketUrl(), "/"); if (credentials == null) { listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", serverUrl); 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 a6b4fbe1c..4f6604027 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -56,6 +56,7 @@ 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; @@ -74,9 +75,11 @@ 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; @@ -238,7 +241,7 @@ public String getBitbucketServerUrl() { return bitbucketServerUrl; } - private String bitbucketServerUrl() { + private String bitbucketUrl() { return StringUtils.defaultIfBlank(bitbucketServerUrl, "https://bitbucket.org"); } @@ -282,9 +285,9 @@ protected void retrieve(@CheckForNull SCMSourceCriteria criteria, @NonNull SCMHe throws IOException, InterruptedException { StandardUsernamePasswordCredentials scanCredentials = getScanCredentials(); if (scanCredentials == null) { - listener.getLogger().format("Connecting to %s with no credentials, anonymous access%n", 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(), CredentialsNameProvider.name(scanCredentials)); + listener.getLogger().format("Connecting to %s using %s%n", bitbucketUrl(), CredentialsNameProvider.name(scanCredentials)); } // Search branches @@ -610,8 +613,17 @@ protected List retrieveActions(@CheckForNull SCMSourceEvent event, if (r != null) { result.add(new BitbucketRepoMetadataAction(r)); } - String serverUrl = StringUtils.removeEnd(bitbucketServerUrl(), "/"); - result.add(new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); + 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.getFullName(), null, + serverUrl + "/projects/" + repoOwner + "/repos/" + repository)); + } else { + result.add(new BitbucketLink("icon-bitbucket-repo", serverUrl + "/" + repoOwner + "/" + repository)); + result.add(new ObjectMetadataAction(r.getFullName(), null, + serverUrl + "/" + repoOwner + "/" + repository)); + } return result; } @@ -623,15 +635,31 @@ protected List retrieveActions(@NonNull SCMHead head, 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(bitbucketServerUrl(), "/"); - String branchUrl; - if (head instanceof PullRequestSCMHead) { - PullRequestSCMHead pr = (PullRequestSCMHead) head; - branchUrl = pr.getRepoOwner() + "/" + pr.getRepository() + "/branch/" + pr.getBranchName(); + 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 { + https: +//bitbucket.beescloud.com/projects/DUB/repos/stunning-adventure/compare/commits?sourceBranch=refs%2Fheads%2Forigin-pr-branch + 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 { - branchUrl = repoOwner + "/" + repository + "/branch/" + head.getName(); + 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)); } - result.add(new BitbucketLink("icon-bitbucket-branch", serverUrl + "/" + branchUrl)); return result; } 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 ad1658ad4..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,6 +1,6 @@ BitbucketLink.DisplayName=Bitbucket BitbucketSCMNavigator.UncategorizedSCMSourceCategory.DisplayName=Repositories -BitbucketSCMSource.UncategorizedSCMHeadCategory.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. 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 240bd369f..d46b85c7a 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java @@ -164,6 +164,9 @@ private static BitbucketPullRequestValue getPullRequest() { 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"); 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 045f6c540..80ba8b823 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/SCMNavigatorTest.java @@ -44,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 { @@ -52,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()); @@ -81,15 +83,17 @@ 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 From 604e7b4529c3b348b7de64977a12e1e6181f990e Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 12 Jan 2017 22:05:24 +0000 Subject: [PATCH 16/17] remove junk line --- .../cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java | 2 -- 1 file changed, 2 deletions(-) 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 4f6604027..8a8ea9106 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -642,8 +642,6 @@ protected List retrieveActions(@NonNull SCMHead head, PullRequestSCMHead pr = (PullRequestSCMHead) head; branchUrl = "projects/" + repoOwner + "/repos/" + repository + "/pull-requests/"+pr.getId()+"/overview"; } else { - https: -//bitbucket.beescloud.com/projects/DUB/repos/stunning-adventure/compare/commits?sourceBranch=refs%2Fheads%2Forigin-pr-branch branchUrl = "projects/" + repoOwner + "/repos/" + repository + "/compare/commits?sourceBranch=" + URLEncoder.encode(Constants.R_HEADS + head.getName(), "UTF-8"); } From 68a7ec40269f0b172c5b6ec0d69411517e4ff846 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Thu, 12 Jan 2017 22:22:49 +0000 Subject: [PATCH 17/17] Findbugs catches bugs... who knew --- .../jenkins/plugins/bitbucket/BitbucketSCMSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8a8ea9106..60d782ac4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketSCMSource.java @@ -617,11 +617,11 @@ protected List retrieveActions(@CheckForNull SCMSourceEvent event, if (StringUtils.isNotEmpty(bitbucketServerUrl)) { result.add(new BitbucketLink("icon-bitbucket-repo", serverUrl + "/projects/" + repoOwner + "/repos/" + repository)); - result.add(new ObjectMetadataAction(r.getFullName(), null, + 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.getFullName(), null, + result.add(new ObjectMetadataAction(r == null ? null : r.getFullName(), null, serverUrl + "/" + repoOwner + "/" + repository)); } return result;