tractive 1.0.1 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb568a67bbfcc96cca6de822691aeeb5209a063317d58a824e67776cd5fe9cb9
4
- data.tar.gz: e5870f1b8715a509ff8ec03c8c478d6894ce2e53d0ca704bb40f1aa1e439b880
3
+ metadata.gz: db1fec86bca93ac81ac4ce8c4f1e6fb99f0983743c55fc3275a95188c8178e0e
4
+ data.tar.gz: f2907430c9a64b4cd5e0004050c1d7eddf11a18f94a4a007bf5315706a73e23b
5
5
  SHA512:
6
- metadata.gz: 96ea4aef128a8084e0905b2f787ba70270ea1dc8651451cbdb08a9e0e792975a1d18b7ee2042aa4ec5ea0ac5cb9e075e1c4c4cd82eb355bdf09b767377fbdae8
7
- data.tar.gz: 51a5011d41d626e5e9a6ecbd7cf9c096e172fe4539523777ddafa3d1132e5919a8285db1048f7594b7e559f50f25437315584425936774d29ccb536ce0356714
6
+ metadata.gz: 1828a51469e9418188cd72195b51b245bb47921b7fbc883287bd089f9b2da58d18fbf08411c9f5cc33ed4a4a92259e365931afe281e1cf810a16c97a0632fa1a
7
+ data.tar.gz: f898f265b58c41ca3ae59ee20cf52ec50ced92045a1595f23ea50e66bb4b0bd911a8695cfdc34b3a41a248a8b11bcb3a20be70eb8193aba8a7867c3f9ba4cc1e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tractive (1.0.1)
4
+ tractive (1.0.5)
5
5
  mysql2
6
6
  ox
7
7
  rest-client
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) SVN repository URL that should be used in RevMap generation.
198
- The URL must start with the `http://` or `https://` prefix (not the `svn://`
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
- | SVN local repository path that should be used in RevMap generation. You can clone
204
- the svn repo locally and provide its local path if the svn repository is offline. (`--svn-url` is not
205
- required if this option is given).
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
- blocker: '#blocker'
388
- critical: '#critical'
389
- major: '#major'
390
- medium: '#medium'
391
- minor: '#minor'
392
- trivial: '#easy'
393
- waiting: '#pending'
394
- n/a:
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: '@low'
402
- High: '@high'
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}: {username on GitHub}
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@example.org: example-matt
424
- valencia: example-vale
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>: <username on GitHub>
28
- matthew@example.org: example-matt
29
- valencia: example-vale
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: '@low'
51
- High: '@high'
57
+ Low:
58
+ name: low
59
+ color: 22dd00
60
+ High:
61
+ name: high
62
+ color: ff0000
52
63
  severity:
53
- blocker: '#high'
54
- critical: '#critical'
55
- major: '#major'
56
- minor: '#minor'
57
- trivial: '#trivial'
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.make_hash("", users),
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.make_hash("severity", severity),
40
- "priority" => Utilities.make_hash("priority_", priorities),
41
- "tracstate" => Utilities.make_hash("tracstate_", tracstates)
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[user] || user
162
+ @users.fetch(user, {})["email"] || user
159
163
  end
160
164
 
161
165
  def map_assignee(user)
162
- @users[user]
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].nil? ? "open" : "closed",
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]["revmapfile"])
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
- if options[:operator].downcase == "like"
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.count == 1
56
- file.puts "#{info[:revision]} | #{commits.values[0].join(",")}"
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].join(",")}"
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
- command = "git rev-list --after=#{info[:timestamp]} --until=#{info[:timestamp]} --committer=#{info[:author]} --all --format='%cd|%h~|~%s' --date=format:'%Y-%m-%dT%H:%M:%SZ'"
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
- `#{command}`
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("\n").each_slice(2) do |sha_hash, commit_info|
112
+ commits.split(regex).each do |commit_info|
101
113
  commit_hash = {}
102
- commit_hash[:sha] = sha_hash.split.last
103
- commit_hash[:short_sha], commit_hash[:message] = commit_info.split("~|~")
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
@@ -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",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tractive
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.5"
5
5
  end
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.1
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 00:00:00.000000000 Z
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