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 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