├── .rspec ├── Rakefile ├── spec ├── fixtures │ └── file │ │ ├── template_1.erb │ │ ├── template_2.erb │ │ ├── pr_1_files.yml │ │ ├── pr_1.yml │ │ ├── pr_4.yml │ │ ├── pr_3.yml │ │ ├── pr_6.yml │ │ └── pr_7.yml ├── support │ ├── capture_support.rb │ └── file_fixture_support.rb ├── spec_helper.rb └── git │ └── pr │ ├── release │ ├── pull_request_spec.rb │ └── cli_spec.rb │ └── release_spec.rb ├── Gemfile ├── .gitignore ├── exe └── git-pr-release ├── lib └── git │ └── pr │ ├── release.rb │ └── release │ ├── dummy_pull_request.rb │ ├── pull_request.rb │ ├── util.rb │ └── cli.rb ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── LICENSE ├── git-pr-release.gemspec ├── CHANGELOG.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | -------------------------------------------------------------------------------- /spec/fixtures/file/template_1.erb: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | -------------------------------------------------------------------------------- /spec/fixtures/file/template_2.erb: -------------------------------------------------------------------------------- 1 | c 2 | d 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | *.gem 3 | Gemfile.lock 4 | /.rspec_status 5 | -------------------------------------------------------------------------------- /exe/git-pr-release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "git/pr/release" 4 | 5 | Git::Pr::Release::CLI.start 6 | -------------------------------------------------------------------------------- /lib/git/pr/release.rb: -------------------------------------------------------------------------------- 1 | require "git/pr/release/util" 2 | require "git/pr/release/pull_request" 3 | require "git/pr/release/dummy_pull_request" 4 | require "git/pr/release/cli" 5 | 6 | module Git 7 | module Pr 8 | module Release 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/capture_support.rb: -------------------------------------------------------------------------------- 1 | module CaptureSupport 2 | def capture(stream) 3 | begin 4 | eval "$#{stream} = StringIO.new" 5 | yield 6 | result = eval("$#{stream}").string 7 | ensure 8 | eval("$#{stream} = #{stream.upcase}") 9 | end 10 | 11 | result 12 | end 13 | end 14 | 15 | RSpec.configure do |config| 16 | config.include CaptureSupport 17 | end 18 | -------------------------------------------------------------------------------- /lib/git/pr/release/dummy_pull_request.rb: -------------------------------------------------------------------------------- 1 | module Git 2 | module Pr 3 | module Release 4 | class DummyPullRequest 5 | def initialize 6 | # nop 7 | end 8 | 9 | def to_checklist_item 10 | "- [ ] #??? THIS IS DUMMY PULL REQUEST" 11 | end 12 | 13 | def html_link 14 | 'http://github.com/DUMMY/DUMMY/issues/?' 15 | end 16 | 17 | def to_hash 18 | { :data => {} } 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release new version to RubyGems.org 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | runs-on: ubuntu-slim 9 | 10 | permissions: 11 | contents: write 12 | id-token: write 13 | 14 | steps: 15 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 16 | with: 17 | persist-credentials: false 18 | 19 | - uses: ruby/setup-ruby@ac793fdd38cc468a4dd57246fa9d0e868aba9085 # v1.270.0 20 | with: 21 | bundler-cache: true 22 | ruby-version: ruby 23 | 24 | - uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 25 | -------------------------------------------------------------------------------- /spec/support/file_fixture_support.rb: -------------------------------------------------------------------------------- 1 | module FileFixtureSupport 2 | def load_yaml(fixture_name) 3 | if YAML.respond_to?(:unsafe_load_file) 4 | YAML.unsafe_load_file(file_fixture(fixture_name)) 5 | else 6 | YAML.load_file(file_fixture(fixture_name)) 7 | end 8 | end 9 | 10 | def file_fixture(fixture_name) 11 | file_fixture_path = RSpec.configuration.file_fixture_path 12 | path = Pathname.new(File.join(file_fixture_path, fixture_name)) 13 | 14 | if path.exist? 15 | path 16 | else 17 | msg = "the directory '%s' does not contain a file named '%s'" 18 | raise ArgumentError, msg % [file_fixture_path, fixture_name] 19 | end 20 | end 21 | end 22 | 23 | RSpec.configure do |config| 24 | config.add_setting :file_fixture_path 25 | config.file_fixture_path = File.expand_path("../fixtures/file", __dir__) 26 | config.include FileFixtureSupport 27 | end 28 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_1_files.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :sha: 2bcc5ec915521df50825ad08118665c66ad072be 3 | :filename: bin/git-pr-release 4 | :status: modified 5 | :additions: 1 6 | :deletions: 1 7 | :changes: 2 8 | :blob_url: https://github.com/motemen/git-pr-release/blob/bbcd2a04ef394e91be44c24e93e52fdbca944060/bin/git-pr-release 9 | :raw_url: https://github.com/motemen/git-pr-release/raw/bbcd2a04ef394e91be44c24e93e52fdbca944060/bin/git-pr-release 10 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/bin/git-pr-release?ref=bbcd2a04ef394e91be44c24e93e52fdbca944060 11 | :patch: "@@ -87,7 +87,7 @@ def build_pr_title_and_body(prs)\n \n template = DEFAULT_PR_TEMPLATE\n 12 | \n- if path = git_config('pr-release.template')\n+ if path = git_config('template')\n 13 | \ template_path = File.join(git('rev-parse', '--show-toplevel').first.chomp, 14 | path)\n template = File.read(template_path)\n end" 15 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "timecop" 2 | require "yaml" 3 | require "git/pr/release" 4 | require "webmock/rspec" 5 | 6 | RSpec.configure do |config| 7 | config.expect_with :rspec do |expectations| 8 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 9 | end 10 | config.mock_with :rspec do |mocks| 11 | mocks.verify_partial_doubles = true 12 | end 13 | config.shared_context_metadata_behavior = :apply_to_host_groups 14 | 15 | config.filter_run_when_matching :focus 16 | config.example_status_persistence_file_path = ".rspec_status" 17 | config.disable_monkey_patching! 18 | config.warnings = true 19 | config.order = :random 20 | Kernel.srand config.seed 21 | 22 | config.around(:each) do |example| 23 | begin 24 | ENV['TZ'], old = 'Asia/Tokyo', ENV['TZ'] 25 | example.run 26 | ensure 27 | ENV['TZ'] = old 28 | end 29 | end 30 | 31 | config.after do 32 | Timecop.return 33 | end 34 | 35 | # Trigger Autoload 36 | Octokit::Client 37 | end 38 | 39 | Dir[File.expand_path("support/**/*.rb", __dir__)].each {|f| require f } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 motemen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /git-pr-release.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "git-pr-release" 7 | spec.version = '2.5.0' 8 | spec.authors = ["motemen"] 9 | spec.email = ["motemen@gmail.com"] 10 | spec.summary = 'Creates a release pull request' 11 | spec.description = 'git-pr-release creates a pull request which summarizes feature branches that are to be released into production' 12 | spec.homepage = 'https://github.com/x-motemen/git-pr-release' 13 | 14 | spec.files = `git ls-files`.split($/) 15 | spec.bindir = "exe" 16 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ["lib"] 19 | 20 | spec.required_ruby_version = '>= 3.0' 21 | 22 | spec.add_dependency 'octokit' 23 | spec.add_dependency 'highline' 24 | spec.add_dependency 'colorize' 25 | spec.add_dependency 'diff-lcs' 26 | 27 | spec.add_development_dependency 'rake' 28 | spec.add_development_dependency 'rspec' 29 | spec.add_development_dependency 'timecop' 30 | spec.add_development_dependency 'webmock' 31 | 32 | spec.license = 'MIT' 33 | end 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | - cron: "0 0 * * *" # JST 09:00 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: 18 | - "3.0" 19 | - "3.1" 20 | - "3.2" 21 | - "3.3" 22 | - "3.4" 23 | - head 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby }} 30 | bundler-cache: true 31 | - name: Cache vendor/bundle 32 | uses: actions/cache@v4 33 | with: 34 | path: vendor/bundle 35 | key: v1-gem-${{ runner.os }}-${{ matrix.ruby }}-${{ github.sha }} 36 | restore-keys: | 37 | v1-gem-${{ runner.os }}-${{ matrix.ruby }}- 38 | continue-on-error: ${{ matrix.allow_failures == 'true' }} 39 | - name: bundle update 40 | run: | 41 | set -xe 42 | bundle config path vendor/bundle 43 | bundle update --jobs $(nproc) --retry 3 44 | continue-on-error: ${{ matrix.allow_failures == 'true' }} 45 | - name: Run ${{ matrix.test_framework }} 46 | run: | 47 | set -xe 48 | bundle exec rspec 49 | continue-on-error: ${{ matrix.allow_failures == 'true' }} 50 | -------------------------------------------------------------------------------- /spec/git/pr/release/pull_request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Git::Pr::Release::PullRequest do 4 | let(:pr_data) { 5 | agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| 6 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 7 | conn.adapter :test, Faraday::Adapter::Test::Stubs.new 8 | end 9 | Sawyer::Resource.new(agent, load_yaml(pr_data_yaml)) 10 | } 11 | subject { described_class.new(pr_data) } 12 | 13 | describe "#mention" do 14 | context "with multiple assignees" do 15 | let(:pr_data_yaml) { "pr_7.yml" } 16 | 17 | it "returns all assignees as mentions" do 18 | expect(subject.mention).to eq " @hakobe @toshimaru @Copilot" 19 | end 20 | end 21 | 22 | context "with no assignees" do 23 | let(:pr_data_yaml) { "pr_3.yml" } 24 | 25 | it "returns PR creator as mention" do 26 | expect(subject.mention).to eq " @hakobe" 27 | end 28 | end 29 | 30 | context "when mention_type is 'author'" do 31 | let(:pr_data_yaml) { "pr_7.yml" } 32 | 33 | before do 34 | allow(Git::Pr::Release::PullRequest).to receive(:mention_type).and_return('author') 35 | end 36 | 37 | it "returns PR author regardless of assignees" do 38 | expect(subject.mention).to eq " @hakobe" 39 | end 40 | end 41 | end 42 | 43 | describe "#to_checklist_item" do 44 | context "with multiple assignees" do 45 | let(:pr_data_yaml) { "pr_7.yml" } 46 | 47 | it "includes all assignees in the checklist item" do 48 | expect(subject.to_checklist_item).to eq "- [ ] #7 @hakobe @toshimaru @Copilot" 49 | end 50 | 51 | it "includes title when print_title is true" do 52 | expect(subject.to_checklist_item(true)).to eq "- [ ] #7 Support multiple assignees @hakobe @toshimaru @Copilot" 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/git/pr/release/pull_request.rb: -------------------------------------------------------------------------------- 1 | module Git 2 | module Pr 3 | module Release 4 | class PullRequest 5 | include Git::Pr::Release::Util 6 | extend Git::Pr::Release::Util 7 | attr_reader :pr 8 | 9 | def initialize(pr) 10 | @pr = pr 11 | end 12 | 13 | def to_checklist_item(print_title = false) 14 | if print_title 15 | "- [ ] ##{pr.number} #{pr.title}" + mention 16 | else 17 | "- [ ] ##{pr.number}" + mention 18 | end 19 | end 20 | 21 | def html_link 22 | pr.rels[:html].href 23 | end 24 | 25 | def to_hash 26 | { :data => @pr.to_hash } 27 | end 28 | 29 | def mention 30 | mention = target_user_login_names.map { |login_name| "@#{login_name}" }.join(" ") 31 | mention.empty? ? "" : " #{mention}" 32 | end 33 | 34 | def target_user_login_names 35 | case PullRequest.mention_type 36 | when 'author' 37 | pr.user ? [pr.user.login] : [] 38 | else 39 | if pr.assignees&.any? && pr.assignees.length > 1 40 | pr.assignees.map(&:login) 41 | elsif pr.assignee 42 | [pr.assignee.login] 43 | elsif pr.user 44 | [pr.user.login] 45 | else 46 | [] 47 | end 48 | end 49 | end 50 | 51 | def self.mention_type 52 | @mention_type ||= (ENV.fetch('GIT_PR_RELEASE_MENTION') { git_config('mention') } || 'default') 53 | end 54 | 55 | def method_missing(name, *args, &block) 56 | @pr.public_send name, *args, &block 57 | end 58 | 59 | def respond_to_missing?(name, include_private = false) 60 | @pr.respond_to?(name, include_private) 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # git-pr-release 2 | 3 | ## v2.5.0 (2025-12-16) 4 | 5 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.4.0...v2.5.0) 6 | 7 | * (#116) Make it backward compatible for Ruby 3.0 (@moznion) 8 | 9 | ## v2.4.0 (2025-12-16) 10 | 11 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.3.0...v2.4.0) 12 | 13 | * (#112) Add release workflow using trusted publishing (@ohbarye) 14 | * (#110) Support `pr-release.assign-pr-author` and `pr-release.request-pr-author-review` options (@moznion) 15 | 16 | ## v2.3.0 (2025-12-15) 17 | 18 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.2.0...v2.3.0) 19 | 20 | * (#108) Support multiple PR assignees (@toshimaru) 21 | * (#107) Update CI matrix (@onk) 22 | * (#106) Use setup-ruby instead of container-based setup (@onk) 23 | * (#100) Allow big bang release PR (@secobaka) 24 | * (#95) Bump actions/cache from v2 to v4 (@ytkg) 25 | * (#94) Bump actions/checkout from v2 to v4 (@ytkg) 26 | * (#91) Test against Ruby v3.1 and Ruby v3.2 (@toshimaru) 27 | * (#90) updated docs to include --squashed flag (@petercorrea) 28 | 29 | ## v2.2.0 (2022-08-17) 30 | 31 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.1.2...v2.2.0) 32 | 33 | * (#88) unshallow if a shallow repository (@Songmu) 34 | * (#89) Add overwrite-description option (@onk) 35 | 36 | ## v2.1.2 (2022-07-29) 37 | 38 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.1.1...v2.1.2) 39 | 40 | * (#87) delegate to `@pr` when `method_missing` in PullRequest (@Songmu) 41 | 42 | ## v2.1.1 (2022-03-09) 43 | 44 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.1.0...v2.1.1) 45 | 46 | * (#81) fix forbidden git config name (#80) (@mtgto) 47 | 48 | ## v2.1.0 (2022-03-03) 49 | 50 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v2.0.0...v2.1.0) 51 | 52 | * (#75) reduce GitHub search API calls when the squashed option is specified (@Songmu) 53 | * (#76) use bulk issue search to reduce API calls (@Songmu) 54 | * (#77) Add option "ssl_no_verify" to skip verifying ssl certificate (@mtgto) 55 | * (#78) add an argument to to_checklist_item to print pr title (@mtgto) 56 | 57 | ## v2.0.0 (2022-02-17) 58 | 59 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.9.0...v2.0.0) 60 | 61 | * (#69) remove duplicated PR entries at squash (@Yuki-Inoue) 62 | * (#70) [Spec] Fix spec for build_pr_title_and_body (@yutailang0119) 63 | * (#71) Introduce CI (@ohbarye) 64 | * (#73) (#74) Use `YAML.unsafe_load_file` instead of `YAML.load_file` (@ohbarye) 65 | 66 | ## v1.9.0 (2021-08-04) 67 | 68 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.8.0...v1.9.0) 69 | 70 | * (#68) Add nil check for release\_pr.body (@w1mvy) 71 | 72 | ## v1.8.0 (2021-06-24) 73 | 74 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.7.0...v1.8.0) 75 | 76 | * (#66) Exclude titles from checklist items (@nhosoya) 77 | 78 | ## v1.7.0 (2021-05-24) 79 | 80 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.6.0...v1.7.0) 81 | 82 | * (#64) fix wrong pr number due to sleep (@mpon) 83 | 84 | ## v1.6.0 (2021-05-15) 85 | 86 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.5.0...v1.6.0) 87 | 88 | * (#63) Sort merged_pull_request_numbers numerically by default (@yutailang0119) 89 | 90 | ## v1.5.0 (2021-04-02) 91 | 92 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.4.0...v1.5.0) 93 | 94 | * (#60, #61) Get issue number from GitHub API for squashed PR (@yuuan) 95 | * (#58) Make stable test (@kachick) 96 | * (#55) Suppress warning for ERB (@ohbarye) 97 | * (#50) support `GIT_PR_RELEASE_MENTION` environment variable (@dabutvin) 98 | * (#49) Transfer repository to x-motemen organization (@onk) 99 | 100 | ## v1.4.0 (2020-02-22) 101 | 102 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.3.0...v1.4.0) 103 | 104 | * (#48) List PR API needs head user or head organization and branch name (@sasasin) 105 | 106 | ## v1.3.0 (2020-02-19) 107 | 108 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.2.0...v1.3.0) 109 | 110 | * (#47) Fix Errno::ENOENT when finding the specified template (@onk) 111 | * (#45) Fix "warning: instance variable @xxx not initialized" (@onk) 112 | 113 | ## v1.2.0 (2020-02-07) 114 | 115 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.1.0...v1.2.0) 116 | 117 | * (#44) Use API option when detecting existing release PR (@onk) 118 | * (#41, #42) Refactor (@onk) 119 | - Some local variables are removed. This will break if you have customized the template ERB. 120 | 121 | ## v1.1.0 (2020-01-02) 122 | 123 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.0.1...v1.1.0) 124 | 125 | * (#38) Fetch changed files as many as possible (@shibayu36) 126 | 127 | ## v1.0.1 (2019-12-17) 128 | 129 | [full changelog](https://github.com/x-motemen/git-pr-release/compare/v1.0.0...v1.0.1) 130 | 131 | * (#37) Fix NameError (@onk) 132 | 133 | ## v1.0.0 (2019-12-14) 134 | 135 | * (#35) Do not define classes and methods at the top level (@onk) 136 | * (#30) Extract logic from bin/git-pr-release (@banyan) 137 | 138 | ... 139 | 140 | ## v0.0.1 (2014-01-21) 141 | 142 | Initial Release 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-pr-release Gem Version 2 | 3 | Creates a "release pull request", whose body consists of features list or 4 | pull requests that are to be released into production. It's especially useful for QA and 5 | pre-release checks. `git-pr-release` automatically collect pull requests 6 | merged into master branch and generates the content of the release 7 | pull request. 8 | 9 | ![Screenshot](https://cloud.githubusercontent.com/assets/113420/3147184/61bf2eec-ea53-11e3-835b-50d63ed11b39.png) 10 | 11 | Suitable for branching strategy like below (similar to git-flow): 12 | 13 | - Feature branches are first merged into "staging" (or release, development) 14 | branch. 15 | - Then the staging branch is merged into "production" branch, which is for 16 | production release. 17 | 18 | ## Usage 19 | 20 | `git-pr-release`: default 21 | 22 | `git-pr-release --squashed`: include PRs containing squashed commits. 23 | 24 | `git-pr-release --overwrite-description`: generate a fresh PR description. 25 | 26 | `git-pr-release -n` | `git-pr-release --dry-run`: perform a dry run; does not update PR. 27 | 28 | `git-pr-release --no-fetch`: Do not fetch from remote repo before determining target PRs (CI friendly). 29 | 30 | `git-pr-release --json`: Show data of target PRs in JSON format. 31 | 32 | ## Configuration 33 | 34 | All configuration are taken using `git config`. You can write these variables 35 | in file `.git-pr-release` (instead of `.git/config` or `~/.gitconfig`) to share project-wise configuration to other 36 | collaborators. 37 | 38 | ### `pr-release.token` 39 | 40 | Token for GitHub API. 41 | 42 | If not set, you will be asked to input username/password for one time only, 43 | and this configuration variable will be stored. 44 | 45 | You can specify this value by `GIT_PR_RELEASE_TOKEN` environment variable. 46 | 47 | ### `pr-release.branch.production` 48 | 49 | The branch name that is deployed in production environment. 50 | 51 | You can specify this value by `GIT_PR_RELEASE_BRANCH_PRODUCTION` environment variable. 52 | 53 | Default value: `master`. 54 | 55 | ### `pr-release.branch.staging` 56 | 57 | The branch name that the feature branches are merged into and is going to be 58 | merged into the "production" branch. 59 | 60 | You can specify this value by `GIT_PR_RELEASE_BRANCH_STAGING` environment variable. 61 | 62 | Default value: `staging`. 63 | 64 | ### `pr-release.template` 65 | 66 | The template file path (relative to the workidir top) for pull requests 67 | created. Its first line is used for the PR title, the rest for the body. This 68 | is an ERB template. 69 | 70 | You can specify this value by `GIT_PR_RELEASE_TEMPLATE` environment variable. 71 | 72 | If not specified, the content below is used as the template (embedded in the code): 73 | 74 | ```erb 75 | Release <%= Time.now %> 76 | <% pull_requests.each do |pr| -%> 77 | <%= pr.to_checklist_item %> 78 | <% end -%> 79 | ``` 80 | 81 | ### `pr-release.labels` 82 | 83 | The labels list for adding to pull requests created. 84 | This value should be comma-separated strings. 85 | 86 | You can specify this value by `GIT_PR_RELEASE_LABELS` environment variable. 87 | 88 | If not specified, any labels will not be added for PRs. 89 | 90 | ### `pr-release.mention` 91 | 92 | The name that is listed next to each PR title. 93 | Accepted values: `author` 94 | 95 | You can specify this value by `GIT_PR_RELEASE_MENTION` environment variable. 96 | 97 | If not specified, the mention will be the PR assignee 98 | 99 | ### `pr-release.assign-pr-author` 100 | 101 | Whether to assign the related users of the merged pull requests to the release pull request. 102 | Accepted values: `true` | `false` 103 | 104 | You can specify this value by `GIT_PR_RELEASE_ASSIGN_PR_AUTHOR` environment variable. 105 | 106 | If not specified, no assignees will be added. 107 | 108 | Note: The user selection follows the same logic as `pr-release.mention`. If `mention` is set to `author`, the PR author will be assigned. Otherwise, the PR assignee will be used (falling back to the author if no assignee exists). 109 | 110 | ### `pr-release.request-pr-author-review` 111 | 112 | Whether to request review from the related users of the merged pull requests on the release pull request. 113 | Accepted values: `true` | `false` 114 | 115 | You can specify this value by `GIT_PR_RELEASE_REQUEST_PR_AUTHOR_REVIEW` environment variable. 116 | 117 | If not specified, no review requests will be made. 118 | 119 | Note: The user selection follows the same logic as `pr-release.mention`. If `mention` is set to `author`, the PR author will be requested for review. Otherwise, the PR assignee will be used (falling back to the author if no assignee exists). 120 | 121 | ### `pr-release.ssl-no-verify` 122 | 123 | Whether to verify SSL certificate or not. 124 | Accepted values: `true` | `false` 125 | 126 | This option might be useful when self-hosted GitHub enterprise server is using self-signed certificate. 127 | 128 | You can specify this value by `GIT_PR_RELEASE_SSL_NO_VERIFY` to `1`. 129 | 130 | If not specified, verify SSL certificate always. 131 | 132 | ## Errors and exit statuses 133 | 134 | ### No pull requests to be released 135 | 136 | exit status is 1. 137 | 138 | ## Author 139 | 140 | motemen , original in-house version written by @hitode909. 141 | -------------------------------------------------------------------------------- /spec/git/pr/release_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Git::Pr::Release do 2 | include Git::Pr::Release::Util 3 | before do 4 | Timecop.freeze(Time.parse("2019-02-20 22:58:35")) 5 | 6 | @stubs = Faraday::Adapter::Test::Stubs.new 7 | @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| 8 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 9 | conn.adapter :test, @stubs 10 | end 11 | @release_pr = Sawyer::Resource.new(@agent, load_yaml("pr_1.yml")) 12 | @merged_prs = [ 13 | Sawyer::Resource.new(@agent, load_yaml("pr_3.yml")), 14 | Sawyer::Resource.new(@agent, load_yaml("pr_6.yml")), 15 | ] 16 | @changed_files = [ 17 | Sawyer::Resource.new(@agent, load_yaml("pr_1_files.yml")), 18 | ] 19 | end 20 | 21 | describe "#build_pr_title_and_body" do 22 | context "without template_path" do 23 | it { 24 | pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files, nil) 25 | expect(pr_title).to eq "Release 2019-02-20 22:58:35 +0900" 26 | expect(new_body).to eq <<~MARKDOWN 27 | - [ ] #3 @hakobe 28 | - [ ] #6 @ninjinkun 29 | MARKDOWN 30 | } 31 | end 32 | 33 | context "with template_path" do 34 | it { 35 | pr_title, new_body = build_pr_title_and_body(@release_pr, @merged_prs, @changed_files, "spec/fixtures/file/template_1.erb") 36 | expect(pr_title).to eq "a" 37 | expect(new_body).to eq <<~MARKDOWN 38 | b 39 | MARKDOWN 40 | } 41 | end 42 | end 43 | 44 | describe "#dump_result_as_json" do 45 | it { 46 | output = capture(:stdout) { dump_result_as_json(@release_pr, @merged_prs, @changed_files) } 47 | parsed_output = JSON.parse(output) 48 | 49 | expect(parsed_output.keys).to eq %w[release_pull_request merged_pull_requests changed_files] 50 | expect(parsed_output["release_pull_request"]).to eq({ "data" => JSON.parse(@release_pr.to_hash.to_json) }) 51 | expect(parsed_output["merged_pull_requests"]).to eq @merged_prs.map {|e| JSON.parse(Git::Pr::Release::PullRequest.new(e).to_hash.to_json) } 52 | expect(parsed_output["changed_files"]).to eq @changed_files.map {|e| JSON.parse(e.to_hash.to_json) } 53 | } 54 | end 55 | 56 | describe "#merge_pr_body" do 57 | context "new pr added" do 58 | it { 59 | actual = merge_pr_body(<<~OLD_BODY, <<~NEW_BODY) 60 | - [x] #3 Provides a creating release pull-request object for template @hakobe 61 | - [ ] #6 Support two factor auth @ninjinkun 62 | OLD_BODY 63 | - [ ] #3 Provides a creating release pull-request object for template @hakobe 64 | - [ ] #4 use user who create PR if there is no assignee @hakobe 65 | - [ ] #6 Support two factor auth @ninjinkun 66 | NEW_BODY 67 | 68 | expect(actual).to eq <<~MARKDOWN.chomp 69 | - [x] #3 Provides a creating release pull-request object for template @hakobe 70 | - [ ] #4 use user who create PR if there is no assignee @hakobe 71 | - [ ] #6 Support two factor auth @ninjinkun 72 | MARKDOWN 73 | } 74 | end 75 | context "new pr added and keeping task status" do 76 | it { 77 | actual = merge_pr_body(<<~OLD_BODY, <<~NEW_BODY) 78 | - [x] #4 use user who create PR if there is no assignee @hakobe 79 | - [x] #6 Support two factor auth @ninjinkun 80 | OLD_BODY 81 | - [ ] #3 Provides a creating release pull-request object for template @hakobe 82 | - [ ] #4 use user who create PR if there is no assignee @hakobe 83 | - [ ] #6 Support two factor auth @ninjinkun 84 | NEW_BODY 85 | 86 | expect(actual).to eq <<~MARKDOWN.chomp 87 | - [ ] #3 Provides a creating release pull-request object for template @hakobe 88 | - [x] #4 use user who create PR if there is no assignee @hakobe 89 | - [x] #6 Support two factor auth @ninjinkun 90 | MARKDOWN 91 | } 92 | end 93 | context "new pr added when the same numbers are included in a forward match" do 94 | it { 95 | actual = merge_pr_body(<<~OLD_BODY, <<~NEW_BODY) 96 | - [x] #3 Provides a creating release pull-request object for template @hakobe 97 | - [ ] #6 Support two factor auth @ninjinkun 98 | OLD_BODY 99 | - [ ] #3 Provides a creating release pull-request object for template @hakobe 100 | - [ ] #4 use user who create PR if there is no assignee @hakobe 101 | - [ ] #6 Support two factor auth @ninjinkun 102 | - [ ] #30 Extract logic from bin/git-pr-release @banyan 103 | NEW_BODY 104 | 105 | expect(actual).to eq <<~MARKDOWN.chomp 106 | - [x] #3 Provides a creating release pull-request object for template @hakobe 107 | - [ ] #4 use user who create PR if there is no assignee @hakobe 108 | - [ ] #6 Support two factor auth @ninjinkun 109 | - [ ] #30 Extract logic from bin/git-pr-release @banyan 110 | MARKDOWN 111 | } 112 | end 113 | end 114 | 115 | describe "#host_and_repository_and_scheme" do 116 | it { 117 | expect(self).to receive(:git).with(:config, "remote.origin.url") { ["https://github.com/motemen/git-pr-release\n"] } 118 | expect(host_and_repository_and_scheme).to eq [nil, "motemen/git-pr-release", "https"] 119 | } 120 | it { 121 | expect(self).to receive(:git).with(:config, "remote.origin.url") { ["ssh://git@github.com/motemen/git-pr-release.git\n"] } 122 | expect(host_and_repository_and_scheme).to eq [nil, "motemen/git-pr-release", "https"] 123 | } 124 | it { 125 | expect(self).to receive(:git).with(:config, "remote.origin.url") { ["http://ghe.example.com/motemen/git-pr-release\n"] } 126 | expect(host_and_repository_and_scheme).to eq ["ghe.example.com", "motemen/git-pr-release", "http"] 127 | } 128 | it { 129 | expect(self).to receive(:git).with(:config, "remote.origin.url") { ["ssh://git@ghe.example.com/motemen/git-pr-release.git\n"] } 130 | expect(host_and_repository_and_scheme).to eq ["ghe.example.com", "motemen/git-pr-release", "https"] 131 | } 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /lib/git/pr/release/util.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'uri' 3 | require 'open3' 4 | require 'json' 5 | require 'colorize' 6 | require 'diff/lcs' 7 | 8 | module Git 9 | module Pr 10 | module Release 11 | module Util 12 | def host_and_repository_and_scheme 13 | @host_and_repository_and_scheme ||= begin 14 | remote = git(:config, 'remote.origin.url').first.chomp 15 | unless %r(^\w+://) === remote 16 | remote = "ssh://#{remote.sub(':', '/')}" 17 | end 18 | 19 | remote_url = URI.parse(remote) 20 | repository = remote_url.path.sub(%r(^/), '').sub(/\.git$/, '') 21 | 22 | host = remote_url.host == 'github.com' ? nil : remote_url.host 23 | [ host, repository, remote_url.scheme === 'http' ? 'http' : 'https' ] 24 | end 25 | end 26 | 27 | def say(message, level) 28 | color = case level 29 | when :trace 30 | return unless ENV['DEBUG'] 31 | nil 32 | when :debug 33 | return unless ENV['DEBUG'] 34 | :blue 35 | when :info 36 | :green 37 | when :notice 38 | :yellow 39 | when :warn 40 | :magenta 41 | when :error 42 | :red 43 | end 44 | 45 | STDERR.puts message.colorize(color) 46 | end 47 | 48 | def git(*command) 49 | command = [ 'git', *command.map(&:to_s) ] 50 | say "Executing `#{command.join(' ')}`", :trace 51 | out, status = Open3.capture2(*command) 52 | unless status.success? 53 | raise "Executing `#{command.join(' ')}` failed: #{status}" 54 | end 55 | out.each_line 56 | end 57 | 58 | def git_config(key) 59 | host, _ = host_and_repository_and_scheme() 60 | 61 | plain_key = [ 'pr-release', key ].join('.') 62 | host_aware_key = [ 'pr-release', host, key ].compact.join('.') 63 | 64 | begin 65 | git(:config, '-f', '.git-pr-release', plain_key).first.chomp 66 | rescue 67 | git(:config, host_aware_key).first.chomp rescue nil 68 | end 69 | end 70 | 71 | def git_config_set(key, value) 72 | host, _ = host_and_repository_and_scheme() 73 | host_aware_key = [ 'pr-release', host, key ].compact.join('.') 74 | 75 | git :config, '--global', host_aware_key, value 76 | end 77 | 78 | # First line will be the title of the PR 79 | DEFAULT_PR_TEMPLATE = < 81 | <% pull_requests.each do |pr| -%> 82 | <%= pr.to_checklist_item %> 83 | <% end -%> 84 | ERB 85 | 86 | def build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) 87 | release_pull_request = target_pull_request = release_pr ? PullRequest.new(release_pr) : DummyPullRequest.new 88 | merged_pull_requests = pull_requests = merged_prs.map { |pr| PullRequest.new(pr) } 89 | 90 | template = if template_path 91 | template_fullpath = File.join(git('rev-parse', '--show-toplevel').first.chomp, template_path) 92 | File.read(template_fullpath) 93 | else 94 | DEFAULT_PR_TEMPLATE 95 | end 96 | 97 | erb = if RUBY_VERSION >= '2.6' 98 | ERB.new template, trim_mode: '-' 99 | else 100 | ERB.new template, nil, '-' 101 | end 102 | content = erb.result binding 103 | content.split(/\n/, 2) 104 | end 105 | 106 | def dump_result_as_json(release_pr, merged_prs, changed_files) 107 | puts( { 108 | :release_pull_request => (release_pr ? PullRequest.new(release_pr) : DummyPullRequest.new).to_hash, 109 | :merged_pull_requests => merged_prs.map { |pr| PullRequest.new(pr).to_hash }, 110 | :changed_files => changed_files.map { |file| file.to_hash } 111 | }.to_json ) 112 | end 113 | 114 | def merge_pr_body(old_body, new_body) 115 | # Try to take over checklist statuses 116 | pr_body_lines = [] 117 | 118 | check_status = {} 119 | old_body.split(/\r?\n/).each { |line| 120 | line.match(/^- \[(?[ x])\] #(?\d+)/) { |m| 121 | say "Found pull-request checkbox \##{m[:issue_number]} is #{m[:check_value]}.", :trace 122 | check_status[m[:issue_number]] = m[:check_value] 123 | } 124 | } 125 | old_body_unchecked = old_body.gsub /^- \[[ x]\] \#(\d+)/, '- [ ] #\1' 126 | 127 | Diff::LCS.traverse_balanced(old_body_unchecked.split(/\r?\n/), new_body.split(/\r?\n/)) do |event| 128 | say "diff: #{event.inspect}", :trace 129 | action, old, new = *event 130 | old_nr, old_line = *old 131 | new_nr, new_line = *new 132 | 133 | case action 134 | when '=', '+' 135 | say "Use line as is: #{new_line}", :trace 136 | pr_body_lines << new_line 137 | when '-' 138 | say "Use old line: #{old_line}", :trace 139 | pr_body_lines << old_line 140 | when '!' 141 | if [ old_line, new_line ].all? { |line| /^- \[ \]/ === line } 142 | say "Found checklist diff; use old one: #{old_line}", :trace 143 | pr_body_lines << old_line 144 | else 145 | # not a checklist diff, use both line 146 | say "Use line as is: #{old_line}", :trace 147 | pr_body_lines << old_line 148 | 149 | say "Use line as is: #{new_line}", :trace 150 | pr_body_lines << new_line 151 | end 152 | else 153 | say "Unknown diff event: #{event}", :warn 154 | end 155 | end 156 | 157 | merged_body = pr_body_lines.join("\n") 158 | check_status.each { |issue_number, check_value| 159 | say "Update pull-request checkbox \##{issue_number} to #{check_value}.", :trace 160 | merged_body.gsub! /^- \[ \] \##{issue_number}\b/, "- [#{check_value}] \##{issue_number}" 161 | } 162 | 163 | merged_body 164 | end 165 | 166 | def obtain_token! 167 | token = ENV.fetch('GIT_PR_RELEASE_TOKEN') { git_config('token') } 168 | 169 | unless token 170 | require 'highline/import' 171 | STDERR.puts 'Could not obtain GitHub API token.' 172 | STDERR.puts 'Trying to generate token...' 173 | 174 | username = ask('username? ') { |q| q.default = ENV['USER'] } 175 | password = ask('password? (not saved) ') { |q| q.echo = '*' } 176 | 177 | temporary_client = Octokit::Client.new :login => username, :password => password 178 | 179 | auth = request_authorization(temporary_client, nil) 180 | 181 | token = auth.token 182 | git_config_set 'token', token 183 | end 184 | 185 | token 186 | end 187 | 188 | def request_authorization(client, two_factor_code) 189 | params = { :scopes => [ 'public_repo', 'repo' ], :note => 'git-pr-release' } 190 | params[:headers] = { "X-GitHub-OTP" => two_factor_code} if two_factor_code 191 | 192 | auth = nil 193 | begin 194 | auth = client.create_authorization(params) 195 | rescue Octokit::OneTimePasswordRequired 196 | two_factor_code = ask('two-factor authentication code? ') 197 | auth = request_authorization(client, two_factor_code) 198 | end 199 | 200 | auth 201 | end 202 | end 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/git/pr/release/cli.rb: -------------------------------------------------------------------------------- 1 | require 'octokit' 2 | require 'optparse' 3 | 4 | module Git 5 | module Pr 6 | module Release 7 | class CLI 8 | include Git::Pr::Release::Util 9 | attr_reader :repository, :production_branch, :staging_branch, :template_path, :labels, :assign_pr_author, :request_pr_author_review 10 | 11 | def self.start 12 | result = self.new.start 13 | exit result 14 | end 15 | 16 | def initialize 17 | @dry_run = false 18 | @json = false 19 | @no_fetch = false 20 | @squashed = false 21 | end 22 | 23 | def start 24 | OptionParser.new do |opts| 25 | opts.on('-n', '--dry-run', 'Do not create/update a PR. Just prints out') do |v| 26 | @dry_run = v 27 | end 28 | opts.on('--json', 'Show data of target PRs in JSON format') do |v| 29 | @json = v 30 | end 31 | opts.on('--no-fetch', 'Do not fetch from remote repo before determining target PRs (CI friendly)') do |v| 32 | @no_fetch = v 33 | end 34 | opts.on('--squashed', 'Handle squash merged PRs') do |v| 35 | @squashed = v 36 | end 37 | opts.on('--overwrite-description', 'Force overwrite PR description') do |v| 38 | @overwrite_description = v 39 | end 40 | end.parse! 41 | 42 | ### Set up configuration 43 | configure 44 | 45 | ### Fetch merged PRs 46 | merged_prs = fetch_merged_prs 47 | if merged_prs.empty? 48 | say 'No pull requests to be released', :error 49 | return 1 50 | end 51 | 52 | ### Create a release PR 53 | create_release_pr(merged_prs) 54 | return 0 55 | end 56 | 57 | def client 58 | @client ||= Octokit::Client.new :access_token => obtain_token! 59 | end 60 | 61 | def configure 62 | host, @repository, scheme = host_and_repository_and_scheme 63 | 64 | if host 65 | if scheme == 'https' # GitHub Enterprise 66 | ssl_no_verify = %w[true 1].include? ENV.fetch('GIT_PR_RELEASE_SSL_NO_VERIFY') { git_config('ssl-no-verify') } 67 | if ssl_no_verify 68 | OpenSSL::SSL.const_set :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE 69 | end 70 | end 71 | 72 | Octokit.configure do |c| 73 | c.api_endpoint = "#{scheme}://#{host}/api/v3" 74 | c.web_endpoint = "#{scheme}://#{host}/" 75 | end 76 | end 77 | 78 | @production_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_PRODUCTION') { git_config('branch.production') } || 'master' 79 | @staging_branch = ENV.fetch('GIT_PR_RELEASE_BRANCH_STAGING') { git_config('branch.staging') } || 'staging' 80 | @template_path = ENV.fetch('GIT_PR_RELEASE_TEMPLATE') { git_config('template') } 81 | 82 | _labels = ENV.fetch('GIT_PR_RELEASE_LABELS') { git_config('labels') } 83 | @labels = _labels && _labels.split(/\s*,\s*/) || [] 84 | 85 | _assign_pr_author = ENV.fetch('GIT_PR_RELEASE_ASSIGN_PR_AUTHOR') { git_config('assign-pr-author') } 86 | @assign_pr_author = %w[true 1].include?(_assign_pr_author) 87 | 88 | _request_pr_author_review = ENV.fetch('GIT_PR_RELEASE_REQUEST_PR_AUTHOR_REVIEW') { git_config('request-pr-author-review') } 89 | @request_pr_author_review = %w[true 1].include?(_request_pr_author_review) 90 | 91 | say "Repository: #{repository}", :debug 92 | say "Production branch: #{production_branch}", :debug 93 | say "Staging branch: #{staging_branch}", :debug 94 | say "Template path: #{template_path}", :debug 95 | say "Labels #{labels}", :debug 96 | say "Assign PR author: #{assign_pr_author}", :debug 97 | say "Request PR author review: #{request_pr_author_review}", :debug 98 | end 99 | 100 | def fetch_merged_prs 101 | bool = git(:'rev-parse', '--is-shallow-repository').first.chomp 102 | if bool == 'true' 103 | git(:fetch, '--unshallow') 104 | end 105 | git :remote, 'update', 'origin' unless @no_fetch 106 | 107 | merged_pull_request_numbers = fetch_merged_pr_numbers_from_git_remote 108 | if @squashed 109 | merged_pull_request_numbers.concat(fetch_squash_merged_pr_numbers_from_github) 110 | end 111 | 112 | merged_prs = merged_pull_request_numbers.uniq.sort.map do |nr| 113 | pr = client.pull_request repository, nr 114 | say "To be released: ##{pr.number} #{pr.title}", :notice 115 | pr 116 | end 117 | 118 | merged_prs 119 | end 120 | 121 | def fetch_merged_pr_numbers_from_git_remote 122 | merged_feature_head_sha1s = git(:log, '--merges', '--pretty=format:%P', "origin/#{production_branch}..origin/#{staging_branch}").map do |line| 123 | main_sha1, feature_sha1 = line.chomp.split /\s+/ 124 | feature_sha1 125 | end 126 | 127 | git('ls-remote', 'origin', 'refs/pull/*/head').map do |line| 128 | sha1, ref = line.chomp.split /\s+/ 129 | 130 | if merged_feature_head_sha1s.include? sha1 131 | if %r<^refs/pull/(\d+)/head$>.match ref 132 | pr_number = $1.to_i 133 | 134 | if git('merge-base', sha1, "origin/#{production_branch}").first.chomp == sha1 135 | say "##{pr_number} (#{sha1}) is already merged into #{production_branch}", :debug 136 | else 137 | pr_number 138 | end 139 | else 140 | say "Bad pull request head ref format: #{ref}", :warn 141 | nil 142 | end 143 | end 144 | end.compact 145 | end 146 | 147 | def search_issue_numbers(query) 148 | sleep 1 149 | say "search issues with query:#{query}", :debug 150 | # Fortunately, we don't need to take care of the page count in response, because 151 | # the default value of per_page is 30 and we can't specify more than 30 commits due to 152 | # the length limit specification of the query string. 153 | client.search_issues("#{query}")[:items].map(&:number) 154 | end 155 | 156 | def fetch_squash_merged_pr_numbers_from_github 157 | # When "--abbrev" is specified, the length of the each line of the stdout isn't fixed. 158 | # It is just a minimum length, and if the commit cannot be uniquely identified with 159 | # that length, a longer commit hash will be displayed. 160 | # We specify this option to minimize the length of the query string, but we use 161 | # "--abbrev=7" because the SHA syntax of the search API requires a string of at 162 | # least 7 characters. 163 | # ref. https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests#search-by-commit-sha 164 | # This is done because there is a length limit on the API query string, and we want 165 | # to create a string with the minimum possible length. 166 | shas = git(:log, '--pretty=format:%h', "--abbrev=7", "--no-merges", "--first-parent", 167 | "origin/#{production_branch}..origin/#{staging_branch}").map(&:chomp) 168 | 169 | pr_nums = [] 170 | query_base = "repo:#{repository} is:pr is:closed" 171 | query = query_base 172 | # Make bulk requests with multiple SHAs of the maximum possible length. 173 | # If multiple SHAs are specified, the issue search API will treat it like an OR search, 174 | # and all the pull requests will be searched. 175 | # This is difficult to read from the current documentation, but that is the current 176 | # behavior and GitHub support has responded that this is the spec. 177 | shas.each do |sha| 178 | # Longer than 256 characters are not supported in the query. 179 | # ref. https://docs.github.com/en/rest/reference/search#limitations-on-query-length 180 | if query.length + 1 + sha.length >= 256 181 | pr_nums.concat(search_issue_numbers(query)) 182 | query = query_base 183 | end 184 | query += " " + sha 185 | end 186 | if query != query_base 187 | pr_nums.concat(search_issue_numbers(query)) 188 | end 189 | pr_nums 190 | end 191 | 192 | def create_release_pr(merged_prs) 193 | found_release_pr = detect_existing_release_pr 194 | create_mode = found_release_pr.nil? 195 | 196 | if create_mode 197 | if @dry_run 198 | release_pr = nil 199 | changed_files = [] 200 | else 201 | release_pr = prepare_release_pr 202 | changed_files = pull_request_files(release_pr) 203 | end 204 | else 205 | release_pr = found_release_pr 206 | changed_files = pull_request_files(release_pr) 207 | end 208 | 209 | pr_title, pr_body = if @overwrite_description 210 | build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) 211 | else 212 | build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files) 213 | end 214 | 215 | if @dry_run 216 | say 'Dry-run. Not updating PR', :info 217 | say pr_title, :notice 218 | say pr_body, :notice 219 | dump_result_as_json( release_pr, merged_prs, changed_files ) if @json 220 | return 221 | end 222 | 223 | update_release_pr(release_pr, pr_title, pr_body, merged_prs) 224 | 225 | say "#{create_mode ? 'Created' : 'Updated'} pull request: #{release_pr.rels[:html].href}", :notice 226 | dump_result_as_json( release_pr, merged_prs, changed_files ) if @json 227 | end 228 | 229 | def detect_existing_release_pr 230 | say 'Searching for existing release pull requests...', :info 231 | user=repository.split("/")[0] 232 | client.pull_requests(repository, head: "#{user}:#{staging_branch}", base: production_branch).first 233 | end 234 | 235 | def prepare_release_pr 236 | client.create_pull_request( 237 | repository, production_branch, staging_branch, 'Preparing release pull request...', '' 238 | ) 239 | end 240 | 241 | def build_and_merge_pr_title_and_body(release_pr, merged_prs, changed_files) 242 | # release_pr is nil when dry_run && create_mode 243 | old_body = (release_pr && release_pr.body != nil) ? release_pr.body : "" 244 | pr_title, new_body = build_pr_title_and_body(release_pr, merged_prs, changed_files, template_path) 245 | 246 | [pr_title, merge_pr_body(old_body, new_body)] 247 | end 248 | 249 | def update_release_pr(release_pr, pr_title, pr_body, merged_prs) 250 | say 'Pull request body:', :debug 251 | say pr_body, :debug 252 | 253 | client.update_pull_request( 254 | repository, release_pr.number, :title => pr_title, :body => pr_body 255 | ) 256 | 257 | unless labels.empty? 258 | client.add_labels_to_an_issue( 259 | repository, release_pr.number, labels 260 | ) 261 | end 262 | 263 | if assign_pr_author 264 | assignees = merged_prs.map { |pr| PullRequest.new(pr).target_user_login_names }.flatten.compact.uniq 265 | unless assignees.empty? 266 | client.add_assignees( 267 | repository, release_pr.number, assignees 268 | ) 269 | end 270 | end 271 | 272 | if request_pr_author_review 273 | reviewers = merged_prs.map { |pr| PullRequest.new(pr).target_user_login_names }.flatten.compact.uniq 274 | unless reviewers.empty? 275 | client.request_pull_request_review( 276 | repository, release_pr.number, reviewers: reviewers 277 | ) 278 | end 279 | end 280 | end 281 | 282 | # Fetch PR files of specified pull_request 283 | def pull_request_files(pull_request) 284 | return [] if pull_request.nil? 285 | 286 | # Fetch files as many as possible 287 | client.auto_paginate = true 288 | files = client.pull_request_files repository, pull_request.number 289 | client.auto_paginate = false 290 | return files 291 | end 292 | end 293 | end 294 | end 295 | end 296 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :url: https://api.github.com/repos/motemen/git-pr-release/pulls/1 3 | :id: 13194147 4 | :node_id: MDExOlB1bGxSZXF1ZXN0MTMxOTQxNDc= 5 | :html_url: https://github.com/motemen/git-pr-release/pull/1 6 | :diff_url: https://github.com/motemen/git-pr-release/pull/1.diff 7 | :patch_url: https://github.com/motemen/git-pr-release/pull/1.patch 8 | :issue_url: https://api.github.com/repos/motemen/git-pr-release/issues/1 9 | :number: 1 10 | :state: closed 11 | :locked: false 12 | :title: No need to append key prefix for template config 13 | :user: 14 | :login: hakobe 15 | :id: 6882 16 | :node_id: MDQ6VXNlcjY4ODI= 17 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 18 | :gravatar_id: '' 19 | :url: https://api.github.com/users/hakobe 20 | :html_url: https://github.com/hakobe 21 | :followers_url: https://api.github.com/users/hakobe/followers 22 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 23 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 24 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 25 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 26 | :organizations_url: https://api.github.com/users/hakobe/orgs 27 | :repos_url: https://api.github.com/users/hakobe/repos 28 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 29 | :received_events_url: https://api.github.com/users/hakobe/received_events 30 | :type: User 31 | :site_admin: false 32 | :body: "`git_config('pr-release.template')` loads config value from key `pr-release.pr-release.template`.\n" 33 | :created_at: 2014-03-05 02:48:51.000000000 Z 34 | :updated_at: 2014-07-10 03:13:44.000000000 Z 35 | :closed_at: 2014-03-05 02:59:52.000000000 Z 36 | :merged_at: 2014-03-05 02:59:52.000000000 Z 37 | :merge_commit_sha: d40cebeaa7cd69c5eabb6e76de7314891d9cd743 38 | :assignee: 39 | :assignees: [] 40 | :requested_reviewers: [] 41 | :requested_teams: [] 42 | :labels: [] 43 | :milestone: 44 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/pulls/1/commits 45 | :review_comments_url: https://api.github.com/repos/motemen/git-pr-release/pulls/1/comments 46 | :review_comment_url: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 47 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/issues/1/comments 48 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/bbcd2a04ef394e91be44c24e93e52fdbca944060 49 | :head: 50 | :label: hakobe:fix/template-config-key 51 | :ref: fix/template-config-key 52 | :sha: bbcd2a04ef394e91be44c24e93e52fdbca944060 53 | :user: 54 | :login: hakobe 55 | :id: 6882 56 | :node_id: MDQ6VXNlcjY4ODI= 57 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 58 | :gravatar_id: '' 59 | :url: https://api.github.com/users/hakobe 60 | :html_url: https://github.com/hakobe 61 | :followers_url: https://api.github.com/users/hakobe/followers 62 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 63 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 64 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 65 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 66 | :organizations_url: https://api.github.com/users/hakobe/orgs 67 | :repos_url: https://api.github.com/users/hakobe/repos 68 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 69 | :received_events_url: https://api.github.com/users/hakobe/received_events 70 | :type: User 71 | :site_admin: false 72 | :repo: 73 | :id: 17425170 74 | :node_id: MDEwOlJlcG9zaXRvcnkxNzQyNTE3MA== 75 | :name: git-pr-release 76 | :full_name: hakobe/git-pr-release 77 | :private: false 78 | :owner: 79 | :login: hakobe 80 | :id: 6882 81 | :node_id: MDQ6VXNlcjY4ODI= 82 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 83 | :gravatar_id: '' 84 | :url: https://api.github.com/users/hakobe 85 | :html_url: https://github.com/hakobe 86 | :followers_url: https://api.github.com/users/hakobe/followers 87 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 88 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 89 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 90 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 91 | :organizations_url: https://api.github.com/users/hakobe/orgs 92 | :repos_url: https://api.github.com/users/hakobe/repos 93 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 94 | :received_events_url: https://api.github.com/users/hakobe/received_events 95 | :type: User 96 | :site_admin: false 97 | :html_url: https://github.com/hakobe/git-pr-release 98 | :description: Creates a pull request which summarizes feature branches that are 99 | to be released into production 100 | :fork: true 101 | :url: https://api.github.com/repos/hakobe/git-pr-release 102 | :forks_url: https://api.github.com/repos/hakobe/git-pr-release/forks 103 | :keys_url: https://api.github.com/repos/hakobe/git-pr-release/keys{/key_id} 104 | :collaborators_url: https://api.github.com/repos/hakobe/git-pr-release/collaborators{/collaborator} 105 | :teams_url: https://api.github.com/repos/hakobe/git-pr-release/teams 106 | :hooks_url: https://api.github.com/repos/hakobe/git-pr-release/hooks 107 | :issue_events_url: https://api.github.com/repos/hakobe/git-pr-release/issues/events{/number} 108 | :events_url: https://api.github.com/repos/hakobe/git-pr-release/events 109 | :assignees_url: https://api.github.com/repos/hakobe/git-pr-release/assignees{/user} 110 | :branches_url: https://api.github.com/repos/hakobe/git-pr-release/branches{/branch} 111 | :tags_url: https://api.github.com/repos/hakobe/git-pr-release/tags 112 | :blobs_url: https://api.github.com/repos/hakobe/git-pr-release/git/blobs{/sha} 113 | :git_tags_url: https://api.github.com/repos/hakobe/git-pr-release/git/tags{/sha} 114 | :git_refs_url: https://api.github.com/repos/hakobe/git-pr-release/git/refs{/sha} 115 | :trees_url: https://api.github.com/repos/hakobe/git-pr-release/git/trees{/sha} 116 | :statuses_url: https://api.github.com/repos/hakobe/git-pr-release/statuses/{sha} 117 | :languages_url: https://api.github.com/repos/hakobe/git-pr-release/languages 118 | :stargazers_url: https://api.github.com/repos/hakobe/git-pr-release/stargazers 119 | :contributors_url: https://api.github.com/repos/hakobe/git-pr-release/contributors 120 | :subscribers_url: https://api.github.com/repos/hakobe/git-pr-release/subscribers 121 | :subscription_url: https://api.github.com/repos/hakobe/git-pr-release/subscription 122 | :commits_url: https://api.github.com/repos/hakobe/git-pr-release/commits{/sha} 123 | :git_commits_url: https://api.github.com/repos/hakobe/git-pr-release/git/commits{/sha} 124 | :comments_url: https://api.github.com/repos/hakobe/git-pr-release/comments{/number} 125 | :issue_comment_url: https://api.github.com/repos/hakobe/git-pr-release/issues/comments{/number} 126 | :contents_url: https://api.github.com/repos/hakobe/git-pr-release/contents/{+path} 127 | :compare_url: https://api.github.com/repos/hakobe/git-pr-release/compare/{base}...{head} 128 | :merges_url: https://api.github.com/repos/hakobe/git-pr-release/merges 129 | :archive_url: https://api.github.com/repos/hakobe/git-pr-release/{archive_format}{/ref} 130 | :downloads_url: https://api.github.com/repos/hakobe/git-pr-release/downloads 131 | :issues_url: https://api.github.com/repos/hakobe/git-pr-release/issues{/number} 132 | :pulls_url: https://api.github.com/repos/hakobe/git-pr-release/pulls{/number} 133 | :milestones_url: https://api.github.com/repos/hakobe/git-pr-release/milestones{/number} 134 | :notifications_url: https://api.github.com/repos/hakobe/git-pr-release/notifications{?since,all,participating} 135 | :labels_url: https://api.github.com/repos/hakobe/git-pr-release/labels{/name} 136 | :releases_url: https://api.github.com/repos/hakobe/git-pr-release/releases{/id} 137 | :deployments_url: https://api.github.com/repos/hakobe/git-pr-release/deployments 138 | :created_at: 2014-03-05 02:20:59.000000000 Z 139 | :updated_at: 2014-05-13 01:42:58.000000000 Z 140 | :pushed_at: 2014-05-12 11:42:34.000000000 Z 141 | :git_url: git://github.com/hakobe/git-pr-release.git 142 | :ssh_url: git@github.com:hakobe/git-pr-release.git 143 | :clone_url: https://github.com/hakobe/git-pr-release.git 144 | :svn_url: https://github.com/hakobe/git-pr-release 145 | :homepage: https://rubygems.org/gems/git-pr-release 146 | :size: 236 147 | :stargazers_count: 0 148 | :watchers_count: 0 149 | :language: Ruby 150 | :has_issues: false 151 | :has_projects: true 152 | :has_downloads: true 153 | :has_wiki: true 154 | :has_pages: false 155 | :forks_count: 0 156 | :mirror_url: 157 | :archived: false 158 | :open_issues_count: 0 159 | :license: 160 | :forks: 0 161 | :open_issues: 0 162 | :watchers: 0 163 | :default_branch: master 164 | :base: 165 | :label: motemen:master 166 | :ref: master 167 | :sha: adcd1d55438db0116f2583368466f1bf101ac0d0 168 | :user: 169 | :login: motemen 170 | :id: 8465 171 | :node_id: MDQ6VXNlcjg0NjU= 172 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 173 | :gravatar_id: '' 174 | :url: https://api.github.com/users/motemen 175 | :html_url: https://github.com/motemen 176 | :followers_url: https://api.github.com/users/motemen/followers 177 | :following_url: https://api.github.com/users/motemen/following{/other_user} 178 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 179 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 180 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 181 | :organizations_url: https://api.github.com/users/motemen/orgs 182 | :repos_url: https://api.github.com/users/motemen/repos 183 | :events_url: https://api.github.com/users/motemen/events{/privacy} 184 | :received_events_url: https://api.github.com/users/motemen/received_events 185 | :type: User 186 | :site_admin: false 187 | :repo: 188 | :id: 15468156 189 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 190 | :name: git-pr-release 191 | :full_name: motemen/git-pr-release 192 | :private: false 193 | :owner: 194 | :login: motemen 195 | :id: 8465 196 | :node_id: MDQ6VXNlcjg0NjU= 197 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 198 | :gravatar_id: '' 199 | :url: https://api.github.com/users/motemen 200 | :html_url: https://github.com/motemen 201 | :followers_url: https://api.github.com/users/motemen/followers 202 | :following_url: https://api.github.com/users/motemen/following{/other_user} 203 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 204 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 205 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 206 | :organizations_url: https://api.github.com/users/motemen/orgs 207 | :repos_url: https://api.github.com/users/motemen/repos 208 | :events_url: https://api.github.com/users/motemen/events{/privacy} 209 | :received_events_url: https://api.github.com/users/motemen/received_events 210 | :type: User 211 | :site_admin: false 212 | :html_url: https://github.com/motemen/git-pr-release 213 | :description: Release pull request generator 214 | :fork: false 215 | :url: https://api.github.com/repos/motemen/git-pr-release 216 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 217 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 218 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 219 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 220 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 221 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 222 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 223 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 224 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 225 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 226 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 227 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 228 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 229 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 230 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 231 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 232 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 233 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 234 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 235 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 236 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 237 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 238 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 239 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 240 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 241 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 242 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 243 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 244 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 245 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 246 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 247 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 248 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 249 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 250 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 251 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 252 | :created_at: 2013-12-27 06:35:52.000000000 Z 253 | :updated_at: 2019-02-18 01:40:52.000000000 Z 254 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 255 | :git_url: git://github.com/motemen/git-pr-release.git 256 | :ssh_url: git@github.com:motemen/git-pr-release.git 257 | :clone_url: https://github.com/motemen/git-pr-release.git 258 | :svn_url: https://github.com/motemen/git-pr-release 259 | :homepage: https://rubygems.org/gems/git-pr-release 260 | :size: 62 261 | :stargazers_count: 359 262 | :watchers_count: 359 263 | :language: Ruby 264 | :has_issues: true 265 | :has_projects: true 266 | :has_downloads: true 267 | :has_wiki: true 268 | :has_pages: false 269 | :forks_count: 28 270 | :mirror_url: 271 | :archived: false 272 | :open_issues_count: 6 273 | :license: 274 | :key: mit 275 | :name: MIT License 276 | :spdx_id: MIT 277 | :url: https://api.github.com/licenses/mit 278 | :node_id: MDc6TGljZW5zZTEz 279 | :forks: 28 280 | :open_issues: 6 281 | :watchers: 359 282 | :default_branch: master 283 | :_links: 284 | :self: 285 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/1 286 | :html: 287 | :href: https://github.com/motemen/git-pr-release/pull/1 288 | :issue: 289 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/1 290 | :comments: 291 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/1/comments 292 | :review_comments: 293 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/1/comments 294 | :review_comment: 295 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 296 | :commits: 297 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/1/commits 298 | :statuses: 299 | :href: https://api.github.com/repos/motemen/git-pr-release/statuses/bbcd2a04ef394e91be44c24e93e52fdbca944060 300 | :author_association: COLLABORATOR 301 | :merged: true 302 | :mergeable: 303 | :rebaseable: 304 | :mergeable_state: unknown 305 | :merged_by: 306 | :login: motemen 307 | :id: 8465 308 | :node_id: MDQ6VXNlcjg0NjU= 309 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 310 | :gravatar_id: '' 311 | :url: https://api.github.com/users/motemen 312 | :html_url: https://github.com/motemen 313 | :followers_url: https://api.github.com/users/motemen/followers 314 | :following_url: https://api.github.com/users/motemen/following{/other_user} 315 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 316 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 317 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 318 | :organizations_url: https://api.github.com/users/motemen/orgs 319 | :repos_url: https://api.github.com/users/motemen/repos 320 | :events_url: https://api.github.com/users/motemen/events{/privacy} 321 | :received_events_url: https://api.github.com/users/motemen/received_events 322 | :type: User 323 | :site_admin: false 324 | :comments: 2 325 | :review_comments: 0 326 | :maintainer_can_modify: false 327 | :commits: 1 328 | :additions: 1 329 | :deletions: 1 330 | :changed_files: 1 331 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_4.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :url: https://api.github.com/repos/motemen/git-pr-release/pulls/4 3 | :id: 13997501 4 | :node_id: MDExOlB1bGxSZXF1ZXN0MTM5OTc1MDE= 5 | :html_url: https://github.com/motemen/git-pr-release/pull/4 6 | :diff_url: https://github.com/motemen/git-pr-release/pull/4.diff 7 | :patch_url: https://github.com/motemen/git-pr-release/pull/4.patch 8 | :issue_url: https://api.github.com/repos/motemen/git-pr-release/issues/4 9 | :number: 4 10 | :state: closed 11 | :locked: false 12 | :title: use user who create PR if there is no assignee 13 | :user: 14 | :login: hakobe 15 | :id: 6882 16 | :node_id: MDQ6VXNlcjY4ODI= 17 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 18 | :gravatar_id: '' 19 | :url: https://api.github.com/users/hakobe 20 | :html_url: https://github.com/hakobe 21 | :followers_url: https://api.github.com/users/hakobe/followers 22 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 23 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 24 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 25 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 26 | :organizations_url: https://api.github.com/users/hakobe/orgs 27 | :repos_url: https://api.github.com/users/hakobe/repos 28 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 29 | :received_events_url: https://api.github.com/users/hakobe/received_events 30 | :type: User 31 | :site_admin: false 32 | :body: '' 33 | :created_at: 2014-03-26 10:17:02.000000000 Z 34 | :updated_at: 2014-07-13 11:45:52.000000000 Z 35 | :closed_at: 2014-03-26 10:49:03.000000000 Z 36 | :merged_at: 2014-03-26 10:49:03.000000000 Z 37 | :merge_commit_sha: 19dd4309bfac0b0358e7e87f48a007ef54634140 38 | :assignee: 39 | :assignees: [] 40 | :requested_reviewers: [] 41 | :requested_teams: [] 42 | :labels: [] 43 | :milestone: 44 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/pulls/4/commits 45 | :review_comments_url: https://api.github.com/repos/motemen/git-pr-release/pulls/4/comments 46 | :review_comment_url: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 47 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/issues/4/comments 48 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/42bd43b80c973c8f348df3521745201be05bf194 49 | :head: 50 | :label: motemen:use-pr-cretor 51 | :ref: use-pr-cretor 52 | :sha: 42bd43b80c973c8f348df3521745201be05bf194 53 | :user: 54 | :login: motemen 55 | :id: 8465 56 | :node_id: MDQ6VXNlcjg0NjU= 57 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 58 | :gravatar_id: '' 59 | :url: https://api.github.com/users/motemen 60 | :html_url: https://github.com/motemen 61 | :followers_url: https://api.github.com/users/motemen/followers 62 | :following_url: https://api.github.com/users/motemen/following{/other_user} 63 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 64 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 65 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 66 | :organizations_url: https://api.github.com/users/motemen/orgs 67 | :repos_url: https://api.github.com/users/motemen/repos 68 | :events_url: https://api.github.com/users/motemen/events{/privacy} 69 | :received_events_url: https://api.github.com/users/motemen/received_events 70 | :type: User 71 | :site_admin: false 72 | :repo: 73 | :id: 15468156 74 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 75 | :name: git-pr-release 76 | :full_name: motemen/git-pr-release 77 | :private: false 78 | :owner: 79 | :login: motemen 80 | :id: 8465 81 | :node_id: MDQ6VXNlcjg0NjU= 82 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 83 | :gravatar_id: '' 84 | :url: https://api.github.com/users/motemen 85 | :html_url: https://github.com/motemen 86 | :followers_url: https://api.github.com/users/motemen/followers 87 | :following_url: https://api.github.com/users/motemen/following{/other_user} 88 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 89 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 90 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 91 | :organizations_url: https://api.github.com/users/motemen/orgs 92 | :repos_url: https://api.github.com/users/motemen/repos 93 | :events_url: https://api.github.com/users/motemen/events{/privacy} 94 | :received_events_url: https://api.github.com/users/motemen/received_events 95 | :type: User 96 | :site_admin: false 97 | :html_url: https://github.com/motemen/git-pr-release 98 | :description: Release pull request generator 99 | :fork: false 100 | :url: https://api.github.com/repos/motemen/git-pr-release 101 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 102 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 103 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 104 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 105 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 106 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 107 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 108 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 109 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 110 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 111 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 112 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 113 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 114 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 115 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 116 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 117 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 118 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 119 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 120 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 121 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 122 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 123 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 124 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 125 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 126 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 127 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 128 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 129 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 130 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 131 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 132 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 133 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 134 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 135 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 136 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 137 | :created_at: 2013-12-27 06:35:52.000000000 Z 138 | :updated_at: 2019-02-18 01:40:52.000000000 Z 139 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 140 | :git_url: git://github.com/motemen/git-pr-release.git 141 | :ssh_url: git@github.com:motemen/git-pr-release.git 142 | :clone_url: https://github.com/motemen/git-pr-release.git 143 | :svn_url: https://github.com/motemen/git-pr-release 144 | :homepage: https://rubygems.org/gems/git-pr-release 145 | :size: 62 146 | :stargazers_count: 359 147 | :watchers_count: 359 148 | :language: Ruby 149 | :has_issues: true 150 | :has_projects: true 151 | :has_downloads: true 152 | :has_wiki: true 153 | :has_pages: false 154 | :forks_count: 28 155 | :mirror_url: 156 | :archived: false 157 | :open_issues_count: 6 158 | :license: 159 | :key: mit 160 | :name: MIT License 161 | :spdx_id: MIT 162 | :url: https://api.github.com/licenses/mit 163 | :node_id: MDc6TGljZW5zZTEz 164 | :forks: 28 165 | :open_issues: 6 166 | :watchers: 359 167 | :default_branch: master 168 | :base: 169 | :label: motemen:master 170 | :ref: master 171 | :sha: ad694b9c2b868e8801f9209f0ad5dd5458c49854 172 | :user: 173 | :login: motemen 174 | :id: 8465 175 | :node_id: MDQ6VXNlcjg0NjU= 176 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 177 | :gravatar_id: '' 178 | :url: https://api.github.com/users/motemen 179 | :html_url: https://github.com/motemen 180 | :followers_url: https://api.github.com/users/motemen/followers 181 | :following_url: https://api.github.com/users/motemen/following{/other_user} 182 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 183 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 184 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 185 | :organizations_url: https://api.github.com/users/motemen/orgs 186 | :repos_url: https://api.github.com/users/motemen/repos 187 | :events_url: https://api.github.com/users/motemen/events{/privacy} 188 | :received_events_url: https://api.github.com/users/motemen/received_events 189 | :type: User 190 | :site_admin: false 191 | :repo: 192 | :id: 15468156 193 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 194 | :name: git-pr-release 195 | :full_name: motemen/git-pr-release 196 | :private: false 197 | :owner: 198 | :login: motemen 199 | :id: 8465 200 | :node_id: MDQ6VXNlcjg0NjU= 201 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 202 | :gravatar_id: '' 203 | :url: https://api.github.com/users/motemen 204 | :html_url: https://github.com/motemen 205 | :followers_url: https://api.github.com/users/motemen/followers 206 | :following_url: https://api.github.com/users/motemen/following{/other_user} 207 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 208 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 209 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 210 | :organizations_url: https://api.github.com/users/motemen/orgs 211 | :repos_url: https://api.github.com/users/motemen/repos 212 | :events_url: https://api.github.com/users/motemen/events{/privacy} 213 | :received_events_url: https://api.github.com/users/motemen/received_events 214 | :type: User 215 | :site_admin: false 216 | :html_url: https://github.com/motemen/git-pr-release 217 | :description: Release pull request generator 218 | :fork: false 219 | :url: https://api.github.com/repos/motemen/git-pr-release 220 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 221 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 222 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 223 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 224 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 225 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 226 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 227 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 228 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 229 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 230 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 231 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 232 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 233 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 234 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 235 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 236 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 237 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 238 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 239 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 240 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 241 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 242 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 243 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 244 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 245 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 246 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 247 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 248 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 249 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 250 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 251 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 252 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 253 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 254 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 255 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 256 | :created_at: 2013-12-27 06:35:52.000000000 Z 257 | :updated_at: 2019-02-18 01:40:52.000000000 Z 258 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 259 | :git_url: git://github.com/motemen/git-pr-release.git 260 | :ssh_url: git@github.com:motemen/git-pr-release.git 261 | :clone_url: https://github.com/motemen/git-pr-release.git 262 | :svn_url: https://github.com/motemen/git-pr-release 263 | :homepage: https://rubygems.org/gems/git-pr-release 264 | :size: 62 265 | :stargazers_count: 359 266 | :watchers_count: 359 267 | :language: Ruby 268 | :has_issues: true 269 | :has_projects: true 270 | :has_downloads: true 271 | :has_wiki: true 272 | :has_pages: false 273 | :forks_count: 28 274 | :mirror_url: 275 | :archived: false 276 | :open_issues_count: 6 277 | :license: 278 | :key: mit 279 | :name: MIT License 280 | :spdx_id: MIT 281 | :url: https://api.github.com/licenses/mit 282 | :node_id: MDc6TGljZW5zZTEz 283 | :forks: 28 284 | :open_issues: 6 285 | :watchers: 359 286 | :default_branch: master 287 | :_links: 288 | :self: 289 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/4 290 | :html: 291 | :href: https://github.com/motemen/git-pr-release/pull/4 292 | :issue: 293 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/4 294 | :comments: 295 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/4/comments 296 | :review_comments: 297 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/4/comments 298 | :review_comment: 299 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 300 | :commits: 301 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/4/commits 302 | :statuses: 303 | :href: https://api.github.com/repos/motemen/git-pr-release/statuses/42bd43b80c973c8f348df3521745201be05bf194 304 | :author_association: COLLABORATOR 305 | :merged: true 306 | :mergeable: 307 | :rebaseable: 308 | :mergeable_state: unknown 309 | :merged_by: 310 | :login: motemen 311 | :id: 8465 312 | :node_id: MDQ6VXNlcjg0NjU= 313 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 314 | :gravatar_id: '' 315 | :url: https://api.github.com/users/motemen 316 | :html_url: https://github.com/motemen 317 | :followers_url: https://api.github.com/users/motemen/followers 318 | :following_url: https://api.github.com/users/motemen/following{/other_user} 319 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 320 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 321 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 322 | :organizations_url: https://api.github.com/users/motemen/orgs 323 | :repos_url: https://api.github.com/users/motemen/repos 324 | :events_url: https://api.github.com/users/motemen/events{/privacy} 325 | :received_events_url: https://api.github.com/users/motemen/received_events 326 | :type: User 327 | :site_admin: false 328 | :comments: 1 329 | :review_comments: 0 330 | :maintainer_can_modify: false 331 | :commits: 1 332 | :additions: 2 333 | :deletions: 1 334 | :changed_files: 1 335 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :url: https://api.github.com/repos/motemen/git-pr-release/pulls/3 3 | :id: 13365285 4 | :node_id: MDExOlB1bGxSZXF1ZXN0MTMzNjUyODU= 5 | :html_url: https://github.com/motemen/git-pr-release/pull/3 6 | :diff_url: https://github.com/motemen/git-pr-release/pull/3.diff 7 | :patch_url: https://github.com/motemen/git-pr-release/pull/3.patch 8 | :issue_url: https://api.github.com/repos/motemen/git-pr-release/issues/3 9 | :number: 3 10 | :state: closed 11 | :locked: false 12 | :title: Provides a creating release pull-request object for template 13 | :user: 14 | :login: hakobe 15 | :id: 6882 16 | :node_id: MDQ6VXNlcjY4ODI= 17 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 18 | :gravatar_id: '' 19 | :url: https://api.github.com/users/hakobe 20 | :html_url: https://github.com/hakobe 21 | :followers_url: https://api.github.com/users/hakobe/followers 22 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 23 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 24 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 25 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 26 | :organizations_url: https://api.github.com/users/hakobe/orgs 27 | :repos_url: https://api.github.com/users/hakobe/repos 28 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 29 | :received_events_url: https://api.github.com/users/hakobe/received_events 30 | :type: User 31 | :site_admin: false 32 | :body: "### Problem\n\nCreating release pull-request info(e.g. url of pull-request) 33 | is not available in template. \n### Solution\n\nEnsures a release pull-request has 34 | already been created before generating a pull-request body.\n" 35 | :created_at: 2014-03-10 10:25:33.000000000 Z 36 | :updated_at: 2014-07-03 00:16:55.000000000 Z 37 | :closed_at: 2014-03-13 11:42:18.000000000 Z 38 | :merged_at: 2014-03-13 11:42:18.000000000 Z 39 | :merge_commit_sha: 2ff5c670d9bebde490a6651f185ecba6cb09b48d 40 | :assignee: 41 | :assignees: [] 42 | :requested_reviewers: [] 43 | :requested_teams: [] 44 | :labels: [] 45 | :milestone: 46 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/pulls/3/commits 47 | :review_comments_url: https://api.github.com/repos/motemen/git-pr-release/pulls/3/comments 48 | :review_comment_url: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 49 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/issues/3/comments 50 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/5c977a1827387ac7b7a85c7b827ee119165f1823 51 | :head: 52 | :label: hakobe:prepare-pr-for-template 53 | :ref: prepare-pr-for-template 54 | :sha: 5c977a1827387ac7b7a85c7b827ee119165f1823 55 | :user: 56 | :login: hakobe 57 | :id: 6882 58 | :node_id: MDQ6VXNlcjY4ODI= 59 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 60 | :gravatar_id: '' 61 | :url: https://api.github.com/users/hakobe 62 | :html_url: https://github.com/hakobe 63 | :followers_url: https://api.github.com/users/hakobe/followers 64 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 65 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 66 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 67 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 68 | :organizations_url: https://api.github.com/users/hakobe/orgs 69 | :repos_url: https://api.github.com/users/hakobe/repos 70 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 71 | :received_events_url: https://api.github.com/users/hakobe/received_events 72 | :type: User 73 | :site_admin: false 74 | :repo: 75 | :id: 17425170 76 | :node_id: MDEwOlJlcG9zaXRvcnkxNzQyNTE3MA== 77 | :name: git-pr-release 78 | :full_name: hakobe/git-pr-release 79 | :private: false 80 | :owner: 81 | :login: hakobe 82 | :id: 6882 83 | :node_id: MDQ6VXNlcjY4ODI= 84 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 85 | :gravatar_id: '' 86 | :url: https://api.github.com/users/hakobe 87 | :html_url: https://github.com/hakobe 88 | :followers_url: https://api.github.com/users/hakobe/followers 89 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 90 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 91 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 92 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 93 | :organizations_url: https://api.github.com/users/hakobe/orgs 94 | :repos_url: https://api.github.com/users/hakobe/repos 95 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 96 | :received_events_url: https://api.github.com/users/hakobe/received_events 97 | :type: User 98 | :site_admin: false 99 | :html_url: https://github.com/hakobe/git-pr-release 100 | :description: Creates a pull request which summarizes feature branches that are 101 | to be released into production 102 | :fork: true 103 | :url: https://api.github.com/repos/hakobe/git-pr-release 104 | :forks_url: https://api.github.com/repos/hakobe/git-pr-release/forks 105 | :keys_url: https://api.github.com/repos/hakobe/git-pr-release/keys{/key_id} 106 | :collaborators_url: https://api.github.com/repos/hakobe/git-pr-release/collaborators{/collaborator} 107 | :teams_url: https://api.github.com/repos/hakobe/git-pr-release/teams 108 | :hooks_url: https://api.github.com/repos/hakobe/git-pr-release/hooks 109 | :issue_events_url: https://api.github.com/repos/hakobe/git-pr-release/issues/events{/number} 110 | :events_url: https://api.github.com/repos/hakobe/git-pr-release/events 111 | :assignees_url: https://api.github.com/repos/hakobe/git-pr-release/assignees{/user} 112 | :branches_url: https://api.github.com/repos/hakobe/git-pr-release/branches{/branch} 113 | :tags_url: https://api.github.com/repos/hakobe/git-pr-release/tags 114 | :blobs_url: https://api.github.com/repos/hakobe/git-pr-release/git/blobs{/sha} 115 | :git_tags_url: https://api.github.com/repos/hakobe/git-pr-release/git/tags{/sha} 116 | :git_refs_url: https://api.github.com/repos/hakobe/git-pr-release/git/refs{/sha} 117 | :trees_url: https://api.github.com/repos/hakobe/git-pr-release/git/trees{/sha} 118 | :statuses_url: https://api.github.com/repos/hakobe/git-pr-release/statuses/{sha} 119 | :languages_url: https://api.github.com/repos/hakobe/git-pr-release/languages 120 | :stargazers_url: https://api.github.com/repos/hakobe/git-pr-release/stargazers 121 | :contributors_url: https://api.github.com/repos/hakobe/git-pr-release/contributors 122 | :subscribers_url: https://api.github.com/repos/hakobe/git-pr-release/subscribers 123 | :subscription_url: https://api.github.com/repos/hakobe/git-pr-release/subscription 124 | :commits_url: https://api.github.com/repos/hakobe/git-pr-release/commits{/sha} 125 | :git_commits_url: https://api.github.com/repos/hakobe/git-pr-release/git/commits{/sha} 126 | :comments_url: https://api.github.com/repos/hakobe/git-pr-release/comments{/number} 127 | :issue_comment_url: https://api.github.com/repos/hakobe/git-pr-release/issues/comments{/number} 128 | :contents_url: https://api.github.com/repos/hakobe/git-pr-release/contents/{+path} 129 | :compare_url: https://api.github.com/repos/hakobe/git-pr-release/compare/{base}...{head} 130 | :merges_url: https://api.github.com/repos/hakobe/git-pr-release/merges 131 | :archive_url: https://api.github.com/repos/hakobe/git-pr-release/{archive_format}{/ref} 132 | :downloads_url: https://api.github.com/repos/hakobe/git-pr-release/downloads 133 | :issues_url: https://api.github.com/repos/hakobe/git-pr-release/issues{/number} 134 | :pulls_url: https://api.github.com/repos/hakobe/git-pr-release/pulls{/number} 135 | :milestones_url: https://api.github.com/repos/hakobe/git-pr-release/milestones{/number} 136 | :notifications_url: https://api.github.com/repos/hakobe/git-pr-release/notifications{?since,all,participating} 137 | :labels_url: https://api.github.com/repos/hakobe/git-pr-release/labels{/name} 138 | :releases_url: https://api.github.com/repos/hakobe/git-pr-release/releases{/id} 139 | :deployments_url: https://api.github.com/repos/hakobe/git-pr-release/deployments 140 | :created_at: 2014-03-05 02:20:59.000000000 Z 141 | :updated_at: 2014-05-13 01:42:58.000000000 Z 142 | :pushed_at: 2014-05-12 11:42:34.000000000 Z 143 | :git_url: git://github.com/hakobe/git-pr-release.git 144 | :ssh_url: git@github.com:hakobe/git-pr-release.git 145 | :clone_url: https://github.com/hakobe/git-pr-release.git 146 | :svn_url: https://github.com/hakobe/git-pr-release 147 | :homepage: https://rubygems.org/gems/git-pr-release 148 | :size: 236 149 | :stargazers_count: 0 150 | :watchers_count: 0 151 | :language: Ruby 152 | :has_issues: false 153 | :has_projects: true 154 | :has_downloads: true 155 | :has_wiki: true 156 | :has_pages: false 157 | :forks_count: 0 158 | :mirror_url: 159 | :archived: false 160 | :open_issues_count: 0 161 | :license: 162 | :forks: 0 163 | :open_issues: 0 164 | :watchers: 0 165 | :default_branch: master 166 | :base: 167 | :label: motemen:master 168 | :ref: master 169 | :sha: b620bead10831d2e4e15be392e0a435d3470a0ad 170 | :user: 171 | :login: motemen 172 | :id: 8465 173 | :node_id: MDQ6VXNlcjg0NjU= 174 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 175 | :gravatar_id: '' 176 | :url: https://api.github.com/users/motemen 177 | :html_url: https://github.com/motemen 178 | :followers_url: https://api.github.com/users/motemen/followers 179 | :following_url: https://api.github.com/users/motemen/following{/other_user} 180 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 181 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 182 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 183 | :organizations_url: https://api.github.com/users/motemen/orgs 184 | :repos_url: https://api.github.com/users/motemen/repos 185 | :events_url: https://api.github.com/users/motemen/events{/privacy} 186 | :received_events_url: https://api.github.com/users/motemen/received_events 187 | :type: User 188 | :site_admin: false 189 | :repo: 190 | :id: 15468156 191 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 192 | :name: git-pr-release 193 | :full_name: motemen/git-pr-release 194 | :private: false 195 | :owner: 196 | :login: motemen 197 | :id: 8465 198 | :node_id: MDQ6VXNlcjg0NjU= 199 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 200 | :gravatar_id: '' 201 | :url: https://api.github.com/users/motemen 202 | :html_url: https://github.com/motemen 203 | :followers_url: https://api.github.com/users/motemen/followers 204 | :following_url: https://api.github.com/users/motemen/following{/other_user} 205 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 206 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 207 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 208 | :organizations_url: https://api.github.com/users/motemen/orgs 209 | :repos_url: https://api.github.com/users/motemen/repos 210 | :events_url: https://api.github.com/users/motemen/events{/privacy} 211 | :received_events_url: https://api.github.com/users/motemen/received_events 212 | :type: User 213 | :site_admin: false 214 | :html_url: https://github.com/motemen/git-pr-release 215 | :description: Release pull request generator 216 | :fork: false 217 | :url: https://api.github.com/repos/motemen/git-pr-release 218 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 219 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 220 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 221 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 222 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 223 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 224 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 225 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 226 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 227 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 228 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 229 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 230 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 231 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 232 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 233 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 234 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 235 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 236 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 237 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 238 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 239 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 240 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 241 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 242 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 243 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 244 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 245 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 246 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 247 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 248 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 249 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 250 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 251 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 252 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 253 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 254 | :created_at: 2013-12-27 06:35:52.000000000 Z 255 | :updated_at: 2019-02-18 01:40:52.000000000 Z 256 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 257 | :git_url: git://github.com/motemen/git-pr-release.git 258 | :ssh_url: git@github.com:motemen/git-pr-release.git 259 | :clone_url: https://github.com/motemen/git-pr-release.git 260 | :svn_url: https://github.com/motemen/git-pr-release 261 | :homepage: https://rubygems.org/gems/git-pr-release 262 | :size: 62 263 | :stargazers_count: 359 264 | :watchers_count: 359 265 | :language: Ruby 266 | :has_issues: true 267 | :has_projects: true 268 | :has_downloads: true 269 | :has_wiki: true 270 | :has_pages: false 271 | :forks_count: 28 272 | :mirror_url: 273 | :archived: false 274 | :open_issues_count: 6 275 | :license: 276 | :key: mit 277 | :name: MIT License 278 | :spdx_id: MIT 279 | :url: https://api.github.com/licenses/mit 280 | :node_id: MDc6TGljZW5zZTEz 281 | :forks: 28 282 | :open_issues: 6 283 | :watchers: 359 284 | :default_branch: master 285 | :_links: 286 | :self: 287 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/3 288 | :html: 289 | :href: https://github.com/motemen/git-pr-release/pull/3 290 | :issue: 291 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/3 292 | :comments: 293 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/3/comments 294 | :review_comments: 295 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/3/comments 296 | :review_comment: 297 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 298 | :commits: 299 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/3/commits 300 | :statuses: 301 | :href: https://api.github.com/repos/motemen/git-pr-release/statuses/5c977a1827387ac7b7a85c7b827ee119165f1823 302 | :author_association: COLLABORATOR 303 | :merged: true 304 | :mergeable: 305 | :rebaseable: 306 | :mergeable_state: unknown 307 | :merged_by: 308 | :login: motemen 309 | :id: 8465 310 | :node_id: MDQ6VXNlcjg0NjU= 311 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 312 | :gravatar_id: '' 313 | :url: https://api.github.com/users/motemen 314 | :html_url: https://github.com/motemen 315 | :followers_url: https://api.github.com/users/motemen/followers 316 | :following_url: https://api.github.com/users/motemen/following{/other_user} 317 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 318 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 319 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 320 | :organizations_url: https://api.github.com/users/motemen/orgs 321 | :repos_url: https://api.github.com/users/motemen/repos 322 | :events_url: https://api.github.com/users/motemen/events{/privacy} 323 | :received_events_url: https://api.github.com/users/motemen/received_events 324 | :type: User 325 | :site_admin: false 326 | :comments: 3 327 | :review_comments: 0 328 | :maintainer_can_modify: false 329 | :commits: 3 330 | :additions: 54 331 | :deletions: 38 332 | :changed_files: 1 333 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_6.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :url: https://api.github.com/repos/motemen/git-pr-release/pulls/6 3 | :id: 16585502 4 | :node_id: MDExOlB1bGxSZXF1ZXN0MTY1ODU1MDI= 5 | :html_url: https://github.com/motemen/git-pr-release/pull/6 6 | :diff_url: https://github.com/motemen/git-pr-release/pull/6.diff 7 | :patch_url: https://github.com/motemen/git-pr-release/pull/6.patch 8 | :issue_url: https://api.github.com/repos/motemen/git-pr-release/issues/6 9 | :number: 6 10 | :state: closed 11 | :locked: false 12 | :title: Support two factor auth 13 | :user: 14 | :login: ninjinkun 15 | :id: 113420 16 | :node_id: MDQ6VXNlcjExMzQyMA== 17 | :avatar_url: https://avatars1.githubusercontent.com/u/113420?v=4 18 | :gravatar_id: '' 19 | :url: https://api.github.com/users/ninjinkun 20 | :html_url: https://github.com/ninjinkun 21 | :followers_url: https://api.github.com/users/ninjinkun/followers 22 | :following_url: https://api.github.com/users/ninjinkun/following{/other_user} 23 | :gists_url: https://api.github.com/users/ninjinkun/gists{/gist_id} 24 | :starred_url: https://api.github.com/users/ninjinkun/starred{/owner}{/repo} 25 | :subscriptions_url: https://api.github.com/users/ninjinkun/subscriptions 26 | :organizations_url: https://api.github.com/users/ninjinkun/orgs 27 | :repos_url: https://api.github.com/users/ninjinkun/repos 28 | :events_url: https://api.github.com/users/ninjinkun/events{/privacy} 29 | :received_events_url: https://api.github.com/users/ninjinkun/received_events 30 | :type: User 31 | :site_admin: false 32 | :body: 'Currently `git-pr-release` don''t support two factor auth in interactive configuration 33 | mode. This patch shows prompt `two-factor authentication code?` and try to request 34 | again. 35 | 36 | ' 37 | :created_at: 2014-06-02 06:55:44.000000000 Z 38 | :updated_at: 2014-06-21 05:22:22.000000000 Z 39 | :closed_at: 2014-06-02 08:02:02.000000000 Z 40 | :merged_at: 2014-06-02 08:02:02.000000000 Z 41 | :merge_commit_sha: c66dfa90eed853507ae0e2876df3cb4dc7ce5286 42 | :assignee: 43 | :assignees: [] 44 | :requested_reviewers: [] 45 | :requested_teams: [] 46 | :labels: [] 47 | :milestone: 48 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/pulls/6/commits 49 | :review_comments_url: https://api.github.com/repos/motemen/git-pr-release/pulls/6/comments 50 | :review_comment_url: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 51 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/issues/6/comments 52 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/67d936fb57851f85db28249e91dfa0d8ccfe530b 53 | :head: 54 | :label: ninjinkun:support-two-factor-auth 55 | :ref: support-two-factor-auth 56 | :sha: 67d936fb57851f85db28249e91dfa0d8ccfe530b 57 | :user: 58 | :login: ninjinkun 59 | :id: 113420 60 | :node_id: MDQ6VXNlcjExMzQyMA== 61 | :avatar_url: https://avatars1.githubusercontent.com/u/113420?v=4 62 | :gravatar_id: '' 63 | :url: https://api.github.com/users/ninjinkun 64 | :html_url: https://github.com/ninjinkun 65 | :followers_url: https://api.github.com/users/ninjinkun/followers 66 | :following_url: https://api.github.com/users/ninjinkun/following{/other_user} 67 | :gists_url: https://api.github.com/users/ninjinkun/gists{/gist_id} 68 | :starred_url: https://api.github.com/users/ninjinkun/starred{/owner}{/repo} 69 | :subscriptions_url: https://api.github.com/users/ninjinkun/subscriptions 70 | :organizations_url: https://api.github.com/users/ninjinkun/orgs 71 | :repos_url: https://api.github.com/users/ninjinkun/repos 72 | :events_url: https://api.github.com/users/ninjinkun/events{/privacy} 73 | :received_events_url: https://api.github.com/users/ninjinkun/received_events 74 | :type: User 75 | :site_admin: false 76 | :repo: 77 | :id: 20395231 78 | :node_id: MDEwOlJlcG9zaXRvcnkyMDM5NTIzMQ== 79 | :name: git-pr-release 80 | :full_name: ninjinkun/git-pr-release 81 | :private: false 82 | :owner: 83 | :login: ninjinkun 84 | :id: 113420 85 | :node_id: MDQ6VXNlcjExMzQyMA== 86 | :avatar_url: https://avatars1.githubusercontent.com/u/113420?v=4 87 | :gravatar_id: '' 88 | :url: https://api.github.com/users/ninjinkun 89 | :html_url: https://github.com/ninjinkun 90 | :followers_url: https://api.github.com/users/ninjinkun/followers 91 | :following_url: https://api.github.com/users/ninjinkun/following{/other_user} 92 | :gists_url: https://api.github.com/users/ninjinkun/gists{/gist_id} 93 | :starred_url: https://api.github.com/users/ninjinkun/starred{/owner}{/repo} 94 | :subscriptions_url: https://api.github.com/users/ninjinkun/subscriptions 95 | :organizations_url: https://api.github.com/users/ninjinkun/orgs 96 | :repos_url: https://api.github.com/users/ninjinkun/repos 97 | :events_url: https://api.github.com/users/ninjinkun/events{/privacy} 98 | :received_events_url: https://api.github.com/users/ninjinkun/received_events 99 | :type: User 100 | :site_admin: false 101 | :html_url: https://github.com/ninjinkun/git-pr-release 102 | :description: Creates a pull request which summarizes feature branches that are 103 | to be released into production 104 | :fork: true 105 | :url: https://api.github.com/repos/ninjinkun/git-pr-release 106 | :forks_url: https://api.github.com/repos/ninjinkun/git-pr-release/forks 107 | :keys_url: https://api.github.com/repos/ninjinkun/git-pr-release/keys{/key_id} 108 | :collaborators_url: https://api.github.com/repos/ninjinkun/git-pr-release/collaborators{/collaborator} 109 | :teams_url: https://api.github.com/repos/ninjinkun/git-pr-release/teams 110 | :hooks_url: https://api.github.com/repos/ninjinkun/git-pr-release/hooks 111 | :issue_events_url: https://api.github.com/repos/ninjinkun/git-pr-release/issues/events{/number} 112 | :events_url: https://api.github.com/repos/ninjinkun/git-pr-release/events 113 | :assignees_url: https://api.github.com/repos/ninjinkun/git-pr-release/assignees{/user} 114 | :branches_url: https://api.github.com/repos/ninjinkun/git-pr-release/branches{/branch} 115 | :tags_url: https://api.github.com/repos/ninjinkun/git-pr-release/tags 116 | :blobs_url: https://api.github.com/repos/ninjinkun/git-pr-release/git/blobs{/sha} 117 | :git_tags_url: https://api.github.com/repos/ninjinkun/git-pr-release/git/tags{/sha} 118 | :git_refs_url: https://api.github.com/repos/ninjinkun/git-pr-release/git/refs{/sha} 119 | :trees_url: https://api.github.com/repos/ninjinkun/git-pr-release/git/trees{/sha} 120 | :statuses_url: https://api.github.com/repos/ninjinkun/git-pr-release/statuses/{sha} 121 | :languages_url: https://api.github.com/repos/ninjinkun/git-pr-release/languages 122 | :stargazers_url: https://api.github.com/repos/ninjinkun/git-pr-release/stargazers 123 | :contributors_url: https://api.github.com/repos/ninjinkun/git-pr-release/contributors 124 | :subscribers_url: https://api.github.com/repos/ninjinkun/git-pr-release/subscribers 125 | :subscription_url: https://api.github.com/repos/ninjinkun/git-pr-release/subscription 126 | :commits_url: https://api.github.com/repos/ninjinkun/git-pr-release/commits{/sha} 127 | :git_commits_url: https://api.github.com/repos/ninjinkun/git-pr-release/git/commits{/sha} 128 | :comments_url: https://api.github.com/repos/ninjinkun/git-pr-release/comments{/number} 129 | :issue_comment_url: https://api.github.com/repos/ninjinkun/git-pr-release/issues/comments{/number} 130 | :contents_url: https://api.github.com/repos/ninjinkun/git-pr-release/contents/{+path} 131 | :compare_url: https://api.github.com/repos/ninjinkun/git-pr-release/compare/{base}...{head} 132 | :merges_url: https://api.github.com/repos/ninjinkun/git-pr-release/merges 133 | :archive_url: https://api.github.com/repos/ninjinkun/git-pr-release/{archive_format}{/ref} 134 | :downloads_url: https://api.github.com/repos/ninjinkun/git-pr-release/downloads 135 | :issues_url: https://api.github.com/repos/ninjinkun/git-pr-release/issues{/number} 136 | :pulls_url: https://api.github.com/repos/ninjinkun/git-pr-release/pulls{/number} 137 | :milestones_url: https://api.github.com/repos/ninjinkun/git-pr-release/milestones{/number} 138 | :notifications_url: https://api.github.com/repos/ninjinkun/git-pr-release/notifications{?since,all,participating} 139 | :labels_url: https://api.github.com/repos/ninjinkun/git-pr-release/labels{/name} 140 | :releases_url: https://api.github.com/repos/ninjinkun/git-pr-release/releases{/id} 141 | :deployments_url: https://api.github.com/repos/ninjinkun/git-pr-release/deployments 142 | :created_at: 2014-06-02 06:41:45.000000000 Z 143 | :updated_at: 2014-06-03 06:26:14.000000000 Z 144 | :pushed_at: 2014-06-03 06:26:13.000000000 Z 145 | :git_url: git://github.com/ninjinkun/git-pr-release.git 146 | :ssh_url: git@github.com:ninjinkun/git-pr-release.git 147 | :clone_url: https://github.com/ninjinkun/git-pr-release.git 148 | :svn_url: https://github.com/ninjinkun/git-pr-release 149 | :homepage: https://rubygems.org/gems/git-pr-release 150 | :size: 192 151 | :stargazers_count: 0 152 | :watchers_count: 0 153 | :language: Ruby 154 | :has_issues: false 155 | :has_projects: true 156 | :has_downloads: true 157 | :has_wiki: true 158 | :has_pages: false 159 | :forks_count: 0 160 | :mirror_url: 161 | :archived: false 162 | :open_issues_count: 0 163 | :license: 164 | :key: mit 165 | :name: MIT License 166 | :spdx_id: MIT 167 | :url: https://api.github.com/licenses/mit 168 | :node_id: MDc6TGljZW5zZTEz 169 | :forks: 0 170 | :open_issues: 0 171 | :watchers: 0 172 | :default_branch: master 173 | :base: 174 | :label: motemen:master 175 | :ref: master 176 | :sha: a0d1fd72732d705270878d22601c44e0533641e1 177 | :user: 178 | :login: motemen 179 | :id: 8465 180 | :node_id: MDQ6VXNlcjg0NjU= 181 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 182 | :gravatar_id: '' 183 | :url: https://api.github.com/users/motemen 184 | :html_url: https://github.com/motemen 185 | :followers_url: https://api.github.com/users/motemen/followers 186 | :following_url: https://api.github.com/users/motemen/following{/other_user} 187 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 188 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 189 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 190 | :organizations_url: https://api.github.com/users/motemen/orgs 191 | :repos_url: https://api.github.com/users/motemen/repos 192 | :events_url: https://api.github.com/users/motemen/events{/privacy} 193 | :received_events_url: https://api.github.com/users/motemen/received_events 194 | :type: User 195 | :site_admin: false 196 | :repo: 197 | :id: 15468156 198 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 199 | :name: git-pr-release 200 | :full_name: motemen/git-pr-release 201 | :private: false 202 | :owner: 203 | :login: motemen 204 | :id: 8465 205 | :node_id: MDQ6VXNlcjg0NjU= 206 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 207 | :gravatar_id: '' 208 | :url: https://api.github.com/users/motemen 209 | :html_url: https://github.com/motemen 210 | :followers_url: https://api.github.com/users/motemen/followers 211 | :following_url: https://api.github.com/users/motemen/following{/other_user} 212 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 213 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 214 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 215 | :organizations_url: https://api.github.com/users/motemen/orgs 216 | :repos_url: https://api.github.com/users/motemen/repos 217 | :events_url: https://api.github.com/users/motemen/events{/privacy} 218 | :received_events_url: https://api.github.com/users/motemen/received_events 219 | :type: User 220 | :site_admin: false 221 | :html_url: https://github.com/motemen/git-pr-release 222 | :description: Release pull request generator 223 | :fork: false 224 | :url: https://api.github.com/repos/motemen/git-pr-release 225 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 226 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 227 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 228 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 229 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 230 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 231 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 232 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 233 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 234 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 235 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 236 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 237 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 238 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 239 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 240 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 241 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 242 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 243 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 244 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 245 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 246 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 247 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 248 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 249 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 250 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 251 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 252 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 253 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 254 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 255 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 256 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 257 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 258 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 259 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 260 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 261 | :created_at: 2013-12-27 06:35:52.000000000 Z 262 | :updated_at: 2019-02-18 01:40:52.000000000 Z 263 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 264 | :git_url: git://github.com/motemen/git-pr-release.git 265 | :ssh_url: git@github.com:motemen/git-pr-release.git 266 | :clone_url: https://github.com/motemen/git-pr-release.git 267 | :svn_url: https://github.com/motemen/git-pr-release 268 | :homepage: https://rubygems.org/gems/git-pr-release 269 | :size: 62 270 | :stargazers_count: 359 271 | :watchers_count: 359 272 | :language: Ruby 273 | :has_issues: true 274 | :has_projects: true 275 | :has_downloads: true 276 | :has_wiki: true 277 | :has_pages: false 278 | :forks_count: 28 279 | :mirror_url: 280 | :archived: false 281 | :open_issues_count: 6 282 | :license: 283 | :key: mit 284 | :name: MIT License 285 | :spdx_id: MIT 286 | :url: https://api.github.com/licenses/mit 287 | :node_id: MDc6TGljZW5zZTEz 288 | :forks: 28 289 | :open_issues: 6 290 | :watchers: 359 291 | :default_branch: master 292 | :_links: 293 | :self: 294 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/6 295 | :html: 296 | :href: https://github.com/motemen/git-pr-release/pull/6 297 | :issue: 298 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/6 299 | :comments: 300 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/6/comments 301 | :review_comments: 302 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/6/comments 303 | :review_comment: 304 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 305 | :commits: 306 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/6/commits 307 | :statuses: 308 | :href: https://api.github.com/repos/motemen/git-pr-release/statuses/67d936fb57851f85db28249e91dfa0d8ccfe530b 309 | :author_association: CONTRIBUTOR 310 | :merged: true 311 | :mergeable: 312 | :rebaseable: 313 | :mergeable_state: unknown 314 | :merged_by: 315 | :login: motemen 316 | :id: 8465 317 | :node_id: MDQ6VXNlcjg0NjU= 318 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 319 | :gravatar_id: '' 320 | :url: https://api.github.com/users/motemen 321 | :html_url: https://github.com/motemen 322 | :followers_url: https://api.github.com/users/motemen/followers 323 | :following_url: https://api.github.com/users/motemen/following{/other_user} 324 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 325 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 326 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 327 | :organizations_url: https://api.github.com/users/motemen/orgs 328 | :repos_url: https://api.github.com/users/motemen/repos 329 | :events_url: https://api.github.com/users/motemen/events{/privacy} 330 | :received_events_url: https://api.github.com/users/motemen/received_events 331 | :type: User 332 | :site_admin: false 333 | :comments: 2 334 | :review_comments: 2 335 | :maintainer_can_modify: false 336 | :commits: 2 337 | :additions: 17 338 | :deletions: 1 339 | :changed_files: 1 340 | -------------------------------------------------------------------------------- /spec/fixtures/file/pr_7.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :url: https://api.github.com/repos/motemen/git-pr-release/pulls/7 3 | :id: 16585507 4 | :node_id: MDExOlB1bGxSZXF1ZXN0MTY1ODU1MDc= 5 | :html_url: https://github.com/motemen/git-pr-release/pull/7 6 | :diff_url: https://github.com/motemen/git-pr-release/pull/7.diff 7 | :patch_url: https://github.com/motemen/git-pr-release/pull/7.patch 8 | :issue_url: https://api.github.com/repos/motemen/git-pr-release/issues/7 9 | :number: 7 10 | :state: closed 11 | :locked: false 12 | :title: Support multiple assignees 13 | :user: 14 | :login: hakobe 15 | :id: 6882 16 | :node_id: MDQ6VXNlcjY4ODI= 17 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 18 | :gravatar_id: '' 19 | :url: https://api.github.com/users/hakobe 20 | :html_url: https://github.com/hakobe 21 | :followers_url: https://api.github.com/users/hakobe/followers 22 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 23 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 24 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 25 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 26 | :organizations_url: https://api.github.com/users/hakobe/orgs 27 | :repos_url: https://api.github.com/users/hakobe/repos 28 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 29 | :received_events_url: https://api.github.com/users/hakobe/received_events 30 | :type: User 31 | :site_admin: false 32 | :body: 'This PR adds support for multiple assignees in git-pr-release. 33 | 34 | ' 35 | :created_at: 2014-06-02 06:55:44.000000000 Z 36 | :updated_at: 2014-06-21 05:22:22.000000000 Z 37 | :closed_at: 2014-06-02 08:02:02.000000000 Z 38 | :merged_at: 2014-06-02 08:02:02.000000000 Z 39 | :merge_commit_sha: c66dfa90eed853507ae0e2876df3cb4dc7ce5286 40 | :assignee: 41 | :login: hakobe 42 | :id: 6882 43 | :node_id: MDQ6VXNlcjY4ODI= 44 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 45 | :gravatar_id: '' 46 | :url: https://api.github.com/users/hakobe 47 | :html_url: https://github.com/hakobe 48 | :followers_url: https://api.github.com/users/hakobe/followers 49 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 50 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 51 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 52 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 53 | :organizations_url: https://api.github.com/users/hakobe/orgs 54 | :repos_url: https://api.github.com/users/hakobe/repos 55 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 56 | :received_events_url: https://api.github.com/users/hakobe/received_events 57 | :type: User 58 | :site_admin: false 59 | :assignees: 60 | - :login: hakobe 61 | :id: 6882 62 | :node_id: MDQ6VXNlcjY4ODI= 63 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 64 | :gravatar_id: '' 65 | :url: https://api.github.com/users/hakobe 66 | :html_url: https://github.com/hakobe 67 | :followers_url: https://api.github.com/users/hakobe/followers 68 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 69 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 70 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 71 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 72 | :organizations_url: https://api.github.com/users/hakobe/orgs 73 | :repos_url: https://api.github.com/users/hakobe/repos 74 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 75 | :received_events_url: https://api.github.com/users/hakobe/received_events 76 | :type: User 77 | :site_admin: false 78 | - :login: toshimaru 79 | :id: 803398 80 | :node_id: MDQ6VXNlcjgwMzM5OA== 81 | :avatar_url: https://avatars.githubusercontent.com/u/803398?v=4 82 | :gravatar_id: '' 83 | :url: https://api.github.com/users/toshimaru 84 | :html_url: https://github.com/toshimaru 85 | :followers_url: https://api.github.com/users/toshimaru/followers 86 | :following_url: https://api.github.com/users/toshimaru/following{/other_user} 87 | :gists_url: https://api.github.com/users/toshimaru/gists{/gist_id} 88 | :starred_url: https://api.github.com/users/toshimaru/starred{/owner}{/repo} 89 | :subscriptions_url: https://api.github.com/users/toshimaru/subscriptions 90 | :organizations_url: https://api.github.com/users/toshimaru/orgs 91 | :repos_url: https://api.github.com/users/toshimaru/repos 92 | :events_url: https://api.github.com/users/toshimaru/events{/privacy} 93 | :received_events_url: https://api.github.com/users/toshimaru/received_events 94 | :type: User 95 | :site_admin: false 96 | - :login: Copilot 97 | :id: 198982749 98 | :node_id: BOT_kgDOC9w8XQ 99 | :avatar_url: https://avatars.githubusercontent.com/in/1143301?v=4 100 | :gravatar_id: '' 101 | :url: https://api.github.com/users/Copilot 102 | :html_url: https://github.com/apps/copilot-swe-agent 103 | :followers_url: https://api.github.com/users/Copilot/followers 104 | :following_url: https://api.github.com/users/Copilot/following{/other_user} 105 | :gists_url: https://api.github.com/users/Copilot/gists{/gist_id} 106 | :starred_url: https://api.github.com/users/Copilot/starred{/owner}{/repo} 107 | :subscriptions_url: https://api.github.com/users/Copilot/subscriptions 108 | :organizations_url: https://api.github.com/users/Copilot/orgs 109 | :repos_url: https://api.github.com/users/Copilot/repos 110 | :events_url: https://api.github.com/users/Copilot/events{/privacy} 111 | :received_events_url: https://api.github.com/users/Copilot/received_events 112 | :type: Bot 113 | :site_admin: false 114 | :requested_reviewers: [] 115 | :requested_teams: [] 116 | :labels: [] 117 | :milestone: 118 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/pulls/7/commits 119 | :review_comments_url: https://api.github.com/repos/motemen/git-pr-release/pulls/7/comments 120 | :review_comment_url: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 121 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/issues/7/comments 122 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/67d936fb57851f85db28249e91dfa0d8ccfe530b 123 | :head: 124 | :label: hakobe:support-multiple-assignees 125 | :ref: support-multiple-assignees 126 | :sha: 67d936fb57851f85db28249e91dfa0d8ccfe530b 127 | :user: 128 | :login: hakobe 129 | :id: 6882 130 | :node_id: MDQ6VXNlcjY4ODI= 131 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 132 | :gravatar_id: '' 133 | :url: https://api.github.com/users/hakobe 134 | :html_url: https://github.com/hakobe 135 | :followers_url: https://api.github.com/users/hakobe/followers 136 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 137 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 138 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 139 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 140 | :organizations_url: https://api.github.com/users/hakobe/orgs 141 | :repos_url: https://api.github.com/users/hakobe/repos 142 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 143 | :received_events_url: https://api.github.com/users/hakobe/received_events 144 | :type: User 145 | :site_admin: false 146 | :repo: 147 | :id: 17425170 148 | :node_id: MDEwOlJlcG9zaXRvcnkxNzQyNTE3MA== 149 | :name: git-pr-release 150 | :full_name: hakobe/git-pr-release 151 | :private: false 152 | :owner: 153 | :login: hakobe 154 | :id: 6882 155 | :node_id: MDQ6VXNlcjY4ODI= 156 | :avatar_url: https://avatars0.githubusercontent.com/u/6882?v=4 157 | :gravatar_id: '' 158 | :url: https://api.github.com/users/hakobe 159 | :html_url: https://github.com/hakobe 160 | :followers_url: https://api.github.com/users/hakobe/followers 161 | :following_url: https://api.github.com/users/hakobe/following{/other_user} 162 | :gists_url: https://api.github.com/users/hakobe/gists{/gist_id} 163 | :starred_url: https://api.github.com/users/hakobe/starred{/owner}{/repo} 164 | :subscriptions_url: https://api.github.com/users/hakobe/subscriptions 165 | :organizations_url: https://api.github.com/users/hakobe/orgs 166 | :repos_url: https://api.github.com/users/hakobe/repos 167 | :events_url: https://api.github.com/users/hakobe/events{/privacy} 168 | :received_events_url: https://api.github.com/users/hakobe/received_events 169 | :type: User 170 | :site_admin: false 171 | :html_url: https://github.com/hakobe/git-pr-release 172 | :description: Creates a pull request which summarizes feature branches that are 173 | to be released into production 174 | :fork: true 175 | :url: https://api.github.com/repos/hakobe/git-pr-release 176 | :forks_url: https://api.github.com/repos/hakobe/git-pr-release/forks 177 | :keys_url: https://api.github.com/repos/hakobe/git-pr-release/keys{/key_id} 178 | :collaborators_url: https://api.github.com/repos/hakobe/git-pr-release/collaborators{/collaborator} 179 | :teams_url: https://api.github.com/repos/hakobe/git-pr-release/teams 180 | :hooks_url: https://api.github.com/repos/hakobe/git-pr-release/hooks 181 | :issue_events_url: https://api.github.com/repos/hakobe/git-pr-release/issues/events{/number} 182 | :events_url: https://api.github.com/repos/hakobe/git-pr-release/events 183 | :assignees_url: https://api.github.com/repos/hakobe/git-pr-release/assignees{/user} 184 | :branches_url: https://api.github.com/repos/hakobe/git-pr-release/branches{/branch} 185 | :tags_url: https://api.github.com/repos/hakobe/git-pr-release/tags 186 | :blobs_url: https://api.github.com/repos/hakobe/git-pr-release/git/blobs{/sha} 187 | :git_tags_url: https://api.github.com/repos/hakobe/git-pr-release/git/tags{/sha} 188 | :git_refs_url: https://api.github.com/repos/hakobe/git-pr-release/git/refs{/sha} 189 | :trees_url: https://api.github.com/repos/hakobe/git-pr-release/git/trees{/sha} 190 | :statuses_url: https://api.github.com/repos/hakobe/git-pr-release/statuses/{sha} 191 | :languages_url: https://api.github.com/repos/hakobe/git-pr-release/languages 192 | :stargazers_url: https://api.github.com/repos/hakobe/git-pr-release/stargazers 193 | :contributors_url: https://api.github.com/repos/hakobe/git-pr-release/contributors 194 | :subscribers_url: https://api.github.com/repos/hakobe/git-pr-release/subscribers 195 | :subscription_url: https://api.github.com/repos/hakobe/git-pr-release/subscription 196 | :commits_url: https://api.github.com/repos/hakobe/git-pr-release/commits{/sha} 197 | :git_commits_url: https://api.github.com/repos/hakobe/git-pr-release/git/commits{/sha} 198 | :comments_url: https://api.github.com/repos/hakobe/git-pr-release/comments{/number} 199 | :issue_comment_url: https://api.github.com/repos/hakobe/git-pr-release/issues/comments{/number} 200 | :contents_url: https://api.github.com/repos/hakobe/git-pr-release/contents/{+path} 201 | :compare_url: https://api.github.com/repos/hakobe/git-pr-release/compare/{base}...{head} 202 | :merges_url: https://api.github.com/repos/hakobe/git-pr-release/merges 203 | :archive_url: https://api.github.com/repos/hakobe/git-pr-release/{archive_format}{/ref} 204 | :downloads_url: https://api.github.com/repos/hakobe/git-pr-release/downloads 205 | :issues_url: https://api.github.com/repos/hakobe/git-pr-release/issues{/number} 206 | :pulls_url: https://api.github.com/repos/hakobe/git-pr-release/pulls{/number} 207 | :milestones_url: https://api.github.com/repos/hakobe/git-pr-release/milestones{/number} 208 | :notifications_url: https://api.github.com/repos/hakobe/git-pr-release/notifications{?since,all,participating} 209 | :labels_url: https://api.github.com/repos/hakobe/git-pr-release/labels{/name} 210 | :releases_url: https://api.github.com/repos/hakobe/git-pr-release/releases{/id} 211 | :deployments_url: https://api.github.com/repos/hakobe/git-pr-release/deployments 212 | :created_at: 2014-03-05 02:20:59.000000000 Z 213 | :updated_at: 2014-05-13 01:42:58.000000000 Z 214 | :pushed_at: 2014-05-12 11:42:34.000000000 Z 215 | :git_url: git://github.com/hakobe/git-pr-release.git 216 | :ssh_url: git@github.com:hakobe/git-pr-release.git 217 | :clone_url: https://github.com/hakobe/git-pr-release.git 218 | :svn_url: https://github.com/hakobe/git-pr-release 219 | :homepage: https://rubygems.org/gems/git-pr-release 220 | :size: 236 221 | :stargazers_count: 0 222 | :watchers_count: 0 223 | :language: Ruby 224 | :has_issues: false 225 | :has_projects: true 226 | :has_downloads: true 227 | :has_wiki: true 228 | :has_pages: false 229 | :forks_count: 0 230 | :mirror_url: 231 | :archived: false 232 | :open_issues_count: 0 233 | :license: 234 | :forks: 0 235 | :open_issues: 0 236 | :watchers: 0 237 | :default_branch: master 238 | :base: 239 | :label: motemen:master 240 | :ref: master 241 | :sha: a0d1fd72732d705270878d22601c44e0533641e1 242 | :user: 243 | :login: motemen 244 | :id: 8465 245 | :node_id: MDQ6VXNlcjg0NjU= 246 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 247 | :gravatar_id: '' 248 | :url: https://api.github.com/users/motemen 249 | :html_url: https://github.com/motemen 250 | :followers_url: https://api.github.com/users/motemen/followers 251 | :following_url: https://api.github.com/users/motemen/following{/other_user} 252 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 253 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 254 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 255 | :organizations_url: https://api.github.com/users/motemen/orgs 256 | :repos_url: https://api.github.com/users/motemen/repos 257 | :events_url: https://api.github.com/users/motemen/events{/privacy} 258 | :received_events_url: https://api.github.com/users/motemen/received_events 259 | :type: User 260 | :site_admin: false 261 | :repo: 262 | :id: 15468156 263 | :node_id: MDEwOlJlcG9zaXRvcnkxNTQ2ODE1Ng== 264 | :name: git-pr-release 265 | :full_name: motemen/git-pr-release 266 | :private: false 267 | :owner: 268 | :login: motemen 269 | :id: 8465 270 | :node_id: MDQ6VXNlcjg0NjU= 271 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 272 | :gravatar_id: '' 273 | :url: https://api.github.com/users/motemen 274 | :html_url: https://github.com/motemen 275 | :followers_url: https://api.github.com/users/motemen/followers 276 | :following_url: https://api.github.com/users/motemen/following{/other_user} 277 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 278 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 279 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 280 | :organizations_url: https://api.github.com/users/motemen/orgs 281 | :repos_url: https://api.github.com/users/motemen/repos 282 | :events_url: https://api.github.com/users/motemen/events{/privacy} 283 | :received_events_url: https://api.github.com/users/motemen/received_events 284 | :type: User 285 | :site_admin: false 286 | :html_url: https://github.com/motemen/git-pr-release 287 | :description: Release pull request generator 288 | :fork: false 289 | :url: https://api.github.com/repos/motemen/git-pr-release 290 | :forks_url: https://api.github.com/repos/motemen/git-pr-release/forks 291 | :keys_url: https://api.github.com/repos/motemen/git-pr-release/keys{/key_id} 292 | :collaborators_url: https://api.github.com/repos/motemen/git-pr-release/collaborators{/collaborator} 293 | :teams_url: https://api.github.com/repos/motemen/git-pr-release/teams 294 | :hooks_url: https://api.github.com/repos/motemen/git-pr-release/hooks 295 | :issue_events_url: https://api.github.com/repos/motemen/git-pr-release/issues/events{/number} 296 | :events_url: https://api.github.com/repos/motemen/git-pr-release/events 297 | :assignees_url: https://api.github.com/repos/motemen/git-pr-release/assignees{/user} 298 | :branches_url: https://api.github.com/repos/motemen/git-pr-release/branches{/branch} 299 | :tags_url: https://api.github.com/repos/motemen/git-pr-release/tags 300 | :blobs_url: https://api.github.com/repos/motemen/git-pr-release/git/blobs{/sha} 301 | :git_tags_url: https://api.github.com/repos/motemen/git-pr-release/git/tags{/sha} 302 | :git_refs_url: https://api.github.com/repos/motemen/git-pr-release/git/refs{/sha} 303 | :trees_url: https://api.github.com/repos/motemen/git-pr-release/git/trees{/sha} 304 | :statuses_url: https://api.github.com/repos/motemen/git-pr-release/statuses/{sha} 305 | :languages_url: https://api.github.com/repos/motemen/git-pr-release/languages 306 | :stargazers_url: https://api.github.com/repos/motemen/git-pr-release/stargazers 307 | :contributors_url: https://api.github.com/repos/motemen/git-pr-release/contributors 308 | :subscribers_url: https://api.github.com/repos/motemen/git-pr-release/subscribers 309 | :subscription_url: https://api.github.com/repos/motemen/git-pr-release/subscription 310 | :commits_url: https://api.github.com/repos/motemen/git-pr-release/commits{/sha} 311 | :git_commits_url: https://api.github.com/repos/motemen/git-pr-release/git/commits{/sha} 312 | :comments_url: https://api.github.com/repos/motemen/git-pr-release/comments{/number} 313 | :issue_comment_url: https://api.github.com/repos/motemen/git-pr-release/issues/comments{/number} 314 | :contents_url: https://api.github.com/repos/motemen/git-pr-release/contents/{+path} 315 | :compare_url: https://api.github.com/repos/motemen/git-pr-release/compare/{base}...{head} 316 | :merges_url: https://api.github.com/repos/motemen/git-pr-release/merges 317 | :archive_url: https://api.github.com/repos/motemen/git-pr-release/{archive_format}{/ref} 318 | :downloads_url: https://api.github.com/repos/motemen/git-pr-release/downloads 319 | :issues_url: https://api.github.com/repos/motemen/git-pr-release/issues{/number} 320 | :pulls_url: https://api.github.com/repos/motemen/git-pr-release/pulls{/number} 321 | :milestones_url: https://api.github.com/repos/motemen/git-pr-release/milestones{/number} 322 | :notifications_url: https://api.github.com/repos/motemen/git-pr-release/notifications{?since,all,participating} 323 | :labels_url: https://api.github.com/repos/motemen/git-pr-release/labels{/name} 324 | :releases_url: https://api.github.com/repos/motemen/git-pr-release/releases{/id} 325 | :deployments_url: https://api.github.com/repos/motemen/git-pr-release/deployments 326 | :created_at: 2013-12-27 06:35:52.000000000 Z 327 | :updated_at: 2019-02-18 01:40:52.000000000 Z 328 | :pushed_at: 2018-11-15 10:45:07.000000000 Z 329 | :git_url: git://github.com/motemen/git-pr-release.git 330 | :ssh_url: git@github.com:motemen/git-pr-release.git 331 | :clone_url: https://github.com/motemen/git-pr-release.git 332 | :svn_url: https://github.com/motemen/git-pr-release 333 | :homepage: https://rubygems.org/gems/git-pr-release 334 | :size: 62 335 | :stargazers_count: 359 336 | :watchers_count: 359 337 | :language: Ruby 338 | :has_issues: true 339 | :has_projects: true 340 | :has_downloads: true 341 | :has_wiki: true 342 | :has_pages: false 343 | :forks_count: 28 344 | :mirror_url: 345 | :archived: false 346 | :open_issues_count: 6 347 | :license: 348 | :key: mit 349 | :name: MIT License 350 | :spdx_id: MIT 351 | :url: https://api.github.com/licenses/mit 352 | :node_id: MDc6TGljZW5zZTEz 353 | :forks: 28 354 | :open_issues: 6 355 | :watchers: 359 356 | :default_branch: master 357 | :_links: 358 | :self: 359 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/7 360 | :html: 361 | :href: https://github.com/motemen/git-pr-release/pull/7 362 | :issue: 363 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/7 364 | :comments: 365 | :href: https://api.github.com/repos/motemen/git-pr-release/issues/7/comments 366 | :review_comments: 367 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/7/comments 368 | :review_comment: 369 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/comments{/number} 370 | :commits: 371 | :href: https://api.github.com/repos/motemen/git-pr-release/pulls/7/commits 372 | :statuses: 373 | :href: https://api.github.com/repos/motemen/git-pr-release/statuses/67d936fb57851f85db28249e91dfa0d8ccfe530b 374 | :author_association: CONTRIBUTOR 375 | :merged: true 376 | :mergeable: 377 | :rebaseable: 378 | :mergeable_state: unknown 379 | :merged_by: 380 | :login: motemen 381 | :id: 8465 382 | :node_id: MDQ6VXNlcjg0NjU= 383 | :avatar_url: https://avatars2.githubusercontent.com/u/8465?v=4 384 | :gravatar_id: '' 385 | :url: https://api.github.com/users/motemen 386 | :html_url: https://github.com/motemen 387 | :followers_url: https://api.github.com/users/motemen/followers 388 | :following_url: https://api.github.com/users/motemen/following{/other_user} 389 | :gists_url: https://api.github.com/users/motemen/gists{/gist_id} 390 | :starred_url: https://api.github.com/users/motemen/starred{/owner}{/repo} 391 | :subscriptions_url: https://api.github.com/users/motemen/subscriptions 392 | :organizations_url: https://api.github.com/users/motemen/orgs 393 | :repos_url: https://api.github.com/users/motemen/repos 394 | :events_url: https://api.github.com/users/motemen/events{/privacy} 395 | :received_events_url: https://api.github.com/users/motemen/received_events 396 | :type: User 397 | :site_admin: false 398 | :comments: 2 399 | :review_comments: 2 400 | :maintainer_can_modify: false 401 | :commits: 2 402 | :additions: 17 403 | :deletions: 1 404 | :changed_files: 1 405 | -------------------------------------------------------------------------------- /spec/git/pr/release/cli_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Git::Pr::Release::CLI do 2 | let(:configured_cli) { 3 | cli = Git::Pr::Release::CLI.new 4 | allow(cli).to receive(:host_and_repository_and_scheme) { 5 | [nil, "motemen/git-pr-release", "https"] 6 | } 7 | allow(cli).to receive(:git_config).with(anything) { nil } 8 | cli.configure 9 | cli 10 | } 11 | 12 | describe "#start" do 13 | subject { @cli.start } 14 | 15 | before { 16 | @cli = Git::Pr::Release::CLI.new 17 | 18 | allow(@cli).to receive(:configure) 19 | allow(@cli).to receive(:fetch_merged_prs) { merged_prs } 20 | allow(@cli).to receive(:create_release_pr) 21 | } 22 | 23 | context "When merged_prs is empty" do 24 | let(:merged_prs) { [] } 25 | it { 26 | is_expected.to eq 1 27 | expect(@cli).to have_received(:configure) 28 | expect(@cli).to have_received(:fetch_merged_prs) 29 | expect(@cli).not_to have_received(:create_release_pr) 30 | } 31 | end 32 | 33 | context "When merged_prs exists" do 34 | let(:merged_prs) { 35 | agent = Sawyer::Agent.new("http://example.com/") do |conn| 36 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 37 | conn.adapter(:test, Faraday::Adapter::Test::Stubs.new) 38 | end 39 | pr_3 = Sawyer::Resource.new(agent, load_yaml("pr_3.yml")) 40 | pr_4 = Sawyer::Resource.new(agent, load_yaml("pr_4.yml")) 41 | [pr_3, pr_4] 42 | } 43 | it { 44 | is_expected.to eq 0 45 | expect(@cli).to have_received(:configure) 46 | expect(@cli).to have_received(:fetch_merged_prs) 47 | expect(@cli).to have_received(:create_release_pr).with(merged_prs) 48 | } 49 | end 50 | end 51 | 52 | describe "#configure" do 53 | subject { @cli.configure } 54 | 55 | before { 56 | @cli = Git::Pr::Release::CLI.new 57 | 58 | allow(@cli).to receive(:git_config).with(anything) { nil } 59 | } 60 | 61 | context "When default" do 62 | before { 63 | allow(@cli).to receive(:host_and_repository_and_scheme) { 64 | [nil, "motemen/git-pr-release", "https"] 65 | } 66 | } 67 | 68 | it "configured as default" do 69 | subject 70 | 71 | expect(Octokit.api_endpoint).to eq "https://api.github.com/" 72 | expect(Octokit.web_endpoint).to eq "https://github.com/" 73 | 74 | expect(@cli.repository).to eq "motemen/git-pr-release" 75 | expect(@cli.production_branch).to eq "master" 76 | expect(@cli.staging_branch).to eq "staging" 77 | expect(@cli.template_path).to eq nil 78 | expect(@cli.labels).to eq [] 79 | expect(@cli.assign_pr_author).to eq false 80 | expect(@cli.request_pr_author_review).to eq false 81 | end 82 | end 83 | 84 | context "When GitHub Enterprise Server" do 85 | before { 86 | allow(@cli).to receive(:host_and_repository_and_scheme) { 87 | ["example.com", "motemen/git-pr-release", "https"] 88 | } 89 | } 90 | after { 91 | Octokit.reset! 92 | } 93 | 94 | it "octokit is configured" do 95 | subject 96 | 97 | expect(Octokit.api_endpoint).to eq "https://example.com/api/v3/" 98 | expect(Octokit.web_endpoint).to eq "https://example.com/" 99 | end 100 | end 101 | 102 | describe "branches" do 103 | context "When branches are set by ENV" do 104 | around do |example| 105 | original = ENV.to_hash 106 | begin 107 | ENV["GIT_PR_RELEASE_BRANCH_PRODUCTION"] = "prod" 108 | ENV["GIT_PR_RELEASE_BRANCH_STAGING"] = "dev" 109 | example.run 110 | ensure 111 | ENV.replace(original) 112 | end 113 | end 114 | 115 | it "branches are configured" do 116 | subject 117 | 118 | expect(@cli.production_branch).to eq "prod" 119 | expect(@cli.staging_branch).to eq "dev" 120 | end 121 | end 122 | 123 | context "When branches are set by git_config" do 124 | before { 125 | allow(@cli).to receive(:git_config).with("branch.production") { "production" } 126 | allow(@cli).to receive(:git_config).with("branch.staging") { "develop" } 127 | } 128 | 129 | it "branches are configured" do 130 | subject 131 | 132 | expect(@cli.production_branch).to eq "production" 133 | expect(@cli.staging_branch).to eq "develop" 134 | end 135 | end 136 | end 137 | 138 | describe "labels" do 139 | context "With ENV" do 140 | around do |example| 141 | original = ENV.to_hash 142 | begin 143 | ENV["GIT_PR_RELEASE_LABELS"] = env_labels 144 | example.run 145 | ensure 146 | ENV.replace(original) 147 | end 148 | end 149 | 150 | context "string" do 151 | let(:env_labels) { "release" } 152 | it "set labels" do 153 | subject 154 | expect(@cli.labels).to eq ["release"] 155 | end 156 | end 157 | 158 | context "comma separated string" do 159 | let(:env_labels) { "release,release2" } 160 | it "set labels" do 161 | subject 162 | expect(@cli.labels).to eq ["release", "release2"] 163 | end 164 | end 165 | 166 | context "empty string" do 167 | let(:env_labels) { "" } 168 | it "set labels as default" do 169 | subject 170 | expect(@cli.labels).to eq [] 171 | end 172 | end 173 | end 174 | 175 | context "With git_config" do 176 | before { 177 | allow(@cli).to receive(:git_config).with("labels") { "release" } 178 | } 179 | 180 | it "set labels" do 181 | subject 182 | expect(@cli.labels).to eq ["release"] 183 | end 184 | end 185 | end 186 | 187 | describe "assign_pr_author" do 188 | context "With ENV set to true" do 189 | around do |example| 190 | original = ENV.to_hash 191 | begin 192 | ENV["GIT_PR_RELEASE_ASSIGN_PR_AUTHOR"] = "true" 193 | example.run 194 | ensure 195 | ENV.replace(original) 196 | end 197 | end 198 | 199 | it "enables assign_pr_author" do 200 | subject 201 | expect(@cli.assign_pr_author).to eq true 202 | end 203 | end 204 | 205 | context "With ENV set to 1" do 206 | around do |example| 207 | original = ENV.to_hash 208 | begin 209 | ENV["GIT_PR_RELEASE_ASSIGN_PR_AUTHOR"] = "1" 210 | example.run 211 | ensure 212 | ENV.replace(original) 213 | end 214 | end 215 | 216 | it "enables assign_pr_author" do 217 | subject 218 | expect(@cli.assign_pr_author).to eq true 219 | end 220 | end 221 | 222 | context "With ENV set to false" do 223 | around do |example| 224 | original = ENV.to_hash 225 | begin 226 | ENV["GIT_PR_RELEASE_ASSIGN_PR_AUTHOR"] = "false" 227 | example.run 228 | ensure 229 | ENV.replace(original) 230 | end 231 | end 232 | 233 | it "disables assign_pr_author" do 234 | subject 235 | expect(@cli.assign_pr_author).to eq false 236 | end 237 | end 238 | 239 | context "With git_config" do 240 | before { 241 | allow(@cli).to receive(:git_config).with("assign-pr-author") { "true" } 242 | } 243 | 244 | it "enables assign_pr_author" do 245 | subject 246 | expect(@cli.assign_pr_author).to eq true 247 | end 248 | end 249 | end 250 | 251 | describe "request_pr_author_review" do 252 | context "With ENV set to true" do 253 | around do |example| 254 | original = ENV.to_hash 255 | begin 256 | ENV["GIT_PR_RELEASE_REQUEST_PR_AUTHOR_REVIEW"] = "true" 257 | example.run 258 | ensure 259 | ENV.replace(original) 260 | end 261 | end 262 | 263 | it "enables request_pr_author_review" do 264 | subject 265 | expect(@cli.request_pr_author_review).to eq true 266 | end 267 | end 268 | 269 | context "With ENV set to 1" do 270 | around do |example| 271 | original = ENV.to_hash 272 | begin 273 | ENV["GIT_PR_RELEASE_REQUEST_PR_AUTHOR_REVIEW"] = "1" 274 | example.run 275 | ensure 276 | ENV.replace(original) 277 | end 278 | end 279 | 280 | it "enables request_pr_author_review" do 281 | subject 282 | expect(@cli.request_pr_author_review).to eq true 283 | end 284 | end 285 | 286 | context "With ENV set to false" do 287 | around do |example| 288 | original = ENV.to_hash 289 | begin 290 | ENV["GIT_PR_RELEASE_REQUEST_PR_AUTHOR_REVIEW"] = "false" 291 | example.run 292 | ensure 293 | ENV.replace(original) 294 | end 295 | end 296 | 297 | it "disables request_pr_author_review" do 298 | subject 299 | expect(@cli.request_pr_author_review).to eq false 300 | end 301 | end 302 | 303 | context "With git_config" do 304 | before { 305 | allow(@cli).to receive(:git_config).with("request-pr-author-review") { "true" } 306 | } 307 | 308 | it "enables request_pr_author_review" do 309 | subject 310 | expect(@cli.request_pr_author_review).to eq true 311 | end 312 | end 313 | end 314 | end 315 | 316 | describe "#fetch_merged_prs" do 317 | subject { @cli.fetch_merged_prs } 318 | 319 | before { 320 | @cli = configured_cli 321 | 322 | agent = Sawyer::Agent.new("http://example.com/") do |conn| 323 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 324 | conn.adapter(:test, Faraday::Adapter::Test::Stubs.new) 325 | end 326 | 327 | expect(@cli).to receive(:git).with(:'rev-parse', "--is-shallow-repository") { ["false\n"] } 328 | expect(@cli).to receive(:git).with(:remote, "update", "origin") { 329 | [] 330 | } 331 | 332 | expect(@cli).to receive(:git).with(:log, "--merges", "--pretty=format:%P", "origin/master..origin/staging") { 333 | <<~GIT_LOG.each_line 334 | ad694b9c2b868e8801f9209f0ad5dd5458c49854 42bd43b80c973c8f348df3521745201be05bf194 335 | b620bead10831d2e4e15be392e0a435d3470a0ad 5c977a1827387ac7b7a85c7b827ee119165f1823 336 | GIT_LOG 337 | } 338 | expect(@cli).to receive(:git).with("ls-remote", "origin", "refs/pull/*/head") { 339 | <<~GIT_LS_REMOTE.each_line 340 | bbcd2a04ef394e91be44c24e93e52fdbca944060 refs/pull/1/head 341 | 5c977a1827387ac7b7a85c7b827ee119165f1823 refs/pull/3/head 342 | 42bd43b80c973c8f348df3521745201be05bf194 refs/pull/4/head 343 | GIT_LS_REMOTE 344 | } 345 | expect(@cli).to receive(:git).with("merge-base", "5c977a1827387ac7b7a85c7b827ee119165f1823", "origin/master") { 346 | "b620bead10831d2e4e15be392e0a435d3470a0ad".each_line 347 | } 348 | expect(@cli).to receive(:git).with("merge-base", "42bd43b80c973c8f348df3521745201be05bf194", "origin/master") { 349 | "b620bead10831d2e4e15be392e0a435d3470a0ad".each_line 350 | } 351 | 352 | client = double(Octokit::Client) 353 | @pr_3 = Sawyer::Resource.new(agent, load_yaml("pr_3.yml")) 354 | @pr_4 = Sawyer::Resource.new(agent, load_yaml("pr_4.yml")) 355 | expect(client).to receive(:pull_request).with("motemen/git-pr-release", 3) { @pr_3 } 356 | expect(client).to receive(:pull_request).with("motemen/git-pr-release", 4) { @pr_4 } 357 | allow(@cli).to receive(:client).with(no_args) { client } 358 | } 359 | 360 | it { is_expected.to eq [@pr_3, @pr_4] } 361 | end 362 | 363 | describe "#create_release_pr" do 364 | subject { @cli.create_release_pr(@merged_prs) } 365 | 366 | before { 367 | @cli = configured_cli 368 | 369 | @agent = Sawyer::Agent.new("http://example.com/") do |conn| 370 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 371 | conn.adapter(:test, Faraday::Adapter::Test::Stubs.new) 372 | end 373 | 374 | @merged_prs = [ 375 | Sawyer::Resource.new(@agent, load_yaml("pr_3.yml")), 376 | Sawyer::Resource.new(@agent, load_yaml("pr_4.yml")), 377 | ] 378 | 379 | allow(@cli).to receive(:detect_existing_release_pr) { existing_release_pr } 380 | @pr_title = "Release 2020-01-04 16:51:09 +0900" 381 | @pr_body = <<~BODY.chomp 382 | - [ ] #3 Provides a creating release pull-request object for template @hakobe 383 | - [ ] #4 use user who create PR if there is no assignee @motemen 384 | BODY 385 | allow(@cli).to receive(:build_and_merge_pr_title_and_body) { 386 | [@pr_title, @pr_body] 387 | } 388 | allow(@cli).to receive(:update_release_pr) 389 | @changed_files = [double(Sawyer::Resource)] 390 | allow(@cli).to receive(:pull_request_files) { @changed_files } 391 | } 392 | 393 | context "When create_mode" do 394 | before { 395 | @created_pr = Sawyer::Resource.new(@agent, load_yaml("pr_1.yml")) 396 | allow(@cli).to receive(:prepare_release_pr) { @created_pr } 397 | } 398 | 399 | let(:existing_release_pr) { nil } 400 | 401 | it { 402 | subject 403 | 404 | expect(@cli).to have_received(:detect_existing_release_pr) 405 | expect(@cli).to have_received(:prepare_release_pr) 406 | expect(@cli).to have_received(:pull_request_files) 407 | expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(@created_pr, @merged_prs, @changed_files) 408 | expect(@cli).to have_received(:update_release_pr).with(@created_pr, @pr_title, @pr_body, @merged_prs) 409 | } 410 | end 411 | 412 | context "When not create_mode" do 413 | before { 414 | allow(@cli).to receive(:prepare_release_pr) 415 | } 416 | 417 | let(:existing_release_pr) { double( 418 | number: 1023, 419 | rels: { html: double(href: "https://github.com/motemen/git-pr-release/pull/1023") }, 420 | )} 421 | 422 | it { 423 | subject 424 | 425 | expect(@cli).to have_received(:detect_existing_release_pr) 426 | expect(@cli).to have_received(:pull_request_files).with(existing_release_pr) 427 | expect(@cli).not_to have_received(:prepare_release_pr) 428 | expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(existing_release_pr, @merged_prs, @changed_files) 429 | expect(@cli).to have_received(:update_release_pr).with(existing_release_pr, @pr_title, @pr_body, @merged_prs) 430 | } 431 | end 432 | 433 | context "When dry_run with create_mode" do 434 | before { 435 | @created_pr = Sawyer::Resource.new(@agent, load_yaml("pr_1.yml")) 436 | allow(@cli).to receive(:prepare_release_pr) { @created_pr } 437 | 438 | @cli.instance_variable_set(:@dry_run, true) 439 | } 440 | 441 | let(:existing_release_pr) { nil } 442 | 443 | it { 444 | subject 445 | 446 | expect(@cli).to have_received(:detect_existing_release_pr) 447 | expect(@cli).not_to have_received(:prepare_release_pr) 448 | expect(@cli).not_to have_received(:pull_request_files) 449 | expect(@cli).to have_received(:build_and_merge_pr_title_and_body).with(nil, @merged_prs, []) 450 | expect(@cli).not_to have_received(:update_release_pr).with(@created_pr, @pr_title, @pr_body, @merged_prs) 451 | } 452 | end 453 | 454 | context "When overwrite_description" do 455 | before { 456 | @cli.instance_variable_set(:@overwrite_description, true) 457 | @new_pr_title = "2022-08-17 12:34:58 +0900" 458 | @new_pr_body = <<~BODY.chomp 459 | - [ ] #3 @hakobe 460 | - [ ] #4 @hakobe 461 | BODY 462 | allow(@cli).to receive(:build_pr_title_and_body) { 463 | [@new_pr_title, @new_pr_body] 464 | } 465 | } 466 | 467 | let(:existing_release_pr) { double( 468 | number: 1023, 469 | rels: { html: double(href: "https://github.com/motemen/git-pr-release/pull/1023") }, 470 | )} 471 | 472 | it { 473 | subject 474 | 475 | expect(@cli).not_to have_received(:build_and_merge_pr_title_and_body) 476 | expect(@cli).to have_received(:update_release_pr).with(existing_release_pr, @new_pr_title, @new_pr_body, @merged_prs) 477 | } 478 | end 479 | end 480 | 481 | describe "#prepare_release_pr" do 482 | subject { @cli.prepare_release_pr } 483 | 484 | before { 485 | @cli = configured_cli 486 | 487 | @client = double(Octokit::Client) 488 | allow(@client).to receive(:create_pull_request) 489 | allow(@cli).to receive(:client) { @client } 490 | } 491 | 492 | it { 493 | subject 494 | 495 | expect(@client).to have_received(:create_pull_request).with( 496 | "motemen/git-pr-release", 497 | "master", 498 | "staging", 499 | "Preparing release pull request...", 500 | "", # empby body 501 | ) 502 | } 503 | end 504 | 505 | describe "#build_and_merge_pr_title_and_body" do 506 | subject { @cli.build_and_merge_pr_title_and_body(release_pr, @merged_prs, changed_files) } 507 | 508 | before { 509 | @cli = configured_cli 510 | 511 | @merged_prs = [double(Sawyer::Resource)] 512 | allow(@cli).to receive(:build_pr_title_and_body) { ["PR Title", "PR Body"] } 513 | allow(@cli).to receive(:merge_pr_body) { "Merged Body" } 514 | } 515 | 516 | context "When release_pr exists" do 517 | let(:release_pr) { double(body: "Old Body") } 518 | let(:changed_files) { [double(Sawyer::Resource)] } 519 | 520 | it { 521 | is_expected.to eq ["PR Title", "Merged Body"] 522 | 523 | expect(@cli).to have_received(:build_pr_title_and_body).with(release_pr, @merged_prs, changed_files, nil) 524 | expect(@cli).to have_received(:merge_pr_body).with("Old Body", "PR Body") 525 | } 526 | end 527 | 528 | context "When release_pr is nil" do 529 | # = When dry_run with create_mode 530 | let(:release_pr) { nil } 531 | let(:changed_files) { [] } 532 | 533 | it { 534 | is_expected.to eq ["PR Title", "Merged Body"] 535 | 536 | expect(@cli).to have_received(:build_pr_title_and_body).with(release_pr, @merged_prs, changed_files, nil) 537 | expect(@cli).to have_received(:merge_pr_body).with("", "PR Body") 538 | } 539 | end 540 | end 541 | 542 | describe "#update_release_pr" do 543 | subject { @cli.update_release_pr(@release_pr, "PR Title", "PR Body", @merged_prs) } 544 | 545 | before { 546 | @cli = configured_cli 547 | 548 | @release_pr = double(number: 1023) 549 | 550 | @agent = Sawyer::Agent.new("http://example.com/") do |conn| 551 | conn.builder.handlers.delete(Faraday::Adapter::NetHttp) 552 | conn.adapter(:test, Faraday::Adapter::Test::Stubs.new) 553 | end 554 | @merged_prs = [ 555 | Sawyer::Resource.new(@agent, load_yaml("pr_3.yml")), 556 | Sawyer::Resource.new(@agent, load_yaml("pr_4.yml")), 557 | Sawyer::Resource.new(@agent, load_yaml("pr_6.yml")), 558 | ] 559 | 560 | @client = double(Octokit::Client) 561 | allow(@client).to receive(:update_pull_request) { @release_pr } 562 | allow(@client).to receive(:add_labels_to_an_issue) 563 | allow(@client).to receive(:add_assignees) 564 | allow(@client).to receive(:request_pull_request_review) 565 | allow(@cli).to receive(:client) { @client } 566 | } 567 | 568 | context "Without labels and without assign_pr_author" do 569 | it { 570 | subject 571 | 572 | expect(@client).to have_received(:update_pull_request).with( 573 | "motemen/git-pr-release", 574 | 1023, 575 | { 576 | title: "PR Title", 577 | body: "PR Body", 578 | } 579 | ) 580 | expect(@client).not_to have_received(:add_labels_to_an_issue) 581 | expect(@client).not_to have_received(:add_assignees) 582 | expect(@client).not_to have_received(:request_pull_request_review) 583 | } 584 | end 585 | 586 | context "With labels" do 587 | before { 588 | allow(@cli).to receive(:labels) { ["release"] } 589 | } 590 | it { 591 | subject 592 | 593 | expect(@client).to have_received(:update_pull_request).with( 594 | "motemen/git-pr-release", 595 | 1023, 596 | { 597 | title: "PR Title", 598 | body: "PR Body", 599 | } 600 | ) 601 | expect(@client).to have_received(:add_labels_to_an_issue).with( 602 | "motemen/git-pr-release", 1023, ["release"] 603 | ) 604 | expect(@client).not_to have_received(:add_assignees) 605 | expect(@client).not_to have_received(:request_pull_request_review) 606 | } 607 | end 608 | 609 | context "With assign_pr_author enabled" do 610 | before { 611 | allow(@cli).to receive(:assign_pr_author) { true } 612 | } 613 | 614 | it { 615 | subject 616 | 617 | expect(@client).to have_received(:update_pull_request).with( 618 | "motemen/git-pr-release", 619 | 1023, 620 | { 621 | title: "PR Title", 622 | body: "PR Body", 623 | } 624 | ) 625 | # Both pr_3.yml and pr_4.yml have hakobe as the author, so only one unique assignee 626 | # And the author of pr_6.yml is ninjinkun. 627 | expect(@client).to have_received(:add_assignees).with( 628 | "motemen/git-pr-release", 1023, ["hakobe", "ninjinkun"] 629 | ) 630 | expect(@client).not_to have_received(:request_pull_request_review) 631 | } 632 | end 633 | 634 | context "With request_pr_author_review enabled" do 635 | before { 636 | allow(@cli).to receive(:request_pr_author_review) { true } 637 | } 638 | 639 | it { 640 | subject 641 | 642 | expect(@client).to have_received(:update_pull_request).with( 643 | "motemen/git-pr-release", 644 | 1023, 645 | { 646 | title: "PR Title", 647 | body: "PR Body", 648 | } 649 | ) 650 | expect(@client).not_to have_received(:add_assignees) 651 | # Both pr_3.yml and pr_4.yml have hakobe as the author 652 | # And the author of pr_6.yml is ninjinkun. 653 | expect(@client).to have_received(:request_pull_request_review).with( 654 | "motemen/git-pr-release", 1023, reviewers: ["hakobe", "ninjinkun"] 655 | ) 656 | } 657 | end 658 | end 659 | 660 | describe "#detect_existing_release_pr" do 661 | subject { @cli.detect_existing_release_pr } 662 | 663 | before { 664 | @cli = configured_cli 665 | 666 | @client = double(Octokit::Client) 667 | allow(@cli).to receive(:client).with(no_args) { @client } 668 | } 669 | 670 | context "When exists" do 671 | before { 672 | @release_pr = double(head: double(ref: "staging"), base: double(ref: "master")) 673 | allow(@client).to receive(:pull_requests) { [@release_pr] } 674 | } 675 | 676 | it { is_expected.to eq @release_pr } 677 | end 678 | 679 | context "When not exists" do 680 | before { 681 | allow(@client).to receive(:pull_requests) { [] } 682 | } 683 | 684 | it { is_expected.to be_nil } 685 | end 686 | end 687 | 688 | describe "#pull_request_files" do 689 | subject { @cli.pull_request_files(@release_pr) } 690 | 691 | before { 692 | @cli = configured_cli 693 | 694 | @release_pr = double(number: 1023) 695 | @client = double(Octokit::Client) 696 | @changed_files = [double(Sawyer::Resource)] 697 | allow(@client).to receive(:pull_request_files) { @changed_files } 698 | allow(@client).to receive(:auto_paginate=) 699 | allow(@cli).to receive(:client) { @client } 700 | } 701 | 702 | it { 703 | is_expected.to eq @changed_files 704 | 705 | expect(@client).to have_received(:auto_paginate=).with(true) 706 | expect(@client).to have_received(:pull_request_files).with("motemen/git-pr-release", 1023) 707 | expect(@client).to have_received(:auto_paginate=).with(false) 708 | } 709 | end 710 | end 711 | --------------------------------------------------------------------------------