From 8668c6aaaed1365abb44a477de6056fb1df895bb Mon Sep 17 00:00:00 2001 From: amuniz Date: Wed, 25 Apr 2018 10:51:45 +0200 Subject: [PATCH 1/7] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 49d8c5519..bbe0dfe44 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ cloudbees-bitbucket-branch-source - 2.2.11 + 2.2.12-SNAPSHOT hpi Bitbucket Branch Source Plugin @@ -74,7 +74,7 @@ scm:git:git://github.com/jenkinsci/bitbucket-branch-source-plugin.git scm:git:git@github.com:jenkinsci/bitbucket-branch-source-plugin.git https://github.com/jenkinsci/bitbucket-branch-source-plugin - cloudbees-bitbucket-branch-source-2.2.11 + HEAD From dcc915e3794ebd0a79acce149e3be28997f0acf6 Mon Sep 17 00:00:00 2001 From: Benoit Guerin Date: Mon, 26 Mar 2018 12:34:36 +0200 Subject: [PATCH 2/7] JENKINS-50314 : Using or not caches should be configurable in servers global configuration --- .../client/BitbucketCloudApiClient.java | 129 +++++++++++------- .../client/BitbucketCloudApiFactory.java | 15 +- .../plugins/bitbucket/client/Cache.java | 104 +++++++++++--- .../endpoints/BitbucketCloudEndpoint.java | 54 +++++++- .../config-detail.jelly | 14 ++ .../plugins/bitbucket/UriResolverTest.java | 4 +- 6 files changed, 247 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java index ece7f0012..68165fc31 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java @@ -71,12 +71,16 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; import jenkins.scm.api.SCMFile; + +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpHost; @@ -122,17 +126,31 @@ public class BitbucketCloudApiClient implements BitbucketApi { private final String owner; private final String repositoryName; private final UsernamePasswordCredentials credentials; + private final boolean enableCache; static { connectionManager.setDefaultMaxPerRoute(20); connectionManager.setMaxTotal(22); connectionManager.setSocketConfig(API_HOST, SocketConfig.custom().setSoTimeout(60 * 1000).build()); } - private static Cache cachedTeam = new Cache(6, TimeUnit.HOURS); - private static Cache> cachedRepositories = new Cache(3, TimeUnit.HOURS); + private static Cache cachedTeam = new Cache(6, HOURS); + private static Cache> cachedRepositories = new Cache(3, HOURS); private transient BitbucketRepository cachedRepository; private transient String cachedDefaultBranch; - public BitbucketCloudApiClient(String owner, String repositoryName, StandardUsernamePasswordCredentials creds) { + public static List stats() { + List stats = new ArrayList<>(); + stats.add("Team: " + cachedTeam.stats().toString()); + stats.add("Repositories : " + cachedRepositories.stats().toString()); + return stats; + } + + public static void clearCaches() { + cachedTeam.evictAll(); + cachedRepositories.evictAll(); + } + + public BitbucketCloudApiClient(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuraction, + String owner, String repositoryName, StandardUsernamePasswordCredentials creds) { if (creds != null) { this.credentials = new UsernamePasswordCredentials(creds.getUsername(), Secret.toString(creds.getPassword())); } else { @@ -140,6 +158,11 @@ public BitbucketCloudApiClient(String owner, String repositoryName, StandardUser } this.owner = owner; this.repositoryName = repositoryName; + this.enableCache = enableCache; + if (enableCache) { + cachedTeam.setExpireDuration(teamCacheDuration, MINUTES); + cachedRepositories.setExpireDuration(repositoriesCacheDuraction, MINUTES); + } // Create Http client HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); @@ -298,7 +321,7 @@ public BitbucketRepository getRepository() throws IOException, InterruptedExcept if (repositoryName == null) { throw new UnsupportedOperationException("Cannot get a repository from an API instance that is not associated with a repository"); } - if (cachedRepository == null) { + if (!enableCache || cachedRepository == null) { String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE) .set("owner", owner) .set("repo", repositoryName) @@ -354,7 +377,7 @@ public boolean checkPathExists(@NonNull String branchOrHash, @NonNull String pat @CheckForNull @Override public String getDefaultBranch() throws IOException, InterruptedException { - if (cachedDefaultBranch == null) { + if (!enableCache || cachedDefaultBranch == null) { String url = UriTemplate.fromTemplate(REPO_URL_TEMPLATE + "/{?fields}") .set("owner", owner) .set("repo", repositoryName) @@ -565,21 +588,28 @@ public BitbucketTeam getTeam() throws IOException, InterruptedException { final String url = UriTemplate.fromTemplate(V2_TEAMS_API_BASE_URL + "{/owner}") .set("owner", owner) .expand(); - try { - return cachedTeam.get(owner, new Callable() { - @Override - public BitbucketTeam call() throws Exception { - try { - String response = getRequest(url); - return JsonParser.toJava(response, BitbucketCloudTeam.class); - } catch (FileNotFoundException e) { - return null; - } catch (IOException e) { - throw new IOException("I/O error when parsing response from URL: " + url, e); - } + + Callable request = new Callable() { + @Override + public BitbucketTeam call() throws Exception { + try { + String response = getRequest(url); + return JsonParser.toJava(response, BitbucketCloudTeam.class); + } catch (FileNotFoundException e) { + return null; + } catch (IOException e) { + throw new IOException("I/O error when parsing response from URL: " + url, e); } - }); - } catch (ExecutionException ex) { + } + }; + + try { + if (enableCache) { + return cachedTeam.get(owner, request); + } else { + return request.call(); + } + } catch (Exception ex) { return null; } } @@ -601,34 +631,39 @@ public List getRepositories(@CheckForNull UserRoleInRe template.set("role", role.getId()); cacheKey.append("::").append(role.getId()); } + Callable> request = new Callable>() { + @Override + public List call() throws Exception { + List repositories = new ArrayList(); + Integer pageNumber = 1; + String url, response; + PaginatedBitbucketRepository page; + do { + response = getRequest(url = template.set("page", pageNumber).expand()); + try { + page = JsonParser.toJava(response, PaginatedBitbucketRepository.class); + repositories.addAll(page.getValues()); + } catch (IOException e) { + throw new IOException("I/O error when parsing response from URL: " + url, e); + } + pageNumber++; + } while (page.getNext() != null); + Collections.sort(repositories, new Comparator() { + @Override + public int compare(BitbucketCloudRepository o1, BitbucketCloudRepository o2) { + return o1.getRepositoryName().compareTo(o2.getRepositoryName()); + } + }); + return repositories; + } + }; try { - return cachedRepositories.get(cacheKey.toString(), new Callable>() { - @Override - public List call() throws Exception { - List repositories = new ArrayList(); - Integer pageNumber = 1; - String url, response; - PaginatedBitbucketRepository page; - do { - response = getRequest(url = template.set("page", pageNumber).expand()); - try { - page = JsonParser.toJava(response, PaginatedBitbucketRepository.class); - repositories.addAll(page.getValues()); - } catch (IOException e) { - throw new IOException("I/O error when parsing response from URL: " + url, e); - } - pageNumber++; - } while (page.getNext() != null); - Collections.sort(repositories, new Comparator() { - @Override - public int compare(BitbucketCloudRepository o1, BitbucketCloudRepository o2) { - return o1.getRepositoryName().compareTo(o2.getRepositoryName()); - } - }); - return repositories; - } - }); - } catch (ExecutionException ex) { + if (enableCache) { + return cachedRepositories.get(cacheKey.toString(), request); + } else { + return request.call(); + } + } catch (Exception ex) { throw new IOException("Error while loading repositories from cache", ex); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java index 1524c80eb..bb8b7b594 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java @@ -2,7 +2,9 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApiFactory; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketCloudEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; @@ -20,6 +22,17 @@ protected boolean isMatch(@Nullable String serverUrl) { @Override protected BitbucketApi create(@Nullable String serverUrl, @Nullable StandardUsernamePasswordCredentials credentials, @NonNull String owner, @CheckForNull String repository) { - return new BitbucketCloudApiClient(owner, repository, credentials); + AbstractBitbucketEndpoint endpoint = BitbucketEndpointConfiguration.get().findEndpoint(BitbucketCloudEndpoint.SERVER_URL); + boolean enableCache = false; + int teamCacheDuration = 0; + int repositoriesCacheDuration = 0; + if (endpoint != null && endpoint instanceof BitbucketCloudEndpoint) { + enableCache = ((BitbucketCloudEndpoint) endpoint).isEnableCache(); + teamCacheDuration = ((BitbucketCloudEndpoint) endpoint).getTeamCacheDuration(); + repositoriesCacheDuration = ((BitbucketCloudEndpoint) endpoint).getRepositoriesCacheDuration(); + } + return new BitbucketCloudApiClient( + enableCache,teamCacheDuration,repositoriesCacheDuration, + owner, repository, credentials); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java index 91ebfbdbe..272172ef9 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java @@ -1,29 +1,28 @@ /* - * The MIT License - * - * Copyright (c) 2017-2018, bguerin - * - * 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. + * The MIT License Copyright (c) 2017-2018, bguerin 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; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -35,7 +34,7 @@ public class Cache { private final Map> entries; - private final long expireAfterNanos; + private long expireAfterNanos; public Cache(final int duration, final TimeUnit unit) { this(duration, unit, MAX_ENTRIES_DEFAULT); @@ -73,6 +72,24 @@ public int size() { return entries.size(); } + public void setExpireDuration(final int duration, final TimeUnit unit) { + this.expireAfterNanos = unit.toNanos(duration); + } + + public Stat stats() { + final List durations = new ArrayList<>(); + if (entries.size() > 0) { + for (final Entry e : entries.values()) { + durations.add(System.nanoTime() - e.nanos); + } + Collections.sort(durations); + } else { + durations.add(0L); + durations.add(0L); + } + return new Stat(entries.size(), durations.get(0), durations.get(durations.size() - 1)); + } + private boolean isExpired(final K key) { final Entry entry = entries.get(key); return entry != null && System.nanoTime() - entry.nanos > expireAfterNanos; @@ -111,4 +128,47 @@ public Entry(final V value) { } } + public static class Stat { + private final int count; + + private final long minDuration; + + private final long maxDuration; + + public Stat(final int count, final long minDuration, final long maxDuration) { + this.count = count; + this.minDuration = minDuration; + this.maxDuration = maxDuration; + } + + public int getCount() { + return count; + } + + public long getMinDuration() { + return minDuration; + } + + public long getMaxDuration() { + return maxDuration; + } + + @Override + public String toString() { + if (count == 0) { + return "No entry."; + } else { + final StringBuilder builder = new StringBuilder(); + if (count == 1) { + builder.append("1 entry, since ").append(NANOSECONDS.toMinutes(minDuration)).append( + " minutes"); + } else { + builder.append(count).append(" entries, since ").append( + NANOSECONDS.toMinutes(minDuration)).append(" (youngest) to ").append( + NANOSECONDS.toMinutes(maxDuration)).append(" (oldest) minutes."); + } + return builder.toString(); + } + } + } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java index fa2b2723b..6d78658b6 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java @@ -23,12 +23,18 @@ */ package com.cloudbees.jenkins.plugins.bitbucket.endpoints; +import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudApiClient; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.damnhandy.uri.template.UriTemplate; + +import java.util.List; + import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.Util; +import hudson.util.FormValidation; + import javax.annotation.Nonnull; import org.kohsuke.stapler.DataBoundConstructor; @@ -48,18 +54,55 @@ public class BitbucketCloudEndpoint extends AbstractBitbucketEndpoint { */ public static final String BAD_SERVER_URL = "http://bitbucket.org"; + /** + * {@code true} if caching should be used to reduce requests to Bitbucket. + */ + private final boolean enableCache; + + /** + * How long, in minutes, to cache the team response. + */ + private final int teamCacheDuration; + + /** + * How long, in minutes, to cache the repositories response. + */ + private final int repositoriesCacheDuration; + + public BitbucketCloudEndpoint(boolean manageHooks, @CheckForNull String credentialsId) { + this(false, 0, 0, manageHooks, credentialsId); + } + /** * Constructor. * + * @param enableCache {@code true} if caching should be used to reduce requests to Bitbucket. + * @param teamCacheDuration How long, in minutes, to cache the team response. + * @param repositoriesCacheDuration How long, in minutes, to cache the repositories response. * @param manageHooks {@code true} if and only if Jenkins is supposed to auto-manage hooks for this end-point. * @param credentialsId The {@link StandardUsernamePasswordCredentials#getId()} of the credentials to use for * auto-management of hooks. */ @DataBoundConstructor - public BitbucketCloudEndpoint(boolean manageHooks, @CheckForNull String credentialsId) { + public BitbucketCloudEndpoint(boolean enableCache, int teamCacheDuration, int repositoriesCacheDuration, boolean manageHooks, @CheckForNull String credentialsId) { super(manageHooks, credentialsId); + this.enableCache = enableCache; + this.teamCacheDuration = teamCacheDuration; + this.repositoriesCacheDuration = repositoriesCacheDuration; } + public boolean isEnableCache() { + return enableCache; + } + + public int getTeamCacheDuration() { + return teamCacheDuration; + } + + public int getRepositoriesCacheDuration() { + return repositoriesCacheDuration; + } + /** * {@inheritDoc} */ @@ -103,5 +146,14 @@ public static class DescriptorImpl extends AbstractBitbucketEndpointDescriptor { public String getDisplayName() { return Messages.BitbucketCloudEndpoint_displayName(); } + + public List getStats() { + return BitbucketCloudApiClient.stats(); + } + + public FormValidation doClear() { + BitbucketCloudApiClient.clearCaches(); + return FormValidation.ok("done"); + } } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly index 72bab88ef..6e0745a42 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly @@ -1,6 +1,20 @@ + + + + + + + + + + ${stat} + + + + diff --git a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/UriResolverTest.java b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/UriResolverTest.java index 82f13f064..e835e3392 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/UriResolverTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/bitbucket/UriResolverTest.java @@ -35,7 +35,7 @@ public class UriResolverTest { @Test public void httpUriResolver() throws Exception { - BitbucketApi api = new BitbucketCloudApiClient("test", null, null); + BitbucketApi api = new BitbucketCloudApiClient(false, 0, 0, "test", null, null); assertEquals("https://bitbucket.org/user1/repo1.git", api.getRepositoryUri( BitbucketRepositoryType.GIT, BitbucketRepositoryProtocol.HTTP, @@ -70,7 +70,7 @@ public void httpUriResolver() throws Exception { @Test public void sshUriResolver() throws Exception { - BitbucketApi api = new BitbucketCloudApiClient("test", null, null); + BitbucketApi api = new BitbucketCloudApiClient(false, 0, 0, "test", null, null); assertEquals("git@bitbucket.org:user1/repo1.git", api.getRepositoryUri( BitbucketRepositoryType.GIT, BitbucketRepositoryProtocol.SSH, From 5058aa2bd56cecd908d6062d8d010d7fc7a8d6d4 Mon Sep 17 00:00:00 2001 From: Benoit Guerin Date: Tue, 27 Mar 2018 12:31:34 +0200 Subject: [PATCH 3/7] Review : Use f:number --- .../endpoints/BitbucketCloudEndpoint/config-detail.jelly | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly index 6e0745a42..1d85ca5a2 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly @@ -3,10 +3,10 @@ xmlns:f="/lib/form"> - + - + From 118b44d2a3d09ed06df6eecac5b38a75c0b88485 Mon Sep 17 00:00:00 2001 From: Benoit Guerin Date: Mon, 7 May 2018 10:37:04 +0200 Subject: [PATCH 4/7] Fix formatting --- .../client/BitbucketCloudApiFactory.java | 2 +- .../plugins/bitbucket/client/Cache.java | 36 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java index bb8b7b594..b64caae6d 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiFactory.java @@ -32,7 +32,7 @@ protected BitbucketApi create(@Nullable String serverUrl, @Nullable StandardUser repositoriesCacheDuration = ((BitbucketCloudEndpoint) endpoint).getRepositoriesCacheDuration(); } return new BitbucketCloudApiClient( - enableCache,teamCacheDuration,repositoriesCacheDuration, + enableCache, teamCacheDuration, repositoriesCacheDuration, owner, repository, credentials); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java index 272172ef9..4a0252e07 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java @@ -1,19 +1,25 @@ /* - * The MIT License Copyright (c) 2017-2018, bguerin 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. + * The MIT License + * + * Copyright (c) 2016-2017, 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; From cf8cdab884b9af8c18eda525f292683c3560bdcc Mon Sep 17 00:00:00 2001 From: Benoit Guerin Date: Tue, 8 May 2018 11:26:11 +0200 Subject: [PATCH 5/7] Remove statistics, will be in another PR --- .../client/BitbucketCloudApiClient.java | 7 --- .../plugins/bitbucket/client/Cache.java | 59 +------------------ .../endpoints/BitbucketCloudEndpoint.java | 4 -- .../config-detail.jelly | 7 +-- 4 files changed, 2 insertions(+), 75 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java index 68165fc31..0d1cc523a 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/BitbucketCloudApiClient.java @@ -137,13 +137,6 @@ public class BitbucketCloudApiClient implements BitbucketApi { private transient BitbucketRepository cachedRepository; private transient String cachedDefaultBranch; - public static List stats() { - List stats = new ArrayList<>(); - stats.add("Team: " + cachedTeam.stats().toString()); - stats.add("Repositories : " + cachedRepositories.stats().toString()); - return stats; - } - public static void clearCaches() { cachedTeam.evictAll(); cachedRepositories.evictAll(); diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java index 4a0252e07..0bedbe3a0 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/Cache.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016-2017, CloudBees, Inc. + * Copyright (c) 2017-2018, bguerin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -82,20 +82,6 @@ public void setExpireDuration(final int duration, final TimeUnit unit) { this.expireAfterNanos = unit.toNanos(duration); } - public Stat stats() { - final List durations = new ArrayList<>(); - if (entries.size() > 0) { - for (final Entry e : entries.values()) { - durations.add(System.nanoTime() - e.nanos); - } - Collections.sort(durations); - } else { - durations.add(0L); - durations.add(0L); - } - return new Stat(entries.size(), durations.get(0), durations.get(durations.size() - 1)); - } - private boolean isExpired(final K key) { final Entry entry = entries.get(key); return entry != null && System.nanoTime() - entry.nanos > expireAfterNanos; @@ -134,47 +120,4 @@ public Entry(final V value) { } } - public static class Stat { - private final int count; - - private final long minDuration; - - private final long maxDuration; - - public Stat(final int count, final long minDuration, final long maxDuration) { - this.count = count; - this.minDuration = minDuration; - this.maxDuration = maxDuration; - } - - public int getCount() { - return count; - } - - public long getMinDuration() { - return minDuration; - } - - public long getMaxDuration() { - return maxDuration; - } - - @Override - public String toString() { - if (count == 0) { - return "No entry."; - } else { - final StringBuilder builder = new StringBuilder(); - if (count == 1) { - builder.append("1 entry, since ").append(NANOSECONDS.toMinutes(minDuration)).append( - " minutes"); - } else { - builder.append(count).append(" entries, since ").append( - NANOSECONDS.toMinutes(minDuration)).append(" (youngest) to ").append( - NANOSECONDS.toMinutes(maxDuration)).append(" (oldest) minutes."); - } - return builder.toString(); - } - } - } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java index 6d78658b6..d8bafb3fe 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint.java @@ -147,10 +147,6 @@ public String getDisplayName() { return Messages.BitbucketCloudEndpoint_displayName(); } - public List getStats() { - return BitbucketCloudApiClient.stats(); - } - public FormValidation doClear() { BitbucketCloudApiClient.clearCaches(); return FormValidation.ok("done"); diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly index 1d85ca5a2..e78b6c230 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketCloudEndpoint/config-detail.jelly @@ -8,12 +8,7 @@ - - - ${stat} - - - + From 6051afe0cd065b6d6a135a2f6b8bf407d61435a7 Mon Sep 17 00:00:00 2001 From: Patrick Ruckstuhl Date: Sat, 17 Mar 2018 14:28:03 +0100 Subject: [PATCH 6/7] check mergability when retrieving pull requests --- .../endpoints/BitbucketServerEndpoint.java | 15 ++++++++++ .../client/BitbucketServerAPIClient.java | 30 +++++++++++++++++++ .../BitbucketServerPullRequest.java | 12 ++++++++ .../BitbucketServerPullRequestCanMerge.java | 17 +++++++++++ .../config-detail.jelly | 3 ++ .../help-callCanMerge.html | 3 ++ 6 files changed, 80 insertions(+) create mode 100644 src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestCanMerge.java create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/help-callCanMerge.html diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java index 5d9962e9a..3653b99be 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint.java @@ -36,6 +36,7 @@ import jenkins.scm.api.SCMName; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; /** @@ -70,6 +71,11 @@ public class BitbucketServerEndpoint extends AbstractBitbucketEndpoint { @NonNull private final String serverUrl; + /** + * Whether to always call the can merge api when retrieving pull requests. + */ + private boolean callCanMerge = true; + /** * @param displayName Optional name to use to describe the end-point. * @param serverUrl The URL of this Bitbucket Server @@ -87,6 +93,15 @@ public BitbucketServerEndpoint(@CheckForNull String displayName, @NonNull String : displayName.trim(); } + public boolean isCallCanMerge() { + return callCanMerge; + } + + @DataBoundSetter + public void setCallCanMerge(boolean callCanMerge) { + this.callCanMerge = callCanMerge; + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java index f192779f4..faca699c8 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/BitbucketServerAPIClient.java @@ -35,11 +35,15 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketTeam; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook; import com.cloudbees.jenkins.plugins.bitbucket.client.repository.UserRoleInRepository; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.AbstractBitbucketEndpoint; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; +import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketServerEndpoint; import com.cloudbees.jenkins.plugins.bitbucket.filesystem.BitbucketSCMFile; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranch; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerBranches; import com.cloudbees.jenkins.plugins.bitbucket.server.client.branch.BitbucketServerCommit; import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequest; +import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequestCanMerge; import com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest.BitbucketServerPullRequests; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerProject; import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketServerRepositories; @@ -119,6 +123,7 @@ public class BitbucketServerAPIClient implements BitbucketApi { private static final String API_TAGS_PATH = API_REPOSITORY_PATH + "/tags{?start,limit}"; private static final String API_PULL_REQUESTS_PATH = API_REPOSITORY_PATH + "/pull-requests{?start,limit}"; private static final String API_PULL_REQUEST_PATH = API_REPOSITORY_PATH + "/pull-requests/{id}"; + private static final String API_PULL_REQUEST_MERGE_PATH = API_REPOSITORY_PATH + "/pull-requests/{id}/merge"; private static final String API_BROWSE_PATH = API_REPOSITORY_PATH + "/browse{/path*}{?at}"; private static final String API_COMMITS_PATH = API_REPOSITORY_PATH + "/commits{/hash}"; private static final String API_PROJECT_PATH = API_BASE_PATH + "/projects/{owner}"; @@ -289,12 +294,37 @@ public List getPullRequests() throws IOException, In page = JsonParser.toJava(response, BitbucketServerPullRequests.class); pullRequests.addAll(page.getValues()); } + + AbstractBitbucketEndpoint endpointConfig = BitbucketEndpointConfiguration.get().findEndpoint(baseURL); + if (endpointConfig instanceof BitbucketServerEndpoint && ((BitbucketServerEndpoint)endpointConfig).isCallCanMerge()) { + // This is required for Bitbucket Server to update the refs/pull-requests/* references + // See https://community.atlassian.com/t5/Bitbucket-questions/Change-pull-request-refs-after-Commit-instead-of-after-Approval/qaq-p/194702#M6829 + for (BitbucketServerPullRequest pullRequest : pullRequests) { + pullRequest.setCanMerge(getPullRequestCanMergeById(Integer.parseInt(pullRequest.getId()))); + } + } + return pullRequests; } catch (IOException e) { throw new IOException("I/O error when accessing URL: " + url, e); } } + private boolean getPullRequestCanMergeById(@NonNull Integer id) throws IOException { + String url = UriTemplate + .fromTemplate(API_PULL_REQUEST_MERGE_PATH) + .set("owner", getUserCentricOwner()) + .set("repo", repositoryName) + .set("id", id) + .expand(); + String response = getRequest(url); + try { + return JsonParser.toJava(response, BitbucketServerPullRequestCanMerge.class).isCanMerge(); + } catch (IOException e) { + throw new IOException("I/O error when accessing URL: " + url, e); + } + } + /** * {@inheritDoc} */ 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 07fc51fa5..34c412524 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 @@ -26,6 +26,7 @@ import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequest; import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketPullRequestSource; +import edu.umd.cs.findbugs.annotations.CheckForNull; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -49,6 +50,8 @@ public class BitbucketServerPullRequest implements BitbucketPullRequest { private String link; private String authorLogin; + + private Boolean canMerge; @JsonProperty @JsonDeserialize(keyAs = String.class, contentUsing = BitbucketHref.Deserializer.class) @@ -114,6 +117,15 @@ public void setAuthor(Author author) { } } + @CheckForNull + public Boolean isCanMerge() { + return canMerge; + } + + public void setCanMerge(Boolean canMerge) { + this.canMerge = canMerge; + } + @JsonIgnore public Map getLinks() { diff --git a/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestCanMerge.java b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestCanMerge.java new file mode 100644 index 000000000..4ebb8b5a8 --- /dev/null +++ b/src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/pullrequest/BitbucketServerPullRequestCanMerge.java @@ -0,0 +1,17 @@ +package com.cloudbees.jenkins.plugins.bitbucket.server.client.pullrequest; + +import org.codehaus.jackson.annotate.JsonProperty; + +public class BitbucketServerPullRequestCanMerge { + private boolean canMerge; + + public boolean isCanMerge() { + return canMerge; + } + + @JsonProperty + public void setCanMerge(boolean canMerge) { + this.canMerge = canMerge; + } + +} diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/config-detail.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/config-detail.jelly index 3460604c6..d2f2f7f7b 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/config-detail.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/config-detail.jelly @@ -7,4 +7,7 @@ + + + diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/help-callCanMerge.html b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/help-callCanMerge.html new file mode 100644 index 000000000..3c2c5bd56 --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/bitbucket/endpoints/BitbucketServerEndpoint/help-callCanMerge.html @@ -0,0 +1,3 @@ +
+ Always call the can merge api on pull requests to ensure the source code references are up to date. +
From 811e7ffa67166b28fc95db04eacf208dce30ad23 Mon Sep 17 00:00:00 2001 From: amuniz Date: Thu, 5 Jul 2018 18:50:55 +0200 Subject: [PATCH 7/7] [maven-release-plugin] prepare release cloudbees-bitbucket-branch-source-2.2.12 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bbe0dfe44..c6a1b6a54 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ cloudbees-bitbucket-branch-source - 2.2.12-SNAPSHOT + 2.2.12 hpi Bitbucket Branch Source Plugin @@ -74,7 +74,7 @@ scm:git:git://github.com/jenkinsci/bitbucket-branch-source-plugin.git scm:git:git@github.com:jenkinsci/bitbucket-branch-source-plugin.git https://github.com/jenkinsci/bitbucket-branch-source-plugin - HEAD + cloudbees-bitbucket-branch-source-2.2.12