aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/.gitignore20
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketAdditionalParameterEnvironmentContributor.java40
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java241
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildListener.java58
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java332
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java54
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java93
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java87
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java346
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java285
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java10
-rw-r--r--src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/Pullrequest.java392
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/BitbucketAdditionalParameterEnvironmentContributor.java39
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildListener.java69
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger.java303
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/BitbucketCause.java80
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/BitbucketHookReceiver.java140
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/ApiClient.java246
-rw-r--r--src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/BuildState.java9
-rw-r--r--src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly48
-rw-r--r--src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html19
-rw-r--r--src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html7
-rw-r--r--src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciSkipPhrases.html5
-rw-r--r--src/main/resources/index.jelly2
-rw-r--r--src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/config.jelly21
-rw-r--r--src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-cancelOutdatedJobs.html (renamed from src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-cancelOutdatedJobs.html)0
-rw-r--r--src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciKey.html (renamed from src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html)0
-rw-r--r--src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciName.html (renamed from src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html)0
28 files changed, 908 insertions, 2038 deletions
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/.gitignore b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/.gitignore
deleted file mode 100644
index 21e5893..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/.gitignore
+++ /dev/null
@@ -1,20 +0,0 @@
-target/
-work/
-
-#
-# Eclipse metadata.
-#
-.project
-.classpath
-.settings/
-
-#
-# Eclipse and Maven build results
-#
-bin/
-
-# IntelliJ metadata.
-*.iml
-*.ipr
-*.iws
-.idea/
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketAdditionalParameterEnvironmentContributor.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketAdditionalParameterEnvironmentContributor.java
deleted file mode 100644
index c531ca1..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketAdditionalParameterEnvironmentContributor.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import hudson.EnvVars;
-import hudson.Extension;
-import hudson.model.*;
-
-import java.io.IOException;
-
-@Extension
-public class BitbucketAdditionalParameterEnvironmentContributor extends EnvironmentContributor {
- @Override
- public void buildEnvironmentFor(Run run, EnvVars envVars, TaskListener taskListener)
- throws IOException, InterruptedException {
-
- BitbucketCause cause = (BitbucketCause) run.getCause(BitbucketCause.class);
- if (cause == null) {
- return;
- }
-
- putEnvVar(envVars, "sourceBranch", cause.getSourceBranch());
- putEnvVar(envVars, "targetBranch", cause.getTargetBranch());
- putEnvVar(envVars, "repositoryOwner", cause.getRepositoryOwner());
- putEnvVar(envVars, "repositoryName", cause.getRepositoryName());
- putEnvVar(envVars, "pullRequestId", cause.getPullRequestId());
- putEnvVar(envVars, "destinationRepositoryOwner", cause.getDestinationRepositoryOwner());
- putEnvVar(envVars, "destinationRepositoryName", cause.getDestinationRepositoryName());
- putEnvVar(envVars, "pullRequestTitle", cause.getPullRequestTitle());
- putEnvVar(envVars, "pullRequestAuthor", cause.getPullRequestAuthor());
-
- }
-
- private static void putEnvVar(EnvVars envs, String name, String value) {
- envs.put(name, getString(value, ""));
- }
-
- private static String getString(String actual, String d) {
- return actual == null ? d : actual;
- }
-
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java
deleted file mode 100644
index 6aa7344..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildFilter.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.regex.Matcher;
-import jenkins.plugins.git.AbstractGitSCMSource;
-import jenkins.scm.api.SCMSource;
-
-/**
- * Mutable wrapper
- */
-class Mutable<T> {
- private T value;
- public Mutable() { this.value = null; }
- public Mutable(T value) { this.value = value; }
- T get() { return this.value; }
- void set(T value) { this.value = value; }
-}
-
-abstract class Filter {
- protected static final Logger logger = Logger.getLogger(BitbucketBuildTrigger.class.getName());
-
- public static final String RX_FILTER_FLAG = "r";
- public static final String RX_FILTER_FLAG_SINGLE = RX_FILTER_FLAG + ":";
-
- public static final String SRC_RX = "s:(" + RX_FILTER_FLAG_SINGLE + ")?";
- public static final String DST_RX = "d:(" + RX_FILTER_FLAG_SINGLE + ")?";
- public static final String AUTHOR_RX = "a:(" + RX_FILTER_FLAG_SINGLE + ")?";
- public static final String BRANCH_FILTER_RX_PART = "([^\\s$]*)";
-
- abstract public boolean apply(String filter, BitbucketCause cause);
- abstract public boolean check(String filter);
-
- static final Pattern RX_SRC_DST_PARTS = Pattern.compile("(s:)|(d:)");
- public static boolean HasSourceOrDestPartsPredicate(String filter) { return RX_SRC_DST_PARTS.matcher(filter).find(); }
-
- static final Pattern RX_AUTHOR_PARTS = Pattern.compile("(a:)");
- public static boolean HasAuthorPartsPredicate(String filter) { return RX_AUTHOR_PARTS.matcher(filter).find(); }
-
- protected boolean applyByRx(Pattern rx, Filter usedFilter, String filter, BitbucketCause cause) {
- Matcher srcMatch = rx.matcher(filter);
- boolean apply = false;
- while (srcMatch.find()) {
- String computedFilter = ((srcMatch.group(1) == null ? "" : srcMatch.group(1)) + srcMatch.group(2)).trim();
- logger.log(Level.FINE, "Apply computed filter: {0}", computedFilter);
- apply = apply || (computedFilter.isEmpty() ? true : usedFilter.apply(computedFilter, cause));
- }
- return apply;
- }
-}
-
-class EmptyFilter extends Filter {
- @Override
- public boolean apply(String filter, BitbucketCause cause) { return true; }
- @Override
- public boolean check(String filter) { return true; }
-}
-
-class AnyFlag extends Filter {
- @Override
- public boolean apply(String filter, BitbucketCause cause) { return true; }
- @Override
- public boolean check(String filter) { return filter.isEmpty() || filter.contains("*") || filter.toLowerCase().contains("any"); }
-}
-
-class OnlySourceFlag extends Filter {
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- String selectedRx = filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter);
- logger.log(Level.FINE, "OnlySourceFlag using filter: {0}", selectedRx);
- Matcher matcher = Pattern.compile(selectedRx, Pattern.CASE_INSENSITIVE).matcher(cause.getSourceBranch());
- return filter.startsWith(RX_FILTER_FLAG_SINGLE) ? matcher.find() : matcher.matches();
- }
- @Override
- public boolean check(String filter) {
- return false;
- }
-}
-
-class OnlyDestFlag extends Filter {
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- String selectedRx = filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter);
- logger.log(Level.FINE, "OnlyDestFlag using filter: {0}", selectedRx);
- Matcher matcher = Pattern.compile(selectedRx, Pattern.CASE_INSENSITIVE).matcher(cause.getTargetBranch());
- return filter.startsWith(RX_FILTER_FLAG_SINGLE) ? matcher.find() : matcher.matches();
- }
- @Override
- public boolean check(String filter) {
- return !HasSourceOrDestPartsPredicate(filter);
- }
-}
-
-class SourceDestFlag extends Filter {
- static final Pattern SRC_MATCHER_RX = Pattern.compile(SRC_RX + BRANCH_FILTER_RX_PART, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);
- static final Pattern DST_MATCHER_RX = Pattern.compile(DST_RX + BRANCH_FILTER_RX_PART, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);
-
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- return this.applyByRx(SRC_MATCHER_RX, new OnlySourceFlag(), filter, cause) &&
- this.applyByRx(DST_MATCHER_RX, new OnlyDestFlag(), filter, cause);
- }
- @Override
- public boolean check(String filter) {
- return HasSourceOrDestPartsPredicate(filter);
- }
-}
-
-class AuthorFlag extends Filter {
- static final Pattern AUTHOR_MATCHER_RX = Pattern.compile(AUTHOR_RX + BRANCH_FILTER_RX_PART, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);
-
- static class AuthorFlagImpl extends Filter {
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- String selectedRx = filter.startsWith(RX_FILTER_FLAG_SINGLE) ? filter.substring(RX_FILTER_FLAG_SINGLE.length()) : Pattern.quote(filter);
- logger.log(Level.FINE, "AuthorFlagImpl using filter: {0}", selectedRx);
- Matcher matcher = Pattern.compile(selectedRx, Pattern.CASE_INSENSITIVE).matcher(cause.getPullRequestAuthor());
- return filter.startsWith(RX_FILTER_FLAG_SINGLE) ? matcher.find() : matcher.matches();
- }
- @Override
- public boolean check(String filter) { return false; }
- }
-
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- return this.applyByRx(AUTHOR_MATCHER_RX, new AuthorFlagImpl(), filter, cause);
- }
- @Override
- public boolean check(String filter) {
- return HasAuthorPartsPredicate(filter);
- }
-}
-
-class CombinedFlags extends Filter {
- private final Filter[] _filters;
- public CombinedFlags(Filter[] filters) {
- _filters = filters;
- }
-
- @Override
- public boolean apply(String filter, BitbucketCause cause) {
- boolean applied = true;
- for(Filter f: _filters)
- if (f.check(filter))
- applied = applied && f.apply(filter, cause);
- return applied;
- }
- @Override
- public boolean check(String filter) {
- for(Filter f: _filters)
- if (f.check(filter))
- return true;
- return false;
- }
-}
-
-/**
- * Created by maxvodo
- */
-public class BitbucketBuildFilter {
- private static final Logger logger = Logger.getLogger(BitbucketBuildTrigger.class.getName());
-
- private final String filter;
- private Filter currFilter = null;
- private static final List<Filter> AvailableFilters;
-
- static {
- ArrayList<Filter> filters = new ArrayList<Filter>();
-
- filters.add(new AnyFlag());
- filters.add(new CombinedFlags(new Filter[] {
- new SourceDestFlag(),
- new AuthorFlag()
- }));
- filters.add(new OnlyDestFlag());
- filters.add(new EmptyFilter());
-
- AvailableFilters = filters;
- }
-
- public BitbucketBuildFilter(String f) {
- this.filter = (f != null ? f : "").trim();
- this.buildFilter(this.filter);
- }
-
- private void buildFilter(String filter) {
- logger.log(Level.FINE, "Build filter by phrase: {0}", filter);
- for(Filter f : AvailableFilters) {
- if (f.check(filter)) {
- this.currFilter = f;
- logger.log(Level.FINE, "Using filter: {0}", f.getClass().getSimpleName());
- break;
- }
- }
- }
-
- public boolean approved(BitbucketCause cause) {
- logger.log(Level.FINE, "Approve cause: {0}", cause.toString());
- return this.currFilter.apply(this.filter, cause);
- }
-
- public static BitbucketBuildFilter instanceByString(String filter) {
- logger.log(Level.FINE, "Filter instance by filter string");
- return new BitbucketBuildFilter(filter);
- }
-
- static public String filterFromGitSCMSource(AbstractGitSCMSource gitscm, String defaultFilter) {
- if (gitscm == null) {
- logger.log(Level.FINE, "Git SCMSource unavailable. Using default value: {0}", defaultFilter);
- return defaultFilter;
- }
-
- StringBuffer filter = new StringBuffer(defaultFilter);
- final String includes = gitscm.getIncludes();
- if (includes != null && !includes.isEmpty()) {
- for(String part : includes.split("\\s+")) {
- filter.append(String.format("%s ", part.replaceAll("\\*\\/", "d:")));
- }
- }
-
- logger.log(Level.FINE, "Git includes transformation to filter result: {1} -> {0}; default: {2}", new Object[]{ filter, includes, defaultFilter });
- return filter.toString().trim();
- }
-
- public static BitbucketBuildFilter instanceBySCM(Collection<SCMSource> scmSources, String defaultFilter) {
- logger.log(Level.FINE, "Filter instance by using SCMSources list with {0} items", scmSources.size());
- AbstractGitSCMSource gitscm = null;
- for(SCMSource scm : scmSources) {
- logger.log(Level.FINE, "Check {0} SCMSource ", scm.getClass());
- if (scm instanceof AbstractGitSCMSource) {
- gitscm = (AbstractGitSCMSource)scm;
- break;
- }
- }
- return new BitbucketBuildFilter(filterFromGitSCMSource(gitscm, defaultFilter));
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildListener.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildListener.java
deleted file mode 100644
index 230253b..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildListener.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import hudson.Extension;
-import hudson.model.AbstractBuild;
-import hudson.model.Job;
-import hudson.model.Run;
-import hudson.model.TaskListener;
-import hudson.model.listeners.RunListener;
-import hudson.triggers.Trigger;
-import jenkins.model.ParameterizedJobMixIn;
-
-import javax.annotation.Nonnull;
-import java.util.logging.Logger;
-
-/**
- * Created by nishio
- */
-@Extension
-public class BitbucketBuildListener extends RunListener<Run<?, ?>> {
- private static final Logger logger = Logger.getLogger(BitbucketBuildListener.class.getName());
-
- @Override
- public void onStarted(Run r, TaskListener listener) {
- logger.fine("BitbucketBuildListener onStarted called.");
- BitbucketBuilds builds = builds(r);
- if (builds != null) {
- builds.onStarted((BitbucketCause) r.getCause(BitbucketCause.class), r);
- }
- }
-
- @Override
- public void onCompleted(Run r, @Nonnull TaskListener listener) {
- logger.fine("BitbucketBuildListener onCompleted called.");
- BitbucketBuilds builds = builds(r);
- if (builds != null) {
- builds.onCompleted((BitbucketCause) r.getCause(BitbucketCause.class), r.getResult(), r.getUrl());
- }
- }
-
- private BitbucketBuilds builds(Run<?, ?> r) {
- BitbucketBuildTrigger trigger = null;
- if (r instanceof AbstractBuild) {
- trigger = BitbucketBuildTrigger.getTrigger(((AbstractBuild) r).getProject());
- } else {
- Job job = r.getParent();
- if (job instanceof ParameterizedJobMixIn.ParameterizedJob) {
-
- for (Trigger<?> t : ((ParameterizedJobMixIn.ParameterizedJob) job).getTriggers().values()) {
- if (t instanceof BitbucketBuildTrigger) {
- trigger = (BitbucketBuildTrigger) t;
- }
- }
- }
- }
- return trigger == null ? null : trigger.getBuilder().getBuilds();
- }
-
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java
deleted file mode 100644
index be36382..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger.java
+++ /dev/null
@@ -1,332 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import antlr.ANTLRException;
-import com.cloudbees.plugins.credentials.CredentialsProvider;
-import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
-import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
-import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
-import hudson.Extension;
-import hudson.model.*;
-import hudson.model.Queue;
-import hudson.model.queue.QueueTaskFuture;
-import hudson.plugins.git.RevisionParameterAction;
-import hudson.triggers.Trigger;
-import hudson.triggers.TriggerDescriptor;
-import hudson.util.ListBoxModel;
-import jenkins.model.Jenkins;
-import jenkins.model.ParameterizedJobMixIn;
-import net.sf.json.JSONObject;
-import org.apache.commons.lang.StringUtils;
-import org.jenkinsci.Symbol;
-import org.kohsuke.stapler.DataBoundConstructor;
-import org.kohsuke.stapler.StaplerRequest;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf;
-
-/**
- * Created by nishio
- */
-public class BitbucketBuildTrigger extends Trigger<Job<?, ?>> {
- private static final Logger logger = Logger.getLogger(BitbucketBuildTrigger.class.getName());
- private final String projectPath;
- private final String cron;
- private final String credentialsId;
- private final String username;
- private final String password;
- private final String repositoryOwner;
- private final String repositoryName;
- private final String branchesFilter;
- private final boolean branchesFilterBySCMIncludes;
- private final String ciKey;
- private final String ciName;
- private final String ciSkipPhrases;
- private final boolean checkDestinationCommit;
- private final boolean approveIfSuccess;
- private final boolean cancelOutdatedJobs;
- private final String commentTrigger;
-
- transient private BitbucketPullRequestsBuilder bitbucketPullRequestsBuilder;
-
- public static final BitbucketBuildTriggerDescriptor descriptor = new BitbucketBuildTriggerDescriptor();
-
- @DataBoundConstructor
- public BitbucketBuildTrigger(
- String projectPath,
- String cron,
- String credentialsId,
- String username,
- String password,
- String repositoryOwner,
- String repositoryName,
- String branchesFilter,
- boolean branchesFilterBySCMIncludes,
- String ciKey,
- String ciName,
- String ciSkipPhrases,
- boolean checkDestinationCommit,
- boolean approveIfSuccess,
- boolean cancelOutdatedJobs,
- String commentTrigger
- ) throws ANTLRException {
- super(cron);
- this.projectPath = projectPath;
- this.cron = cron;
- this.credentialsId = credentialsId;
- this.username = username;
- this.password = password;
- this.repositoryOwner = repositoryOwner;
- this.repositoryName = repositoryName;
- this.branchesFilter = branchesFilter;
- this.branchesFilterBySCMIncludes = branchesFilterBySCMIncludes;
- this.ciKey = ciKey;
- this.ciName = ciName;
- this.ciSkipPhrases = ciSkipPhrases;
- this.checkDestinationCommit = checkDestinationCommit;
- this.approveIfSuccess = approveIfSuccess;
- this.cancelOutdatedJobs = cancelOutdatedJobs;
- this.commentTrigger = commentTrigger;
- }
-
- public String getProjectPath() {
- return this.projectPath;
- }
-
- public String getCron() {
- return this.cron;
- }
-
- public String getCredentialsId() {
- return credentialsId;
- }
-
- public String getUsername() {
- return username;
- }
-
- public String getPassword() {
- return password;
- }
-
- public String getRepositoryOwner() {
- return repositoryOwner;
- }
-
- public String getRepositoryName() {
- return repositoryName;
- }
-
- public String getBranchesFilter() {
- return branchesFilter;
- }
-
- public boolean getBranchesFilterBySCMIncludes() {
- return branchesFilterBySCMIncludes;
- }
-
- public String getCiKey() {
- return ciKey;
- }
-
- public String getCiName() {
- return ciName;
- }
-
- public String getCiSkipPhrases() {
- return ciSkipPhrases;
- }
-
- public boolean getCheckDestinationCommit() {
- return checkDestinationCommit;
- }
-
- public boolean getApproveIfSuccess() {
- return approveIfSuccess;
- }
-
- public boolean getCancelOutdatedJobs() {
- return cancelOutdatedJobs;
- }
- /**
- * @return a phrase that when entered in a comment will trigger a new build
- */
- public String getCommentTrigger() {
- return commentTrigger;
- }
-
- @Override
- public void start(Job<?, ?> project, boolean newInstance) {
- try {
- this.bitbucketPullRequestsBuilder = BitbucketPullRequestsBuilder.getBuilder();
- this.bitbucketPullRequestsBuilder.setProject(project);
- this.bitbucketPullRequestsBuilder.setTrigger(this);
- this.bitbucketPullRequestsBuilder.setupBuilder();
- } catch(IllegalStateException e) {
- logger.log(Level.SEVERE, "Can't start trigger", e);
- return;
- }
- super.start(project, newInstance);
- }
-
- public static BitbucketBuildTrigger getTrigger(AbstractProject project) {
- Trigger trigger = project.getTrigger(BitbucketBuildTrigger.class);
- return (BitbucketBuildTrigger)trigger;
- }
-
- public BitbucketPullRequestsBuilder getBuilder() {
- return this.bitbucketPullRequestsBuilder;
- }
-
- private ParameterizedJobMixIn retrieveScheduleJob(final Job<?, ?> job) {
- // TODO 1.621+ use standard method
- return new ParameterizedJobMixIn() {
- @Override
- protected Job asJob() {
- return job;
- }
- };
- }
-
- public QueueTaskFuture<?> startJob(BitbucketCause cause) {
- Map<String, ParameterValue> values = this.getDefaultParameters();
-
- if (getCancelOutdatedJobs()) {
- cancelPreviousJobsInQueueThatMatch(cause);
- abortRunningJobsThatMatch(cause);
- }
-
- return retrieveScheduleJob(this.job).scheduleBuild2(0,
- new CauseAction(cause),
- new ParametersAction(new ArrayList(values.values())),
- new RevisionParameterAction(cause.getSourceCommitHash()));
- }
-
- private void cancelPreviousJobsInQueueThatMatch(@Nonnull BitbucketCause bitbucketCause) {
- logger.fine("Looking for queued jobs that match PR ID: " + bitbucketCause.getPullRequestId());
- Queue queue = getInstance().getQueue();
-
- for (Queue.Item item : queue.getItems()) {
- if (hasCauseFromTheSamePullRequest(item.getCauses(), bitbucketCause)) {
- logger.fine("Canceling item in queue: " + item);
- queue.cancel(item);
- }
- }
- }
-
- private Jenkins getInstance() {
- final Jenkins instance = Jenkins.getInstance();
- if (instance == null){
- throw new IllegalStateException("Jenkins instance is NULL!");
- }
- return instance;
- }
-
- private void abortRunningJobsThatMatch(@Nonnull BitbucketCause bitbucketCause) {
- logger.fine("Looking for running jobs that match PR ID: " + bitbucketCause.getPullRequestId());
- for (Object o : job.getBuilds()) {
- if (o instanceof Run) {
- Run build = (Run) o;
- if (build.isBuilding() && hasCauseFromTheSamePullRequest(build.getCauses(), bitbucketCause)) {
- logger.fine("Aborting build: " + build + " since PR is outdated");
- setBuildDescription(build);
- final Executor executor = build.getExecutor();
- if (executor == null){
- throw new IllegalStateException("Executor can't be NULL");
- }
- executor.interrupt(Result.ABORTED);
- }
- }
- }
- }
-
- private void setBuildDescription(final Run build) {
- try {
- build.setDescription("Aborting build by `Bitbucket Pullrequest Builder Plugin`: " + build + " since PR is outdated");
- } catch (IOException e) {
- logger.warning("Can't set up build description due to an IOException: " + e.getMessage());
- }
- }
-
- private boolean hasCauseFromTheSamePullRequest(@Nullable List<Cause> causes, @Nullable BitbucketCause pullRequestCause) {
- if (causes != null && pullRequestCause != null) {
- for (Cause cause : causes) {
- if (cause instanceof BitbucketCause) {
- BitbucketCause sc = (BitbucketCause) cause;
- if (StringUtils.equals(sc.getPullRequestId(), pullRequestCause.getPullRequestId()) &&
- StringUtils.equals(sc.getRepositoryName(), pullRequestCause.getRepositoryName())) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private Map<String, ParameterValue> getDefaultParameters() {
- Map<String, ParameterValue> values = new HashMap<String, ParameterValue>();
- ParametersDefinitionProperty definitionProperty = this.job.getProperty(ParametersDefinitionProperty.class);
-
- if (definitionProperty != null) {
- for (ParameterDefinition definition : definitionProperty.getParameterDefinitions()) {
- values.put(definition.getName(), definition.getDefaultParameterValue());
- }
- }
- return values;
- }
-
- @Override
- public void run() {
- Job<?,?> project = this.getBuilder().getProject();
- if (project instanceof AbstractProject && ((AbstractProject)project).isDisabled()) {
- logger.fine("Build Skip.");
- } else {
- this.bitbucketPullRequestsBuilder.run();
- this.getDescriptor().save();
- }
- }
-
- @Override
- public void stop() {
- super.stop();
- }
-
- @Extension
- @Symbol("bitbucketpr")
- public static final class BitbucketBuildTriggerDescriptor extends TriggerDescriptor {
- public BitbucketBuildTriggerDescriptor() {
- load();
- }
-
- @Override
- public boolean isApplicable(Item item) {
- return item instanceof Job && item instanceof ParameterizedJobMixIn.ParameterizedJob;
- }
-
- @Override
- public String getDisplayName() {
- return "Bitbucket Pull Requests Builder";
- }
-
- @Override
- public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
- save();
- return super.configure(req, json);
- }
-
- public ListBoxModel doFillCredentialsIdItems() {
- return new StandardListBoxModel()
- .withEmptySelection()
- .withMatching(instanceOf(UsernamePasswordCredentials.class),
- CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class));
- }
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java
deleted file mode 100644
index 6ba3dab..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuilds.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState;
-import hudson.model.*;
-import jenkins.model.JenkinsLocationConfiguration;
-
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Created by nishio
- */
-public class BitbucketBuilds {
- private static final Logger logger = Logger.getLogger(BitbucketBuilds.class.getName());
- private BitbucketBuildTrigger trigger;
- private BitbucketRepository repository;
-
- public BitbucketBuilds(BitbucketBuildTrigger trigger, BitbucketRepository repository) {
- this.trigger = trigger;
- this.repository = repository;
- this.repository.init();
- }
-
- void onStarted(BitbucketCause cause, Run<?, ?> build) {
- if (cause == null) {
- return;
- }
- try {
- build.setDescription(cause.getShortDescription());
- } catch (IOException e) {
- logger.log(Level.SEVERE, "Can't update build description", e);
- }
- }
-
- void onCompleted(BitbucketCause cause, Result result, String buildUrl) {
- if (cause == null) {
- return;
- }
- JenkinsLocationConfiguration globalConfig = new JenkinsLocationConfiguration();
- String rootUrl = globalConfig.getUrl();
- if (rootUrl == null) {
- logger.warning("PLEASE SET JENKINS ROOT URL IN GLOBAL CONFIGURATION FOR BUILD STATE REPORTING");
- } else {
- buildUrl = rootUrl + buildUrl;
- BuildState state = result == Result.SUCCESS ? BuildState.SUCCESSFUL : BuildState.FAILED;
- repository.setBuildStatus(cause, state, buildUrl);
- }
-
- if (this.trigger.getApproveIfSuccess() && result == Result.SUCCESS) {
- this.repository.postPullRequestApproval(cause.getPullRequestId());
- }
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java
deleted file mode 100644
index 3cb107b..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketCause.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import hudson.model.Cause;
-
-/**
- * Created by nishio
- */
-public class BitbucketCause extends Cause {
- private final String sourceBranch;
- private final String targetBranch;
- private final String repositoryOwner;
- private final String repositoryName;
- private final String pullRequestId;
- private final String destinationRepositoryOwner;
- private final String destinationRepositoryName;
- private final String pullRequestTitle;
- private final String sourceCommitHash;
- private final String destinationCommitHash;
- private final String pullRequestAuthor;
- public static final String BITBUCKET_URL = "https://bitbucket.org/";
-
- public BitbucketCause(String sourceBranch,
- String targetBranch,
- String repositoryOwner,
- String repositoryName,
- String pullRequestId,
- String destinationRepositoryOwner,
- String destinationRepositoryName,
- String pullRequestTitle,
- String sourceCommitHash,
- String destinationCommitHash,
- String pullRequestAuthor) {
- this.sourceBranch = sourceBranch;
- this.targetBranch = targetBranch;
- this.repositoryOwner = repositoryOwner;
- this.repositoryName = repositoryName;
- this.pullRequestId = pullRequestId;
- this.destinationRepositoryOwner = destinationRepositoryOwner;
- this.destinationRepositoryName = destinationRepositoryName;
- this.pullRequestTitle = pullRequestTitle;
- this.sourceCommitHash = sourceCommitHash;
- this.destinationCommitHash = destinationCommitHash;
- this.pullRequestAuthor = pullRequestAuthor;
- }
-
- public String getSourceBranch() {
- return sourceBranch;
- }
- public String getTargetBranch() {
- return targetBranch;
- }
-
- public String getRepositoryOwner() {
- return repositoryOwner;
- }
-
- public String getRepositoryName() {
- return repositoryName;
- }
-
- public String getPullRequestId() {
- return pullRequestId;
- }
-
-
- public String getDestinationRepositoryOwner() {
- return destinationRepositoryOwner;
- }
-
- public String getDestinationRepositoryName() {
- return destinationRepositoryName;
- }
-
- public String getPullRequestTitle() {
- return pullRequestTitle;
- }
-
- public String getSourceCommitHash() { return sourceCommitHash; }
-
- public String getDestinationCommitHash() { return destinationCommitHash; }
-
- @Override
- public String getShortDescription() {
- String description = "<a href=\"" + BITBUCKET_URL + this.getDestinationRepositoryOwner() + "/";
- description += this.getDestinationRepositoryName() + "/pull-request/" + this.getPullRequestId();
- description += "\">#" + this.getPullRequestId() + " " + this.getPullRequestTitle() + "</a>";
- return description;
- }
-
- public String getPullRequestAuthor() {
- return this.pullRequestAuthor;
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java
deleted file mode 100644
index 5b37fea..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketPullRequestsBuilder.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-import java.util.Collection;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import hudson.model.Job;
-import org.apache.commons.codec.binary.Hex;
-
-/**
- * Created by nishio
- */
-public class BitbucketPullRequestsBuilder {
- private static final Logger logger = Logger.getLogger(BitbucketBuildTrigger.class.getName());
- private Job<?, ?> project;
- private BitbucketBuildTrigger trigger;
- private BitbucketRepository repository;
- private BitbucketBuilds builds;
-
- public static BitbucketPullRequestsBuilder getBuilder() {
- return new BitbucketPullRequestsBuilder();
- }
-
- public void stop() {
- // TODO?
- }
-
- public void run() {
- logger.fine("Build Start.");
- this.repository.init();
- Collection<Pullrequest> targetPullRequests = this.repository.getTargetPullRequests();
- this.repository.addFutureBuildTasks(targetPullRequests);
- }
-
- public BitbucketPullRequestsBuilder setupBuilder() {
- if (this.project == null || this.trigger == null) {
- throw new IllegalStateException();
- }
- this.repository = new BitbucketRepository(this.trigger.getProjectPath(), this);
- this.repository.init();
- this.builds = new BitbucketBuilds(this.trigger, this.repository);
- return this;
- }
-
- public void setProject(Job<?, ?> project) {
- this.project = project;
- }
-
- public void setTrigger(BitbucketBuildTrigger trigger) {
- this.trigger = trigger;
- }
-
- public Job<?, ?> getProject() {
- return this.project;
- }
-
- /**
- * Return MD5 hashed full project name or full project name, if MD5 hash provider inaccessible
- * @return unique project id
- */
- public String getProjectId() {
- try {
- final MessageDigest MD5 = MessageDigest.getInstance("MD5");
- return new String(Hex.encodeHex(MD5.digest(this.project.getFullName().getBytes("UTF-8"))));
- } catch (NoSuchAlgorithmException exc) {
- logger.log(Level.WARNING, "Failed to produce hash", exc);
- } catch (UnsupportedEncodingException exc) {
- logger.log(Level.WARNING, "Failed to produce hash", exc);
- }
- return this.project.getFullName();
-
- }
-
- public BitbucketBuildTrigger getTrigger() {
- return this.trigger;
- }
-
- public BitbucketBuilds getBuilds() {
- return this.builds;
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java
deleted file mode 100644
index 3b0a314..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketRepository.java
+++ /dev/null
@@ -1,346 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.BuildState;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest;
-
-import java.util.LinkedList;
-import java.util.logging.Level;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.cloudbees.plugins.credentials.CredentialsMatchers;
-import com.cloudbees.plugins.credentials.CredentialsProvider;
-import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
-import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
-
-import jenkins.model.Jenkins;
-import jenkins.scm.api.SCMSource;
-import jenkins.scm.api.SCMSourceOwner;
-import jenkins.scm.api.SCMSourceOwners;
-
-import org.apache.commons.lang.StringUtils;
-
-import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf;
-
-/**
- * Created by nishio
- */
-public class BitbucketRepository {
- private static final Logger logger = Logger.getLogger(BitbucketRepository.class.getName());
- private static final String BUILD_DESCRIPTION = "%s: %s into %s";
- private static final String BUILD_REQUEST_DONE_MARKER = "ttp build flag";
- private static final String BUILD_REQUEST_MARKER_TAG_SINGLE_RX = "\\#[\\w\\-\\d]+";
- private static final String BUILD_REQUEST_MARKER_TAGS_RX = "\\[bid\\:\\s?(.*)\\]";
- /**
- * Default value for comment trigger.
- */
- public static final String DEFAULT_COMMENT_TRIGGER = "test this please";
-
- private String projectPath;
- private BitbucketPullRequestsBuilder builder;
- private BitbucketBuildTrigger trigger;
- private ApiClient client;
-
- public BitbucketRepository(String projectPath, BitbucketPullRequestsBuilder builder) {
- this.projectPath = projectPath;
- this.builder = builder;
- }
-
- public void init() {
- this.init(null, null);
- }
-
- public <T extends ApiClient.HttpClientFactory> void init(T httpFactory) {
- this.init(null, httpFactory);
- }
-
- public void init(ApiClient client) {
- this.init(client, null);
- }
-
- public <T extends ApiClient.HttpClientFactory> void init(ApiClient client, T httpFactory) {
- this.trigger = this.builder.getTrigger();
-
- if (client == null) {
- String username = trigger.getUsername();
- String password = trigger.getPassword();
- StandardUsernamePasswordCredentials credentials = getCredentials(trigger.getCredentialsId());
- if (credentials != null) {
- username = credentials.getUsername();
- password = credentials.getPassword().getPlainText();
- }
- this.client = new ApiClient(
- username,
- password,
- trigger.getRepositoryOwner(),
- trigger.getRepositoryName(),
- trigger.getCiKey(),
- trigger.getCiName(),
- httpFactory
- );
-
- } else this.client = client;
- }
-
- public Collection<Pullrequest> getTargetPullRequests() {
- logger.fine("Fetch PullRequests.");
- List<Pullrequest> pullRequests = client.getPullRequests();
- List<Pullrequest> targetPullRequests = new ArrayList<Pullrequest>();
- for(Pullrequest pullRequest : pullRequests) {
- if (isBuildTarget(pullRequest)) {
- targetPullRequests.add(pullRequest);
- }
- }
- return targetPullRequests;
- }
-
- public ApiClient getClient() {
- return this.client;
- }
-
- public void addFutureBuildTasks(Collection<Pullrequest> pullRequests) {
- for(Pullrequest pullRequest : pullRequests) {
- if ( this.trigger.getApproveIfSuccess() ) {
- deletePullRequestApproval(pullRequest.getId());
- }
- BitbucketCause cause = new BitbucketCause(
- pullRequest.getSource().getBranch().getName(),
- pullRequest.getDestination().getBranch().getName(),
- pullRequest.getSource().getRepository().getOwnerName(),
- pullRequest.getSource().getRepository().getRepositoryName(),
- pullRequest.getId(),
- pullRequest.getDestination().getRepository().getOwnerName(),
- pullRequest.getDestination().getRepository().getRepositoryName(),
- pullRequest.getTitle(),
- pullRequest.getSource().getCommit().getHash(),
- pullRequest.getDestination().getCommit().getHash(),
- pullRequest.getAuthor().getCombinedUsername()
- );
- setBuildStatus(cause, BuildState.INPROGRESS, getInstance().getRootUrl());
- this.builder.getTrigger().startJob(cause);
- }
- }
-
- private Jenkins getInstance() {
- final Jenkins instance = Jenkins.getInstance();
- if (instance == null){
- throw new IllegalStateException("Jenkins instance is NULL!");
- }
- return instance;
- }
-
-
- public void setBuildStatus(BitbucketCause cause, BuildState state, String buildUrl) {
- String comment = null;
- String sourceCommit = cause.getSourceCommitHash();
- String owner = cause.getRepositoryOwner();
- String repository = cause.getRepositoryName();
- String destinationBranch = cause.getTargetBranch();
-
- logger.fine("setBuildStatus " + state + " for commit: " + sourceCommit + " with url " + buildUrl);
-
- if (state == BuildState.FAILED || state == BuildState.SUCCESSFUL) {
- comment = String.format(BUILD_DESCRIPTION, builder.getProject().getDisplayName(), sourceCommit, destinationBranch);
- }
-
- this.client.setBuildStatus(owner, repository, sourceCommit, state, buildUrl, comment, this.builder.getProjectId());
- }
-
- public void deletePullRequestApproval(String pullRequestId) {
- this.client.deletePullRequestApproval(pullRequestId);
- }
-
- public void postPullRequestApproval(String pullRequestId) {
- this.client.postPullRequestApproval(pullRequestId);
- }
-
- public String getMyBuildTag(String buildKey) {
- return "#" + this.client.buildStatusKey(buildKey);
- }
-
- final static Pattern BUILD_TAGS_RX = Pattern.compile(BUILD_REQUEST_MARKER_TAGS_RX, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);
- final static Pattern SINGLE_BUILD_TAG_RX = Pattern.compile(BUILD_REQUEST_MARKER_TAG_SINGLE_RX, Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);
- final static String CONTENT_PART_TEMPLATE = "```[bid: %s]```";
-
- private List<String> getAvailableBuildTagsFromTTPComment(String buildTags) {
- logger.log(Level.FINE, "Parse {0}", new Object[]{ buildTags });
- List<String> availableBuildTags = new LinkedList<String>();
- Matcher subBuildTagMatcher = SINGLE_BUILD_TAG_RX.matcher(buildTags);
- while(subBuildTagMatcher.find()) availableBuildTags.add(subBuildTagMatcher.group(0).trim());
- return availableBuildTags;
- }
-
- public boolean hasMyBuildTagInTTPComment(String content, String buildKey) {
- Matcher tagsMatcher = BUILD_TAGS_RX.matcher(content);
- if (tagsMatcher.find()) {
- logger.log(Level.FINE, "Content {0} g[1]:{1} mykey:{2}", new Object[] { content, tagsMatcher.group(1).trim(), this.getMyBuildTag(buildKey) });
- return this.getAvailableBuildTagsFromTTPComment(tagsMatcher.group(1).trim()).contains(this.getMyBuildTag(buildKey));
- }
- else return false;
- }
-
- private void postBuildTagInTTPComment(String pullRequestId, String content, String buildKey) {
- logger.log(Level.FINE, "Update build tag for {0} build key", buildKey);
- List<String> builds = this.getAvailableBuildTagsFromTTPComment(content);
- builds.add(this.getMyBuildTag(buildKey));
- content += " " + String.format(CONTENT_PART_TEMPLATE, StringUtils.join(builds, " "));
- logger.log(Level.FINE, "Post comment: {0} with original content {1}", new Object[]{ content, this.client.postPullRequestComment(pullRequestId, content).getId() });
- }
-
- private boolean isTTPComment(String content) {
- // special case: in unit tests, trigger is null and can't be mocked
- String commentTrigger = DEFAULT_COMMENT_TRIGGER;
- if(trigger != null && StringUtils.isNotBlank(trigger.getCommentTrigger())) {
- commentTrigger = trigger.getCommentTrigger();
- }
- return content.toLowerCase().contains(commentTrigger);
- }
-
- private boolean isTTPCommentBuildTags(String content) {
- return content.toLowerCase().contains(BUILD_REQUEST_DONE_MARKER.toLowerCase());
- }
-
- public List<Pullrequest.Comment> filterPullRequestComments(List<Pullrequest.Comment> comments) {
- logger.fine("Filter PullRequest Comments.");
- Collections.sort(comments);
- Collections.reverse(comments);
- List<Pullrequest.Comment> filteredComments = new LinkedList<Pullrequest.Comment>();
- for(Pullrequest.Comment comment : comments) {
- String content = comment.getContent();
- if (content == null || content.isEmpty()) continue;
- boolean isTTP = this.isTTPComment(content);
- boolean isTTPBuild = this.isTTPCommentBuildTags(content);
- if (isTTP || isTTPBuild) filteredComments.add(comment);
- if (isTTP) break;
- }
- return filteredComments;
- }
-
- private boolean isBuildTarget(Pullrequest pullRequest) {
- if (pullRequest.getState() != null && pullRequest.getState().equals("OPEN")) {
- if (isSkipBuild(pullRequest.getTitle()) || !isFilteredBuild(pullRequest)) {
- return false;
- }
-
- Pullrequest.Revision source = pullRequest.getSource();
- String sourceCommit = source.getCommit().getHash();
- Pullrequest.Revision destination = pullRequest.getDestination();
- String owner = destination.getRepository().getOwnerName();
- String repositoryName = destination.getRepository().getRepositoryName();
-
- Pullrequest.Repository sourceRepository = source.getRepository();
- String buildKeyPart = this.builder.getProjectId();
-
- final boolean commitAlreadyBeenProcessed = this.client.hasBuildStatus(
- sourceRepository.getOwnerName(), sourceRepository.getRepositoryName(), sourceCommit, buildKeyPart
- );
- if (commitAlreadyBeenProcessed) logger.log(Level.FINE,
- "Commit {0}#{1} has already been processed",
- new Object[]{ sourceCommit, buildKeyPart }
- );
-
- final String id = pullRequest.getId();
- List<Pullrequest.Comment> comments = client.getPullRequestComments(owner, repositoryName, id);
-
- boolean rebuildCommentAvailable = false;
- if (comments != null) {
- Collection<Pullrequest.Comment> filteredComments = this.filterPullRequestComments(comments);
- boolean hasMyBuildTag = false;
- for (Pullrequest.Comment comment : filteredComments) {
- String content = comment.getContent();
- if (this.isTTPComment(content)) {
- rebuildCommentAvailable = true;
- logger.log(Level.FINE,
- "Rebuild comment available for commit {0} and comment #{1}",
- new Object[]{ sourceCommit, comment.getId() }
- );
- }
- if (isTTPCommentBuildTags(content))
- hasMyBuildTag |= this.hasMyBuildTagInTTPComment(content, buildKeyPart);
- }
- rebuildCommentAvailable &= !hasMyBuildTag;
- }
- if (rebuildCommentAvailable) this.postBuildTagInTTPComment(id, "TTP build flag", buildKeyPart);
-
- final boolean canBuildTarget = rebuildCommentAvailable || !commitAlreadyBeenProcessed;
- logger.log(Level.FINE, "Build target? {0} [rebuild:{1} processed:{2}]", new Object[]{ canBuildTarget, rebuildCommentAvailable, commitAlreadyBeenProcessed});
- return canBuildTarget;
- }
-
- return false;
- }
-
- private boolean isSkipBuild(String pullRequestTitle) {
- String skipPhrases = this.trigger.getCiSkipPhrases();
- if (skipPhrases != null && !"".equals(skipPhrases)) {
- String[] phrases = skipPhrases.split(",");
- for(String phrase : phrases) {
- if (pullRequestTitle.toLowerCase().contains(phrase.trim().toLowerCase())) {
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean isFilteredBuild(Pullrequest pullRequest) {
-
- final String pullRequestId = pullRequest.getId();
- final String pullRequestTitle = pullRequest.getTitle();
- final String destinationRepoName = pullRequest.getDestination().getRepository().getRepositoryName();
-
- // pullRequest.getDestination().getCommit() may return null for pull requests with merge conflicts
- // * see: https://github.com/nishio-dens/bitbucket-pullrequest-builder-plugin/issues/119
- // * see: https://github.com/nishio-dens/bitbucket-pullrequest-builder-plugin/issues/98
- final String destinationCommitHash;
- if (pullRequest.getDestination().getCommit() == null) {
- logger.log(Level.INFO, "Pull request #{0} ''{1}'' in repo ''{2}'' has a null value for destination commit.",
- new Object[]{pullRequestId, pullRequestTitle, destinationRepoName});
- destinationCommitHash = null;
- } else {
- destinationCommitHash = pullRequest.getDestination().getCommit().getHash();
- }
-
- BitbucketCause cause = new BitbucketCause(
- pullRequest.getSource().getBranch().getName(),
- pullRequest.getDestination().getBranch().getName(),
- pullRequest.getSource().getRepository().getOwnerName(),
- pullRequest.getSource().getRepository().getRepositoryName(),
- pullRequestId,
- pullRequest.getDestination().getRepository().getOwnerName(),
- destinationRepoName,
- pullRequestTitle,
- pullRequest.getSource().getCommit().getHash(),
- destinationCommitHash,
- pullRequest.getAuthor().getCombinedUsername()
- );
-
- //@FIXME: Way to iterate over all available SCMSources
- List<SCMSource> sources = new LinkedList<SCMSource>();
- for(SCMSourceOwner owner : SCMSourceOwners.all())
- for(SCMSource src : owner.getSCMSources())
- sources.add(src);
-
- BitbucketBuildFilter filter = !this.trigger.getBranchesFilterBySCMIncludes() ?
- BitbucketBuildFilter.instanceByString(this.trigger.getBranchesFilter()) :
- BitbucketBuildFilter.instanceBySCM(sources, this.trigger.getBranchesFilter());
-
- return filter.approved(cause);
- }
-
- private StandardUsernamePasswordCredentials getCredentials(String credentialsId) {
- if (null == credentialsId) return null;
- return CredentialsMatchers
- .firstOrNull(
- CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class),
- CredentialsMatchers.allOf(CredentialsMatchers.withId(credentialsId),
- instanceOf(UsernamePasswordCredentials.class)));
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java
deleted file mode 100644
index 0c21806..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/ApiClient.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket;
-
-import org.apache.commons.httpclient.*;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.DeleteMethod;
-import org.apache.commons.httpclient.params.HttpClientParams;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.codehaus.jackson.map.type.TypeFactory;
-import org.codehaus.jackson.type.JavaType;
-import org.codehaus.jackson.type.TypeReference;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import jenkins.model.Jenkins;
-import hudson.ProxyConfiguration;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.httpclient.util.EncodingUtil;
-
-/**
- * Created by nishio
- */
-public class ApiClient {
- private static final Logger logger = Logger.getLogger(ApiClient.class.getName());
- private static final String V1_API_BASE_URL = "https://bitbucket.org/api/1.0/repositories/";
- private static final String V2_API_BASE_URL = "https://bitbucket.org/api/2.0/repositories/";
- private static final String COMPUTED_KEY_FORMAT = "%s-%s";
- private String owner;
- private String repositoryName;
- private Credentials credentials;
- private String key;
- private String name;
- private HttpClientFactory factory;
-
- public static final byte MAX_KEY_SIZE_BB_API = 40;
-
- public static class HttpClientFactory {
- public static final HttpClientFactory INSTANCE = new HttpClientFactory();
- private static final int DEFAULT_TIMEOUT = 60000;
-
- public HttpClient getInstanceHttpClient() {
- HttpClient client = new HttpClient();
-
- HttpClientParams params = client.getParams();
- params.setConnectionManagerTimeout(DEFAULT_TIMEOUT);
- params.setSoTimeout(DEFAULT_TIMEOUT);
-
- if (Jenkins.getInstance() == null) return client;
-
- ProxyConfiguration proxy = getInstance().proxy;
- if (proxy == null) return client;
-
- logger.log(Level.FINE, "Jenkins proxy: {0}:{1}", new Object[]{ proxy.name, proxy.port });
- client.getHostConfiguration().setProxy(proxy.name, proxy.port);
- String username = proxy.getUserName();
- String password = proxy.getPassword();
-
- // Consider it to be passed if username specified. Sufficient?
- if (username != null && !"".equals(username.trim())) {
- logger.log(Level.FINE, "Using proxy authentication (user={0})", username);
- client.getState().setProxyCredentials(AuthScope.ANY,
- new UsernamePasswordCredentials(username, password));
- }
-
- return client;
- }
-
- private Jenkins getInstance() {
- final Jenkins instance = Jenkins.getInstance();
- if (instance == null){
- throw new IllegalStateException("Jenkins instance is NULL!");
- }
- return instance;
- }
- }
-
-
-
- public <T extends HttpClientFactory> ApiClient(
- String username, String password,
- String owner, String repositoryName,
- String key, String name,
- T httpFactory
- ) {
- this.credentials = new UsernamePasswordCredentials(username, password);
- this.owner = owner;
- this.repositoryName = repositoryName;
- this.key = key;
- this.name = name;
- this.factory = httpFactory != null ? httpFactory : HttpClientFactory.INSTANCE;
- }
-
- public List<Pullrequest> getPullRequests() {
- return getAllValues(v2("/pullrequests/"), 50, Pullrequest.class);
- }
-
- public List<Pullrequest.Comment> getPullRequestComments(String commentOwnerName, String commentRepositoryName, String pullRequestId) {
- return getAllValues(v2("/pullrequests/" + pullRequestId + "/comments"), 100, Pullrequest.Comment.class);
- }
-
- public String getName() {
- return this.name;
- }
-
- private static MessageDigest SHA1 = null;
-
- /**
- * Retrun
- * @param keyExPart
- * @return key parameter for call BitBucket API
- */
- private String computeAPIKey(String keyExPart) {
- String computedKey = String.format(COMPUTED_KEY_FORMAT, this.key, keyExPart);
-
- if (computedKey.length() > MAX_KEY_SIZE_BB_API) {
- try {
- if (SHA1 == null) SHA1 = MessageDigest.getInstance("SHA1");
- return new String(Hex.encodeHex(SHA1.digest(computedKey.getBytes("UTF-8"))));
- } catch(NoSuchAlgorithmException e) {
- logger.log(Level.WARNING, "Failed to create hash provider", e);
- } catch (UnsupportedEncodingException e) {
- logger.log(Level.WARNING, "Failed to create hash provider", e);
- }
- }
- return (computedKey.length() <= MAX_KEY_SIZE_BB_API) ? computedKey : computedKey.substring(0, MAX_KEY_SIZE_BB_API);
- }
-
- public String buildStatusKey(String bsKey) {
- return this.computeAPIKey(bsKey);
- }
-
- public boolean hasBuildStatus(String owner, String repositoryName, String revision, String keyEx) {
- String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build/" + this.computeAPIKey(keyEx));
- String reqBody = get(url);
- return reqBody != null && reqBody.contains("\"state\"");
- }
-
- public void setBuildStatus(String owner, String repositoryName, String revision, BuildState state, String buildUrl, String comment, String keyEx) {
- String url = v2(owner, repositoryName, "/commit/" + revision + "/statuses/build");
- String computedKey = this.computeAPIKey(keyEx);
- NameValuePair[] data = new NameValuePair[]{
- new NameValuePair("description", comment),
- new NameValuePair("key", computedKey),
- new NameValuePair("name", this.name),
- new NameValuePair("state", state.toString()),
- new NameValuePair("url", buildUrl),
- };
- logger.log(Level.FINE, "POST state {0} to {1} with key {2} with response {3}", new Object[]{
- state, url, computedKey, post(url, data)}
- );
- }
-
- public void deletePullRequestApproval(String pullRequestId) {
- delete(v2("/pullrequests/" + pullRequestId + "/approve"));
- }
-
- public void deletePullRequestComment(String pullRequestId, String commentId) {
- delete(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId));
- }
-
- public void updatePullRequestComment(String pullRequestId, String content, String commentId) {
- NameValuePair[] data = new NameValuePair[] {
- new NameValuePair("content", content),
- };
- put(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId), data);
- }
-
- public Pullrequest.Participant postPullRequestApproval(String pullRequestId) {
- try {
- return parse(post(v2("/pullrequests/" + pullRequestId + "/approve"),
- new NameValuePair[]{}), Pullrequest.Participant.class);
- } catch (IOException e) {
- logger.log(Level.WARNING, "Invalid pull request approval response.", e);
- }
- return null;
- }
-
- public Pullrequest.Comment postPullRequestComment(String pullRequestId, String content) {
- NameValuePair[] data = new NameValuePair[] {
- new NameValuePair("content", content),
- };
- try {
- return parse(post(v1("/pullrequests/" + pullRequestId + "/comments"), data), new TypeReference<Pullrequest.Comment>() {});
- } catch(Exception e) {
- logger.log(Level.WARNING, "Invalid pull request comment response.", e);
- }
- return null;
- }
-
- private <T> List<T> getAllValues(String rootUrl, int pageLen, Class<T> cls) {
- List<T> values = new ArrayList<T>();
- try {
- String url = rootUrl + "?pagelen=" + pageLen;
- do {
- final JavaType type = TypeFactory.defaultInstance().constructParametricType(Pullrequest.Response.class, cls);
- Pullrequest.Response<T> response = parse(get(url), type);
- values.addAll(response.getValues());
- url = response.getNext();
- } while (url != null);
- } catch (Exception e) {
- logger.log(Level.WARNING, "invalid response.", e);
- }
- return values;
- }
-
- private HttpClient getHttpClient() {
- return this.factory.getInstanceHttpClient();
- }
-
- private String v1(String url) {
- return V1_API_BASE_URL + this.owner + "/" + this.repositoryName + url;
- }
-
- private String v2(String path) {
- return v2(this.owner, this.repositoryName, path);
- }
-
- private String v2(String owner, String repositoryName, String path) {
- return V2_API_BASE_URL + owner + "/" + repositoryName + path;
- }
-
- private String get(String path) {
- return send(new GetMethod(path));
- }
-
- private String post(String path, NameValuePair[] data) {
- PostMethod req = new PostMethod(path);
- req.setRequestBody(data);
- req.getParams().setContentCharset("utf-8");
- return send(req);
- }
-
- private void delete(String path) {
- send(new DeleteMethod(path));
- }
-
- private void put(String path, NameValuePair[] data) {
- PutMethod req = new PutMethod(path);
- req.setRequestBody(EncodingUtil.formUrlEncode(data, "utf-8"));
- req.getParams().setContentCharset("utf-8");
- send(req);
- }
-
- private String send(HttpMethodBase req) {
- HttpClient client = getHttpClient();
- client.getState().setCredentials(AuthScope.ANY, credentials);
- client.getParams().setAuthenticationPreemptive(true);
- try {
- int statusCode = client.executeMethod(req);
- if (statusCode != HttpStatus.SC_OK) {
- logger.log(Level.WARNING, "Response status: " + req.getStatusLine()+" URI: "+req.getURI());
- }else{
- return req.getResponseBodyAsString();
- }
- } catch (HttpException e) {
- logger.log(Level.WARNING, "Failed to send request.", e);
- } catch (IOException e) {
- logger.log(Level.WARNING, "Failed to send request.", e);
- } finally {
- req.releaseConnection();
- }
- return null;
- }
-
- private <R> R parse(String response, Class<R> cls) throws IOException {
- return new ObjectMapper().readValue(response, cls);
- }
- private <R> R parse(String response, JavaType type) throws IOException {
- return new ObjectMapper().readValue(response, type);
- }
- private <R> R parse(String response, TypeReference<R> ref) throws IOException {
- return new ObjectMapper().readValue(response, ref);
- }
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java
deleted file mode 100644
index a3ef1f1..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/BuildState.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket;
-
-/**
- * Valid build states for a pull request
- *
- * @see "https://confluence.atlassian.com/bitbucket/buildstatus-resource-779295267.html"
- */
-public enum BuildState {
- FAILED, INPROGRESS, SUCCESSFUL
-}
diff --git a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/Pullrequest.java b/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/Pullrequest.java
deleted file mode 100644
index b69805e..0000000
--- a/src/main/java/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/bitbucket/Pullrequest.java
+++ /dev/null
@@ -1,392 +0,0 @@
-package bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket;
-
-import java.util.List;
-import java.util.Comparator;
-import java.util.Map;
-
-import org.codehaus.jackson.annotate.JsonIgnoreProperties;
-import org.codehaus.jackson.annotate.JsonProperty;
-
-/**
- * POJOs representing the pull-requests extracted from the
- * JSON response of the Bitbucket API V2.
- *
- * @see "https://confluence.atlassian.com/bitbucket/pullrequests-resource-423626332.html#pullrequestsResource-GETaspecificpullrequest"
- */
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class Pullrequest {
-
- private String description;
- private Boolean closeSourceBranch;
- private String title;
- private Revision destination;
- private String reason;
- private String closedBy;
- private Revision source;
- private String state;
- private String createdOn;
- private String updatedOn;
- private String mergeCommit;
- private String id;
- private Author author;
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Response<T> {
- private int pageLength;
- private List<T> values;
- private int page;
- private int size;
- private String next;
-
- @JsonProperty("pagelen")
- public int getPageLength() {
- return pageLength;
- }
- @JsonProperty("pagelen")
- public void setPageLength(int pageLength) {
- this.pageLength = pageLength;
- }
- public List<T> getValues() {
- return values;
- }
- public void setValues(List<T> values) {
- this.values = values;
- }
- public int getPage() {
- return page;
- }
- public void setPage(int page) {
- this.page = page;
- }
- public int getSize() {
- return size;
- }
- public void setSize(int size) {
- this.size = size;
- }
- public String getNext() {
- return next;
- }
- public void setNext(String next) {
- this.next = next;
- }
- }
-
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Revision {
- private Repository repository;
- private Branch branch;
- private Commit commit;
-
- public Repository getRepository() {
- return repository;
- }
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
- public Branch getBranch() {
- return branch;
- }
- public void setBranch(Branch branch) {
- this.branch = branch;
- }
- public Commit getCommit() {
- return commit;
- }
- public void setCommit(Commit commit) {
- this.commit = commit;
- }
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Repository {
- private String fullName;
- private String name;
- private String ownerName;
- private String repositoryName;
-
- @JsonProperty("full_name")
- public String getFullName() {
- return fullName;
- }
- @JsonProperty("full_name")
- public void setFullName(String fullName) {
- // Also extract owner- and reponame
- if (fullName != null) {
- this.ownerName = fullName.split("/")[0];
- this.repositoryName = fullName.split("/")[1];
- }
- this.fullName = fullName;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getOwnerName() {
- return ownerName;
- }
- public String getRepositoryName() {
- return repositoryName;
- }
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Branch {
- private String name;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Commit {
- private String hash;
-
- public String getHash() {
- return hash;
- }
-
- public void setHash(String hash) {
- this.hash = hash;
- }
- }
-
- // Was: Approval
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Participant {
- private String role;
- private Boolean approved;
-
- public String getRole() {
- return role;
- }
- public void setRole(String role) {
- this.role = role;
- }
- public Boolean getApproved() {
- return approved;
- }
- public void setApproved(Boolean approved) {
- this.approved = approved;
- }
- }
-
- // https://confluence.atlassian.com/bitbucket/pullrequests-resource-1-0-296095210.html#pullrequestsResource1.0-POSTanewcomment
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Comment implements Comparable<Comment> {
- private Integer id;
- private String filename;
- private String content;
-
- @Override
- public int compareTo(Comment target) {
- if (target == null){
- return -1;
- } else if (this.getId() > target.getId()) {
- return 1;
- } else if (this.getId().equals(target.getId())) {
- return 0;
- } else {
- return -1;
- }
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final Comment comment = (Comment) o;
-
- return getId() != null ? getId().equals(comment.getId()) : comment.getId() == null;
- }
-
- @Override
- public int hashCode() {
- return getId() != null ? getId().hashCode() : 0;
- }
-
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-
- public String getFilename() {
- return filename;
- }
-
- public void setFilename(String filename) {
- this.filename = filename;
- }
-
- public String getContent() {
- return content;
- }
-
- public void setContent(Object content) {
- if (content instanceof String) {
- this.content = (String)content;
- } else if (content instanceof Map){
- this.content = (String)((Map)content).get("raw");
- }
- return;
- }
-
- }
-
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Author {
- private String username;
- private String display_name;
- public static final String COMBINED_NAME = "%s <@%s>";
-
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
-
- @JsonProperty("display_name")
- public String getDisplayName() {
- return display_name;
- }
-
- @JsonProperty("display_name")
- public void setDisplayName(String display_name) {
- this.display_name = display_name;
- }
- public String getCombinedUsername() {
- return String.format(COMBINED_NAME, this.getDisplayName(), this.getUsername());
- }
- }
-
- //-------------------- only getters and setters follow -----------------
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- @JsonProperty("close_source_branch")
- public Boolean getCloseSourceBranch() {
- return closeSourceBranch;
- }
-
- @JsonProperty("close_source_branch")
- public void setCloseSourceBranch(Boolean closeSourceBranch) {
- this.closeSourceBranch = closeSourceBranch;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public Revision getDestination() {
- return destination;
- }
-
- public void setDestination(Revision destination) {
- this.destination = destination;
- }
-
- public String getReason() {
- return reason;
- }
-
- public void setReason(String reason) {
- this.reason = reason;
- }
-
- @JsonProperty("closed_by")
- public String getClosedBy() {
- return closedBy;
- }
-
- @JsonProperty("closed_by")
- public void setClosedBy(String closedBy) {
- this.closedBy = closedBy;
- }
-
- public Revision getSource() {
- return source;
- }
-
- public void setSource(Revision source) {
- this.source = source;
- }
-
- public String getState() {
- return state;
- }
-
- public void setState(String state) {
- this.state = state;
- }
-
- @JsonProperty("created_on")
- public String getCreatedOn() {
- return createdOn;
- }
-
- @JsonProperty("created_on")
- public void setCreatedOn(String createdOn) {
- this.createdOn = createdOn;
- }
-
- @JsonProperty("updated_on")
- public String getUpdatedOn() {
- return updatedOn;
- }
-
- @JsonProperty("updated_on")
- public void setUpdatedOn(String updatedOn) {
- this.updatedOn = updatedOn;
- }
-
- @JsonProperty("merge_commit")
- public String getMergeCommit() {
- return mergeCommit;
- }
-
- @JsonProperty("merge_commit")
- public void setMergeCommit(String mergeCommit) {
- this.mergeCommit = mergeCommit;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public Author getAuthor() {
- return this.author;
- }
-
- public void setAutohor(Author author) {
- this.author = author;
- }
-
-}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketAdditionalParameterEnvironmentContributor.java b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketAdditionalParameterEnvironmentContributor.java
new file mode 100644
index 0000000..2e9d8bc
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketAdditionalParameterEnvironmentContributor.java
@@ -0,0 +1,39 @@
+package org.jenkinsci.plugins.bbprb;
+
+import hudson.EnvVars;
+import hudson.Extension;
+import hudson.model.*;
+
+import java.io.IOException;
+
+@Extension
+public class BitbucketAdditionalParameterEnvironmentContributor
+ extends EnvironmentContributor {
+ @Override
+ public void buildEnvironmentFor(Run run, EnvVars envVars,
+ TaskListener taskListener)
+ throws IOException, InterruptedException {
+
+ BitbucketCause cause = (BitbucketCause)run.getCause(BitbucketCause.class);
+ if (cause == null) {
+ return;
+ }
+
+ putEnvVar(envVars, "destinationRepository",
+ cause.getDestinationRepository());
+ putEnvVar(envVars, "pullRequestAuthor", cause.getPullRequestAuthor());
+ putEnvVar(envVars, "pullRequestId", cause.getPullRequestId());
+ putEnvVar(envVars, "pullRequestTitle", cause.getPullRequestTitle());
+ putEnvVar(envVars, "sourceBranch", cause.getSourceBranch());
+ putEnvVar(envVars, "sourceRepository", cause.getSourceRepository());
+ putEnvVar(envVars, "targetBranch", cause.getTargetBranch());
+ }
+
+ private static void putEnvVar(EnvVars envs, String name, String value) {
+ envs.put(name, getString(value, ""));
+ }
+
+ private static String getString(String actual, String d) {
+ return actual == null ? d : actual;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildListener.java b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildListener.java
new file mode 100644
index 0000000..efcc13a
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildListener.java
@@ -0,0 +1,69 @@
+package org.jenkinsci.plugins.bbprb;
+
+import hudson.Extension;
+import hudson.model.AbstractBuild;
+import hudson.model.Job;
+import hudson.model.TaskListener;
+import hudson.model.listeners.RunListener;
+import hudson.triggers.Trigger;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import hudson.model.Result;
+
+import org.jenkinsci.plugins.bbprb.bitbucket.BuildState;
+
+@Extension
+public class BitbucketBuildListener extends RunListener<AbstractBuild<?, ?>> {
+
+ @Override
+ public void onStarted(AbstractBuild<?, ?> build, TaskListener listener) {
+ BitbucketCause cause = build.getCause(BitbucketCause.class);
+ if (cause == null) {
+ return;
+ }
+
+ BitbucketBuildTrigger trigger = extractTrigger(build);
+ if (trigger == null) {
+ return;
+ }
+
+ LOGGER.log(Level.FINE, "Started by BitbucketBuildTrigger");
+ trigger.setPRState(cause, BuildState.INPROGRESS, build.getUrl());
+ try {
+ build.setDescription(
+ build.getCause(BitbucketCause.class).getShortDescription());
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Could not set build description: {0}",
+ e.getMessage());
+ }
+ }
+
+ @Override
+ public void onCompleted(AbstractBuild<?, ?> build, TaskListener listener) {
+ BitbucketBuildTrigger trigger = extractTrigger(build);
+ if (trigger != null) {
+ LOGGER.log(Level.FINE, "Completed after BitbucketBuildTrigger");
+ Result result = build.getResult();
+ BuildState state = (result == Result.SUCCESS) ? BuildState.SUCCESSFUL
+ : BuildState.FAILED;
+ BitbucketCause cause = build.getCause(BitbucketCause.class);
+ trigger.setPRState(cause, state, build.getUrl());
+ }
+ }
+
+ private static BitbucketBuildTrigger
+ extractTrigger(AbstractBuild<?, ?> build) {
+ BitbucketBuildTrigger trigger =
+ build.getProject().getTrigger(BitbucketBuildTrigger.class);
+
+ if ((trigger != null) && (trigger instanceof BitbucketBuildTrigger)) {
+ return trigger;
+ } else {
+ return null;
+ }
+ }
+
+ private static final Logger LOGGER =
+ Logger.getLogger(BitbucketBuildListener.class.getName());
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger.java b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger.java
new file mode 100644
index 0000000..b0c6ece
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger.java
@@ -0,0 +1,303 @@
+package org.jenkinsci.plugins.bbprb;
+
+import antlr.ANTLRException;
+import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
+import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
+import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
+import com.cloudbees.plugins.credentials.CredentialsMatchers;
+import com.cloudbees.plugins.credentials.CredentialsProvider;
+import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
+import hudson.Extension;
+import hudson.model.AbstractProject;
+import hudson.model.Cause;
+import hudson.model.Executor;
+import hudson.model.Item;
+import hudson.model.ParameterDefinition;
+import hudson.model.ParametersAction;
+import hudson.model.ParametersDefinitionProperty;
+import hudson.model.ParameterValue;
+import hudson.model.queue.QueueTaskFuture;
+import hudson.model.Queue;
+import hudson.model.Result;
+import hudson.model.Run;
+import hudson.plugins.git.RevisionParameterAction;
+import hudson.security.ACL;
+import hudson.triggers.Trigger;
+import hudson.triggers.TriggerDescriptor;
+import hudson.util.ListBoxModel;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import jenkins.model.Jenkins;
+import jenkins.model.ParameterizedJobMixIn;
+import net.sf.json.JSONObject;
+import org.acegisecurity.context.SecurityContext;
+import org.acegisecurity.context.SecurityContextHolder;
+import org.apache.commons.lang.StringUtils;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.StaplerRequest;
+import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf;
+
+import org.jenkinsci.plugins.bbprb.bitbucket.ApiClient;
+import org.jenkinsci.plugins.bbprb.bitbucket.BuildState;
+
+public class BitbucketBuildTrigger extends Trigger<AbstractProject<?, ?>> {
+ private final String ciKey;
+ private final String ciName;
+ private final String credentialsId;
+ private final String destinationRepository;
+ private final boolean cancelOutdatedJobs;
+ private final boolean checkDestinationCommit;
+
+ // XXX: This is for Jelly.
+ // https://wiki.jenkins.io/display/JENKINS/Basic+guide+to+Jelly+usage+in+Jenkins
+ public String getCiKey() {
+ return this.ciKey;
+ }
+ public String getCiName() {
+ return this.ciName;
+ }
+ public String getCredentialsId() {
+ return this.credentialsId;
+ }
+ public String getDestinationRepository() {
+ return this.destinationRepository;
+ }
+ public boolean getCancelOutdatedJobs() {
+ return this.cancelOutdatedJobs;
+ }
+ public boolean getCheckDestinationCommit() {
+ return this.checkDestinationCommit;
+ }
+
+ private transient ApiClient apiClient;
+
+ public static final BitbucketBuildTriggerDescriptor descriptor =
+ new BitbucketBuildTriggerDescriptor();
+
+ @DataBoundConstructor
+ public BitbucketBuildTrigger(String credentialsId,
+ String destinationRepository, String ciKey,
+ String ciName, boolean checkDestinationCommit,
+ boolean cancelOutdatedJobs)
+ throws ANTLRException {
+ super();
+ this.apiClient = null;
+ this.cancelOutdatedJobs = cancelOutdatedJobs;
+ this.checkDestinationCommit = checkDestinationCommit;
+ this.ciKey = ciKey;
+ this.ciName = ciName;
+ this.credentialsId = credentialsId;
+ this.destinationRepository = destinationRepository;
+ }
+
+ @Override
+ public void start(AbstractProject<?, ?> project, boolean newInstance) {
+ logger.log(Level.FINE, "Started for `{0}`", project.getFullName());
+
+ super.start(project, newInstance);
+
+ if (credentialsId != null && !credentialsId.isEmpty()) {
+ logger.log(Level.FINE, "Looking up credentials `{0}`",
+ this.credentialsId);
+ List<UsernamePasswordCredentials> all =
+ CredentialsProvider.lookupCredentials(
+ UsernamePasswordCredentials.class, (Item)null, ACL.SYSTEM,
+ URIRequirementBuilder.fromUri("https://bitbucket.org").build());
+ UsernamePasswordCredentials creds = CredentialsMatchers.firstOrNull(
+ all, CredentialsMatchers.withId(this.credentialsId));
+ if (creds != null) {
+ logger.log(Level.INFO, "Creating Bitbucket API client");
+ this.apiClient = new ApiClient(
+ creds.getUsername(), creds.getPassword().getPlainText(),
+ this.destinationRepository, this.ciKey, this.ciName);
+ } else {
+ logger.log(Level.SEVERE, "Credentials `{0}` not found",
+ this.credentialsId);
+ }
+ } else {
+ logger.log(Level.WARNING, "Missing Bitbucket API credentials");
+ }
+ }
+
+ public void setPRState(BitbucketCause cause, BuildState state, String path) {
+ if (this.apiClient != null) {
+ logger.log(Level.INFO, "Setting status of PR #{0} to {1} for {2}",
+ new Object[] {cause.getPullRequestId(), state,
+ cause.getDestinationRepository()});
+ this.apiClient.setBuildStatus(cause.getSourceCommitHash(), state,
+ getInstance().getRootUrl() + path, null,
+ this.job.getFullName());
+ } else {
+ logger.log(Level.INFO,
+ "Will not set Bitbucket PR build status (not configured)");
+ }
+ }
+
+ private void startJob(BitbucketCause cause) {
+ if (this.cancelOutdatedJobs) {
+ SecurityContext orig = ACL.impersonate(ACL.SYSTEM);
+ cancelPreviousJobsInQueueThatMatch(cause);
+ abortRunningJobsThatMatch(cause);
+ SecurityContextHolder.setContext(orig);
+ }
+
+ setPRState(cause, BuildState.INPROGRESS, this.job.getUrl());
+
+ this.job.scheduleBuild2(
+ 0, cause, new ParametersAction(this.getDefaultParameters()),
+ new RevisionParameterAction(cause.getSourceCommitHash()));
+ }
+
+ private void
+ cancelPreviousJobsInQueueThatMatch(@Nonnull BitbucketCause cause) {
+ logger.log(Level.FINE, "Looking for queued jobs that match PR #{0}",
+ cause.getPullRequestId());
+ Queue queue = getInstance().getQueue();
+
+ for (Queue.Item item : queue.getItems()) {
+ if (hasCauseFromTheSamePullRequest(item.getCauses(), cause)) {
+ logger.fine("Canceling item in queue: " + item);
+ queue.cancel(item);
+ }
+ }
+ }
+
+ private Jenkins getInstance() {
+ final Jenkins instance = Jenkins.getInstance();
+ if (instance == null) {
+ throw new IllegalStateException("Jenkins instance is NULL!");
+ }
+ return instance;
+ }
+
+ private void
+ abortRunningJobsThatMatch(@Nonnull BitbucketCause bitbucketCause) {
+ logger.log(Level.FINE, "Looking for running jobs that match PR #{0}",
+ bitbucketCause.getPullRequestId());
+ for (Object o : job.getBuilds()) {
+ if (o instanceof Run) {
+ Run build = (Run)o;
+ if (build.isBuilding() &&
+ hasCauseFromTheSamePullRequest(build.getCauses(), bitbucketCause)) {
+ logger.fine("Aborting build: " + build + " since PR is outdated");
+ setBuildDescription(build);
+ final Executor executor = build.getExecutor();
+ if (executor == null) {
+ throw new IllegalStateException("Executor can't be NULL");
+ }
+ executor.interrupt(Result.ABORTED);
+ }
+ }
+ }
+ }
+
+ private void setBuildDescription(final Run build) {
+ try {
+ build.setDescription(
+ "Aborting build by `Bitbucket Pullrequest Builder Plugin`: " + build +
+ " since PR is outdated");
+ } catch (IOException e) {
+ logger.warning("Could not set build description: " + e.getMessage());
+ }
+ }
+
+ private boolean
+ hasCauseFromTheSamePullRequest(@Nullable List<Cause> causes,
+ @Nullable BitbucketCause pullRequestCause) {
+ if (causes != null && pullRequestCause != null) {
+ for (Cause cause : causes) {
+ if (cause instanceof BitbucketCause) {
+ BitbucketCause sc = (BitbucketCause)cause;
+ if (StringUtils.equals(sc.getPullRequestId(),
+ pullRequestCause.getPullRequestId()) &&
+ StringUtils.equals(sc.getSourceRepository(),
+ pullRequestCause.getSourceRepository())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private ArrayList<ParameterValue> getDefaultParameters() {
+ Map<String, ParameterValue> values = new HashMap<String, ParameterValue>();
+ ParametersDefinitionProperty definitionProperty =
+ this.job.getProperty(ParametersDefinitionProperty.class);
+
+ if (definitionProperty != null) {
+ for (ParameterDefinition definition :
+ definitionProperty.getParameterDefinitions()) {
+ values.put(definition.getName(), definition.getDefaultParameterValue());
+ }
+ }
+ return new ArrayList<ParameterValue>(values.values());
+ }
+
+ public void handlePR(JSONObject pr) {
+ JSONObject src = pr.getJSONObject("source");
+ JSONObject dst = pr.getJSONObject("destination");
+ String dstRepository =
+ dst.getJSONObject("repository").getString("full_name");
+ BitbucketCause cause = new BitbucketCause(
+ src.getJSONObject("branch").getString("name"),
+ dst.getJSONObject("branch").getString("name"),
+ src.getJSONObject("repository").getString("full_name"),
+ pr.getString("id"), // FIXME: it is integer
+ dstRepository, pr.getString("title"),
+ src.getJSONObject("commit").getString("hash"),
+ dst.getJSONObject("commit").getString("hash"),
+ pr.getJSONObject("author").getString("username"));
+ if (!dstRepository.equals(this.destinationRepository)) {
+ logger.log(Level.FINE,
+ "Job `{0}`: repository `{1}` does not match `{2}`. Skipping.",
+ new Object[] {this.job.getFullName(), dstRepository,
+ this.destinationRepository});
+ return;
+ }
+ startJob(cause);
+ }
+
+ @Extension
+ @Symbol("bbprb")
+ public static final class BitbucketBuildTriggerDescriptor
+ extends TriggerDescriptor {
+ public BitbucketBuildTriggerDescriptor() {
+ load();
+ }
+
+ @Override
+ public boolean isApplicable(Item item) {
+ return item instanceof AbstractProject;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Bitbucket Pull Requests Builder";
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json)
+ throws FormException {
+ save();
+ return super.configure(req, json);
+ }
+
+ public ListBoxModel doFillCredentialsIdItems() {
+ return new StandardListBoxModel().withEmptySelection().withMatching(
+ instanceOf(UsernamePasswordCredentials.class),
+ CredentialsProvider.lookupCredentials(
+ StandardUsernamePasswordCredentials.class));
+ }
+ }
+ private static final Logger logger =
+ Logger.getLogger(BitbucketBuildTrigger.class.getName());
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketCause.java b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketCause.java
new file mode 100644
index 0000000..a4a20a6
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketCause.java
@@ -0,0 +1,80 @@
+package org.jenkinsci.plugins.bbprb;
+
+import hudson.model.Cause;
+
+/**
+ * Created by nishio
+ */
+public class BitbucketCause extends Cause {
+ private final String sourceBranch;
+ private final String targetBranch;
+ private final String sourceRepository;
+ private final String pullRequestId;
+ private final String destinationRepository;
+ private final String pullRequestTitle;
+ private final String sourceCommitHash;
+ private final String destinationCommitHash;
+ private final String pullRequestAuthor;
+ public static final String BITBUCKET_URL = "https://bitbucket.org/";
+
+ public BitbucketCause(String sourceBranch, String targetBranch,
+ String sourceRepository, String pullRequestId,
+ String destinationRepository, String pullRequestTitle,
+ String sourceCommitHash, String destinationCommitHash,
+ String pullRequestAuthor) {
+ this.sourceBranch = sourceBranch;
+ this.targetBranch = targetBranch;
+ this.sourceRepository = sourceRepository;
+ this.pullRequestId = pullRequestId;
+ this.destinationRepository = destinationRepository;
+ this.pullRequestTitle = pullRequestTitle;
+ this.sourceCommitHash = sourceCommitHash;
+ this.destinationCommitHash = destinationCommitHash;
+ this.pullRequestAuthor = pullRequestAuthor;
+ }
+
+ public String getSourceBranch() {
+ return sourceBranch;
+ }
+ public String getTargetBranch() {
+ return targetBranch;
+ }
+
+ public String getSourceRepository() {
+ return sourceRepository;
+ }
+
+ public String getPullRequestId() {
+ return pullRequestId;
+ }
+
+ public String getDestinationRepository() {
+ return destinationRepository;
+ }
+
+ public String getPullRequestTitle() {
+ return pullRequestTitle;
+ }
+
+ public String getSourceCommitHash() {
+ return sourceCommitHash;
+ }
+
+ public String getDestinationCommitHash() {
+ return destinationCommitHash;
+ }
+
+ @Override
+ public String getShortDescription() {
+ String description =
+ "<a href=\"" + BITBUCKET_URL + this.getDestinationRepository();
+ description += "/pull-request/" + this.getPullRequestId();
+ description += "\">#" + this.getPullRequestId() + " " +
+ this.getPullRequestTitle() + "</a>";
+ return description;
+ }
+
+ public String getPullRequestAuthor() {
+ return this.pullRequestAuthor;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketHookReceiver.java b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketHookReceiver.java
new file mode 100644
index 0000000..18b2688
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/BitbucketHookReceiver.java
@@ -0,0 +1,140 @@
+package org.jenkinsci.plugins.bbprb;
+
+import hudson.Extension;
+import hudson.model.UnprotectedRootAction;
+import hudson.security.ACL;
+import hudson.triggers.Trigger;
+import hudson.triggers.TriggerDescriptor;
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jenkins.model.Jenkins;
+import jenkins.model.ParameterizedJobMixIn.ParameterizedJob;
+import net.sf.json.JSONException;
+import net.sf.json.JSONObject;
+import org.acegisecurity.context.SecurityContext;
+import org.acegisecurity.context.SecurityContextHolder;
+import org.apache.commons.io.IOUtils;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+
+@Extension
+public class BitbucketHookReceiver implements UnprotectedRootAction {
+
+ private static final String BITBUCKET_HOOK_URL = "bbprb-hook";
+ private static final String BITBUCKET_UA = "Bitbucket-Webhooks/2.0";
+
+ public void doIndex(StaplerRequest req, StaplerResponse resp)
+ throws IOException {
+
+ String userAgent = req.getHeader("user-agent");
+ if (!BITBUCKET_UA.equals(userAgent)) {
+ LOGGER.log(Level.WARNING, "Bad user agent: `{0}`, expected `{1}`",
+ new Object[] {userAgent, BITBUCKET_UA});
+ resp.setStatus(StaplerResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ String uri = req.getRequestURI();
+ if (!uri.contains("/" + BITBUCKET_HOOK_URL + "/")) {
+ LOGGER.log(Level.WARNING,
+ "BitBucket hook URI does not contain `/{0}/`: `{1}`",
+ new Object[] {BITBUCKET_HOOK_URL, uri});
+ resp.setStatus(StaplerResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ String event = req.getHeader("x-event-key");
+ if (event == null) {
+ LOGGER.log(Level.WARNING, "Missing the `x-event-key` header");
+ resp.setStatus(StaplerResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ String body = IOUtils.toString(req.getInputStream());
+ if (body.isEmpty()) {
+ LOGGER.log(Level.WARNING, "Received empty request body");
+ resp.setStatus(StaplerResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ String contentType = req.getContentType();
+ if (contentType != null &&
+ contentType.startsWith("application/x-www-form-urlencoded")) {
+ body = URLDecoder.decode(body, "UTF-8");
+ }
+ if (body.startsWith("payload=")) {
+ body = body.substring(8);
+ }
+
+ LOGGER.log(Level.FINE,
+ "Received commit hook notification, key: `{0}`, body: `{1}`",
+ new Object[] {event, body});
+
+ try {
+ JSONObject payload = JSONObject.fromObject(body);
+ if (event.startsWith("pullrequest:")) {
+ JSONObject pr = payload.getJSONObject("pullrequest");
+ String state = pr.getString("state");
+ if (!"OPEN".equals(state)) {
+ LOGGER.log(
+ Level.INFO, "Ignoring closed PR ({0}): #{1} {2}",
+ new Object[] {state, pr.getInt("id"), pr.getString("title")});
+ return;
+ }
+ for (BitbucketBuildTrigger trigger : getBitbucketTriggers()) {
+ trigger.handlePR(pr);
+ }
+ return;
+ }
+ } catch (JSONException e) {
+ LOGGER.log(Level.WARNING, e.getMessage());
+ resp.setStatus(StaplerResponse.SC_BAD_REQUEST);
+ return;
+ }
+ }
+
+ private static List<BitbucketBuildTrigger> getBitbucketTriggers() {
+ List<BitbucketBuildTrigger> bbtriggers = new ArrayList<>();
+
+ SecurityContext orig = ACL.impersonate(ACL.SYSTEM);
+ List<ParameterizedJob> jobs =
+ Jenkins.getInstance().getAllItems(ParameterizedJob.class);
+ SecurityContextHolder.setContext(orig);
+
+ for (ParameterizedJob job : jobs) {
+ String jobName = job.getFullName();
+ LOGGER.log(Level.FINER, "Found job: `{0}`", jobName);
+
+ Map<TriggerDescriptor, Trigger<?>> triggers = job.getTriggers();
+
+ for (Trigger<?> trigger : triggers.values()) {
+ if (trigger instanceof BitbucketBuildTrigger) {
+ LOGGER.log(Level.FINE, "Will consider job: `{0}`", jobName);
+ bbtriggers.add((BitbucketBuildTrigger)trigger);
+ }
+ }
+ }
+
+ return bbtriggers;
+ }
+
+ private static final Logger LOGGER =
+ Logger.getLogger(BitbucketHookReceiver.class.getName());
+
+ public String getIconFileName() {
+ return null;
+ }
+
+ public String getDisplayName() {
+ return null;
+ }
+
+ public String getUrlName() {
+ return BITBUCKET_HOOK_URL;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/ApiClient.java b/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/ApiClient.java
new file mode 100644
index 0000000..80ae3aa
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/ApiClient.java
@@ -0,0 +1,246 @@
+package org.jenkinsci.plugins.bbprb.bitbucket;
+
+import org.apache.commons.httpclient.*;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.type.TypeFactory;
+import org.codehaus.jackson.type.JavaType;
+import org.codehaus.jackson.type.TypeReference;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jenkins.model.Jenkins;
+import hudson.ProxyConfiguration;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.util.EncodingUtil;
+
+/**
+ * Created by nishio
+ */
+public class ApiClient {
+ private static final Logger logger =
+ Logger.getLogger(ApiClient.class.getName());
+ private static final String V1_API_BASE_URL =
+ "https://bitbucket.org/api/1.0/repositories/";
+ private static final String V2_API_BASE_URL =
+ "https://bitbucket.org/api/2.0/repositories/";
+ private static final String COMPUTED_KEY_FORMAT = "%s-%s";
+ private String repository;
+ private Credentials credentials;
+ private String key;
+ private String name;
+ private HttpClientFactory factory;
+
+ public static final byte MAX_KEY_SIZE_BB_API = 40;
+
+ public static class HttpClientFactory {
+ public static final HttpClientFactory INSTANCE = new HttpClientFactory();
+ private static final int DEFAULT_TIMEOUT = 60000;
+
+ public HttpClient getInstanceHttpClient() {
+ HttpClient client = new HttpClient();
+
+ HttpClientParams params = client.getParams();
+ params.setConnectionManagerTimeout(DEFAULT_TIMEOUT);
+ params.setSoTimeout(DEFAULT_TIMEOUT);
+
+ if (Jenkins.getInstance() == null)
+ return client;
+
+ ProxyConfiguration proxy = getInstance().proxy;
+ if (proxy == null)
+ return client;
+
+ logger.log(Level.FINE, "Jenkins proxy: {0}:{1}",
+ new Object[] {proxy.name, proxy.port});
+ client.getHostConfiguration().setProxy(proxy.name, proxy.port);
+ String username = proxy.getUserName();
+ String password = proxy.getPassword();
+
+ // Consider it to be passed if username specified. Sufficient?
+ if (username != null && !"".equals(username.trim())) {
+ logger.log(Level.FINE, "Using proxy authentication (user={0})",
+ username);
+ client.getState().setProxyCredentials(
+ AuthScope.ANY, new UsernamePasswordCredentials(username, password));
+ }
+
+ return client;
+ }
+
+ private Jenkins getInstance() {
+ final Jenkins instance = Jenkins.getInstance();
+ if (instance == null) {
+ throw new IllegalStateException("Jenkins instance is NULL!");
+ }
+ return instance;
+ }
+ }
+
+ public <T extends HttpClientFactory> ApiClient(String username,
+ String password,
+ String repository, String key,
+ String name) {
+ this.credentials = new UsernamePasswordCredentials(username, password);
+ this.repository = repository;
+ this.key = key;
+ this.name = name;
+ this.factory = HttpClientFactory.INSTANCE;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ private static MessageDigest SHA1 = null;
+
+ /**
+ * Retrun
+ * @param keyExPart
+ * @return key parameter for call BitBucket API
+ */
+ private String computeAPIKey(String keyExPart) {
+ String computedKey =
+ String.format(COMPUTED_KEY_FORMAT, this.key, keyExPart);
+
+ if (computedKey.length() > MAX_KEY_SIZE_BB_API) {
+ try {
+ if (SHA1 == null)
+ SHA1 = MessageDigest.getInstance("SHA1");
+ return new String(
+ Hex.encodeHex(SHA1.digest(computedKey.getBytes("UTF-8"))));
+ } catch (NoSuchAlgorithmException e) {
+ logger.log(Level.WARNING, "Failed to create hash provider", e);
+ } catch (UnsupportedEncodingException e) {
+ logger.log(Level.WARNING, "Failed to create hash provider", e);
+ }
+ }
+ return (computedKey.length() <= MAX_KEY_SIZE_BB_API)
+ ? computedKey
+ : computedKey.substring(0, MAX_KEY_SIZE_BB_API);
+ }
+
+ public String buildStatusKey(String bsKey) {
+ return this.computeAPIKey(bsKey);
+ }
+
+ public boolean hasBuildStatus(String revision, String keyEx) {
+ String url = v2("/commit/" + revision + "/statuses/build/" +
+ this.computeAPIKey(keyEx));
+ String reqBody = get(url);
+ return reqBody != null && reqBody.contains("\"state\"");
+ }
+
+ public void setBuildStatus(String revision, BuildState state, String buildUrl,
+ String comment, String keyEx) {
+ String url = v2("/commit/" + revision + "/statuses/build");
+ String computedKey = this.computeAPIKey(keyEx);
+ NameValuePair[] data = new NameValuePair[] {
+ new NameValuePair("description", comment),
+ new NameValuePair("key", computedKey),
+ new NameValuePair("name", this.name),
+ new NameValuePair("state", state.toString()),
+ new NameValuePair("url", buildUrl),
+ };
+ logger.log(Level.FINE,
+ "POST state {0} to {1} with key {2} with response {3}",
+ new Object[] {state, url, computedKey, post(url, data)});
+ }
+
+ public void deletePullRequestApproval(String pullRequestId) {
+ delete(v2("/pullrequests/" + pullRequestId + "/approve"));
+ }
+
+ public void deletePullRequestComment(String pullRequestId, String commentId) {
+ delete(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId));
+ }
+
+ public void updatePullRequestComment(String pullRequestId, String content,
+ String commentId) {
+ NameValuePair[] data = new NameValuePair[] {
+ new NameValuePair("content", content),
+ };
+ put(v1("/pullrequests/" + pullRequestId + "/comments/" + commentId), data);
+ }
+
+ private HttpClient getHttpClient() {
+ return this.factory.getInstanceHttpClient();
+ }
+
+ private String v1(String path) {
+ return V1_API_BASE_URL + this.repository + path;
+ }
+
+ private String v2(String path) {
+ return V2_API_BASE_URL + this.repository + path;
+ }
+
+ private String get(String path) {
+ return send(new GetMethod(path));
+ }
+
+ private String post(String path, NameValuePair[] data) {
+ PostMethod req = new PostMethod(path);
+ req.setRequestBody(data);
+ req.getParams().setContentCharset("utf-8");
+ return send(req);
+ }
+
+ private void delete(String path) {
+ send(new DeleteMethod(path));
+ }
+
+ private void put(String path, NameValuePair[] data) {
+ PutMethod req = new PutMethod(path);
+ req.setRequestBody(EncodingUtil.formUrlEncode(data, "utf-8"));
+ req.getParams().setContentCharset("utf-8");
+ send(req);
+ }
+
+ private String send(HttpMethodBase req) {
+ HttpClient client = getHttpClient();
+ client.getState().setCredentials(AuthScope.ANY, credentials);
+ client.getParams().setAuthenticationPreemptive(true);
+ try {
+ int statusCode = client.executeMethod(req);
+ if (statusCode != HttpStatus.SC_OK) {
+ logger.log(Level.WARNING, "Response status: " + req.getStatusLine() +
+ " URI: " + req.getURI());
+ } else {
+ return req.getResponseBodyAsString();
+ }
+ } catch (HttpException e) {
+ logger.log(Level.WARNING, "Failed to send request.", e);
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Failed to send request.", e);
+ } finally {
+ req.releaseConnection();
+ }
+ return null;
+ }
+
+ private <R> R parse(String response, Class<R> cls) throws IOException {
+ return new ObjectMapper().readValue(response, cls);
+ }
+ private <R> R parse(String response, JavaType type) throws IOException {
+ return new ObjectMapper().readValue(response, type);
+ }
+ private <R> R parse(String response, TypeReference<R> ref)
+ throws IOException {
+ return new ObjectMapper().readValue(response, ref);
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/BuildState.java b/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/BuildState.java
new file mode 100644
index 0000000..8fe1cdd
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/bbprb/bitbucket/BuildState.java
@@ -0,0 +1,9 @@
+package org.jenkinsci.plugins.bbprb.bitbucket;
+
+/**
+ * Valid build states for a pull request
+ *
+ * @see
+ * "https://confluence.atlassian.com/bitbucket/buildstatus-resource-779295267.html"
+ */
+public enum BuildState { FAILED, INPROGRESS, SUCCESSFUL }
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly
deleted file mode 100644
index 1d7c4e4..0000000
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/config.jelly
+++ /dev/null
@@ -1,48 +0,0 @@
-<?jelly escape-by-default='true'?>
-<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials">
- <f:entry title="Cron" field="cron">
- <f:textbox />
- </f:entry>
- <f:entry title="${%Credentials}" field="credentialsId">
- <c:select/>
- </f:entry>
- <f:entry title="Bitbucket BasicAuth Username" field="username">
- <f:textbox />
- </f:entry>
- <f:entry title="Bitbucket BasicAuth Password" field="password">
- <f:password />
- </f:entry>
- <f:entry title="RepositoryOwner" field="repositoryOwner">
- <f:textbox />
- </f:entry>
- <f:entry title="RepositoryName" field="repositoryName">
- <f:textbox />
- </f:entry>
- <f:entry title="BranchesFilter" field="branchesFilter">
- <f:textbox />
- </f:entry>
- <f:entry title="Using Git SCM 'Branches to build' option to filter pull requests?" field="branchesFilterBySCMIncludes">
- <f:checkbox />
- </f:entry>
- <f:entry title="CI Identifier" field="ciKey">
- <f:textbox default="jenkins" />
- </f:entry>
- <f:entry title="CI Name" field="ciName">
- <f:textbox default="Jenkins" />
- </f:entry>
- <f:entry title="CI Skip Phrases" field="ciSkipPhrases">
- <f:textbox />
- </f:entry>
- <f:entry title="Rebuild if destination branch changes?" field="checkDestinationCommit">
- <f:checkbox />
- </f:entry>
- <f:entry title="Approve if build success?" field="approveIfSuccess">
- <f:checkbox />
- </f:entry>
- <f:entry title="Cancel outdated jobs?" field="cancelOutdatedJobs">
- <f:checkbox default="false"/>
- </f:entry>
- <f:entry title="Comment phrase to trigger build" field="commentTrigger">
- <f:textbox default="test this please"/>
- </f:entry>
-</j:jelly>
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html
deleted file mode 100644
index 30e5f5e..0000000
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilter.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<div>
- Filter option in custom format. Default value is empty or <tt>any</tt>.<br/>
- Available formats:
- <ul>
- <li>any pull requests applied for this project: <tt>any</tt>, <tt>*</tt> or empty string</li>
- <li>filtered by destination branch: <tt>my-branch</tt> or more complex reg-ex filter <tt>r:^master</tt> (must be started with <tt>r:</tt> and case insensitive match).</li>
- <li>filtered by source and destination branches: <tt>s:source-branch d:dest-branch</tt></li>
- <li>filtered by source and destination branches with regex: <tt>s:r:^feature d:r:master$</tt></li>
- <li>filtered by many destination/source branches: <tt>s:one s:two s:three d:master d:r:master$</tt></li>
- <li>filtered by many sources branches: <tt>s:one s:two s:r:^three d:</tt></li>
- </ul>
- <p/>
- When you using format with source branch filter <tt>s</tt> or destination filter <tt>d</tt>, you must specify great than one source and destination filter, eg <tt>s:1 s:2 s:... d:</tt>.<br/>
- Any sources and any destinations for pull request:
- <ul>
- <li>filter string: <tt>*</tt></li>
- <li>filter string: <tt>s: d:</tt></li>
- </ul>
-</div>
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html
deleted file mode 100644
index cc9d47c..0000000
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-branchesFilterBySCMIncludes.html
+++ /dev/null
@@ -1,7 +0,0 @@
-Uses the Git SCM option "Branches to build" option as the value for
-"BranchesFilter". If the "BranchesFilter" field itself has any content,
-it will be ignored.
-<br>
-If the "Branches to build" option has values
-"*/master */feature-master */build-with-jenkins", then "BranchesFilter"
-field will have value "d:master d:feature-master d:build-with-jenkins".
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciSkipPhrases.html b/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciSkipPhrases.html
deleted file mode 100644
index f30f306..0000000
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciSkipPhrases.html
+++ /dev/null
@@ -1,5 +0,0 @@
-A comma-separated list of strings to search the pull request title for.
-<br>
-e.g. If set to "trivial,[skiptest]", any PRs containing either "trivial" or
-"[skiptest]" (case-insensitive) will not be built.
-
diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly
index 372d9f8..b295683 100644
--- a/src/main/resources/index.jelly
+++ b/src/main/resources/index.jelly
@@ -3,5 +3,5 @@
This view is used to render the installed plugins page.
-->
<div>
- This plugin polls BitBucket to determine whether there are Pull Requests that should be built.
+ This plugin build Bitbucket pull requests via web-hooks
</div>
diff --git a/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/config.jelly b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/config.jelly
new file mode 100644
index 0000000..f61601a
--- /dev/null
+++ b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/config.jelly
@@ -0,0 +1,21 @@
+<?jelly escape-by-default='true'?>
+<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:c="/lib/credentials">
+ <f:entry title="${%Credentials}" field="credentialsId">
+ <c:select/>
+ </f:entry>
+ <f:entry title="Repository" field="destinationRepository">
+ <f:textbox />
+ </f:entry>
+ <f:entry title="CI Identifier" field="ciKey">
+ <f:textbox default="jenkins" />
+ </f:entry>
+ <f:entry title="CI Name" field="ciName">
+ <f:textbox default="Jenkins" />
+ </f:entry>
+ <f:entry title="Rebuild if destination branch changes" field="checkDestinationCommit">
+ <f:checkbox default="false"/>
+ </f:entry>
+ <f:entry title="Cancel outdated jobs" field="cancelOutdatedJobs">
+ <f:checkbox default="false"/>
+ </f:entry>
+</j:jelly>
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-cancelOutdatedJobs.html b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-cancelOutdatedJobs.html
index c03651c..c03651c 100644
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-cancelOutdatedJobs.html
+++ b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-cancelOutdatedJobs.html
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciKey.html
index 97696b6..97696b6 100644
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciKey.html
+++ b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciKey.html
diff --git a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciName.html
index 32f248d..32f248d 100644
--- a/src/main/resources/bitbucketpullrequestbuilder/bitbucketpullrequestbuilder/BitbucketBuildTrigger/help-ciName.html
+++ b/src/main/resources/org/jenkinsci/plugins/bbprb/BitbucketBuildTrigger/help-ciName.html