tractive 1.0.1 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.adoc +69 -20
- data/config.example.yaml +41 -10
- data/exe/tractive +3 -2
- data/lib/tractive/github_api/client/labels.rb +33 -0
- data/lib/tractive/github_api/client.rb +2 -0
- data/lib/tractive/info.rb +4 -4
- data/lib/tractive/migrator/converter/trac_to_github.rb +25 -6
- data/lib/tractive/migrator/engine.rb +1 -1
- data/lib/tractive/models/ticket.rb +4 -1
- data/lib/tractive/revmap_generator.rb +22 -9
- data/lib/tractive/utilities.rb +7 -0
- data/lib/tractive/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db1fec86bca93ac81ac4ce8c4f1e6fb99f0983743c55fc3275a95188c8178e0e
|
4
|
+
data.tar.gz: f2907430c9a64b4cd5e0004050c1d7eddf11a18f94a4a007bf5315706a73e23b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1828a51469e9418188cd72195b51b245bb47921b7fbc883287bd089f9b2da58d18fbf08411c9f5cc33ed4a4a92259e365931afe281e1cf810a16c97a0632fa1a
|
7
|
+
data.tar.gz: f898f265b58c41ca3ae59ee20cf52ec50ced92045a1595f23ea50e66bb4b0bd911a8695cfdc34b3a41a248a8b11bcb3a20be70eb8193aba8a7867c3f9ba4cc1e
|
data/Gemfile.lock
CHANGED
data/README.adoc
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
= Tractive: migrating from Trac to GitHub
|
2
|
-
|
3
2
|
== Purpose
|
4
3
|
|
5
4
|
Tractive is a tool that helps you migrate Trac instances to GitHub.
|
@@ -194,15 +193,17 @@ user interface.
|
|
194
193
|
|
195
194
|
| `--svn-url`
|
196
195
|
|
|
197
|
-
(required
|
198
|
-
|
199
|
-
prefix).
|
196
|
+
(required unless `--svn-local-path` set)
|
197
|
+
SVN repository URL that should be used in RevMap generation. The URL must start
|
198
|
+
with the `http://` or `https://` prefix (not the `svn://` prefix).
|
200
199
|
| String
|
201
200
|
|
202
201
|
| `--svn-local-path`
|
203
|
-
|
|
204
|
-
|
205
|
-
|
202
|
+
|
|
203
|
+
(required unless `-svn-url` set)
|
204
|
+
SVN local repository path that should be used in RevMap generation. You can
|
205
|
+
clone the svn repo locally and provide its local path if the svn repository is
|
206
|
+
offline.
|
206
207
|
| String
|
207
208
|
|
208
209
|
| `--rev-timestamp-file`
|
@@ -384,25 +385,55 @@ The pattern of a mapping is like:
|
|
384
385
|
+
|
385
386
|
[source,yaml]
|
386
387
|
----
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
388
|
+
trivial:
|
389
|
+
name: trivial
|
390
|
+
color: ff0000
|
391
|
+
major:
|
392
|
+
name: major
|
393
|
+
color: b44647
|
394
|
+
minor:
|
395
|
+
name: minor
|
396
|
+
color: f7347a
|
397
|
+
medium:
|
398
|
+
name: medium
|
399
|
+
color: f3c77c
|
395
400
|
----
|
396
401
|
|
397
402
|
`priority:`::: Priority of the Trac ticket.
|
398
403
|
+
|
399
404
|
[source,yaml]
|
400
405
|
----
|
401
|
-
Low:
|
402
|
-
|
406
|
+
Low:
|
407
|
+
name: low
|
408
|
+
color: 22dd00
|
409
|
+
High:
|
410
|
+
name: high
|
411
|
+
color: ff0000
|
412
|
+
----
|
413
|
+
|
414
|
+
|
415
|
+
`tracstate:`::: Status of the Trac ticket.
|
416
|
+
+
|
417
|
+
[source,yaml]
|
418
|
+
----
|
419
|
+
accepted:
|
420
|
+
name: accepted
|
421
|
+
color: 22dd00
|
422
|
+
assigned:
|
423
|
+
name: assigned
|
424
|
+
color: aadd88
|
425
|
+
closed:
|
426
|
+
name: closed
|
427
|
+
color: ee00aa
|
428
|
+
new:
|
429
|
+
name: new
|
430
|
+
color:
|
403
431
|
----
|
404
432
|
|
405
433
|
|
434
|
+
NOTE: As `severity`, `priority` and `tracstate` are converted into `labels` on github so there is an option to specify the `color` for those labels.
|
435
|
+
|
436
|
+
|
406
437
|
==== User mapping
|
407
438
|
|
408
439
|
`users:`:: a one-to-one mapping between Trac usernames or email addresses to
|
@@ -411,7 +442,10 @@ GitHub usernames for users, in the following pattern:
|
|
411
442
|
[source,yaml]
|
412
443
|
----
|
413
444
|
users:
|
414
|
-
{Trac email or username}:
|
445
|
+
{Trac email or username}:
|
446
|
+
email: {Github email}
|
447
|
+
name: {name of the person}
|
448
|
+
username: {username on GitHub}
|
415
449
|
...
|
416
450
|
----
|
417
451
|
|
@@ -420,8 +454,23 @@ EXAMPLE:
|
|
420
454
|
[source,yaml]
|
421
455
|
----
|
422
456
|
users:
|
423
|
-
matthew@
|
424
|
-
|
457
|
+
matthew@gmail.org:
|
458
|
+
email: matthew@example.org
|
459
|
+
name: Matthew
|
460
|
+
username: example-matt
|
461
|
+
valencia:
|
462
|
+
email: valencia
|
463
|
+
name: Valencia
|
464
|
+
username: example-vale
|
465
|
+
----
|
466
|
+
|
467
|
+
If you don't want to map a user, you can just leave the `username` empty like below:
|
468
|
+
----
|
469
|
+
users:
|
470
|
+
matthew@gmail.org:
|
471
|
+
email: matthew@example.org
|
472
|
+
name: Matthew
|
473
|
+
username:
|
425
474
|
----
|
426
475
|
|
427
476
|
==== Milestone mapping
|
data/config.example.yaml
CHANGED
@@ -24,9 +24,16 @@ revmap_path: ./example-revmap.txt
|
|
24
24
|
# the issue migration process will fail if the GitHub user specified as owner
|
25
25
|
# (assignee) does not exist.
|
26
26
|
users:
|
27
|
-
# <Trac email or username
|
28
|
-
|
29
|
-
|
27
|
+
# email: <Trac email or username>
|
28
|
+
# name: <Name of the person (optional)>
|
29
|
+
# username: <username on GitHub>
|
30
|
+
- email: matthew@example.org
|
31
|
+
name: Matthew
|
32
|
+
username: example-matt
|
33
|
+
|
34
|
+
- email: valencia
|
35
|
+
name: Valencia
|
36
|
+
username: example-vale
|
30
37
|
|
31
38
|
# Label mapping from Trac ticket to GitHub label
|
32
39
|
labels:
|
@@ -47,14 +54,38 @@ labels:
|
|
47
54
|
|
48
55
|
# less useful, but also possible:
|
49
56
|
priority:
|
50
|
-
Low:
|
51
|
-
|
57
|
+
Low:
|
58
|
+
name: low
|
59
|
+
color: 22dd00
|
60
|
+
High:
|
61
|
+
name: high
|
62
|
+
color: ff0000
|
52
63
|
severity:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
64
|
+
trivial:
|
65
|
+
name: trivial
|
66
|
+
color: ff0000
|
67
|
+
major:
|
68
|
+
name: major
|
69
|
+
color: b44647
|
70
|
+
minor:
|
71
|
+
name: minor
|
72
|
+
color: f7347a
|
73
|
+
medium:
|
74
|
+
name: medium
|
75
|
+
color: f3c77c
|
76
|
+
tracstate:
|
77
|
+
accepted:
|
78
|
+
name: accepted
|
79
|
+
color: 22dd00
|
80
|
+
assigned:
|
81
|
+
name: assigned
|
82
|
+
color: aadd88
|
83
|
+
closed:
|
84
|
+
name: closed
|
85
|
+
color: ee00aa
|
86
|
+
new:
|
87
|
+
name: new
|
88
|
+
color:
|
58
89
|
version:
|
59
90
|
'1.3': v1.3
|
60
91
|
'1.4': v1.4
|
data/exe/tractive
CHANGED
@@ -5,6 +5,8 @@ require_relative "../lib/tractive"
|
|
5
5
|
|
6
6
|
class TractiveCommand < Thor
|
7
7
|
default_command :migrate
|
8
|
+
class_option "logfile", type: :string, aliases: ["-L", "--log-file"],
|
9
|
+
desc: "Name of the logfile to output logs to."
|
8
10
|
|
9
11
|
desc "<OPTIONS>", "Migrate Trac instances to modern Git management platforms like GitHub and GitLab"
|
10
12
|
method_option "attachmentexporter", type: :string, aliases: ["-A", "--attachment-exporter"],
|
@@ -33,8 +35,6 @@ class TractiveCommand < Thor
|
|
33
35
|
desc: "Import issues from a json file"
|
34
36
|
method_option "info", type: :boolean, aliases: ["-i", "--info"],
|
35
37
|
desc: "Reports existing labels and users in the database"
|
36
|
-
method_option "logfile", type: :string, aliases: ["-L", "--log-file"],
|
37
|
-
desc: "Name of the logfile to output logs to."
|
38
38
|
method_option "mockdeleted", type: :boolean, aliases: ["-M", "--mockup"],
|
39
39
|
desc: "Import from 0 and mocking tickets deleted on trac"
|
40
40
|
method_option "openedonly", type: :boolean, aliases: ["-o", "--opened-only"],
|
@@ -64,6 +64,7 @@ class TractiveCommand < Thor
|
|
64
64
|
def generate_revmap
|
65
65
|
verify_revmap_generator_options!(options)
|
66
66
|
|
67
|
+
Tractive::Utilities.setup_logger(output_stream: options[:log_file] || $stderr, verbose: options[:verbose])
|
67
68
|
Tractive::RevmapGenerator.new(
|
68
69
|
options["revtimestampfile"],
|
69
70
|
options["svnurl"],
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubApi
|
4
|
+
class Client
|
5
|
+
module Labels
|
6
|
+
def list_labels(repo, params = {})
|
7
|
+
JSON.parse(
|
8
|
+
RestClient.get(
|
9
|
+
"https://api.github.com/repos/#{repo}/labels",
|
10
|
+
{
|
11
|
+
"Authorization" => "token #{@token}",
|
12
|
+
params: params
|
13
|
+
}
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
alias labels list_labels
|
18
|
+
|
19
|
+
def create_label(repo, params)
|
20
|
+
JSON.parse(
|
21
|
+
RestClient.post(
|
22
|
+
"https://api.github.com/repos/#{repo}/labels",
|
23
|
+
params.to_json,
|
24
|
+
{
|
25
|
+
"Authorization" => "token #{@token}",
|
26
|
+
"Accept" => "application/vnd.github.v3+json"
|
27
|
+
}
|
28
|
+
)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "client/issues"
|
4
|
+
require_relative "client/labels"
|
4
5
|
require_relative "client/milestones"
|
5
6
|
|
6
7
|
# Service to perform github actions
|
7
8
|
module GithubApi
|
8
9
|
class Client
|
9
10
|
include GithubApi::Client::Issues
|
11
|
+
include GithubApi::Client::Labels
|
10
12
|
include GithubApi::Client::Milestones
|
11
13
|
|
12
14
|
def initialize(options = {})
|
data/lib/tractive/info.rb
CHANGED
@@ -30,15 +30,15 @@ module Tractive
|
|
30
30
|
tracstates = Ticket.distinct.select(:status).select_map(:status).compact
|
31
31
|
|
32
32
|
{
|
33
|
-
"users" => Utilities.
|
33
|
+
"users" => Utilities.make_each_hash(users, %w[email name username]),
|
34
34
|
"milestones" => milestones,
|
35
35
|
"labels" => {
|
36
36
|
"type" => Utilities.make_hash("type_", types),
|
37
37
|
"component" => Utilities.make_hash("component_", components),
|
38
38
|
"resolution" => Utilities.make_hash("resolution_", resolutions),
|
39
|
-
"severity" => Utilities.
|
40
|
-
"priority" => Utilities.
|
41
|
-
"tracstate" => Utilities.
|
39
|
+
"severity" => Utilities.make_each_hash(severity, %w[name color]),
|
40
|
+
"priority" => Utilities.make_each_hash(priorities, %w[name color]),
|
41
|
+
"tracstate" => Utilities.make_each_hash(tracstates, %w[name color])
|
42
42
|
}
|
43
43
|
}
|
44
44
|
end
|
@@ -17,6 +17,9 @@ module Migrator
|
|
17
17
|
@wiki_attachments_url = args[:cfg]["trac"]["wiki_attachments_url"]
|
18
18
|
|
19
19
|
load_milestone_map
|
20
|
+
create_labels_on_github(@labels_cfg["severity"].values)
|
21
|
+
create_labels_on_github(@labels_cfg["priority"].values)
|
22
|
+
create_labels_on_github(@labels_cfg["tracstate"].values)
|
20
23
|
|
21
24
|
@uri_parser = URI::Parser.new
|
22
25
|
@twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachurl, @changeset_base_url, @wiki_attachments_url)
|
@@ -116,7 +119,8 @@ module Migrator
|
|
116
119
|
# compose body
|
117
120
|
body = [badgetable, body, footer].join("\n\n___\n")
|
118
121
|
|
119
|
-
labels.add("owner:#{github_assignee}")
|
122
|
+
labels.add("name" => "owner:#{github_assignee}") unless github_assignee.nil? || github_assignee.empty?
|
123
|
+
labels = labels.map { |label| label["name"] }
|
120
124
|
|
121
125
|
issue = {
|
122
126
|
"title" => ticket[:summary],
|
@@ -129,7 +133,7 @@ module Migrator
|
|
129
133
|
|
130
134
|
if @users.key?(ticket[:owner])
|
131
135
|
owner = trac_mail(ticket[:owner])
|
132
|
-
github_owner = @users[owner]
|
136
|
+
github_owner = @users[owner]["username"]
|
133
137
|
$logger.debug("..owner in trac: #{owner}")
|
134
138
|
$logger.debug("..assignee in GitHub: #{github_owner}")
|
135
139
|
issue["assignee"] = github_owner
|
@@ -155,11 +159,11 @@ module Migrator
|
|
155
159
|
private
|
156
160
|
|
157
161
|
def map_user(user)
|
158
|
-
@users[
|
162
|
+
@users.fetch(user, {})["email"] || user
|
159
163
|
end
|
160
164
|
|
161
165
|
def map_assignee(user)
|
162
|
-
@users[
|
166
|
+
@users.fetch(user, {})["email"]
|
163
167
|
end
|
164
168
|
|
165
169
|
def load_milestone_map
|
@@ -168,9 +172,9 @@ module Migrator
|
|
168
172
|
newmilestonekeys = @milestonesfromtrac.keys - @milestonemap.keys
|
169
173
|
|
170
174
|
newmilestonekeys.each do |milestonelabel|
|
171
|
-
milestone
|
175
|
+
milestone = {
|
172
176
|
"title" => milestonelabel.to_s,
|
173
|
-
"state" => @milestonesfromtrac[milestonelabel][:completed].
|
177
|
+
"state" => @milestonesfromtrac[milestonelabel][:completed].to_i.zero? ? "open" : "closed",
|
174
178
|
"description" => @milestonesfromtrac[milestonelabel][:description] || "no description in trac",
|
175
179
|
"due_on" => "2012-10-09T23:39:01Z"
|
176
180
|
}
|
@@ -194,6 +198,21 @@ module Migrator
|
|
194
198
|
nil
|
195
199
|
end
|
196
200
|
|
201
|
+
def create_labels_on_github(labels)
|
202
|
+
return if labels.nil? || labels.empty?
|
203
|
+
|
204
|
+
existing_labels = @client.labels(@repo, per_page: 100).map { |label| label["name"] }
|
205
|
+
new_labels = labels.reject { |label| existing_labels.include?(label["name"]) }
|
206
|
+
|
207
|
+
new_labels.each do |label|
|
208
|
+
params = { name: label["name"] }
|
209
|
+
params["color"] = label["color"] unless label["color"].nil?
|
210
|
+
|
211
|
+
@client.create_label(@repo, params)
|
212
|
+
$logger.info("Created label: #{label["name"]}")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
197
216
|
def ticket_change(append, meta)
|
198
217
|
# kind
|
199
218
|
kind = if meta[:ticket]
|
@@ -58,7 +58,7 @@ module Migrator
|
|
58
58
|
@dry_run = args[:opts][:dryrun]
|
59
59
|
@output_file = File.new(dry_run_output_file, "w+")
|
60
60
|
@delimiter = "{"
|
61
|
-
@revmap = load_revmap_file(args[:opts][:revmapfile] || args[:cfg]["
|
61
|
+
@revmap = load_revmap_file(args[:opts][:revmapfile] || args[:cfg]["revmap_path"])
|
62
62
|
@safetychecks = safetychecks
|
63
63
|
@start_ticket = (start_ticket || (@last_created_issue + 1)).to_i
|
64
64
|
@filter_closed = filter_closed
|
@@ -19,8 +19,11 @@ module Tractive
|
|
19
19
|
def filter_column(options)
|
20
20
|
return self if options.nil? || options.values.compact.empty?
|
21
21
|
|
22
|
-
|
22
|
+
case options[:operator].downcase
|
23
|
+
when "like"
|
23
24
|
where { Sequel.like(options[:column_name].to_sym, options[:column_value]) }
|
25
|
+
when "not like"
|
26
|
+
where { ~Sequel.like(options[:column_name].to_sym, options[:column_value]) }
|
24
27
|
else
|
25
28
|
where { Sequel.lit("#{options[:column_name]} #{options[:operator]} '#{options[:column_value]}'") }
|
26
29
|
end
|
@@ -11,6 +11,7 @@ module Tractive
|
|
11
11
|
@duplicate_message_commits = {}
|
12
12
|
@last_revision = nil
|
13
13
|
@pinwheel = %w[| / - \\]
|
14
|
+
@skipped = []
|
14
15
|
@output_file = output_file
|
15
16
|
end
|
16
17
|
|
@@ -20,6 +21,7 @@ module Tractive
|
|
20
21
|
|
21
22
|
File.open(@output_file, "w+") do |file|
|
22
23
|
File.foreach(@input_file) do |line|
|
24
|
+
i += 1
|
23
25
|
info = extract_info_from_line(line)
|
24
26
|
next if @last_revision == info[:revision]
|
25
27
|
|
@@ -29,9 +31,10 @@ module Tractive
|
|
29
31
|
percent = ((i.to_f / line_count) * 100).round(2)
|
30
32
|
progress = "=" * (percent.to_i / 2) unless i < 2
|
31
33
|
printf("\rProgress: [%<progress>-50s] %<percent>.2f%% %<spinner>s", progress: progress, percent: percent, spinner: @pinwheel.rotate!.first)
|
32
|
-
i += 1
|
33
34
|
end
|
34
35
|
end
|
36
|
+
|
37
|
+
$logger.info "\n\nFollowing revisions are skipped because they don't have a corresponding git commit. #{@skipped}"
|
35
38
|
end
|
36
39
|
|
37
40
|
private
|
@@ -52,11 +55,13 @@ module Tractive
|
|
52
55
|
# get sha from git api
|
53
56
|
commits = git_commits(info)
|
54
57
|
|
55
|
-
if commits.
|
56
|
-
|
58
|
+
if commits.empty?
|
59
|
+
@skipped << info[:revision]
|
60
|
+
elsif commits.count == 1
|
61
|
+
file.puts "#{info[:revision]} | #{commits.values[0]&.join(",")}"
|
57
62
|
else
|
58
63
|
message = commit_message_from_svn(info[:revision])
|
59
|
-
file.puts "#{info[:revision]} | #{@duplicate_commits[info[:timestamp]][message]
|
64
|
+
file.puts "#{info[:revision]} | #{@duplicate_commits[info[:timestamp]][message]&.join(",")}"
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
@@ -91,16 +96,24 @@ module Tractive
|
|
91
96
|
end
|
92
97
|
|
93
98
|
def commits_from_git_repo(info)
|
94
|
-
|
99
|
+
shas_command = "git rev-list --after=#{info[:timestamp]} --until=#{info[:timestamp]} --committer=#{info[:author]} --all"
|
100
|
+
shas = Dir.chdir(@git_local_repo_path) do
|
101
|
+
`#{shas_command}`
|
102
|
+
end
|
103
|
+
|
104
|
+
commits_command = "git rev-list --after=#{info[:timestamp]} --until=#{info[:timestamp]} --committer=#{info[:author]} --all --format='medium'"
|
95
105
|
commits = Dir.chdir(@git_local_repo_path) do
|
96
|
-
`#{
|
106
|
+
`#{commits_command}`
|
97
107
|
end
|
98
108
|
|
109
|
+
regex = /#{shas.split("\n").map { |sha| "(?=commit #{sha})" }.join "|"}/
|
110
|
+
|
99
111
|
commits_arr = []
|
100
|
-
commits.split(
|
112
|
+
commits.split(regex).each do |commit_info|
|
101
113
|
commit_hash = {}
|
102
|
-
|
103
|
-
commit_hash[:
|
114
|
+
info = commit_info.split("\n", 4)
|
115
|
+
commit_hash[:sha] = info[0].split.last
|
116
|
+
commit_hash[:message] = info.last.strip.gsub("\n", "").gsub(/\s+/, " ")
|
104
117
|
|
105
118
|
commits_arr << commit_hash
|
106
119
|
end
|
data/lib/tractive/utilities.rb
CHANGED
@@ -7,6 +7,13 @@ module Tractive
|
|
7
7
|
array.map { |i| [i, "#{prefix}#{i}"] }.to_h
|
8
8
|
end
|
9
9
|
|
10
|
+
def make_each_hash(values, keys)
|
11
|
+
values.map do |value|
|
12
|
+
value = [value] unless value.is_a?(Array)
|
13
|
+
[value[0], keys.zip(value).to_h]
|
14
|
+
end.to_h
|
15
|
+
end
|
16
|
+
|
10
17
|
def setup_db!(db_url)
|
11
18
|
files_to_load = [
|
12
19
|
"lib/tractive/models/attachment.rb",
|
data/lib/tractive/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tractive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mysql2
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- lib/tractive/github_api.rb
|
141
141
|
- lib/tractive/github_api/client.rb
|
142
142
|
- lib/tractive/github_api/client/issues.rb
|
143
|
+
- lib/tractive/github_api/client/labels.rb
|
143
144
|
- lib/tractive/github_api/client/milestones.rb
|
144
145
|
- lib/tractive/graceful_quit.rb
|
145
146
|
- lib/tractive/info.rb
|