From bb2d5a4c7e6302e5b7f3b763fb9bb247aa00020b Mon Sep 17 00:00:00 2001 From: Gunter Grodotzki Date: Fri, 19 Jan 2018 10:46:40 +0200 Subject: [PATCH] init implementation (#1) * init implementation * sigh... again? --- .bash/functions.shlib | 40 +++ .gitignore | 8 + .travis.yml | 21 ++ README.md | 46 ++- app/CommandLoaderFactory.php | 23 ++ app/Commands/BitbucketCommand.php | 28 ++ .../Repositories/PullRequests/GetCommand.php | 37 +++ .../PullRequests/GetDescriptionCommand.php | 36 ++ .../PullRequests/UpdateCommand.php | 53 +++ .../PullRequests/UpdateDescriptionCommand.php | 67 ++++ .../Repositories/PullRequestsCommand.php | 23 ++ app/Providers/BitbucketConfigProvider.php | 29 ++ app/helpers.php | 11 + bitbucket | 3 + build.sh | 42 +++ cli.php | 33 ++ composer.json | 25 ++ composer.lock | 313 ++++++++++++++++++ create-phar.php | 36 ++ payload.json | 9 + 20 files changed, 882 insertions(+), 1 deletion(-) create mode 100644 .bash/functions.shlib create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 app/CommandLoaderFactory.php create mode 100644 app/Commands/BitbucketCommand.php create mode 100644 app/Commands/Repositories/PullRequests/GetCommand.php create mode 100644 app/Commands/Repositories/PullRequests/GetDescriptionCommand.php create mode 100644 app/Commands/Repositories/PullRequests/UpdateCommand.php create mode 100644 app/Commands/Repositories/PullRequests/UpdateDescriptionCommand.php create mode 100644 app/Commands/Repositories/PullRequestsCommand.php create mode 100644 app/Providers/BitbucketConfigProvider.php create mode 100644 app/helpers.php create mode 100755 bitbucket create mode 100755 build.sh create mode 100644 cli.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 create-phar.php create mode 100644 payload.json diff --git a/.bash/functions.shlib b/.bash/functions.shlib new file mode 100644 index 0000000..2c18c00 --- /dev/null +++ b/.bash/functions.shlib @@ -0,0 +1,40 @@ +throw_exception() { + consolelog "Ooops!" "error" + echo 'Stack trace:' 1>&2 + while caller $((n++)) 1>&2; do :; done; + exit 1 +} + +consolelog() { + local color + local ts + + # el-cheapo way to detect if timestamp prefix needed + if [[ ! -z "${JENKINS_HOME}" ]]; then + ts='' + else + ts="[$(date -u +'%Y-%m-%d %H:%M:%S')] " + fi + + color_reset='\e[0m' + + case "${2}" in + success ) + color='\e[0;32m' + ;; + error ) + color='\e[1;31m' + ;; + * ) + color='\e[0;37m' + ;; + esac + + if [[ ! -z "${1}" ]] && [[ ! -z "${2}" ]] && [[ "${2}" = "error" ]]; then + printf "${color}%s%s: %s${color_reset}\n" "${ts}" "${0##*/}" "${1}" >&2 + elif [[ ! -z "${1}" ]]; then + printf "${color}%s%s: %s${color_reset}\n" "${ts}" "${0##*/}" "${1}" + fi + + return 0 +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bdc72d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.buildpath +/.project +/.settings/ + +/version.txt +/bitbucket.phar + +/vendor/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ece0455 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: php + +matrix: + include: + - php: 7.2 + - php: 7.1 + env: DEPLOY=true + +sudo: false + +script: "./build.sh" + +deploy: + skip_cleanup: true + provider: releases + api_key: + secure: my141Ro0d++ChMEqF1lk6jMNI0VDguJMxdJ39A/x+snmxPBX79750vtdarvyEb3ZSE5oaPt6u3iVlhgK0PWI0wCg0hYkMA8qdIU7UB6pI500HmyVNA0X6kkloypDY6zFFOn7E7mtBT8jxl9+sQscw5Ihojny3Nn0ajXxgxkJbLDzXMZDiBUPUuAHrQ4e9OOfAEVhWj7E9yiP6AOu5xJZgXrh8pILQ3/uvkGLFujcbpjeZFk4wLVechJrj78Jc6uoX5CdgaIvlVnQIQGYEzom6A3ZdQaP72vfjaopaZ7cCV6e/Z2w05v0HkbbTMpsu/5amXoYVtQaeejhy1PTrFZIeczEM252N7T+MPDNKUX/ISNxb4nchliaIRnrREj6YeyiQMIy1x0cq9PkMkSpj/4v5AGy9pb5ZWuwWrkS/ncQi7fxdUq8Ir3guRFEUXlHlQml9iKTx1AqcmENgqgj/uh5dK6+FDe4d5wZsOopNCk18fv+eTs6jW9hTrCja9wyK3Esf/ovNEZ+HGQ7FqsM45+LlLuhSCBmydpDWsvM3q3So5XjH81VJvRb05sv3yQf4PO94SFFr7/08dG974nTZX27K/EL0yNKve892YCHQawI7QE8YMkpqq3mnZCZhVuwHZbT8gfBl8SjYsZNExscJEp+grOXRTBx8GznF+QZsD7F/Fw= + file: bitbucket.phar + on: + tags: true + condition: $DEPLOY = true diff --git a/README.md b/README.md index 3d357ff..257e52a 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ -# bitbucket-pr \ No newline at end of file +[![Build Status](https://travis-ci.org/NINEJKH/bitbucket-cli.svg?branch=master)](https://travis-ci.org/NINEJKH/bitbucket-cli) + +# bitbucket-cli + +A bitbucket cli tool. + +## Installation + +```bash +$ sudo curl -#fLo /usr/local/bin/bitbucket https://github.com/NINEJKH/bitbucket-cli/releases/download/0.0.1/bitbucket.phar && sudo chmod +x /usr/local/bin/bitbucket +``` + +## Auth + +1. Create App password: https://bitbucket.org/account/user/YOUR_USERNAME/app-passwords +2. Add those details in `~/.bitbucket` + +``` +[auth] +username = +password = +``` + +## Usage + +### Pull requests + +#### Get pull request + +```bash +$ bitbucket repositories:pullrequests:get NINEJKH/bitbucket-cli 4 +``` + +#### Update pull request + +```bash +$ bitbucket repositories:pullrequests:update NINEJKH/bitbucket-cli 4 < payload.json +``` + +### Update pull request description + +```bash +$ bitbucket repositories:pullrequests:update:description NINEJKH/bitbucket-cli 4 "new description (or via STDIN)" +``` + diff --git a/app/CommandLoaderFactory.php b/app/CommandLoaderFactory.php new file mode 100644 index 0000000..3046b0e --- /dev/null +++ b/app/CommandLoaderFactory.php @@ -0,0 +1,23 @@ +getContent(); + if (preg_match('~^{.*}$~', $error_message)) { + $error_message = json_decode($error_message, true); + if (!empty($error_message['error'])) { + $error_message = sprintf('[%s]%s%s', $error_message['error']['message'], PHP_EOL, $error_message['error']['detail']); + } + } + + throw new RuntimeException($error_message, $message->getStatusCode()); + } + + protected function splitRepo($repo) + { + return explode('/', $repo, 2); + } +} diff --git a/app/Commands/Repositories/PullRequests/GetCommand.php b/app/Commands/Repositories/PullRequests/GetCommand.php new file mode 100644 index 0000000..ba32685 --- /dev/null +++ b/app/Commands/Repositories/PullRequests/GetCommand.php @@ -0,0 +1,37 @@ +setName('repositories:pullrequests:get') + ->setDescription('Get a specific pull request') + ->addArgument('repo', InputArgument::REQUIRED, 'owner/repository_slug') + ->addArgument('pull_request_id', InputArgument::REQUIRED, 'The id of the pull request.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $pull = $this->create(); + + list($username, $repo_slug) = $this->splitRepo($input->getArgument('repo')); + + $pr = $pull->get($username, $repo_slug, $input->getArgument('pull_request_id')); + + if (!$pr->isOk()) { + $this->throwApiResponseError($pr); + } + + //$output->writeln(json_encode(json_decode($pr->getContent(), true), JSON_PRETTY_PRINT)); + $output->writeln($pr->getContent()); + } +} diff --git a/app/Commands/Repositories/PullRequests/GetDescriptionCommand.php b/app/Commands/Repositories/PullRequests/GetDescriptionCommand.php new file mode 100644 index 0000000..2b81fc9 --- /dev/null +++ b/app/Commands/Repositories/PullRequests/GetDescriptionCommand.php @@ -0,0 +1,36 @@ +setName('repositories:pullrequests:get:description') + ->setDescription('Get a specific pull request description') + ->addArgument('repo', InputArgument::REQUIRED, 'owner/repository_slug') + ->addArgument('pull_request_id', InputArgument::REQUIRED, 'The id of the pull request.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $pull = $this->create(); + + list($username, $repo_slug) = $this->splitRepo($input->getArgument('repo')); + + $pr = $pull->get($username, $repo_slug, $input->getArgument('pull_request_id')); + + if (!$pr->isOk()) { + $this->throwApiResponseError($pr); + } + + $output->writeln(json_decode($pr->getContent(), true)['description']); + } +} diff --git a/app/Commands/Repositories/PullRequests/UpdateCommand.php b/app/Commands/Repositories/PullRequests/UpdateCommand.php new file mode 100644 index 0000000..4488f1f --- /dev/null +++ b/app/Commands/Repositories/PullRequests/UpdateCommand.php @@ -0,0 +1,53 @@ +setName('repositories:pullrequests:update') + ->setDescription('Get a specific pull request') + ->addOption('file', 'f', InputOption::VALUE_OPTIONAL,'Path to JSON formatted input file') + ->addArgument('repo', InputArgument::REQUIRED, 'owner/repository_slug') + ->addArgument('pull_request_id', InputArgument::REQUIRED, 'The id of the pull request.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($filename = $input->getOption('file')) { + $payload = file_get_contents($filename); + } elseif (ftell(STDIN) === 0) { + $payload = stream_get_contents(STDIN); + } else { + throw new RuntimeException('Please provide a filename or pipe content to STDIN.'); + } + + if (empty($payload)) { + throw new RuntimeException('Empty payload.'); + } + + $payload = json_decode($payload, true); + + $pull = $this->create(); + + list($username, $repo_slug) = $this->splitRepo($input->getArgument('repo')); + + $pr = $pull->update($username, $repo_slug, $input->getArgument('pull_request_id'), $payload); + + if (!$pr->isOk()) { + $this->throwApiResponseError($pr); + } + + return true; + } +} diff --git a/app/Commands/Repositories/PullRequests/UpdateDescriptionCommand.php b/app/Commands/Repositories/PullRequests/UpdateDescriptionCommand.php new file mode 100644 index 0000000..50bfe34 --- /dev/null +++ b/app/Commands/Repositories/PullRequests/UpdateDescriptionCommand.php @@ -0,0 +1,67 @@ +setName('repositories:pullrequests:update:description') + ->setDescription('Get a specific pull request') + ->addArgument('repo', InputArgument::REQUIRED, 'owner/repository_slug') + ->addArgument('pull_request_id', InputArgument::REQUIRED, 'The id of the pull request.') + ->addArgument('description', InputArgument::OPTIONAL, 'New description to set.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->hasArgument('description')) { + $description = $input->getArgument('description'); + } elseif (ftell(STDIN) === 0) { + $description = stream_get_contents(STDIN); + } else { + throw new RuntimeException('Empty description.'); + } + + if (empty($description)) { + throw new RuntimeException('Empty description.'); + } + + $pull = $this->create(); + + list($username, $repo_slug) = $this->splitRepo($input->getArgument('repo')); + + $pr = $pull->get($username, $repo_slug, $input->getArgument('pull_request_id')); + if (!$pr->isOk()) { + $this->throwApiResponseError($pr); + } + + $current_pr = json_decode($pr->getContent(), true); + + $payload = [ + 'title' => $current_pr['title'], + 'description' => $description, + 'destination' => [ + 'branch' => [ + 'name' => $current_pr['destination']['branch']['name'] + ] + ] + ]; + + $pr = $pull->update($username, $repo_slug, $input->getArgument('pull_request_id'), $payload); + + if (!$pr->isOk()) { + $this->throwApiResponseError($pr); + } + + return true; + } +} diff --git a/app/Commands/Repositories/PullRequestsCommand.php b/app/Commands/Repositories/PullRequestsCommand.php new file mode 100644 index 0000000..8384566 --- /dev/null +++ b/app/Commands/Repositories/PullRequestsCommand.php @@ -0,0 +1,23 @@ +getClient()->addListener( + new BasicAuthListener($bitbucket_config->getBasicAuthUsername(), $bitbucket_config->getBasicAuthPassword()) + ); + + return $pull; + } +} diff --git a/app/Providers/BitbucketConfigProvider.php b/app/Providers/BitbucketConfigProvider.php new file mode 100644 index 0000000..2d4fd3e --- /dev/null +++ b/app/Providers/BitbucketConfigProvider.php @@ -0,0 +1,29 @@ + version.txt +elif [[ ! -z "${TRAVIS_COMMIT}" ]]; then + echo "${TRAVIS_COMMIT}" > version.txt +fi + +# composer +consolelog "composer install" +composer install \ + --no-interaction \ + --prefer-dist \ + --no-suggest \ + --quiet \ + --verbose + +consolelog "composer cleanup" +composer install \ + --no-dev \ + --quiet \ + --verbose + +composer dump-autoload \ + --no-dev \ + --classmap-authoritative \ + --quiet \ + --verbose + +php create-phar.php bitbucket.phar +chmod +x bitbucket.phar + +./bitbucket.phar --version diff --git a/cli.php b/cli.php new file mode 100644 index 0000000..e1c376c --- /dev/null +++ b/cli.php @@ -0,0 +1,33 @@ +add(new GetCommand); +$app->add(new GetDescriptionCommand); +$app->add(new UpdateCommand); +$app->add(new UpdateDescriptionCommand); +$app->run(); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fb4314c --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "ninejkh/bitbucket-cli", + "description": "A bitbucket cli tool.", + "type": "project", + "license": "MIT", + "authors": [ + { + "name": "9JKH (Pty) Ltd.", + "email": "hello@9jkh.co.za" + } + ], + "require": { + "php": ">=7.1.0", + "gentle/bitbucket-api": "^1.1", + "symfony/console": "^4.0" + }, + "autoload": { + "psr-4": { + "App\\": "app/" + }, + "files": [ + "app/helpers.php" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..d5d619c --- /dev/null +++ b/composer.lock @@ -0,0 +1,313 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "4c29164e197474fe830894deea3f46ed", + "packages": [ + { + "name": "gentle/bitbucket-api", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/gentlero/bitbucket-api.git", + "reference": "90d744716ebc755f1f683a619abe9569ccca8557" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gentlero/bitbucket-api/zipball/90d744716ebc755f1f683a619abe9569ccca8557", + "reference": "90d744716ebc755f1f683a619abe9569ccca8557", + "shasum": "" + }, + "require": { + "jacobkiers/oauth": "~1.0", + "kriswallsmith/buzz": "~0.15", + "php": ">=5.4.0" + }, + "conflict": { + "eabay/bitbucket-repo-sync": "*", + "rlacerda83/bitbucket-api": "*" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "~2.7" + }, + "suggest": { + "ext-curl": "*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Bitbucket\\": "lib/" + }, + "files": [ + "polyfill-55.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexandru Guzinschi", + "email": "alex@gentle.ro", + "homepage": "http://www.sebi.me", + "role": "Developer" + } + ], + "description": "Bitbucket API wrapper for PHP >= 5.4", + "homepage": "https://bitbucket.org/gentlero/bitbucket-api", + "keywords": [ + "api", + "bitbucket" + ], + "time": "2017-11-06T09:44:29+00:00" + }, + { + "name": "jacobkiers/oauth", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/jacobkiers/OAuth.git", + "reference": "95477a77107436e67a10e799fad04d82719a5f1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jacobkiers/OAuth/zipball/95477a77107436e67a10e799fad04d82719a5f1e", + "reference": "95477a77107436e67a10e799fad04d82719a5f1e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "mockery/mockery": "*", + "phpunit/phpunit": "3.7.*@stable", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-0": { + "JacobKiers\\OAuth": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gary Jones", + "email": "gary@garyjones.co.uk", + "role": "developer" + }, + { + "name": "Alexandre Eher", + "role": "Composer packager" + }, + { + "name": "Jacob Kiers", + "email": "jacob@alphacomm.nl", + "role": "developer" + }, + { + "name": "Andy Smith", + "role": "original author" + } + ], + "description": "Based on Andy Smith's basic PHP library for OAuth 1.0a", + "homepage": "https://github.com/jacobkiers/OAuth", + "keywords": [ + "oauth" + ], + "time": "2015-10-02T13:50:51+00:00" + }, + { + "name": "kriswallsmith/buzz", + "version": "v0.15.2", + "source": { + "type": "git", + "url": "https://github.com/kriswallsmith/Buzz.git", + "reference": "d0b82d8a6169276f91f445c26792ff4a2b11e831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kriswallsmith/Buzz/zipball/d0b82d8a6169276f91f445c26792ff4a2b11e831", + "reference": "d0b82d8a6169276f91f445c26792ff4a2b11e831", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.3" + }, + "suggest": { + "ext-curl": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Buzz\\": "lib/Buzz" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Lightweight HTTP client", + "homepage": "https://github.com/kriswallsmith/Buzz", + "keywords": [ + "curl", + "http client" + ], + "time": "2017-11-24T19:18:50+00:00" + }, + { + "name": "symfony/console", + "version": "v4.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", + "reference": "fe0e69d7162cba0885791cf7eea5f0d7bc0f897e", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-10-11T12:05:26+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.0" + }, + "platform-dev": [] +} diff --git a/create-phar.php b/create-phar.php new file mode 100644 index 0000000..1a2a544 --- /dev/null +++ b/create-phar.php @@ -0,0 +1,36 @@ +buildFromDirectory(dirname(__FILE__), '~^(?!(.*(\.git|\.bash)))(.*)$~i'); + +$stub = <<<"EOT" +#!/usr/bin/env php +setStub($stub); + +// plus - compressing it into gzip +//$p->compress(Phar::GZ); +unset($p); diff --git a/payload.json b/payload.json new file mode 100644 index 0000000..5299192 --- /dev/null +++ b/payload.json @@ -0,0 +1,9 @@ +{ + "title": "just a test!", + "description": "foobar", + "destination": { + "branch": { + "name": "master" + } + } +}