export-pull-requests 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|