tractive 1.0.12 → 1.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/README.adoc +5 -0
- data/exe/command_base.rb +2 -0
- data/exe/tractive +2 -0
- data/lib/tractive/github_api/graph_ql_client/issues.rb +29 -0
- data/lib/tractive/github_api/graph_ql_client.rb +31 -0
- data/lib/tractive/github_api.rb +1 -0
- data/lib/tractive/info.rb +13 -0
- data/lib/tractive/main.rb +5 -0
- data/lib/tractive/migrator/converter/trac_to_github.rb +17 -13
- data/lib/tractive/migrator/converter/twf_to_markdown.rb +16 -8
- data/lib/tractive/migrator/engine/migrate_from_db.rb +41 -0
- data/lib/tractive/migrator/engine.rb +2 -0
- data/lib/tractive/migrator/wikis/migrate_from_db.rb +1 -1
- data/lib/tractive/version.rb +1 -1
- data/tractive.gemspec +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71d04fdb0054f5a9ced6c246dc346d2b9c39f9bf115894805cbd1eb9411c7442
|
4
|
+
data.tar.gz: 59c54357c8495e8e505228d7eb2a65401505704ad208eb553124f637a5eb00f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2251a5f5cb89a617fcae72eea174736f8df978aa73907b89e7c8a5e4176e2cec9f9ad3e48a12b09f9111cf7ef3a0ede6692fbb1f70b0ef6577e72864b4717875
|
7
|
+
data.tar.gz: 802c59f3396270a1ccc072a13c202fe21b6899e3fe3746d95f1b1cce619fddef878c1f255089e72c21595d0fa06ebae4b2da0f28ea4393675b01fb6528bb006a
|
data/.rubocop.yml
CHANGED
data/README.adoc
CHANGED
@@ -506,6 +506,8 @@ milestones:
|
|
506
506
|
==== Attachments migration configuration
|
507
507
|
`ticket | wiki:`:: specifies the options for the tickets or wikis
|
508
508
|
|
509
|
+
`delete_mocked:`::: Whether to delete mocked tickets after migration or not
|
510
|
+
|
509
511
|
`attachments:`::: specifies method of obtaining attachments from Trac.
|
510
512
|
|
511
513
|
`url:`:::: URL to obtain Trac attachments from
|
@@ -517,9 +519,12 @@ milestones:
|
|
517
519
|
`export_script:`:::: output of a script that utilizes `trac-admin` to download
|
518
520
|
all attachments from Trac.
|
519
521
|
|
522
|
+
NOTE: To delete the issues, an organization owner must enable deleting an issue for the organization's repositories, and you must have admin or owner permissions in the repository. For more information, see "https://docs.github.com/en/issues/tracking-your-work-with-issues/deleting-an-issue[deleting an issue]".
|
523
|
+
|
520
524
|
[source,yaml]
|
521
525
|
----
|
522
526
|
ticket:
|
527
|
+
delete_mocked: true
|
523
528
|
attachments:
|
524
529
|
url: https://abc.com/raw-attachment/ticket
|
525
530
|
hashed: true
|
data/exe/command_base.rb
CHANGED
@@ -7,5 +7,7 @@ class CommandBase < Thor
|
|
7
7
|
desc: "Name of the logfile to output logs to."
|
8
8
|
class_option "config", type: :string, default: "tractive.config.yaml", banner: "<PATH>", aliases: "-c",
|
9
9
|
desc: "Set the configuration file"
|
10
|
+
class_option "git-token", type: :string,
|
11
|
+
desc: "The access token for Github actions."
|
10
12
|
class_option "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
|
11
13
|
end
|
data/exe/tractive
CHANGED
@@ -47,6 +47,8 @@ class TractiveCommand < CommandBase
|
|
47
47
|
desc: "Put all issue comments in the first message."
|
48
48
|
method_option "start", type: :numeric, aliases: ["-s", "--start-at"], banner: "<ID>",
|
49
49
|
desc: "Start migration from ticket with number <ID>"
|
50
|
+
method_option "make-owners-labels", type: :boolean,
|
51
|
+
desc: "If true, this will make a tag like `owner:<owner name>` and add it to the issue."
|
50
52
|
def migrate_tickets
|
51
53
|
Tractive::Main.new(options).run
|
52
54
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubApi
|
4
|
+
class GraphQlClient
|
5
|
+
# Methods for the Issues API
|
6
|
+
module Issues
|
7
|
+
DELETE_ISSUE_QUERY = <<~QUERY
|
8
|
+
mutation ($input: DeleteIssueInput!) {
|
9
|
+
deleteIssue(input: $input) {
|
10
|
+
repository {
|
11
|
+
name
|
12
|
+
url
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
QUERY
|
17
|
+
|
18
|
+
def delete_issue(issue_id)
|
19
|
+
variables = {
|
20
|
+
"input" => {
|
21
|
+
"issueId" => issue_id
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
Client.query(DeleteIssueQuery, variables: variables)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "graph_ql_client/issues"
|
4
|
+
|
5
|
+
require "graphql/client"
|
6
|
+
require "graphql/client/http"
|
7
|
+
|
8
|
+
# Service to perform github actions
|
9
|
+
module GithubApi
|
10
|
+
class GraphQlClient
|
11
|
+
include GithubApi::GraphQlClient::Issues
|
12
|
+
|
13
|
+
HttpAdapter = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do
|
14
|
+
attr_writer :token
|
15
|
+
|
16
|
+
def headers(_context)
|
17
|
+
{
|
18
|
+
"Authorization" => "bearer #{@token}"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.add_constants(token)
|
24
|
+
HttpAdapter.token = token
|
25
|
+
|
26
|
+
GithubApi::GraphQlClient.const_set("Schema", GraphQL::Client.load_schema(HttpAdapter))
|
27
|
+
GithubApi::GraphQlClient.const_set("Client", GraphQL::Client.new(schema: Schema, execute: HttpAdapter))
|
28
|
+
GithubApi::GraphQlClient.const_set("DeleteIssueQuery", Client.parse(DELETE_ISSUE_QUERY))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/tractive/github_api.rb
CHANGED
data/lib/tractive/info.rb
CHANGED
@@ -29,12 +29,25 @@ module Tractive
|
|
29
29
|
priorities = Ticket.distinct.select(:priority).select_map(:priority).compact
|
30
30
|
tracstates = Ticket.distinct.select(:status).select_map(:status).compact
|
31
31
|
|
32
|
+
keywords = Ticket.distinct
|
33
|
+
.select(:keywords)
|
34
|
+
.select_map(:keywords)
|
35
|
+
.map do |keyword|
|
36
|
+
keyword&.split(",")&.map do |k|
|
37
|
+
k.strip.gsub(" ", "_")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
.flatten
|
41
|
+
.uniq
|
42
|
+
.compact
|
43
|
+
|
32
44
|
{
|
33
45
|
"users" => Utilities.make_each_hash(users, %w[email name username]),
|
34
46
|
"milestones" => milestones,
|
35
47
|
"labels" => {
|
36
48
|
"type" => Utilities.make_hash("type_", types),
|
37
49
|
"resolution" => Utilities.make_hash("resolution_", resolutions),
|
50
|
+
"keywords" => Utilities.make_hash("keyword_", keywords),
|
38
51
|
"component" => Utilities.make_each_hash(components, %w[name color], "component: "),
|
39
52
|
"severity" => Utilities.make_each_hash(severity, %w[name color]),
|
40
53
|
"priority" => Utilities.make_each_hash(priorities, %w[name color]),
|
data/lib/tractive/main.rb
CHANGED
@@ -8,6 +8,11 @@ module Tractive
|
|
8
8
|
@opts = opts
|
9
9
|
@cfg = YAML.load_file(@opts[:config])
|
10
10
|
|
11
|
+
@cfg["github"] ||= {}
|
12
|
+
@cfg["github"]["token"] = @opts["git-token"] if @opts["git-token"]
|
13
|
+
|
14
|
+
GithubApi::GraphQlClient.add_constants(@cfg["github"]["token"]) unless @opts[:info]
|
15
|
+
|
11
16
|
Tractive::Utilities.setup_logger(output_stream: @opts[:logfile] || $stderr, verbose: @opts[:verbose])
|
12
17
|
@db = Tractive::Utilities.setup_db!(@opts["trac-database-path"] || @cfg["trac"]["database"])
|
13
18
|
rescue Sequel::DatabaseConnectionError, Sequel::AdapterNotFound, URI::InvalidURIError, Sequel::DatabaseError => e
|
@@ -16,6 +16,7 @@ module Migrator
|
|
16
16
|
@client = GithubApi::Client.new(access_token: args[:cfg]["github"]["token"])
|
17
17
|
@wiki_attachments_url = args[:cfg].dig("wiki", "attachments", "url")
|
18
18
|
@revmap_file_path = args[:opts][:revmapfile] || args[:cfg]["revmap_path"]
|
19
|
+
@make_owners_label = args[:opts]["make-owners-labels"] || args[:cfg]["make_owners_labels"]
|
19
20
|
@attachment_options = {
|
20
21
|
url: @attachurl,
|
21
22
|
hashed: args[:cfg].dig("ticket", "attachments", "hashed")
|
@@ -101,14 +102,11 @@ module Migrator
|
|
101
102
|
|
102
103
|
labels.delete(nil)
|
103
104
|
|
104
|
-
keywords = ticket[:keywords]
|
105
|
-
|
106
|
-
|
107
|
-
labels.add(@labels_cfg.fetch("keywords", {})[ticket[:keywords].downcase])
|
108
|
-
else
|
109
|
-
badges.add(@labels_cfg.fetch("keywords", {})[ticket[:keywords]])
|
110
|
-
end
|
105
|
+
keywords = ticket[:keywords]&.split(",") || []
|
106
|
+
keywords.each do |keyword|
|
107
|
+
badges.add(@labels_cfg.fetch("keywords", {})[keyword.strip])
|
111
108
|
end
|
109
|
+
|
112
110
|
# If the field is not set, it will be nil and generate an unprocessable json
|
113
111
|
|
114
112
|
milestone = @milestonemap[ticket[:milestone]]
|
@@ -121,6 +119,14 @@ module Migrator
|
|
121
119
|
|
122
120
|
github_assignee = map_assignee(ticket[:owner])
|
123
121
|
|
122
|
+
unless github_assignee.nil? || github_assignee.empty?
|
123
|
+
if @make_owners_label
|
124
|
+
labels.add("name" => "owner:#{github_assignee}")
|
125
|
+
else
|
126
|
+
badges.add("owner:#{github_assignee}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
124
130
|
badges = badges.to_a.compact.sort
|
125
131
|
badgetable = badges.map { |i| %(`#{i}`) }.join(" ")
|
126
132
|
badgetable += begin
|
@@ -132,8 +138,6 @@ module Migrator
|
|
132
138
|
|
133
139
|
# compose body
|
134
140
|
body = [badgetable, body, footer].join("\n\n___\n")
|
135
|
-
|
136
|
-
labels.add("name" => "owner:#{github_assignee}") unless github_assignee.nil? || github_assignee.empty?
|
137
141
|
labels = labels.map { |label| label["name"] }
|
138
142
|
|
139
143
|
issue = {
|
@@ -265,7 +269,7 @@ module Migrator
|
|
265
269
|
end
|
266
270
|
|
267
271
|
case kind
|
268
|
-
when "owner", "status", "title", "resolution", "priority", "component", "type", "severity", "platform", "milestone"
|
272
|
+
when "owner", "status", "title", "resolution", "priority", "component", "type", "severity", "platform", "milestone", "keywords"
|
269
273
|
old = meta[:oldvalue]
|
270
274
|
new = meta[:newvalue]
|
271
275
|
if old && new
|
@@ -281,7 +285,7 @@ module Migrator
|
|
281
285
|
# text += "created the issue\n\n"
|
282
286
|
if body && !body.lstrip.empty?
|
283
287
|
# text += "\n___\n" if not append
|
284
|
-
text += @twf_to_markdown.convert(body)
|
288
|
+
text += @twf_to_markdown.convert(body, id: meta[:ticket])
|
285
289
|
end
|
286
290
|
|
287
291
|
when "comment"
|
@@ -295,7 +299,7 @@ module Migrator
|
|
295
299
|
end
|
296
300
|
|
297
301
|
text += "\n___\n" unless append
|
298
|
-
text += @twf_to_markdown.convert(body) if body
|
302
|
+
text += @twf_to_markdown.convert(body, id: meta[:ticket]) if body
|
299
303
|
|
300
304
|
when "attachment"
|
301
305
|
text += "_uploaded file "
|
@@ -345,7 +349,7 @@ module Migrator
|
|
345
349
|
end
|
346
350
|
|
347
351
|
def interested_in_change?(kind, newvalue)
|
348
|
-
!(%w[
|
352
|
+
!(%w[cc reporter version].include?(kind) ||
|
349
353
|
(kind == "comment" && (newvalue.nil? || newvalue.lstrip.empty?)))
|
350
354
|
end
|
351
355
|
|
@@ -16,11 +16,11 @@ module Migrator
|
|
16
16
|
|
17
17
|
@git_repo = options[:git_repo]
|
18
18
|
@home_page_name = options[:home_page_name]
|
19
|
-
@wiki_extensions = options[:wiki_extensions]
|
20
|
-
@source_folders = options[:source_folders]
|
19
|
+
@wiki_extensions = options[:wiki_extensions]
|
20
|
+
@source_folders = options[:source_folders]
|
21
21
|
end
|
22
22
|
|
23
|
-
def convert(str)
|
23
|
+
def convert(str, image_options = {})
|
24
24
|
# Fix 'Windows EOL' to 'Linux EOL'
|
25
25
|
str.gsub!("\r\n", "\n")
|
26
26
|
|
@@ -33,7 +33,7 @@ module Migrator
|
|
33
33
|
convert_links(str, @git_repo)
|
34
34
|
convert_font_styles(str)
|
35
35
|
convert_changeset(str, @changeset_base_url)
|
36
|
-
convert_image(str, @base_url, @attach_url, @wiki_attachments_url)
|
36
|
+
convert_image(str, @base_url, @attach_url, @wiki_attachments_url, image_options)
|
37
37
|
convert_ticket(str, @base_url)
|
38
38
|
revert_intermediate_references(str)
|
39
39
|
|
@@ -247,13 +247,13 @@ module Migrator
|
|
247
247
|
def file?(trac_path)
|
248
248
|
return false unless trac_path
|
249
249
|
|
250
|
-
@wiki_extensions
|
250
|
+
@wiki_extensions&.any? { |extension| trac_path.end_with?(extension) }
|
251
251
|
end
|
252
252
|
|
253
253
|
def wiki_path(path, line_number = "")
|
254
254
|
# TODO: This will not work for folders given in the source_folder parameter and
|
255
255
|
# will not work for subfolders paths like `personal/rjs` unless given in the parameters.
|
256
|
-
return "branches/all?query=#{path}" if @source_folders
|
256
|
+
return "branches/all?query=#{path}" if @source_folders&.any? { |folder| folder == path }
|
257
257
|
return index_paths[path] if index_paths[path]
|
258
258
|
|
259
259
|
prefix = if file?(path)
|
@@ -294,7 +294,7 @@ module Migrator
|
|
294
294
|
"[#{wiki_name}](https://github.com/#{git_repo}/wiki/#{wiki_name})"
|
295
295
|
end
|
296
296
|
|
297
|
-
def convert_image(str, base_url, attach_url, wiki_attachments_url)
|
297
|
+
def convert_image(str, base_url, attach_url, wiki_attachments_url, options = {})
|
298
298
|
# https://trac.edgewall.org/wiki/WikiFormatting#Images
|
299
299
|
# [[Image(picture.gif)]] Current page (Ticket, Wiki, Comment)
|
300
300
|
# [[Image(wiki:WikiFormatting:picture.gif)]] (referring to attachment on another page)
|
@@ -316,7 +316,15 @@ module Migrator
|
|
316
316
|
# [[Image(http://example.org/s.jpg)]]
|
317
317
|
"!{{#{path}}}(#{path})"
|
318
318
|
else
|
319
|
-
|
319
|
+
tmp = path.split(":")
|
320
|
+
id, file = case tmp.size
|
321
|
+
when 3
|
322
|
+
[tmp[1], tmp[2]]
|
323
|
+
when 2
|
324
|
+
tmp
|
325
|
+
else
|
326
|
+
[options[:id].to_s, tmp[0]]
|
327
|
+
end
|
320
328
|
file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
|
321
329
|
"!{{#{path}}}(#{file_path})"
|
322
330
|
end
|
@@ -23,6 +23,7 @@ module Migrator
|
|
23
23
|
begin
|
24
24
|
lasttracid = tractickets.last[:id]
|
25
25
|
rescue StandardError
|
26
|
+
delete_mocked_tickets if can_delete_mocked_tickets?
|
26
27
|
raise("trac has no ticket #{start_ticket}")
|
27
28
|
end
|
28
29
|
|
@@ -89,6 +90,46 @@ module Migrator
|
|
89
90
|
|
90
91
|
@last_created_issue = ticket[:id]
|
91
92
|
end
|
93
|
+
|
94
|
+
delete_mocked_tickets if can_delete_mocked_tickets?
|
95
|
+
end
|
96
|
+
|
97
|
+
def can_delete_mocked_tickets?
|
98
|
+
@delete_mocked_tickets
|
99
|
+
end
|
100
|
+
|
101
|
+
def delete_mocked_tickets
|
102
|
+
page = 1
|
103
|
+
issues = @client.issues(@repo, { filter: "all",
|
104
|
+
state: "closed",
|
105
|
+
page: page })
|
106
|
+
|
107
|
+
until issues.empty?
|
108
|
+
deleted = false
|
109
|
+
|
110
|
+
issues.each do |issue|
|
111
|
+
next if issue["title"] != "Placeholder issue #{issue["number"]} created to align Github issue and trac ticket numbers during migration."
|
112
|
+
|
113
|
+
response = @graph_ql_client.delete_issue(issue["node_id"])
|
114
|
+
|
115
|
+
if response.data.errors.any?
|
116
|
+
error_message = response.data
|
117
|
+
.errors
|
118
|
+
.messages
|
119
|
+
.map { |k, v| "#{k}: #{v}" }
|
120
|
+
.join(", ")
|
121
|
+
raise StandardError, error_message
|
122
|
+
end
|
123
|
+
|
124
|
+
deleted = true
|
125
|
+
puts "Successfully deleted issue ##{issue["number"]}, Title: #{issue["title"]}"
|
126
|
+
end
|
127
|
+
|
128
|
+
page += 1 unless deleted
|
129
|
+
issues = @client.issues(@repo, { filter: "all",
|
130
|
+
state: "closed",
|
131
|
+
page: page })
|
132
|
+
end
|
92
133
|
end
|
93
134
|
end
|
94
135
|
end
|
@@ -32,6 +32,7 @@ module Migrator
|
|
32
32
|
@trac = Tractive::Trac.new(db)
|
33
33
|
@repo = github["repo"]
|
34
34
|
@client = GithubApi::Client.new(access_token: github["token"])
|
35
|
+
@graph_ql_client = GithubApi::GraphQlClient.new
|
35
36
|
|
36
37
|
if input_file_name
|
37
38
|
@from_file = input_file_name
|
@@ -62,6 +63,7 @@ module Migrator
|
|
62
63
|
@safetychecks = safetychecks
|
63
64
|
@start_ticket = (start_ticket || (@last_created_issue + 1)).to_i
|
64
65
|
@filter_closed = filter_closed
|
66
|
+
@delete_mocked_tickets = args[:cfg]["ticket"]["delete_mocked"]
|
65
67
|
end
|
66
68
|
|
67
69
|
def migrate
|
@@ -63,7 +63,7 @@ module Migrator
|
|
63
63
|
$logger.info("Working with file [#{file_name}]")
|
64
64
|
$logger.debug("Object: #{wiki}")
|
65
65
|
|
66
|
-
wiki_markdown_text = @twf_to_markdown.convert(wiki[:text])
|
66
|
+
wiki_markdown_text = @twf_to_markdown.convert(wiki[:text], id: wiki[:name])
|
67
67
|
wiki_markdown_text += wiki_attachments(wiki)
|
68
68
|
|
69
69
|
# Create file with content
|
data/lib/tractive/version.rb
CHANGED
data/tractive.gemspec
CHANGED
@@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
+
spec.add_dependency "graphql", "1.13.3"
|
31
|
+
spec.add_dependency "graphql-client"
|
30
32
|
spec.add_dependency "mysql2"
|
31
33
|
spec.add_dependency "ox"
|
32
34
|
spec.add_dependency "rest-client"
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
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.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.13.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.13.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: graphql-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: mysql2
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +173,8 @@ files:
|
|
145
173
|
- lib/tractive/github_api/client/issues.rb
|
146
174
|
- lib/tractive/github_api/client/labels.rb
|
147
175
|
- lib/tractive/github_api/client/milestones.rb
|
176
|
+
- lib/tractive/github_api/graph_ql_client.rb
|
177
|
+
- lib/tractive/github_api/graph_ql_client/issues.rb
|
148
178
|
- lib/tractive/graceful_quit.rb
|
149
179
|
- lib/tractive/http/client.rb
|
150
180
|
- lib/tractive/http/client/request.rb
|