export-pull-requests 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes +23 -0
- data/README.md +83 -0
- data/bin/epr +128 -58
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d378c2d847231e7d5b29e98aeec72a952100258
|
4
|
+
data.tar.gz: f0a364c762a48ec22d0dc35ed1f94fb501bf76ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 007731af9e311ef0f38155aa1c0a13c3dafb61211939239f3f2a5ac7483d953d1e1daac26ac1c511c51d6297595b151368c7a093918b84fab7bd84a7f879e1e6
|
7
|
+
data.tar.gz: 663950b5114a6634b51d063885fc9e862766694f4f9817b7da5f900affa39ff868629de0aab642fd4ba005febbf4bae1aceb3eba75c0923838d45ff67fc6a335
|
data/Changes
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
--------------------
|
2
|
+
v0.2.0 2017-09-21
|
3
|
+
--------------------
|
4
|
+
Enhancements:
|
5
|
+
* Add support for issues
|
6
|
+
|
7
|
+
Changes:
|
8
|
+
* Always show Repository column (was excluded if only 1 repo was given)
|
9
|
+
|
10
|
+
--------------------
|
11
|
+
v0.1.1 2017-07-23
|
12
|
+
--------------------
|
13
|
+
Enhancements:
|
14
|
+
* Add support for Bitbucket
|
15
|
+
|
16
|
+
--------------------
|
17
|
+
v0.1.0 2017-07-23
|
18
|
+
--------------------
|
19
|
+
Enhancements:
|
20
|
+
* Add support for GitLab
|
21
|
+
|
22
|
+
Changes:
|
23
|
+
* Do not require a token
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Export Pull Requests
|
2
|
+
|
3
|
+
Export pull requests/merge requests and/or issues to a CSV file.
|
4
|
+
|
5
|
+
Supports GitHub, GitLab, and Bitbucket.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
[Ruby](https://www.ruby-lang.org/en/documentation/installation/) is required.
|
10
|
+
|
11
|
+
With Ruby installed run:
|
12
|
+
|
13
|
+
gem install export-pull-requests
|
14
|
+
|
15
|
+
This installs the `epr` executable.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
usage: epr [-hv] [-s state] [-t token] [-c user1,user2...] user/repo1 [user/repo2...]
|
20
|
+
-c, --creator=USER1,USER2,... Export PRs created by given username(s); prepend `!' to exclude user
|
21
|
+
-h, --help Show this message
|
22
|
+
-p, --provider=NAME Service provider: bitbucket, github, or gitlab; defaults to github
|
23
|
+
-t, --token=TOKEN API token
|
24
|
+
-s, --state=STATE Export PRs in the given state, defaults to open
|
25
|
+
-v, --version epr version
|
26
|
+
-x, --export=TYPE What to export: pr, issues, or all; defaults to all
|
27
|
+
|
28
|
+
### Config
|
29
|
+
|
30
|
+
These can all be set by one of the below methods or [via the command line](#usage).
|
31
|
+
|
32
|
+
#### Token
|
33
|
+
|
34
|
+
The API token can be set by:
|
35
|
+
|
36
|
+
* `EPR_TOKEN` environment variable
|
37
|
+
* `epr.token` setting in `.gitconfig`
|
38
|
+
* `github.oauth-token` setting in `.gitconfig`
|
39
|
+
|
40
|
+
#### Default Service
|
41
|
+
|
42
|
+
github is the default. You can set a new default via `EPR_SERVICE`.
|
43
|
+
|
44
|
+
### Examples
|
45
|
+
|
46
|
+
Export open PRs and issues in `sshaw/git-link` and `sshaw/itunes_store_transporter`:
|
47
|
+
|
48
|
+
epr sshaw/git-link sshaw/itunes_store_transporter > pr.csv
|
49
|
+
|
50
|
+
Export open pull request not created by `sshaw` in `padrino/padrino-framework`:
|
51
|
+
|
52
|
+
epr -x pr -c '!sshaw' padrino/padrino-framework > pr.csv
|
53
|
+
|
54
|
+
Export open merge requests from a GitLab project:
|
55
|
+
|
56
|
+
epr -x pr -p gitlab gitlab-org/gitlab-ce > pr.csv
|
57
|
+
|
58
|
+
Export all issues from a GitLab project:
|
59
|
+
|
60
|
+
epr -x issues -p gitlab gitlab-org/gitlab-ce > pr.csv
|
61
|
+
|
62
|
+
## Service Notes
|
63
|
+
|
64
|
+
### Bitbucket
|
65
|
+
|
66
|
+
You can use [app passwords](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html) for the API token.
|
67
|
+
Just provide your token HTTP Auth style using: `username:app_password`.
|
68
|
+
|
69
|
+
### GitLab
|
70
|
+
|
71
|
+
Authentication can be done via a [personal access token](https://gitlab.com/profile/personal_access_tokens).
|
72
|
+
|
73
|
+
Currently the API endpoint URL is hardcoded to `https://gitlab.com/api/v4`.
|
74
|
+
|
75
|
+
Enterprise editions of GitLab have an [issue export feature](https://docs.gitlab.com/ee/user/project/issues/csv_export.html).
|
76
|
+
|
77
|
+
## Author
|
78
|
+
|
79
|
+
Skye Shaw [skye.shaw AT gmail]
|
80
|
+
|
81
|
+
## License
|
82
|
+
|
83
|
+
Released under the MIT License: www.opensource.org/licenses/MIT
|
data/bin/epr
CHANGED
@@ -9,8 +9,15 @@ require "github_api"
|
|
9
9
|
require "gitlab"
|
10
10
|
require "bitbucket_rest_api"
|
11
11
|
|
12
|
-
VERSION = "0.
|
12
|
+
VERSION = "0.2.0"
|
13
13
|
SERVICES = %w[github gitlab bitbucket]
|
14
|
+
GIT_CONFIGS = %w[epr.token github.oauth-token]
|
15
|
+
|
16
|
+
TYPE_ISSUE = "Issue"
|
17
|
+
TYPE_PR = "PR"
|
18
|
+
|
19
|
+
EXPORT_ISSUES = "issues"
|
20
|
+
EXPORT_PRS = "pr"
|
14
21
|
|
15
22
|
def localtime(t)
|
16
23
|
Time.parse(t).localtime.strftime("%x %X")
|
@@ -31,7 +38,7 @@ def lookup_token
|
|
31
38
|
return ENV["EPR_TOKEN"] unless ENV["EPR_TOKEN"].to_s.strip.empty?
|
32
39
|
|
33
40
|
begin
|
34
|
-
|
41
|
+
GIT_CONFIGS.each do |setting|
|
35
42
|
token = `git config #{setting}`.chomp
|
36
43
|
return token unless token.empty?
|
37
44
|
end
|
@@ -41,33 +48,78 @@ def lookup_token
|
|
41
48
|
end
|
42
49
|
|
43
50
|
def bitbucket(user, repo)
|
44
|
-
page = 0
|
45
|
-
rows = []
|
46
|
-
|
47
|
-
$bitbucket ||= BitBucket.new(:basic_auth => $token)
|
48
51
|
# TODO: make sure no need to translate any states
|
49
52
|
# https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/pullrequests
|
50
53
|
|
51
|
-
|
52
|
-
page += 1
|
54
|
+
$bitbucket ||= BitBucket.new(:basic_auth => $token)
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
rows = []
|
57
|
+
no_user = "Anonymous"
|
58
|
+
repo_name = "#{user}/#{repo}"
|
59
|
+
|
60
|
+
pull_requests = lambda do
|
61
|
+
page = 0
|
62
|
+
|
63
|
+
loop do
|
64
|
+
page += 1
|
65
|
+
|
66
|
+
prs = $bitbucket.repos.pull_request.all(user, repo, :page => page, :state => $filter.upcase)
|
67
|
+
prs["values"].each do |pr|
|
68
|
+
next if pr.author && skip_user?(pr.author.username)
|
69
|
+
|
70
|
+
rows << [
|
71
|
+
repo_name,
|
72
|
+
TYPE_PR,
|
73
|
+
pr.id,
|
74
|
+
pr.author ? pr.author.username : no_user,
|
75
|
+
pr.title,
|
76
|
+
pr.state,
|
77
|
+
localtime(pr.created_on),
|
78
|
+
localtime(pr.updated_on),
|
79
|
+
pr["links"].html.href
|
80
|
+
]
|
81
|
+
end
|
82
|
+
|
83
|
+
break unless prs["next"]
|
84
|
+
end
|
85
|
+
end
|
57
86
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
87
|
+
issues = lambda do
|
88
|
+
start = 0
|
89
|
+
|
90
|
+
loop do
|
91
|
+
issues = $bitbucket.issues.list_repo(user, repo, :start => start, :status => $filter)
|
92
|
+
break unless issues.any?
|
93
|
+
|
94
|
+
issues.each do |issue|
|
95
|
+
next if issue["reported_by"] && skip_user?(issue["reported_by"]["username"])
|
96
|
+
|
97
|
+
rows << [
|
98
|
+
repo_name,
|
99
|
+
TYPE_ISSUE,
|
100
|
+
issue["local_id"],
|
101
|
+
issue["reported_by"] ? issue["reported_by"]["username"] : no_user,
|
102
|
+
issue["title"],
|
103
|
+
issue["status"],
|
104
|
+
localtime(issue["utc_created_on"]),
|
105
|
+
localtime(issue["utc_last_updated"]),
|
106
|
+
# Not in response
|
107
|
+
sprintf("https://bitbucket.org/%s/issues/%s", repo_name, issue["local_id"])
|
108
|
+
]
|
109
|
+
end
|
110
|
+
|
111
|
+
start += issues.size
|
68
112
|
end
|
113
|
+
end
|
69
114
|
|
70
|
-
|
115
|
+
case $export
|
116
|
+
when EXPORT_PRS
|
117
|
+
pull_requests[]
|
118
|
+
when EXPORT_ISSUES
|
119
|
+
issues[]
|
120
|
+
else
|
121
|
+
pull_requests[]
|
122
|
+
issues[]
|
71
123
|
end
|
72
124
|
|
73
125
|
rows
|
@@ -75,23 +127,27 @@ end
|
|
75
127
|
|
76
128
|
def github(user, repo)
|
77
129
|
rows = []
|
130
|
+
method = $export == EXPORT_PRS ? :pull_requests : :issues
|
78
131
|
|
79
132
|
$gh ||= Github.new(:oauth_token => $token, :auto_pagination => true)
|
80
|
-
$gh.
|
133
|
+
$gh.public_send(method).list(:user => user, :repo => repo, :state => $filter).each_page do |page|
|
81
134
|
next if page.size.zero? # Needed for auto_pagination
|
82
135
|
|
83
|
-
page.each do |
|
84
|
-
|
136
|
+
page.each do |item|
|
137
|
+
# issues method will return issues and PRs
|
138
|
+
next if $export == EXPORT_ISSUES && item.pull_request
|
139
|
+
next if skip_user?(item.user.login)
|
85
140
|
|
86
141
|
rows << [
|
87
142
|
"#{user}/#{repo}",
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
localtime(
|
94
|
-
|
143
|
+
item.pull_request ? TYPE_PR : TYPE_ISSUE,
|
144
|
+
item.number,
|
145
|
+
item.user.login,
|
146
|
+
item.title,
|
147
|
+
item.state,
|
148
|
+
localtime(item.created_at),
|
149
|
+
localtime(item.updated_at),
|
150
|
+
item.html_url,
|
95
151
|
]
|
96
152
|
end
|
97
153
|
end
|
@@ -102,34 +158,47 @@ end
|
|
102
158
|
def gitlab(user, repo)
|
103
159
|
rows = []
|
104
160
|
|
161
|
+
case $export
|
162
|
+
when EXPORT_PRS
|
163
|
+
methods = [:merge_requests]
|
164
|
+
when EXPORT_ISSUES
|
165
|
+
methods = [:issues]
|
166
|
+
else
|
167
|
+
methods = [:merge_requests, :issues]
|
168
|
+
end
|
169
|
+
|
105
170
|
# Do we care about this differing in output?
|
106
171
|
state = $filter == "open" ? "opened" : $filter
|
107
172
|
|
108
173
|
# TODO: custom endpoint
|
109
|
-
$gitlab ||= Gitlab.client(:
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
174
|
+
$gitlab ||= Gitlab.client(:auth_token => $token, :endpoint => "https://gitlab.com/api/v4")
|
175
|
+
methods.each do |method|
|
176
|
+
$gitlab.public_send(method, "#{user}/#{repo}", :state => state).auto_paginate do |item|
|
177
|
+
next if skip_user?(item.author.username)
|
178
|
+
|
179
|
+
rows << [
|
180
|
+
"#{user}/#{repo}",
|
181
|
+
method == :issues ? TYPE_ISSUE : TYPE_PR,
|
182
|
+
# Yes, it's called iid
|
183
|
+
item.iid,
|
184
|
+
item.author.username,
|
185
|
+
item.title,
|
186
|
+
item.state,
|
187
|
+
localtime(item.created_at),
|
188
|
+
localtime(item.updated_at),
|
189
|
+
item.web_url
|
190
|
+
]
|
191
|
+
end
|
124
192
|
end
|
125
193
|
|
126
194
|
rows
|
127
195
|
end
|
128
196
|
|
129
|
-
def export_repos(
|
197
|
+
def export_repos(argv)
|
130
198
|
rows = []
|
131
|
-
rows << %w[Repository # User Title State Created Updated URL]
|
199
|
+
rows << %w[Repository Type # User Title State Created Updated URL]
|
132
200
|
|
201
|
+
repos = parse_repos(argv)
|
133
202
|
repos.each do |user, repo|
|
134
203
|
case $provider
|
135
204
|
when "github"
|
@@ -142,11 +211,7 @@ def export_repos(repos)
|
|
142
211
|
abort "unknown service provider: #$provider"
|
143
212
|
end
|
144
213
|
|
145
|
-
rows.each
|
146
|
-
# Drop Repository column if we only have one repo
|
147
|
-
r.shift unless repos.size > 1
|
148
|
-
puts r.to_csv
|
149
|
-
end
|
214
|
+
rows.each { |r| puts r.to_csv }
|
150
215
|
rows.clear
|
151
216
|
end
|
152
217
|
end
|
@@ -157,6 +222,7 @@ Hashie.logger = Logger.new(File::NULL) if defined?(Hashie)
|
|
157
222
|
|
158
223
|
$exclude_users = []
|
159
224
|
$include_users = []
|
225
|
+
$export = "all"
|
160
226
|
$filter = "open"
|
161
227
|
$provider = ENV["EPR_SERVICE"] || SERVICES[0]
|
162
228
|
$token = lookup_token
|
@@ -164,7 +230,7 @@ $token = lookup_token
|
|
164
230
|
parser = OptionParser.new do |opts|
|
165
231
|
opts.banner = "usage: #{File.basename($0)} [-hv] [-s state] [-t token] [-c user1,user2...] user/repo1 [user/repo2...]"
|
166
232
|
|
167
|
-
opts.on "-c", "--creator=
|
233
|
+
opts.on "-c", "--creator=USER1,USER2,...", Array, "Export PRs created by given username(s); prepend `!' to exclude user" do |u|
|
168
234
|
$exclude_users, $include_users = u.partition { |name| name.start_with?("!") }
|
169
235
|
$exclude_users.map! { |name| name[1..-1] } # remove "!"
|
170
236
|
end
|
@@ -178,14 +244,18 @@ parser = OptionParser.new do |opts|
|
|
178
244
|
$token = t
|
179
245
|
end
|
180
246
|
|
181
|
-
opts.on "-p, --provider=NAME", SERVICES, "Service provider,
|
247
|
+
opts.on "-p, --provider=NAME", SERVICES, "Service provider: bitbucket, github, or gitlab; defaults to github" do |name|
|
182
248
|
$provider = name
|
183
249
|
end
|
184
250
|
|
185
|
-
opts.on "-s", "--state=STATE",
|
251
|
+
opts.on "-s", "--state=STATE", "Export items in the given state, defaults to open" do |f|
|
186
252
|
$filter = f
|
187
253
|
end
|
188
254
|
|
255
|
+
opts.on "-x", "--export=WHAT", %w[pr issues all], "What to export: pr, issues, or all; defaults to all" do |x|
|
256
|
+
$export = x
|
257
|
+
end
|
258
|
+
|
189
259
|
opts.on "-v", "--version", "epr version" do
|
190
260
|
puts "v#{VERSION} (GitHub v#{Github::VERSION}, GitLab v#{Gitlab::VERSION}, Bitbucket v#{BitBucket::VERSION::STRING})"
|
191
261
|
exit
|
@@ -196,7 +266,7 @@ parser.parse!
|
|
196
266
|
abort parser.banner if ARGV.empty?
|
197
267
|
|
198
268
|
begin
|
199
|
-
export_repos(
|
269
|
+
export_repos(ARGV)
|
200
270
|
rescue => e
|
201
271
|
abort "Export failed: #{e}"
|
202
272
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: export-pull-requests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: github_api
|
@@ -67,19 +67,23 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.9'
|
69
69
|
description: Program to export GitHub, GitLab, or Bitbucket pull requests/merge requests
|
70
|
-
to CSV a file.
|
70
|
+
and issues to CSV a file.
|
71
71
|
email: skye.shaw@gmail.com
|
72
72
|
executables:
|
73
73
|
- epr
|
74
74
|
extensions: []
|
75
|
-
extra_rdoc_files:
|
75
|
+
extra_rdoc_files:
|
76
|
+
- README.md
|
77
|
+
- Changes
|
76
78
|
files:
|
79
|
+
- Changes
|
80
|
+
- README.md
|
77
81
|
- bin/epr
|
78
82
|
homepage: https://github.com/sshaw/export-pull-requests
|
79
83
|
licenses:
|
80
84
|
- MIT
|
81
85
|
metadata: {}
|
82
|
-
post_install_message:
|
86
|
+
post_install_message: Use the `epr' command to export your pull requests.
|
83
87
|
rdoc_options: []
|
84
88
|
require_paths:
|
85
89
|
- lib
|
@@ -98,5 +102,5 @@ rubyforge_project:
|
|
98
102
|
rubygems_version: 2.4.5.1
|
99
103
|
signing_key:
|
100
104
|
specification_version: 4
|
101
|
-
summary: Export pull requests to a CSV file.
|
105
|
+
summary: Export pull requests and issues to a CSV file.
|
102
106
|
test_files: []
|