aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2017-12-05 11:37:56 +0300
committerIgor Pashev <pashev.igor@gmail.com>2017-12-13 21:39:09 +0300
commit5fa1952a2e582f2c428584c5ccc1800132559df0 (patch)
tree7446f557a3bc5448fd48292a327eae7f0f3afa19
parent92cf04a50b051cb6c96d0000eb8763797b239496 (diff)
downloadbbprb-5fa1952a2e582f2c428584c5ccc1800132559df0.tar.gz
Version 0.1.00.1.0
-rw-r--r--.clang-format22
-rw-r--r--.gitignore4
-rw-r--r--.travis.yml11
-rw-r--r--ChangeLog.md5
-rw-r--r--README.md133
-rw-r--r--pom.xml48
-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
-rw-r--r--src/test/java/BitbucketBuildFilterTest.java308
-rw-r--r--src/test/java/BitbucketBuildRepositoryTest.java354
36 files changed, 955 insertions, 2876 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..419e545
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,22 @@
+---
+Language: Java
+
+AccessModifierOffset: 0
+AlignAfterOpenBracket: true
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+BreakAfterJavaFieldAnnotations: true
+BreakBeforeBinaryOperators: false
+BreakBeforeBraces: Attach
+ColumnLimit: 80
+IndentCaseLabels: true
+IndentWidth: 2
+MaxEmptyLinesToKeep: 1
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpacesInAngles: false
+SpacesInParentheses: false
+TabWidth: 2
+UseTab: Never
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 4ed3c09..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.idea
-bitbucket-pullrequest-builder.iml
-target/
-work
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6a2566e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: java
-
-jdk:
- - openjdk7
- - oraclejdk7
-
-script:
- mvn install -U
-
-after_failure:
- - cat target/surefire-reports/*.txt
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
index 0000000..ea3d117
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+0.1.0
+=====
+
+ * Can handle creating and updating pull requests.
+
diff --git a/README.md b/README.md
index 88e74ea..74514e7 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,16 @@
-Bitbucket Pull Request Builder Plugin
-=====================================
+BBPRB
+=====
-This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results.
+This plugin is a revision of original [Bitbucket Pull Request Builder Plugin](https://wiki.jenkins.io/display/JENKINS/Bitbucket+pullrequest+builder+plugin).
+It was started by adding support for [Bitbucket webhooks](https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html)
+and resulted in massive rewrite and deleting the code.
-[![Build Status](https://travis-ci.org/nishio-dens/bitbucket-pullrequest-builder-plugin.svg?branch=master)](https://travis-ci.org/nishio-dens/bitbucket-pullrequest-builder-plugin)
-
-
-Prerequisites
--------------
-
-- Jenkins 1.625.3 or higher.
-- https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin
-
-
-Creating a Job
--------------
-
-- Create a new job
-- Select and configure Git SCM
- - Add Repository URL, `git@bitbucket.org:${repositoryOwner}/${repositoryName}.git`
- - In Branch Specifier, type `*/${sourceBranch}`
-- Under Build Triggers, check Bitbucket Pull Request Builder
-- In Cron, enter crontab for this job.
- - e.g. `* * * * *` will check for new pull requests every minute
-- In Bitbucket BasicAuth Username, write your bitbucket username, like `jenkins@densan-labs.net`
-- In Bitbucket BasicAuth Password, write your password
-- In CI Identifier, enter an unique identifier among your Jenkins jobs related to the repo
-- In CI Name, enter a human readable name for your Jenkins server
-- Write RepositoryOwner
-- Write RepositoryName
-- Save to preserve your changes
-
-
-Jenkins pipeline
--------------
-```
-pipeline {
- agent any
- triggers{
- bitbucketpr(projectPath:'<BIT_BUCKET_PATH>',
- cron:'H/15 * * * *',
- credentialsId:'',
- username:'',
- password:'',
- repositoryOwner:'',
- repositoryName:'',
- branchesFilter:'',
- branchesFilterBySCMIncludes:false,
- ciKey:'',
- ciName:'',
- ciSkipPhrases:'',
- checkDestinationCommit:false,
- approveIfSuccess:false,
- cancelOutdatedJobs:true,
- commentTrigger:'')
- }
-}
-```
-
-After you set up your Jenkins pipeline, run the job for the first time manually (otherwise the trigger may not work!)
-
-
-Merge the Pull Request's Source Branch into the Target Branch Before Building
------------------------------------------------------------------------------
-
-You may want Jenkins to attempt to merge your PR before building.
-This may help expose inconsistencies between the source branch and target branch.
-Note that if the merge cannot be completed, the build will fail immediately.
-
-- Follow the steps above in "Creating a Job"
-- In the "Source Code Management" > "Git" > "Additional Behaviors" section, click "Add" > "Merge Before Building"
-- In "Name of Repository" put "origin" (or, if not using default name, use your remote repository's name. Note: unlike in the main part of the Git Repository config, you cannot leave this item blank for "default").
-- In "Branch to merge to" put "${targetBranch}"
-- Note that as long as you don't push these changes to your remote repository, the merge only happens in your local repository.
-
-If you are merging into your target branch, you might want Jenkins to do a new build of the Pull Request when the target branch changes.
-- There is a checkbox that says, "Rebuild if destination branch changes?" which enables this check.
-
-
-Rerun a Build
--------------
-
-If you want to rerun a pull request build, write a comment on your pull request reading “test this please”.
-
-
-Environment Variables Provided
-------------------------------
-
-- `sourceBranch`
-- `targetBranch`
-- `repositoryOwner`
-- `repositoryName`
-- `pullRequestId`
-- `destinationRepositoryOwner`
-- `destinationRepositoryName`
-- `pullRequestTitle`
-- `pullRequestAuthor`
-
-
-Contributing to Bitbucket Pull Request Builder Plugin
------------------------------------------------------
-
-- Do not use Fork [jenkinsci/bitbucket-pullrequest-builder-plugin](https://github.com/jenkinsci/bitbucket-pullrequest-builder-plugin) for contribution
-
-- Use project [nishio-dens/bitbucket-pullrequest-builder-plugin](https://github.com/nishio-dens/bitbucket-pullrequest-builder-plugin)
-
-- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
-
-- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
-
-- Fork the project.
-
-- Start a feature/bugfix branch.
-
-- Commit and push until you are happy with your contribution.
-
-
-
-Donations
------------------------------------------------------
-Do you like this plugin? feel free to donate!
-
-Paypal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LTXCF78GJ7224
-
-BTC: 1KgwyVzefeNzJhuzqLq36E3bZi2WFjibMr
-
-Thank you!
Copyright
---------
+Copyright © 2017 Igor Pashev <pashev.igor@gmail.com>
+
Copyright © 2014 S.nishio + Martin Damovsky
diff --git a/pom.xml b/pom.xml
index f204feb..3239296 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -5,22 +6,24 @@
<artifactId>plugin</artifactId>
<version>2.11</version>
</parent>
-
- <artifactId>bitbucket-pullrequest-builder</artifactId>
+ <artifactId>bbprb</artifactId>
<name>Bitbucket Pullrequest Builder Plugin</name>
- <version>1.4.26-SNAPSHOT</version>
+ <version>0.1.0</version>
<description>This Jenkins plugin builds pull requests from Bitbucket.org and will report the test results.</description>
<packaging>hpi</packaging>
- <url>https://wiki.jenkins-ci.org/display/JENKINS/Bitbucket+pullrequest+builder+plugin</url>
-
+ <url></url>
<scm>
- <connection>scm:git:ssh://git@github.com/jenkinsci/bitbucket-pullrequest-builder-plugin.git</connection>
- <developerConnection>scm:git:ssh://git@github.com/jenkinsci/bitbucket-pullrequest-builder-plugin.git</developerConnection>
- <url>https://github.com/jenkinsci/bitbucket-pullrequest-builder-plugin.git</url>
+ <connection>https://github.com/ip1981/bbprb.git</connection>
+ <url>https://github.com/ip1981/bbprb.git</url>
<tag>HEAD</tag>
</scm>
<developers>
<developer>
+ <id>ip1981</id>
+ <name>Igor Pashev</name>
+ <email>pashev.igor@gmail.com</email>
+ </developer>
+ <developer>
<id>nishio_dens</id>
<name>nishio_dens</name>
<email>nishio@densan-labs.net</email>
@@ -30,33 +33,16 @@
<name>Martin Damovsky</name>
<email>martin.damovsky@gmail.com</email>
</developer>
- </developers>
-
+ </developers>
<!-- get every artifact through repo.jenkins-ci.org, which proxies all the artifacts that we need -->
- <repositories>
+ <repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
-
<dependencies>
<dependency>
- <groupId>org.apache.maven.wagon</groupId>
- <artifactId>wagon-http</artifactId>
- <version>2.4</version>
- </dependency>
- <dependency>
- <groupId>commons-httpclient</groupId>
- <artifactId>commons-httpclient</artifactId>
- <version>3.1</version>
- </dependency>
- <dependency>
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <version>1.9</version>
- </dependency>
- <dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
@@ -76,19 +62,11 @@
<artifactId>guava</artifactId>
<version>14.0-rc3</version>
</dependency>
- <dependency>
- <groupId>org.easymock</groupId>
- <artifactId>easymock</artifactId>
- <version>3.4</version>
- <scope>test</scope>
- </dependency>
</dependencies>
-
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
-
</project>
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
diff --git a/src/test/java/BitbucketBuildFilterTest.java b/src/test/java/BitbucketBuildFilterTest.java
deleted file mode 100644
index ad9892c..0000000
--- a/src/test/java/BitbucketBuildFilterTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-
-import antlr.ANTLRException;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildFilter;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildTrigger;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketCause;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketPullRequestsBuilder;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketRepository;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest;
-
-import java.util.Calendar;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import jenkins.plugins.git.AbstractGitSCMSource;
-import org.jvnet.hudson.test.JenkinsRule;
-import org.jvnet.hudson.test.WithoutJenkins;
-
-import org.easymock.*;
-import org.junit.Test;
-import org.junit.Rule;
-import static org.junit.Assert.*;
-
-/**
- * Tests
- */
-public class BitbucketBuildFilterTest {
-
- @Rule
- public JenkinsRule jRule = new JenkinsRule();
-
- @Test
- @WithoutJenkins
- public void mockTest() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("mock").anyTimes();
- EasyMock.replay(cause);
- for(Integer i : new Integer[] {1, 2, 3, 4, 5}) assertEquals("mock", cause.getTargetBranch());
- }
-
- @Test
- @WithoutJenkins
- public void anyFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"", "*", "any"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"foo", "bar", " baz "}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void onlyDestinationFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master-branch").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"master-branch", "r:^master", "r:branch$", " master-branch "}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"develop", "feature-good-thing", "r:develop$"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void rxSourceDestCheck() {
- for(String f : new String[] {"", "master", "r:master", "*"})
- assertFalse(Pattern.compile("(s:)|(d:)").matcher(f).find());
-
- for(String f : new String[] {"s:master d:feature-master", "s:master d:r:^feature", "s:r:^master d:r:^feature"})
- assertTrue(Pattern.compile("(s:)|(d:)").matcher(f).find());
- }
-
- @Test
- @WithoutJenkins
- public void sourceAndDestFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.expect(cause.getSourceBranch()).andReturn("feature-for-master").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"s:feature-for-master d:master", "s:r:^feature d:master", "s:feature-for-master d:r:^m", "s:r:^feature d:r:^master"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"s:feature-for-master d:foo", "s:bar d:master", "s:foo d:bar"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void multipleSrcDestFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.expect(cause.getSourceBranch()).andReturn("feature-master").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"s: d:", "s:r:^feature s:good-branch d:r:.*", "s:good-branch s:feature-master d:r:.*", "s: d:r:.*", "d:master d:foo d:bar s:"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"d:ggg d:ooo d:333 s:feature-master", "s:111 s:222 s:333 d:master"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void sourceAndDestPartiallyFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.expect(cause.getSourceBranch()).andReturn("feature-master").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"s:feature-master d:", "d:master s:"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"s:feature-master", "d:master"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void authorFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.expect(cause.getSourceBranch()).andReturn("feature-master").anyTimes();
- EasyMock.expect(cause.getPullRequestAuthor()).andReturn("test").anyTimes();
- EasyMock.replay(cause);
-
- for(String f : new String[] {"a:test", "a:r:^test", "d: s: a:", "a:", "a:foo a:test"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertTrue(filter.approved(cause));
- }
-
- for(String f : new String[] {"s:feature-master", "d:master", "s:feature-master d: a:foo", "a:bar"}) {
- BitbucketBuildFilter filter = BitbucketBuildFilter.instanceByString(f);
- assertFalse(filter.approved(cause));
- }
- }
-
- @Test
- @WithoutJenkins
- public void emptyGitSCMFilter() {
- BitbucketCause cause = EasyMock.createMock(BitbucketCause.class);
- EasyMock.expect(cause.getTargetBranch()).andReturn("master").anyTimes();
- EasyMock.replay(cause);
-
- assertTrue(BitbucketBuildFilter.filterFromGitSCMSource(null, "").isEmpty());
- assertEquals("default", BitbucketBuildFilter.filterFromGitSCMSource(null, "default"));
-
- assertTrue(BitbucketBuildFilter.instanceByString(
- BitbucketBuildFilter.filterFromGitSCMSource(null, "")).approved(cause)
- );
- }
-
- @Test
- @WithoutJenkins
- public void fromGitSCMFilter() {
- AbstractGitSCMSource git = EasyMock.createMock(AbstractGitSCMSource.class);
- EasyMock.expect(git.getIncludes())
- .andReturn("").times(1)
- .andReturn("").times(1)
- .andReturn("*/master */feature-branch").times(1)
- .andReturn("*/master").anyTimes();
- EasyMock.replay(git);
-
- assertTrue(git.getIncludes().isEmpty());
- assertEquals("", BitbucketBuildFilter.filterFromGitSCMSource(git, ""));
- assertEquals("d:master d:feature-branch", BitbucketBuildFilter.filterFromGitSCMSource(git, ""));
- assertEquals("d:master", BitbucketBuildFilter.filterFromGitSCMSource(git, ""));
- }
-
- @Test
- @WithoutJenkins
- public void filterPRComments() throws ANTLRException {
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes();
- EasyMock.replay(builder);
-
- List<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>();
- for(String commentContent : new String[] {
- "check",
- "",
- "Hello from mock",
- "Jenkins: test this please",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]",
- "check",
- "",
- "Hello from mock",
- "Jenkins: test this please",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo]",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]",
- }) {
- Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class);
- EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes();
- EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes();
- EasyMock.replay(comment);
- comments.add(comment);
- }
-
- // Check twice
- assertEquals("check", comments.get(0).getContent());
- assertEquals("check", comments.get(0).getContent());
-
- assertEquals("Hello from mock", comments.get(2).getContent());
-
- BitbucketRepository repo = new BitbucketRepository("", builder);
- repo.init(EasyMock.createNiceMock(ApiClient.class));
-
- List<Pullrequest.Comment> filteredComments = repo.filterPullRequestComments(comments);
-
- assertTrue(filteredComments.size() == 4);
- assertEquals("Jenkins: test this please", filteredComments.get(filteredComments.size() - 1).getContent());
- }
-
- @Test
- @WithoutJenkins
- public void checkHashMyBuildTagTrue() {
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes();
- EasyMock.replay(builder);
-
- IMockBuilder<BitbucketRepository> repoBuilder = EasyMock.partialMockBuilder(BitbucketRepository.class);
- repoBuilder.addMockedMethod("getMyBuildTag");
- BitbucketRepository repo = repoBuilder.createMock();
- EasyMock.expect(repo.getMyBuildTag(EasyMock.anyString())).andReturn("#jenkins-902f259e962ff16100843123480a0970").anyTimes();
- EasyMock.replay(repo);
-
- List<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>();
- for(String commentContent : new String[] {
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970]",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo]",
- "TTP build flag [bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]",
- "TTP build flag ```[bid: #jenkins-902f259e962ff16100843123480a0970 #jenkins-foo #jenkins-bar]```",
- }) {
- Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class);
- EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes();
- EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes();
- EasyMock.replay(comment);
- comments.add(comment);
- }
-
- String myBuildKey = "902f259e962ff16100843123480a0970";
- for(Pullrequest.Comment comment : comments)
- assertTrue(repo.hasMyBuildTagInTTPComment(comment.getContent(), myBuildKey));
- }
-
- @Test
- @WithoutJenkins
- public void checkHashMyBuildTagFalse() {
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(null).anyTimes();
- EasyMock.replay(builder);
-
- IMockBuilder<BitbucketRepository> repoBuilder = EasyMock.partialMockBuilder(BitbucketRepository.class);
- repoBuilder.addMockedMethod("getMyBuildTag");
- BitbucketRepository repo = repoBuilder.createMock();
- EasyMock.expect(repo.getMyBuildTag(EasyMock.anyString())).andReturn("#jenkins-902f259e962ff16100843123480a0970").anyTimes();
- EasyMock.replay(repo);
-
- List<Pullrequest.Comment> comments = new LinkedList<Pullrequest.Comment>();
- for(String commentContent : new String[] {
- "check",
- "",
- "Hello from mock",
- "Jenkins: test this please",
- "TTP build flag [bid: #jenkins]",
- "TTP build flag [bid: #jenkins-foo]",
- "TTP build flag [bid: #jenkins-foo #jenkins-bar]",
- "TTP build flag ```[bid: #jenkins-foo #jenkins-bar]```",
- }) {
- Pullrequest.Comment comment = EasyMock.createNiceMock(Pullrequest.Comment.class);
- EasyMock.expect(comment.getContent()).andReturn(commentContent).anyTimes();
- EasyMock.expect(comment.getId()).andReturn(new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()).getNanos()).anyTimes();
- EasyMock.replay(comment);
- comments.add(comment);
- }
-
- String myBuildKey = "902f259e962ff16100843123480a0970";
- for(Pullrequest.Comment comment : comments)
- assertFalse(repo.hasMyBuildTagInTTPComment(comment.getContent(), myBuildKey));
- }
-}
diff --git a/src/test/java/BitbucketBuildRepositoryTest.java b/src/test/java/BitbucketBuildRepositoryTest.java
deleted file mode 100644
index 42694a3..0000000
--- a/src/test/java/BitbucketBuildRepositoryTest.java
+++ /dev/null
@@ -1,354 +0,0 @@
-
-import antlr.ANTLRException;
-
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketBuildTrigger;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketPullRequestsBuilder;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.BitbucketRepository;
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.ApiClient;
-
-import bitbucketpullrequestbuilder.bitbucketpullrequestbuilder.bitbucket.Pullrequest;
-import com.cloudbees.plugins.credentials.CredentialsProvider;
-import com.cloudbees.plugins.credentials.CredentialsScope;
-import com.cloudbees.plugins.credentials.CredentialsStore;
-import com.cloudbees.plugins.credentials.domains.Domain;
-import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
-import org.easymock.*;
-import org.junit.Test;
-import static org.junit.Assert.*;
-import org.junit.Rule;
-import org.junit.Assert;
-import org.jvnet.hudson.test.JenkinsRule;
-
-import jenkins.model.Jenkins;
-
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpState;
-import org.apache.commons.httpclient.UsernamePasswordCredentials;
-import org.apache.commons.httpclient.auth.AuthScope;
-
-
-interface ICredentialsInterceptor {
- void assertCredentials(Credentials actual);
-}
-
-/**
- * Utility class for interceptor functionality
- * @param <T>
- */
-class HttpClientInterceptor<T extends ICredentialsInterceptor> extends HttpClient {
- private static final Logger logger = Logger.getLogger(HttpClientInterceptor.class.getName());
-
- class CredentialsInterceptor<T extends ICredentialsInterceptor> extends HttpState {
- private final T interceptor;
- public CredentialsInterceptor(T interceptor) { this.interceptor = interceptor; }
-
- @Override
- public synchronized void setCredentials(AuthScope authscope, Credentials credentials) {
- logger.fine("Inject setCredentials");
- super.setCredentials(authscope, credentials);
- this.interceptor.assertCredentials(credentials);
- throw new AssertionError();
- }
- }
-
- private final T interceptor;
- public HttpClientInterceptor(T interceptor) { this.interceptor = interceptor; }
-
- @Override
- public synchronized HttpState getState() { return new CredentialsInterceptor(this.interceptor); }
-}
-
-/**
- * Utility class for credentials assertion
- * Used with
- * @author maxvodo
- */
-class AssertCredentials implements ICredentialsInterceptor {
- private static final Logger logger = Logger.getLogger(AssertCredentials.class.getName());
-
- private final Credentials expected;
- public AssertCredentials(Credentials expected) { this.expected = expected; }
-
- public void assertCredentials(Credentials actual) {
- logger.fine("Assert credential");
- if (actual == null) assertTrue(this.expected == null);
- else assertTrue(this.expected != null);
-
- if (actual instanceof UsernamePasswordCredentials) {
- UsernamePasswordCredentials actual_ = (UsernamePasswordCredentials)actual,
- expected_ = (UsernamePasswordCredentials)this.expected;
- assertNotNull(expected_);
- Assert.assertArrayEquals(new Object[] {
- actual_.getUserName(), actual_.getPassword()
- }, new Object[] {
- expected_.getUserName(), expected_.getPassword()
- });
- }
- }
-}
-
-/**
- * Tests
- */
-public class BitbucketBuildRepositoryTest {
-
- @Rule
- public JenkinsRule jRule = new JenkinsRule();
-
- @Test
- public void repositorySimpleUserPasswordTest() throws Exception {
- BitbucketBuildTrigger trigger = new BitbucketBuildTrigger(
- "", "@hourly",
- "JenkinsCID",
- "foo",
- "bar",
- "", "",
- "", true,
- "", "", "",
- true,
- true,
- false, BitbucketRepository.DEFAULT_COMMENT_TRIGGER
- );
-
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes();
- EasyMock.replay(builder);
-
- ApiClient.HttpClientFactory httpFactory = EasyMock.createNiceMock(ApiClient.HttpClientFactory.class);
- EasyMock.expect(httpFactory.getInstanceHttpClient()).andReturn(
- new HttpClientInterceptor(new AssertCredentials(new UsernamePasswordCredentials("foo", "bar")))
- ).anyTimes();
- EasyMock.replay(httpFactory);
-
- BitbucketRepository repo = new BitbucketRepository("", builder);
- repo.init(httpFactory);
-
- try { repo.postPullRequestApproval("prId"); } catch(Error e) { assertTrue(e instanceof AssertionError); }
- }
-
- @Test
- public void repositoryCtorWithTriggerTest() throws Exception {
- BitbucketBuildTrigger trigger = new BitbucketBuildTrigger(
- "", "@hourly",
- "JenkinsCID",
- "foo",
- "bar",
- "", "",
- "", true,
- "", "", "",
- true,
- true,
- false, BitbucketRepository.DEFAULT_COMMENT_TRIGGER
- );
-
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes();
- EasyMock.replay(builder);
-
- CredentialsStore store = CredentialsProvider.lookupStores(Jenkins.getInstance()).iterator().next();
- assertNotNull(store);
- store.addCredentials(Domain.global(), new UsernamePasswordCredentialsImpl(
- CredentialsScope.GLOBAL, "JenkinsCID", "description", "username", "password"
- ));
-
- ApiClient.HttpClientFactory httpFactory = EasyMock.createNiceMock(ApiClient.HttpClientFactory.class);
- EasyMock.expect(httpFactory.getInstanceHttpClient()).andReturn(
- new HttpClientInterceptor(new AssertCredentials(new UsernamePasswordCredentials("username", "password")))
- ).anyTimes();
- EasyMock.replay(httpFactory);
-
- BitbucketRepository repo = new BitbucketRepository("", builder);
- repo.init(httpFactory);
-
- try { repo.postPullRequestApproval("prId"); } catch(Error e) { assertTrue(e instanceof AssertionError); }
- }
-
- class MD5HasherFunction implements Function<String, String> {
- protected final MessageDigest MD5;
- public MD5HasherFunction(MessageDigest md5) { this.MD5 = md5; }
- public String apply(String f) {
- try { return new String(Hex.encodeHex(MD5.digest(f.getBytes("UTF-8")))); } catch(UnsupportedEncodingException e) { }
- return null;
- }
- }
-
- class SHA1HasherFunction implements Function<String, String> {
- protected final MessageDigest SHA1;
- public SHA1HasherFunction(MessageDigest sha1) { this.SHA1 = sha1; }
- public String apply(String f) {
- try { return new String(Hex.encodeHex(SHA1.digest(f.getBytes("UTF-8")))); } catch(UnsupportedEncodingException e) { }
- return null;
- }
- }
-
- @Test
- public void repositoryProjectIdTest() throws ANTLRException, NoSuchAlgorithmException, UnsupportedEncodingException {
- BitbucketBuildTrigger trigger = new BitbucketBuildTrigger(
- "", "@hourly",
- "JenkinsCID",
- "foo",
- "bar",
- "", "",
- "", true,
- "jenkins", "Jenkins", "",
- true,
- true,
- false, BitbucketRepository.DEFAULT_COMMENT_TRIGGER
- );
-
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes();
-
- final MessageDigest MD5 = MessageDigest.getInstance("MD5");
-
- String[] projectIds = new String[] {
- "one",
- "Second project",
- "Project abstract 1.1",
- "Good project, careated at " + (new java.util.Date()).toString(),
- };
-
- Collection<String> hashedProjectIdsCollection = Collections2.transform(Arrays.asList(projectIds), new MD5HasherFunction(MD5));
- String[] hashedPojectIds = hashedProjectIdsCollection.toArray(new String[hashedProjectIdsCollection.size()]);
-
- for(String projectId : hashedPojectIds) {
- EasyMock.expect(builder.getProjectId()).andReturn(projectId).times(1);
- }
- EasyMock.replay(builder);
-
- BitbucketRepository repo = new BitbucketRepository("", builder);
- repo.init();
-
- for(String projectId : projectIds) {
- String hashMD5 = new String(Hex.encodeHex(MD5.digest(projectId.getBytes("UTF-8"))));
- String buildStatusKey = repo.getClient().buildStatusKey(builder.getProjectId());
-
- assertTrue(buildStatusKey.length() <= ApiClient.MAX_KEY_SIZE_BB_API);
- assertEquals(buildStatusKey, "jenkins-" + hashMD5);
- }
- }
-
- @Test
- public void triggerLongCIKeyTest() throws ANTLRException, NoSuchAlgorithmException {
- BitbucketBuildTrigger trigger = new BitbucketBuildTrigger(
- "", "@hourly",
- "JenkinsCID",
- "foo",
- "bar",
- "", "",
- "", true,
- "jenkins-too-long-ci-key", "Jenkins", "",
- true,
- true,
- false, BitbucketRepository.DEFAULT_COMMENT_TRIGGER
- );
-
- final MessageDigest MD5 = MessageDigest.getInstance("MD5");
- final MessageDigest SHA1 = MessageDigest.getInstance("SHA1");
-
- BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes();
- EasyMock.expect(builder.getProjectId()).andReturn((new MD5HasherFunction(MD5)).apply("projectId")).anyTimes();
- EasyMock.replay(builder);
-
- BitbucketRepository repo = new BitbucketRepository("", builder);
- repo.init();
-
- String buildStatusKey = repo.getClient().buildStatusKey(builder.getProjectId());
- assertTrue(buildStatusKey.length() <= ApiClient.MAX_KEY_SIZE_BB_API);
- assertFalse(buildStatusKey.startsWith("jenkins-"));
- assertEquals((new SHA1HasherFunction(SHA1)).apply("jenkins-too-long-ci-key" + "-" + builder.getProjectId()), buildStatusKey);
- }
-
- @Test
- public void getTargetPullRequestsWithNullDestinationCommit() throws Exception {
- // arrange
-
- // setup mock BitbucketBuildTrigger
- final BitbucketBuildTrigger trigger = EasyMock.createMock(BitbucketBuildTrigger.class);
- EasyMock.expect(trigger.getCiSkipPhrases()).andReturn("");
- EasyMock.expect(trigger.getBranchesFilterBySCMIncludes()).andReturn(false);
- EasyMock.expect(trigger.getBranchesFilter()).andReturn("");
- EasyMock.replay(trigger);
-
- // setup mock BitbucketPullRequestsBuilder
- final BitbucketPullRequestsBuilder builder = EasyMock.createMock(BitbucketPullRequestsBuilder.class);
- EasyMock.expect(builder.getTrigger()).andReturn(trigger).anyTimes();
- EasyMock.expect(builder.getProjectId()).andReturn("").anyTimes();
- EasyMock.replay(builder);
-
- // setup PRs to return from mock ApiClient
- final Pullrequest pullRequest = new Pullrequest();
-
- final Pullrequest.Repository sourceRepo = new Pullrequest.Repository();
- sourceRepo.setFullName("Owner/Name");
-
- final Pullrequest.Repository destRepo = new Pullrequest.Repository();
- destRepo.setFullName("Owner/Name");
-
- final Pullrequest.Branch sourceBranch = new Pullrequest.Branch();
- sourceBranch.setName("Name");
-
- final Pullrequest.Branch destBranch = new Pullrequest.Branch();
- destBranch.setName("Name");
-
- final Pullrequest.Commit sourceCommit = new Pullrequest.Commit();
- sourceCommit.setHash("Hash");
-
- final Pullrequest.Commit destCommit = null; // the crux of the test
-
- final Pullrequest.Revision sourceRevision = new Pullrequest.Revision();
- sourceRevision.setBranch(sourceBranch);
- sourceRevision.setRepository(sourceRepo);
- sourceRevision.setCommit(sourceCommit);
-
- final Pullrequest.Revision destRevision = new Pullrequest.Revision();
- destRevision.setBranch(destBranch);
- destRevision.setRepository(destRepo);
- destRevision.setCommit(destCommit);
-
- final Pullrequest.Author author = new Pullrequest.Author();
- author.setDisplayName("DisplayName");
- author.setUsername("Username");
-
- pullRequest.setSource(sourceRevision);
- pullRequest.setDestination(destRevision);
- pullRequest.setId("Id");
- pullRequest.setTitle("Title");
- pullRequest.setState("OPEN");
- pullRequest.setAutohor(author);
-
- final List<Pullrequest> pullRequests = new ArrayList<>(Arrays.asList(pullRequest));
-
- // setup mock ApiClient
- final ApiClient client = EasyMock.createNiceMock(ApiClient.class);
- EasyMock.expect(client.getPullRequests()).andReturn(pullRequests);
- EasyMock.replay(client);
-
- // setup SUT
- final BitbucketRepository repo = new BitbucketRepository("", builder);
-
- // act
- repo.init(client);
-
- // assert
- Collection<Pullrequest> targetPullRequests = repo.getTargetPullRequests();
-
- assertEquals(pullRequests.size(), targetPullRequests.size());
- assertEquals(pullRequest, targetPullRequests.iterator().next());
- }
-}