pr-merger 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +67 -0
- data/Rakefile +1 -0
- data/bin/pr-merger +80 -0
- data/lib/pr-merger.rb +122 -0
- data/lib/pr-merger/version.rb +3 -0
- data/pr-merger.gemspec +26 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 07bcad60e1e3ea9859a006c2d128d9d6064b1e28d203af88807dea4085e19d70
|
4
|
+
data.tar.gz: cf613b6b664e8000846cbd262237a926911360927aa3420c1809bb6ea5b0e360
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 848dd327551abe6cd100640ef9b9844f7838e6c308b5a2a165e250e1564c33783e28edebdb8ec349b8cc7b9c3cb191ea665c67dc6a0216bf1026805ec4d1db0c
|
7
|
+
data.tar.gz: 3f15cdf97645f1d4ec9582154b660f3df64cf1412fb492b780dcd8c8cf491603aaa4c23255c943dc68c5f4523a22faacd46afe771b15cc00da4216f2c18eed64
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016+ Cloudaper
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Pull Request Merger
|
2
|
+
|
3
|
+
Merge open pull requests on GitHub all together to create a new branch with all changes.
|
4
|
+
|
5
|
+
Read more about the workflow we use the PR Merger for at [@cloudaper](https://github.com/cloudaper) in our [Medium story](https://medium.com/cloudaper/devops-hack-make-all-the-work-in-progress-accessible-with-pull-request-merger-47b58dc51207).
|
6
|
+
|
7
|
+
Any feedback or even a pull request welcomed!
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
You need Ruby to use PR Merger.
|
12
|
+
|
13
|
+
Run this command:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
gem install pr-merger
|
17
|
+
```
|
18
|
+
|
19
|
+
or add
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'pr-merger'
|
23
|
+
```
|
24
|
+
|
25
|
+
to your Gemfile.
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
```shell
|
30
|
+
$ pr-merger --help
|
31
|
+
Usage: pr-merger --access-token TOKEN --base-repo REPO --base-branch BRANCH --merge-branch BRANCH [--fork-repo]
|
32
|
+
```
|
33
|
+
|
34
|
+
You have to provide several arguments to PR Merger:
|
35
|
+
|
36
|
+
- **`--access-token`**
|
37
|
+
This is GitHub personal access token to access the repository details and update [statuses](https://help.github.com/articles/about-status-checks). You can generate one in [user's settings](https://github.com/settings/tokens); select `repo` scope. Please read the [know issues](#known-issues) section below.
|
38
|
+
E.g.: `472a3a8f5315a3435a295091a365d5f9fb736d84`.
|
39
|
+
|
40
|
+
- **`--base-repo`**
|
41
|
+
This is the name of the base repository.
|
42
|
+
E.g.: `cloudaper/pr-merger`.
|
43
|
+
|
44
|
+
- **`--base-branch`**
|
45
|
+
This is the name of the base branch, where the pull requests are merged to – usually master.
|
46
|
+
E.g.: `master`.
|
47
|
+
|
48
|
+
- **`--merge-branch`**
|
49
|
+
This is the name of newly created branch with merged pull requests.
|
50
|
+
E.g.: `merged-prs`
|
51
|
+
|
52
|
+
- **`--fork-repo`**
|
53
|
+
Add this option if merging from forked repository: this means the base repository will be used instead of fork for base branch.
|
54
|
+
|
55
|
+
The assembled command should look like this:
|
56
|
+
|
57
|
+
```shell
|
58
|
+
pr-merger --access-token 2a3a8f5315a3435a295091a365d5f9fb736d84 --base-repo "cloudaper/pr-merger" --base-branch master --merge-branch merged-prs
|
59
|
+
```
|
60
|
+
|
61
|
+
If there is any pull request you don't want to merge, just add `[skip merge]` after the pull request title.
|
62
|
+
|
63
|
+
## Known issues
|
64
|
+
|
65
|
+
Currently there are two possible security issues, which you should take into account before using PR Merger. First, PR Merger is using _Personal access token_, which basically equals to your GitHub password, it can therefore access all the repositories the user has access to. Second, if you want to merge PRs from forked repositories, the machine you're running PR Merger at has to have access to all those repositories – this means [SSH deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) cannot be used.
|
66
|
+
|
67
|
+
Recommended way to solve both those issues is to create a separate [machine user](https://developer.github.com/v3/guides/managing-deploy-keys/#machine-users) with access only to the repositories in question. However, the token still enables a full control of those repositories.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/pr-merger
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.expand_path('lib')
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'pr-merger'
|
6
|
+
|
7
|
+
def show_version
|
8
|
+
puts "pr-merger v#{PrMerger::VERSION}"
|
9
|
+
|
10
|
+
exit 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def show_help
|
14
|
+
puts <<~HELP
|
15
|
+
Usage: pr-merger --access-token TOKEN --base-repo REPO --base-branch BRANCH --merge-branch BRANCH [--fork-repo]
|
16
|
+
HELP
|
17
|
+
|
18
|
+
exit 0
|
19
|
+
end
|
20
|
+
|
21
|
+
options = {}
|
22
|
+
|
23
|
+
parser = OptionParser.new do|opts|
|
24
|
+
opts.banner = "Usage: pr-merger [options]"
|
25
|
+
|
26
|
+
opts.on('--access-token TOKEN') do |option|
|
27
|
+
options[:access_token] = option
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('--base-repo REPO') do |option|
|
31
|
+
options[:base_repo] = option
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on('--base-branch BRANCH') do |option|
|
35
|
+
options[:base_branch] = option
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('--merge-branch BRANCH') do |option|
|
39
|
+
options[:merge_branch] = option
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('--fork-repo') do
|
43
|
+
options[:fork_repo] = true
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('-v', '--version') do
|
47
|
+
show_version
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on('--debug') do
|
51
|
+
options[:debug] = true
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('-h', '--help') do
|
55
|
+
show_help
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
parser.parse!
|
61
|
+
|
62
|
+
show_help if options.empty?
|
63
|
+
|
64
|
+
merger = PrMerger::Merger.new(access_token: options[:access_token])
|
65
|
+
|
66
|
+
exit merger.run(base_repo: options[:base_repo],
|
67
|
+
base_branch: options[:base_branch],
|
68
|
+
merge_branch: options[:merge_branch],
|
69
|
+
fork_repo: options[:fork_repo])
|
70
|
+
|
71
|
+
rescue => e
|
72
|
+
STDERR.puts "ERROR: #{e.message}"
|
73
|
+
|
74
|
+
if options[:debug]
|
75
|
+
STDERR.puts
|
76
|
+
STDERR.puts e.backtrace
|
77
|
+
end
|
78
|
+
|
79
|
+
exit 1
|
80
|
+
end
|
data/lib/pr-merger.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'pr-merger/version'
|
2
|
+
require 'tty-command'
|
3
|
+
require 'octokit'
|
4
|
+
|
5
|
+
Octokit.auto_paginate = true
|
6
|
+
|
7
|
+
# Merges all opened GitHub PRs to a new branch
|
8
|
+
module PrMerger
|
9
|
+
class Merger
|
10
|
+
APP_CONTEXT = 'pr-merger'.freeze
|
11
|
+
SKIP_MERGE = '[skip merge]'.freeze
|
12
|
+
|
13
|
+
def initialize(access_token:)
|
14
|
+
@gh = Octokit::Client.new(access_token: access_token)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(base_repo:, base_branch:, merge_branch:, fork_repo:)
|
18
|
+
@base_repo = base_repo
|
19
|
+
@base_branch = base_branch
|
20
|
+
|
21
|
+
pull_requests = @gh.pull_requests(@base_repo, state: 'open').reverse
|
22
|
+
|
23
|
+
cmd = TTY::Command.new
|
24
|
+
active_branch_result = cmd.run! 'git rev-parse --abbrev-ref HEAD'
|
25
|
+
|
26
|
+
if active_branch_result.success?
|
27
|
+
previous_branch = active_branch_result.out.strip
|
28
|
+
else
|
29
|
+
previous_branch = cmd.run('cat .git/HEAD').strip
|
30
|
+
end
|
31
|
+
|
32
|
+
cmd.run 'git checkout', @base_branch
|
33
|
+
|
34
|
+
if fork_repo
|
35
|
+
cmd.run! 'git remote add upstream', "git@github.com:#{@base_repo}.git"
|
36
|
+
cmd.run 'git fetch upstream', @base_branch
|
37
|
+
cmd.run 'git reset --hard', "upstream/#{@base_branch}"
|
38
|
+
else
|
39
|
+
cmd.run 'git reset --hard', "origin/#{@base_branch}"
|
40
|
+
end
|
41
|
+
|
42
|
+
cmd.run 'git checkout -b', merge_branch
|
43
|
+
|
44
|
+
merge_statuses = pull_requests.map do |pr|
|
45
|
+
process_pr(pr, cmd)
|
46
|
+
end
|
47
|
+
|
48
|
+
cmd.run 'git checkout', previous_branch
|
49
|
+
|
50
|
+
merge_statuses.all? { |status| status }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Process a given pull request
|
56
|
+
def process_pr(pr, cmd)
|
57
|
+
head = pr[:head]
|
58
|
+
repo = head[:repo]
|
59
|
+
|
60
|
+
pending_status(pr, 'Merge in progress.')
|
61
|
+
|
62
|
+
return true if skip_pr?(pr)
|
63
|
+
|
64
|
+
begin
|
65
|
+
cmd.run 'git fetch', repo[:ssh_url], head[:ref] if repo
|
66
|
+
|
67
|
+
merge_status = cmd.run! 'git merge --no-ff --no-edit', head[:sha]
|
68
|
+
|
69
|
+
if merge_status.success?
|
70
|
+
success_status(pr, "Merge with '#{@base_branch}' was successful.")
|
71
|
+
else
|
72
|
+
cmd.run 'git merge --abort'
|
73
|
+
|
74
|
+
message = "Failed to merge '#{head[:ref]} with #{@base_branch}."
|
75
|
+
failure_status(pr, message)
|
76
|
+
end
|
77
|
+
rescue => e
|
78
|
+
failure_status(pr, "Merge encountered an error: #{e.message}.")
|
79
|
+
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Skip a given pull request if it includes skip merge message
|
87
|
+
def skip_pr?(pr)
|
88
|
+
return false unless pr[:title].include?(SKIP_MERGE)
|
89
|
+
|
90
|
+
failure_status(pr, "Skipping #{pr[:head][:ref]}.")
|
91
|
+
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def pending_status(pr, message)
|
96
|
+
send_status(pr, 'pending', message)
|
97
|
+
end
|
98
|
+
|
99
|
+
def error_status(pr, message)
|
100
|
+
send_status(pr, 'error', message)
|
101
|
+
end
|
102
|
+
|
103
|
+
def failure_status(pr, message)
|
104
|
+
send_status(pr, 'failure', message)
|
105
|
+
end
|
106
|
+
|
107
|
+
def success_status(pr, message)
|
108
|
+
send_status(pr, 'success', message)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Send Octokit status
|
112
|
+
def send_status(pr, status, message)
|
113
|
+
@gh.create_status(
|
114
|
+
@base_repo,
|
115
|
+
pr[:head][:sha],
|
116
|
+
status,
|
117
|
+
context: APP_CONTEXT,
|
118
|
+
description: message[0..140]
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/pr-merger.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'pr-merger/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'pr-merger'
|
7
|
+
spec.version = PrMerger::VERSION
|
8
|
+
spec.authors = ['Tibor Szolár', 'Josef Strzibny']
|
9
|
+
spec.email = ['tibor.szolar@seznam.cz', 'strzibny@strzibny.name']
|
10
|
+
|
11
|
+
spec.summary = %q{Merges all opened GitHub PRs to a new branch.}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = 'https://github.com/cloudaper/pr-merger'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = 'bin'
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.8'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'octokit', '~> 4.0'
|
25
|
+
spec.add_runtime_dependency 'tty-command', '~> 0.2'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pr-merger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tibor Szolár
|
8
|
+
- Josef Strzibny
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-09-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.8'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.8'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: octokit
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '4.0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '4.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: tty-command
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.2'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.2'
|
70
|
+
description: ''
|
71
|
+
email:
|
72
|
+
- tibor.szolar@seznam.cz
|
73
|
+
- strzibny@strzibny.name
|
74
|
+
executables:
|
75
|
+
- pr-merger
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/pr-merger
|
85
|
+
- lib/pr-merger.rb
|
86
|
+
- lib/pr-merger/version.rb
|
87
|
+
- pr-merger.gemspec
|
88
|
+
homepage: https://github.com/cloudaper/pr-merger
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.7.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Merges all opened GitHub PRs to a new branch.
|
112
|
+
test_files: []
|