tractive 1.0.9 → 1.0.10

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: 5ebcddc1621be979b25f4041cc4eac2429e6a6a08b05611154c230d49d9de86c
4
- data.tar.gz: f61bd5af4741266ae56d81c277c7aaec723a49358f81dcb6eebdc3dbaae2e37c
3
+ metadata.gz: 4d048cfbc81f42620ae076e33691d303559fb14b097048e2d4dcfb9574ed5bae
4
+ data.tar.gz: 3015ae96353d4e65c10569115d3c7b43d08474cfdf9223e82f8c2710e1663efc
5
5
  SHA512:
6
- metadata.gz: b29d97cc67c43cc4d9ee72d88563e01dc6d7cdc7b988ed3b987c73a3cbc9a2fc8746597c3d3b1e175ebca83073fd6e4b19f8b51f60c9792849893a6816483f5d
7
- data.tar.gz: d07192c1b845a3a4ca266e881ead6a7583ba5575313d68372ffa9df6785c0f4e8856be09cafdf736bb279e5e651a4d3eb1e6f294b0754b0dd4c3a5dfded9ac03
6
+ metadata.gz: f8ff314239f2b28ba8b2d87dadb07136c050d80a32a34e214e02d9826b375db886f09fe911cb8f42a63cbdf2a4ba244b8745d65bbf7be9177bc79c3bed20a835
7
+ data.tar.gz: 474c1ecad5be50f06599fca50aeee7c33bdfeba778310c77f0830653235de30c34fed85f144a24044555cdce551ab28e645af268afbf1da55674e2e38c48f372
data/README.adoc CHANGED
@@ -174,7 +174,7 @@ With this information you can now build the RevMap with:
174
174
 
175
175
  [source,sh]
176
176
  ----
177
- tractive generate-revmap \
177
+ tractive generate revmap \
178
178
  --svn-url <url of the SVN repository> \
179
179
  --rev-timestamp-file <reposurgeon timestamp map, e.g. {name-of-repo}.fo> \
180
180
  --git-local-repo-path <path to converted Git repository, e.g. {name-of-repo}-git> \
@@ -186,7 +186,7 @@ issues and commits will replace references to SVN revisions with the
186
186
  corresponding Git commit SHA, enabling GitHub to expose those linkages in the
187
187
  user interface.
188
188
 
189
- .Options for `tractive generate-revmap` command
189
+ .Options for `tractive generate revmap` command
190
190
  [cols="3a,5a,2a",options="header"]
191
191
  |===
192
192
  | Option | Description | Type
@@ -315,7 +315,7 @@ e.g. 'ghp_fpsc4de1f0c46e01576810740c9242097cba4619486'.
315
315
  e.g. '/Users/user/repo-git'.
316
316
 
317
317
  `revmap_path:`::: Local path to the RevMap file generated via the
318
- `tractive generate-revmap` command.
318
+ `tractive generate revmap` command.
319
319
 
320
320
 
321
321
  EXAMPLE:
@@ -326,7 +326,8 @@ github:
326
326
  repo: 'example-org/target-repository'
327
327
  token: 'ghp_fpsc4de1f0c46e01576810740c9242097cba4619486'
328
328
  local_repo_path: '/Users/user/repo-git'
329
- revmap_path: ./example-revmap.txt
329
+
330
+ revmap_path: ./example-revmap.txt
330
331
  ----
331
332
 
332
333
 
@@ -359,8 +360,12 @@ The pattern of a mapping is like:
359
360
  +
360
361
  [source,yaml]
361
362
  ----
362
- configuration: conf
363
- documentation: doc
363
+ configuration:
364
+ name: conf
365
+ color: ff00ff
366
+ documentation:
367
+ name: doc
368
+ color: 00ff00
364
369
  ----
365
370
 
366
371
  `resolution:`::: Resolution of the Trac ticket. e.g.
@@ -433,7 +438,7 @@ The pattern of a mapping is like:
433
438
  ----
434
439
 
435
440
 
436
- NOTE: As `severity`, `priority` and `tracstate` are converted into `labels` on github so there is an option to specify the `color` for those labels.
441
+ NOTE: As `component`, `severity`, `priority` and `tracstate` are converted into `labels` on github so there is an option to specify the `color` for those labels.
437
442
 
438
443
 
439
444
  ==== User mapping
@@ -499,22 +504,32 @@ milestones:
499
504
 
500
505
 
501
506
  ==== Attachments migration configuration
507
+ `ticket | wiki:`:: specifies the options for the tickets or wikis
508
+
509
+ `attachments:`::: specifies method of obtaining attachments from Trac.
502
510
 
503
- `attachments:`:: specifies method of obtaining attachments from Trac.
511
+ `url:`:::: URL to obtain Trac attachments from
504
512
 
505
- `url:`::: URL to obtain Trac attachments from
513
+ `hashed:`:::: Whether the url has hased or plain image names and ids
506
514
 
507
- `export_folder:`::: folder where the attachments will be downloaded to from Trac.
515
+ `export_folder:`:::: folder where the attachments will be downloaded to from Trac.
508
516
 
509
- `export_script:`::: output of a script that utilizes `trac-admin` to download
510
- all attachments from Trac.
517
+ `export_script:`:::: output of a script that utilizes `trac-admin` to download
518
+ all attachments from Trac.
511
519
 
512
520
  [source,yaml]
513
521
  ----
514
- attachments:
515
- url: https://abc.com/raw-attachment/ticket
516
- export_folder: ./attachments
517
- export_script: attachments.sh
522
+ ticket:
523
+ attachments:
524
+ url: https://abc.com/raw-attachment/ticket
525
+ hashed: true
526
+ export_folder: ./attachments
527
+ export_script: attachments.sh
528
+
529
+ wiki:
530
+ attachments:
531
+ url: https://abc.com/raw-attachment/wiki
532
+ hashed: true
518
533
  ----
519
534
 
520
535
  By using the <<gather-info,`-i` option>>, you can easily produce a YAML file
@@ -746,6 +761,50 @@ The following options are allowed (at least one necessary):
746
761
  |===
747
762
 
748
763
 
764
+ === Migrating Wikis
765
+
766
+ You need the following for wiki migration
767
+
768
+ * Clone of the github wiki repo.
769
+
770
+ Then you can run the migration with:
771
+
772
+ [source,sh]
773
+ ----
774
+ tractive migrate-wikis \
775
+ --attachment-base-url <url for the attachments> \
776
+ --trac-database-path <full path of trac database> \
777
+ --repo-path <path to cloned Git wiki repository, e.g. {name-of-repo}.wiki>
778
+ ----
779
+
780
+ After that open a terminal in the git wiki folder and run `git push`
781
+
782
+ .Options for `tractive migrate-wikis` command
783
+ [cols="3a,5a,2a",options="header"]
784
+ |===
785
+ | Option | Description | Type
786
+
787
+ | `a`, `--attachment-base-url`
788
+ |
789
+ (required unless specified in config file)
790
+ If attachment files are reachable via a URL we reference this here.
791
+ | String
792
+
793
+ | `-d`, `--trac-database-path`
794
+ | (required unless specified in config file) Full path of the Trac sqlite3 database export file.
795
+ | String
796
+
797
+ | `-r`, `--repo-path`
798
+ | (required) Full path to the root of the git-repository that is our destination.
799
+ | String
800
+
801
+ | `-c`, `--config`
802
+ | Path to config file.
803
+ | String
804
+
805
+ |===
806
+
807
+
749
808
  == Implementation details
750
809
 
751
810
  === Usage of GitHub Issue Import API
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/tractive"
4
+
5
+ class CommandBase < Thor
6
+ class_option "logfile", type: :string, aliases: ["-L", "--log-file"],
7
+ desc: "Name of the logfile to output logs to."
8
+ class_option "config", type: :string, default: "tractive.config.yaml", banner: "<PATH>", aliases: "-c",
9
+ desc: "Set the configuration file"
10
+ class_option "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
11
+ end
data/exe/generate.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./command_base"
4
+
5
+ class Generate < CommandBase
6
+ desc "revmap <OPTIONS>", "Generate a mapping from svn revision number to git sha hash."
7
+ method_option "svnurl", type: :string, aliases: ["--svn-url"],
8
+ desc: "Svn url that should be used in revmap generation"
9
+ method_option "svnlocalpath", type: :string, aliases: ["--svn-local-path"],
10
+ desc: "Local SVN repo path"
11
+ method_option "gitlocalrepopath", type: :string, aliases: ["--git-local-repo-path"],
12
+ desc: "Local git repo path that should be used in revmap generation"
13
+ method_option "revtimestampfile", type: :string, aliases: ["--rev-timestamp-file"],
14
+ desc: "File containing svn revision and timestamps that should be used in revmap generation"
15
+ method_option "revoutputfile", type: :string, aliases: ["--revmap-output-file"],
16
+ desc: "File to output the generated revmap"
17
+ def revmap
18
+ verify_revmap_generator_options!(options)
19
+
20
+ Tractive::Utilities.setup_logger(output_stream: options[:log_file] || $stderr, verbose: options[:verbose])
21
+ Tractive::RevmapGenerator.new(
22
+ options["revtimestampfile"],
23
+ options["svnurl"],
24
+ options["svnlocalpath"],
25
+ options["gitlocalrepopath"],
26
+ options["revoutputfile"]
27
+ ).generate
28
+ end
29
+
30
+ no_commands do
31
+ def verify_revmap_generator_options!(options)
32
+ required_options = {}
33
+ required_options["--svn-url OR --svn-local-path"] = options["svnurl"] || options["svnlocalpath"]
34
+ required_options["--git-local-repo-path"] = options["gitlocalrepopath"]
35
+ required_options["--rev-timestamp-file"] = options["revtimestampfile"]
36
+ required_options["--revmap-output-file"] = options["revoutputfile"]
37
+
38
+ missing_options = {}
39
+ required_options.each do |key, value|
40
+ missing_options[key] = value if value.nil? || value.strip.empty?
41
+ end
42
+
43
+ return if missing_options.empty?
44
+
45
+ warn("missing revmap generator options (#{missing_options.keys}).\nRun with `--help` or `-h` to see available options")
46
+ exit 1
47
+ end
48
+ end
49
+ end
data/exe/tractive CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/tractive"
4
+ require_relative "./command_base"
5
+ require_relative "./generate"
5
6
 
6
- class TractiveCommand < Thor
7
- default_command :migrate
8
- class_option "logfile", type: :string, aliases: ["-L", "--log-file"],
9
- desc: "Name of the logfile to output logs to."
7
+ class TractiveCommand < CommandBase
8
+ default_command :migrate_tickets
9
+
10
+ desc "generate", "Generate different files/scripts"
11
+ subcommand "generate", Generate
10
12
 
11
13
  desc "<OPTIONS>", "Migrate Trac instances to modern Git management platforms like GitHub and GitLab"
12
14
  method_option "attachmentexporter", type: :string, aliases: ["-A", "--attachment-exporter"],
13
15
  desc: "Generate an attachment exporter script according to config.yaml"
14
16
  method_option "attachurl", type: :string, aliases: ["-a", "--attachment-url"], banner: "<URL>",
15
17
  desc: "If attachment files are reachable via a URL we reference this here"
16
- method_option "config", type: :string, default: "tractive.config.yaml", banner: "<PATH>", aliases: "-c",
17
- desc: "Set the configuration file"
18
18
  method_option "dryrun", type: :boolean, aliases: ["-d", "--dry-run"],
19
19
  desc: "Write data to a file instead of pushing it to github"
20
20
  method_option "exportattachments", type: :string, aliases: ["-e", "--export-attachments"],
@@ -47,33 +47,19 @@ class TractiveCommand < Thor
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 "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
51
- def migrate
50
+ def migrate_tickets
52
51
  Tractive::Main.new(options).run
53
52
  end
54
53
 
55
- desc "generate-revmap <OPTIONS>", "Generate a mapping from svn revision number to git sha hash."
56
- method_option "svnurl", type: :string, aliases: ["--svn-url"],
57
- desc: "Svn url that should be used in revmap generation"
58
- method_option "svnlocalpath", type: :string, aliases: ["--svn-local-path"],
59
- desc: "Local SVN repo path"
60
- method_option "gitlocalrepopath", type: :string, aliases: ["--git-local-repo-path"],
61
- desc: "Local git repo path that should be used in revmap generation"
62
- method_option "revtimestampfile", type: :string, aliases: ["--rev-timestamp-file"],
63
- desc: "File containing svn revision and timestamps that should be used in revmap generation"
64
- method_option "revoutputfile", type: :string, aliases: ["--revmap-output-file"],
65
- desc: "File to output the generated revmap"
66
- def generate_revmap
67
- verify_revmap_generator_options!(options)
68
-
69
- Tractive::Utilities.setup_logger(output_stream: options[:log_file] || $stderr, verbose: options[:verbose])
70
- Tractive::RevmapGenerator.new(
71
- options["revtimestampfile"],
72
- options["svnurl"],
73
- options["svnlocalpath"],
74
- options["gitlocalrepopath"],
75
- options["revoutputfile"]
76
- ).generate
54
+ desc "migrate-wikis", "Migrate Trac wikis to a git repository, preserving history (including authors, dates, and comments)"
55
+ method_option "attachment-base-url", type: :string, aliases: ["-a"], banner: "http://<base-url>",
56
+ desc: "If attachment files are reachable via a URL we reference this here"
57
+ method_option "trac-database-path", type: :string, aliases: ["-d"], banner: "/PATH/TO/EXPORTFILE",
58
+ desc: "Full path of the Trac sqlite3 database export file"
59
+ method_option "repo-path", type: :string, aliases: ["-r"], banner: "/GIT/ROOT/DIR",
60
+ desc: "Full path to the root of the git-repository that is our destination"
61
+ def migrate_wikis
62
+ Tractive::Main.new(options).migrate_wikis
77
63
  end
78
64
 
79
65
  def self.exit_on_failure?
@@ -89,26 +75,6 @@ class TractiveCommand < Thor
89
75
  def respond_to_missing?
90
76
  true
91
77
  end
92
-
93
- no_commands do
94
- def verify_revmap_generator_options!(options)
95
- required_options = {}
96
- required_options["--svn-url OR --svn-local-path"] = options["svnurl"] || options["svnlocalpath"]
97
- required_options["--git-local-repo-path"] = options["gitlocalrepopath"]
98
- required_options["--rev-timestamp-file"] = options["revtimestampfile"]
99
- required_options["--revmap-output-file"] = options["revoutputfile"]
100
-
101
- missing_options = {}
102
- required_options.each do |key, value|
103
- missing_options[key] = value if value.nil? || value.strip.empty?
104
- end
105
-
106
- return if missing_options.empty?
107
-
108
- warn("missing revmap generator options (#{missing_options.keys}).\nRun with `--help` or `-h` to see available options")
109
- exit 1
110
- end
111
- end
112
78
  end
113
79
 
114
80
  TractiveCommand.start(ARGV)
@@ -6,7 +6,7 @@ module GithubApi
6
6
  module Issues
7
7
  def create_issue(repo, params)
8
8
  JSON.parse(
9
- RestClient.post(
9
+ Http::Client::Request.post(
10
10
  "https://api.github.com/repos/#{repo}/import/issues",
11
11
  params.to_json,
12
12
  {
@@ -20,7 +20,7 @@ module GithubApi
20
20
 
21
21
  def list_issues(repo, params)
22
22
  JSON.parse(
23
- RestClient.get(
23
+ Http::Client::Request.get(
24
24
  "https://api.github.com/repos/#{repo}/issues",
25
25
  {
26
26
  "Authorization" => "token #{@token}",
@@ -33,7 +33,7 @@ module GithubApi
33
33
 
34
34
  def issue(repo, number)
35
35
  JSON.parse(
36
- RestClient.get(
36
+ Http::Client::Request.get(
37
37
  "https://api.github.com/repos/#{repo}/issues/#{number}",
38
38
  { "Authorization" => "token #{@token}" }
39
39
  )
@@ -42,7 +42,7 @@ module GithubApi
42
42
 
43
43
  def issue_import_status(repo, id)
44
44
  JSON.parse(
45
- RestClient.get(
45
+ Http::Client::Request.get(
46
46
  "https://api.github.com/repos/#{repo}/import/issues/#{id}",
47
47
  {
48
48
  "Authorization" => "token #{@token}",
@@ -54,7 +54,7 @@ module GithubApi
54
54
 
55
55
  def issue_comments(repo, issue_id)
56
56
  JSON.parse(
57
- RestClient.get(
57
+ Http::Client::Request.get(
58
58
  "https://api.github.com/repos/#{repo}/issues/#{issue_id}/comments",
59
59
  {
60
60
  "Authorization" => "token #{@token}",
@@ -66,7 +66,7 @@ module GithubApi
66
66
 
67
67
  def update_issue_comment(repo, comment_id, comment_body)
68
68
  JSON.parse(
69
- RestClient.patch(
69
+ Http::Client::Request.patch(
70
70
  "https://api.github.com/repos/#{repo}/issues/comments/#{comment_id}",
71
71
  { body: comment_body }.to_json,
72
72
  { "Authorization" => "token #{@token}" }
@@ -5,7 +5,7 @@ module GithubApi
5
5
  module Labels
6
6
  def list_labels(repo, params = {})
7
7
  JSON.parse(
8
- RestClient.get(
8
+ Http::Client::Request.get(
9
9
  "https://api.github.com/repos/#{repo}/labels",
10
10
  {
11
11
  "Authorization" => "token #{@token}",
@@ -18,7 +18,7 @@ module GithubApi
18
18
 
19
19
  def create_label(repo, params)
20
20
  JSON.parse(
21
- RestClient.post(
21
+ Http::Client::Request.post(
22
22
  "https://api.github.com/repos/#{repo}/labels",
23
23
  params.to_json,
24
24
  {
@@ -6,7 +6,7 @@ module GithubApi
6
6
  module Milestones
7
7
  def list_milestones(repo, params)
8
8
  JSON.parse(
9
- RestClient.get(
9
+ Http::Client::Request.get(
10
10
  "https://api.github.com/repos/#{repo}/milestones?per_page=100",
11
11
  {
12
12
  "Authorization" => "token #{@token}",
@@ -19,7 +19,7 @@ module GithubApi
19
19
 
20
20
  def create_milestone(repo, params)
21
21
  JSON.parse(
22
- RestClient.post(
22
+ Http::Client::Request.post(
23
23
  "https://api.github.com/repos/#{repo}/milestones",
24
24
  params.to_json,
25
25
  {
@@ -4,6 +4,8 @@ require_relative "client/issues"
4
4
  require_relative "client/labels"
5
5
  require_relative "client/milestones"
6
6
 
7
+ require_relative "../http/client"
8
+
7
9
  # Service to perform github actions
8
10
  module GithubApi
9
11
  class Client
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Http
4
+ module Client
5
+ class Request
6
+ def initialize(args)
7
+ @args = args
8
+ @max_retries = args[:max_retries] || 3
9
+ end
10
+
11
+ def execute(&block)
12
+ retries = 0
13
+
14
+ begin
15
+ retries += 1
16
+ RestClient::Request.execute(@args, &block)
17
+ rescue RestClient::Forbidden => e
18
+ retry_after = e.http_headers[:x_ratelimit_reset].to_i - Time.now.to_i
19
+ raise e if retry_after.negative? || retries > @max_retries
20
+
21
+ while retry_after.positive?
22
+ minutes = retry_after / 60
23
+ seconds = retry_after % 60
24
+
25
+ print "\rRate Limit Exceeded, Will retry in #{minutes} min #{seconds} sec"
26
+ sleep(1)
27
+
28
+ retry_after = e.http_headers[:x_ratelimit_reset].to_i - Time.now.to_i
29
+ end
30
+ retry if retries <= @max_retries
31
+ end
32
+ end
33
+
34
+ attr_reader :response
35
+
36
+ class << self
37
+ def get(url, headers = {}, &block)
38
+ execute(method: :get, url: url, headers: headers, &block)
39
+ end
40
+
41
+ def post(url, payload, headers = {}, &block)
42
+ execute(method: :post, url: url, payload: payload, headers: headers, &block)
43
+ end
44
+
45
+ def put(url, payload, headers = {}, &block)
46
+ execute(method: :put, url: url, payload: payload, headers: headers, &block)
47
+ end
48
+
49
+ def patch(url, payload, headers = {}, &block)
50
+ execute(method: :patch, url: url, payload: payload, headers: headers, &block)
51
+ end
52
+
53
+ def execute(args, &block)
54
+ new(args).execute(&block)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "client/request"
data/lib/tractive/main.rb CHANGED
@@ -9,7 +9,7 @@ module Tractive
9
9
  @cfg = YAML.load_file(@opts[:config])
10
10
 
11
11
  Tractive::Utilities.setup_logger(output_stream: @opts[:logfile] || $stderr, verbose: @opts[:verbose])
12
- @db = Tractive::Utilities.setup_db!(@cfg["trac"]["database"])
12
+ @db = Tractive::Utilities.setup_db!(@opts["trac-database-path"] || @cfg["trac"]["database"])
13
13
  rescue Sequel::DatabaseConnectionError, Sequel::AdapterNotFound, URI::InvalidURIError, Sequel::DatabaseError => e
14
14
  $logger.error e.message
15
15
  exit 1
@@ -33,6 +33,10 @@ module Tractive
33
33
  Migrator::Engine.new(opts: @opts, cfg: @cfg, db: @db).migrate
34
34
  end
35
35
 
36
+ def migrate_wikis
37
+ Migrator::Wikis::MigrateFromDb.new(opts: @opts, cfg: @cfg).migrate_wikis
38
+ end
39
+
36
40
  def info
37
41
  Tractive::Info.new.print
38
42
  end
@@ -4,8 +4,8 @@ module Migrator
4
4
  module Converter
5
5
  class TracToGithub
6
6
  def initialize(args)
7
- @tracticketbaseurl = args[:cfg]["trac"]["ticketbaseurl"]
8
- @attachurl = args[:opts][:attachurl] || args[:cfg].dig("attachments", "url")
7
+ @trac_ticket_base_url = args[:cfg]["trac"]["ticketbaseurl"]
8
+ @attachurl = args[:opts][:attachurl] || args[:cfg].dig("ticket", "attachments", "url")
9
9
  @changeset_base_url = args[:cfg]["trac"]["changeset_base_url"] || ""
10
10
  @singlepost = args[:opts][:singlepost]
11
11
  @labels_cfg = args[:cfg]["labels"].transform_values(&:to_h)
@@ -14,9 +14,12 @@ module Migrator
14
14
  @trac_mails_cache = {}
15
15
  @repo = args[:cfg]["github"]["repo"]
16
16
  @client = GithubApi::Client.new(access_token: args[:cfg]["github"]["token"])
17
- @wiki_attachments_url = args[:cfg]["trac"]["wiki_attachments_url"]
17
+ @wiki_attachments_url = args[:cfg].dig("wiki", "attachments", "url")
18
18
  @revmap_file_path = args[:opts][:revmapfile] || args[:cfg]["revmap_path"]
19
- @attachment_options = { hashed: args[:cfg].dig("attachments", "hashed") }
19
+ @attachment_options = {
20
+ url: @attachurl,
21
+ hashed: args[:cfg].dig("ticket", "attachments", "hashed")
22
+ }
20
23
 
21
24
  load_milestone_map
22
25
  create_labels_on_github(@labels_cfg["severity"].values)
@@ -25,12 +28,18 @@ module Migrator
25
28
  create_labels_on_github(@labels_cfg["component"].values)
26
29
 
27
30
  @uri_parser = URI::Parser.new
28
- @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachurl, @changeset_base_url, @wiki_attachments_url, @revmap_file_path)
31
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(
32
+ @trac_ticket_base_url,
33
+ @attachment_options,
34
+ @changeset_base_url,
35
+ @wiki_attachments_url,
36
+ @revmap_file_path
37
+ )
29
38
  end
30
39
 
31
40
  def compose(ticket)
32
- body = ""
33
- closed = nil
41
+ body = ""
42
+ closed_time = nil
34
43
 
35
44
  # summary line:
36
45
  # body += %i[id component priority resolution].map do |cat|
@@ -71,7 +80,7 @@ module Migrator
71
80
  @labels_cfg.fetch(x[:field], {})[x[:newvalue]]
72
81
  labels.delete(del) if del
73
82
  # labels.add(add) if add
74
- closed = x[:time] if (x[:field] == "status") && (x[:newvalue] == "closed")
83
+ closed_time = x[:time] if x[:field] == "status" && x[:newvalue] == "closed"
75
84
  end
76
85
 
77
86
  # we separate labels from badges
@@ -150,8 +159,12 @@ module Migrator
150
159
  # issue["updated_at"] = format_time(ticket[:changetime])
151
160
  end
152
161
 
153
- if issue["closed"] && closed
154
- # issue["closed_at"] = format_time(closed)
162
+ if issue["closed"]
163
+ issue["closed_at"] = if closed_time
164
+ format_time(closed_time)
165
+ else
166
+ format_time(ticket[:closed_at].to_i)
167
+ end
155
168
  end
156
169
 
157
170
  {
@@ -288,7 +301,7 @@ module Migrator
288
301
  name = meta[:filename]
289
302
  body = meta[:description]
290
303
  if @attachurl
291
- url = @uri_parser.escape("#{@attachurl}/#{attachment_path(meta[:id], name, @attachment_options)}")
304
+ url = @uri_parser.escape("#{@attachurl}/#{Tractive::Utilities.attachment_path(meta[:id], name, @attachment_options)}")
292
305
  text += "[`#{name}`](#{url})"
293
306
  body += "\n![#{name}](#{url})" if [".png", ".jpg", ".gif"].include? File.extname(name).downcase
294
307
  else
@@ -336,20 +349,9 @@ module Migrator
336
349
  end
337
350
 
338
351
  def trac_ticket_link(ticket)
339
- return "trac:#{ticket[:id]}" unless @tracticketbaseurl
340
-
341
- "[trac:#{ticket[:id]}](#{@tracticketbaseurl}/#{ticket[:id]})"
342
- end
343
-
344
- def attachment_path(id, filename, options = {})
345
- return "#{id}/#{filename}" unless options[:hashed]
346
-
347
- folder_name = Digest::SHA1.hexdigest(id)
348
- parent_folder_name = folder_name[0..2]
349
- hashed_filename = Digest::SHA1.hexdigest(filename)
350
- file_extension = File.extname(filename)
352
+ return "trac:#{ticket[:id]}" unless @trac_ticket_base_url
351
353
 
352
- "#{parent_folder_name}/#{folder_name}/#{hashed_filename}#{file_extension}"
354
+ "[trac:#{ticket[:id]}](#{@trac_ticket_base_url}/#{ticket[:id]})"
353
355
  end
354
356
  end
355
357
  end
@@ -4,9 +4,10 @@ module Migrator
4
4
  module Converter
5
5
  # twf => Trac wiki format
6
6
  class TwfToMarkdown
7
- def initialize(base_url, attach_url, changeset_base_url, wiki_attachments_url, revmap_file_path)
7
+ def initialize(base_url, attachment_options, changeset_base_url, wiki_attachments_url, revmap_file_path)
8
8
  @base_url = base_url
9
- @attach_url = attach_url
9
+ @attach_url = attachment_options[:url]
10
+ @attach_hashed = attachment_options[:hashed]
10
11
  @changeset_base_url = changeset_base_url
11
12
  @wiki_attachments_url = wiki_attachments_url
12
13
  @revmap = load_revmap_file(revmap_file_path)
@@ -127,15 +128,15 @@ module Migrator
127
128
  image_path = if mod == "source"
128
129
  "![#{path.split("/").last}](#{base_url}#{path})"
129
130
  elsif mod == "wiki"
130
- _, file = path.split(":")
131
- upload_path = "#{wiki_attachments_url}/#{file}"
131
+ id, file = path.split(":")
132
+ upload_path = "#{wiki_attachments_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
132
133
  "![#{file}](#{upload_path})"
133
134
  elsif path.start_with?("http")
134
135
  # [[Image(http://example.org/s.jpg)]]
135
136
  "![#{d[:path]}](#{d[:path]})"
136
137
  else
137
138
  _, id, file = path.split(":")
138
- file_path = "#{attach_url}/#{id}/#{file}"
139
+ file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
139
140
  "![#{d[:path]}](#{file_path})"
140
141
  end
141
142
 
@@ -96,7 +96,7 @@ module Migrator
96
96
 
97
97
  def mock_ticket_details(ticket_id)
98
98
  summary = if @filter_applied
99
- "Not available in trac #{ticket_id}"
99
+ "Placeholder issue #{ticket_id} created to align Github issue and trac ticket numbers during migration."
100
100
  else
101
101
  "DELETED in trac #{ticket_id}"
102
102
  end
@@ -105,7 +105,8 @@ module Migrator
105
105
  summary: summary,
106
106
  time: Time.now.to_i,
107
107
  status: "closed",
108
- reporter: "tractive"
108
+ reporter: "tractive",
109
+ closed_at: Time.at(0).utc
109
110
  }
110
111
  end
111
112
 
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Migrator
6
+ module Wikis
7
+ class MigrateFromDb
8
+ def initialize(args)
9
+ $logger.debug("OPTIONS = #{args}")
10
+
11
+ @config = args[:cfg]
12
+ @options = args[:opts]
13
+ @authors_map = @config["users"].to_h
14
+
15
+ @tracticketbaseurl = @config["trac"]["ticketbaseurl"]
16
+ @changeset_base_url = @config["trac"]["changeset_base_url"] || ""
17
+ @wiki_attachments_url = @options["attachment-base-url"] || @config.dig("wiki", "attachments", "url") || ""
18
+ @repo_path = @options["repo-path"] || ""
19
+ @revmap_path = @config["revmap_path"]
20
+
21
+ @attachment_options = {
22
+ hashed: @config.dig("ticket", "attachments", "hashed")
23
+ }
24
+
25
+ verify_options
26
+ verify_locations
27
+
28
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachment_options, @changeset_base_url, @wiki_attachments_url, @revmap_path)
29
+ end
30
+
31
+ def migrate_wikis
32
+ $logger.info("Processing the wiki...")
33
+
34
+ Dir.chdir(@options["repo-path"]) do
35
+ # For every version of every file in the wiki...
36
+ Tractive::Wiki.for_migration.each do |wiki|
37
+ next if skip_file(wiki[:name])
38
+
39
+ comment = if wiki[:comment].nil? || wiki[:comment].empty?
40
+ "Initial load of version #{wiki[:version]} of trac-file #{wiki[:name]}"
41
+ else
42
+ wiki[:comment].gsub('"', '\"')
43
+ end
44
+
45
+ file_name = "#{cleanse_filename(wiki[:name])}.md"
46
+ $logger.info("Working with file [#{file_name}]")
47
+ $logger.debug("Object: #{wiki}")
48
+
49
+ wiki_markdown_text = @twf_to_markdown.convert(wiki[:text])
50
+
51
+ # Create file with content
52
+ File.open(file_name, "w") do |f|
53
+ f.puts(wiki_markdown_text)
54
+ end
55
+
56
+ # git-add it
57
+ unless execute_command("git add #{file_name}").success?
58
+ $logger.error("ERROR at git-add #{file_name}!!!")
59
+ exit(1)
60
+ end
61
+
62
+ author = generate_author(wiki[:author])
63
+
64
+ # git-commit it
65
+ commit_command = "git commit --allow-empty -m \"#{comment}\" --author \"#{author}\" --date \"#{wiki[:fixeddate]}\""
66
+ unless execute_command(commit_command).success?
67
+ $logger.error("ERROR at git-commit #{file_name}!!!")
68
+ exit(1)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def verify_options
77
+ $logger.info("Verifying options...")
78
+
79
+ missing_options = []
80
+ missing_options << "attachment-base-url" if @wiki_attachments_url.empty?
81
+ missing_options << "repo-path" if @repo_path.empty?
82
+
83
+ return if missing_options.empty?
84
+
85
+ $logger.error("Following options are missing: #{missing_options} - exiting...")
86
+ exit(1)
87
+ end
88
+
89
+ def verify_locations
90
+ $logger.info("Verifying locations...")
91
+ missing_directories = []
92
+
93
+ # git-root exists?
94
+ missing_directories << "repo-path" unless Dir.exist?(@repo_path)
95
+
96
+ return if missing_directories.empty?
97
+
98
+ $logger.error("Following directories are missing: #{missing_directories} - exiting ...")
99
+ exit(1)
100
+ end
101
+
102
+ def cleanse_filename(name)
103
+ # Get rid of 'magic' characters from potential filename - replace with '_'
104
+ # Magic: [ /<>- ]
105
+ name.gsub(%r{[/<>-]}, "_")
106
+ end
107
+
108
+ def skip_file(file_name)
109
+ file_name.start_with?("Trac") || (file_name.start_with?("Wiki") && !file_name.start_with?("WikiStart"))
110
+ end
111
+
112
+ def generate_author(author)
113
+ return "" if author.nil? || author.empty?
114
+
115
+ author_name = @authors_map[author]&.[]("name") || author.split("@").first
116
+ author_email = @authors_map[author]&.[]("email") || author
117
+
118
+ "#{author_name} <#{author_email}>"
119
+ end
120
+
121
+ def execute_command(command)
122
+ `#{command}`
123
+ $CHILD_STATUS
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wikis/migrate_from_db"
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "migrator/engine"
4
+ require_relative "migrator/wikis"
@@ -4,6 +4,7 @@ module Tractive
4
4
  class Attachment < Sequel::Model(:attachment)
5
5
  dataset_module do
6
6
  where(:tickets_attachments, type: "ticket")
7
+ where(:wiki_attachments, type: "wiki")
7
8
  select(:for_export, :id, :filename)
8
9
  end
9
10
  end
@@ -39,5 +39,9 @@ module Tractive
39
39
  change_arr = changes + attachments
40
40
  change_arr.sort_by { |change| change[:time] }
41
41
  end
42
+
43
+ def closed_comments
44
+ changes_dataset.where(field: "status", newvalue: "closed")
45
+ end
42
46
  end
43
47
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tractive
4
+ class Wiki < Sequel::Model(:wiki)
5
+ set_primary_key :name
6
+ one_to_many :attachments, class: Attachment, key: :id, conditions: { type: "wiki" }
7
+
8
+ dataset_module do
9
+ def for_migration
10
+ select(:name, :version, :author, :comment, Sequel.lit("datetime(time/1000000, 'unixepoch')").as(:fixeddate), :text).order(:name, :version)
11
+ end
12
+
13
+ def latest_versions
14
+ select(:name, :version, :text).group(:name).having { version =~ MAX(version) }
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/tractive/trac.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tractive
4
4
  class Trac
5
- attr_reader :tickets, :changes, :sessions, :attachments
5
+ attr_reader :tickets, :changes, :sessions, :attachments, :wikis
6
6
 
7
7
  def initialize(db)
8
8
  $logger.info("loading tickets")
@@ -11,6 +11,7 @@ module Tractive
11
11
  @changes = TicketChange
12
12
  @sessions = Session
13
13
  @attachments = Attachment
14
+ @wikis = Wiki
14
15
  end
15
16
  end
16
17
  end
@@ -22,7 +22,8 @@ module Tractive
22
22
  "lib/tractive/models/revision.rb",
23
23
  "lib/tractive/models/session.rb",
24
24
  "lib/tractive/models/ticket_change.rb",
25
- "lib/tractive/models/ticket.rb"
25
+ "lib/tractive/models/ticket.rb",
26
+ "lib/tractive/models/wiki.rb"
26
27
  ]
27
28
  db = Sequel.connect(db_url) if db_url
28
29
 
@@ -35,6 +36,17 @@ module Tractive
35
36
  db
36
37
  end
37
38
 
39
+ def attachment_path(id, filename, options = {})
40
+ return "#{id}/#{filename}" unless options[:hashed]
41
+
42
+ folder_name = Digest::SHA1.hexdigest(id)
43
+ parent_folder_name = folder_name[0..2]
44
+ hashed_filename = Digest::SHA1.hexdigest(filename)
45
+ file_extension = File.extname(filename)
46
+
47
+ "#{parent_folder_name}/#{folder_name}/#{hashed_filename}#{file_extension}"
48
+ end
49
+
38
50
  def setup_logger(options = {})
39
51
  $logger = Logger.new(options[:output_stream])
40
52
  $logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
@@ -46,7 +58,7 @@ module Tractive
46
58
 
47
59
  # returns the git commit hash for a specified revision (using revmap hash)
48
60
  def map_changeset(str, revmap, changeset_base_url = "")
49
- if revmap&.key?(str)
61
+ if revmap&.key?(str) && !revmap[str].nil?
50
62
  base_url = changeset_base_url
51
63
  base_url += "/" if base_url[-1] && base_url[-1] != "/"
52
64
  "#{base_url}#{revmap[str].strip}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tractive
4
- VERSION = "1.0.9"
4
+ VERSION = "1.0.10"
5
5
  end
data/lib/tractive.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "tractive/main"
10
10
  require_relative "tractive/utilities"
11
11
  require_relative "tractive/github_api"
12
12
  require_relative "tractive/revmap_generator"
13
+ require "English"
13
14
  require "json"
14
15
  require "logger"
15
16
  require "yaml"
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.9
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-01 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mysql2
@@ -112,6 +112,8 @@ description:
112
112
  email:
113
113
  - open.source@ribose.com
114
114
  executables:
115
+ - command_base.rb
116
+ - generate.rb
115
117
  - tractive
116
118
  extensions: []
117
119
  extra_rdoc_files: []
@@ -133,6 +135,8 @@ files:
133
135
  - db/trac-test.db
134
136
  - docker/Dockerfile
135
137
  - docker/docker-compose.yml
138
+ - exe/command_base.rb
139
+ - exe/generate.rb
136
140
  - exe/tractive
137
141
  - lib/tractive.rb
138
142
  - lib/tractive/attachment_exporter.rb
@@ -142,6 +146,8 @@ files:
142
146
  - lib/tractive/github_api/client/labels.rb
143
147
  - lib/tractive/github_api/client/milestones.rb
144
148
  - lib/tractive/graceful_quit.rb
149
+ - lib/tractive/http/client.rb
150
+ - lib/tractive/http/client/request.rb
145
151
  - lib/tractive/info.rb
146
152
  - lib/tractive/main.rb
147
153
  - lib/tractive/migrator.rb
@@ -152,6 +158,8 @@ files:
152
158
  - lib/tractive/migrator/engine/migrate_from_db.rb
153
159
  - lib/tractive/migrator/engine/migrate_from_file.rb
154
160
  - lib/tractive/migrator/engine/migrate_to_file.rb
161
+ - lib/tractive/migrator/wikis.rb
162
+ - lib/tractive/migrator/wikis/migrate_from_db.rb
155
163
  - lib/tractive/models/attachment.rb
156
164
  - lib/tractive/models/milestone.rb
157
165
  - lib/tractive/models/report.rb
@@ -159,6 +167,7 @@ files:
159
167
  - lib/tractive/models/session.rb
160
168
  - lib/tractive/models/ticket.rb
161
169
  - lib/tractive/models/ticket_change.rb
170
+ - lib/tractive/models/wiki.rb
162
171
  - lib/tractive/revmap_generator.rb
163
172
  - lib/tractive/trac.rb
164
173
  - lib/tractive/utilities.rb