tractive 1.0.7 → 1.0.11

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: 55fc3cea20d6e9afb060ab67f70582da9e5940a58b8b5c3a2e8e52612a2f3c8b
4
- data.tar.gz: 6b86875665351d16687fdc2c12dfca715fb7ec340c2d4d2e7f9fc98ecd7df460
3
+ metadata.gz: 8ebb8048967ee1732b68d54ed6c10798e0959d533e7ed9744a843f1a129f764d
4
+ data.tar.gz: 4799587764dcaa848e76971b50df220e72ffa914a5f1bc7c3cad20ef2e5de83d
5
5
  SHA512:
6
- metadata.gz: 0f584eaf4348c98152368e9dfbd38090b136e52ac334f1755f5fda6b944a374775ef97acab214aa87a8a2228f86a1363e8a7230e6851760517a40449f3424572
7
- data.tar.gz: 8fe4530ec8f978e9742c9f7e6f1b06c1b2ea17efc6a25f4c9db497dffb26733b267b6e4a2d3b079f99ea7b7af9e3b39e4c55bc58c7ca763b1e28cbbbcac9560c
6
+ metadata.gz: 0c052a1f936f6e4d4749e4fd9f8ec9fcab3042f4220e2c4f10e6a7ed127f2c1ee8a856f49a58ae8b960a38f12ffdc35d9097a55361142b39bfa605a269ea2d1b
7
+ data.tar.gz: 6d0025c7682c74526aa3a0fb8a4d93d4f46fad4cab5e560898e3b2c504e8981cffe10203e59afb7c8d818d3598efe3729b854c2939d7b8ca64105846de55f131
data/.rubocop.yml CHANGED
@@ -34,6 +34,9 @@ Metrics/AbcSize:
34
34
  Metrics/MethodLength:
35
35
  Enabled: false
36
36
 
37
+ Metrics/ModuleLength:
38
+ Max: 150
39
+
37
40
  Security/Open:
38
41
  Enabled: false
39
42
 
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
@@ -682,6 +697,7 @@ The following options are allowed (at least one necessary):
682
697
  * `column-name`
683
698
  * `operator`
684
699
  * `column-value`
700
+ * `include-null`
685
701
 
686
702
  | Boolean
687
703
 
@@ -697,6 +713,10 @@ The following options are allowed (at least one necessary):
697
713
  | Value of the column to filter.
698
714
  | String
699
715
 
716
+ | `--include-null`
717
+ | Include rows having null value in filtered column.
718
+ | Boolean
719
+
700
720
  | `-h`, `help`
701
721
  | Display the Tractive help message, or you can provide a command to know more
702
722
  about a single command via `tractive help {command}`.
@@ -741,6 +761,50 @@ The following options are allowed (at least one necessary):
741
761
  |===
742
762
 
743
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
+
744
808
  == Implementation details
745
809
 
746
810
  === Usage of GitHub Issue Import API
data/db/trac-test.db CHANGED
Binary file
@@ -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"],
@@ -30,6 +30,8 @@ class TractiveCommand < Thor
30
30
  desc: "Operator for filter."
31
31
  method_option "columnvalue", type: :string, aliases: ["--column-value"],
32
32
  desc: "Value of the column to filter."
33
+ method_option "includenull", type: :boolean, aliases: ["--include-null"],
34
+ desc: "Flag for including null values in the filter result."
33
35
 
34
36
  method_option "importfromfile", type: :string, aliases: ["-I", "--import-from-file"],
35
37
  desc: "Import issues from a json file"
@@ -45,33 +47,19 @@ class TractiveCommand < Thor
45
47
  desc: "Put all issue comments in the first message."
46
48
  method_option "start", type: :numeric, aliases: ["-s", "--start-at"], banner: "<ID>",
47
49
  desc: "Start migration from ticket with number <ID>"
48
- method_option "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
49
- def migrate
50
+ def migrate_tickets
50
51
  Tractive::Main.new(options).run
51
52
  end
52
53
 
53
- desc "generate-revmap <OPTIONS>", "Generate a mapping from svn revision number to git sha hash."
54
- method_option "svnurl", type: :string, aliases: ["--svn-url"],
55
- desc: "Svn url that should be used in revmap generation"
56
- method_option "svnlocalpath", type: :string, aliases: ["--svn-local-path"],
57
- desc: "Local SVN repo path"
58
- method_option "gitlocalrepopath", type: :string, aliases: ["--git-local-repo-path"],
59
- desc: "Local git repo path that should be used in revmap generation"
60
- method_option "revtimestampfile", type: :string, aliases: ["--rev-timestamp-file"],
61
- desc: "File containing svn revision and timestamps that should be used in revmap generation"
62
- method_option "revoutputfile", type: :string, aliases: ["--revmap-output-file"],
63
- desc: "File to output the generated revmap"
64
- def generate_revmap
65
- verify_revmap_generator_options!(options)
66
-
67
- Tractive::Utilities.setup_logger(output_stream: options[:log_file] || $stderr, verbose: options[:verbose])
68
- Tractive::RevmapGenerator.new(
69
- options["revtimestampfile"],
70
- options["svnurl"],
71
- options["svnlocalpath"],
72
- options["gitlocalrepopath"],
73
- options["revoutputfile"]
74
- ).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
75
63
  end
76
64
 
77
65
  def self.exit_on_failure?
@@ -87,26 +75,6 @@ class TractiveCommand < Thor
87
75
  def respond_to_missing?
88
76
  true
89
77
  end
90
-
91
- no_commands do
92
- def verify_revmap_generator_options!(options)
93
- required_options = {}
94
- required_options["--svn-url OR --svn-local-path"] = options["svnurl"] || options["svnlocalpath"]
95
- required_options["--git-local-repo-path"] = options["gitlocalrepopath"]
96
- required_options["--rev-timestamp-file"] = options["revtimestampfile"]
97
- required_options["--revmap-output-file"] = options["revoutputfile"]
98
-
99
- missing_options = {}
100
- required_options.each do |key, value|
101
- missing_options[key] = value if value.nil? || value.strip.empty?
102
- end
103
-
104
- return if missing_options.empty?
105
-
106
- warn("missing revmap generator options (#{missing_options.keys}).\nRun with `--help` or `-h` to see available options")
107
- exit 1
108
- end
109
- end
110
78
  end
111
79
 
112
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,8 +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 = {
20
+ url: @attachurl,
21
+ hashed: args[:cfg].dig("ticket", "attachments", "hashed")
22
+ }
19
23
 
20
24
  load_milestone_map
21
25
  create_labels_on_github(@labels_cfg["severity"].values)
@@ -24,12 +28,18 @@ module Migrator
24
28
  create_labels_on_github(@labels_cfg["component"].values)
25
29
 
26
30
  @uri_parser = URI::Parser.new
27
- @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
+ )
28
38
  end
29
39
 
30
40
  def compose(ticket)
31
- body = ""
32
- closed = nil
41
+ body = ""
42
+ closed_time = nil
33
43
 
34
44
  # summary line:
35
45
  # body += %i[id component priority resolution].map do |cat|
@@ -70,7 +80,7 @@ module Migrator
70
80
  @labels_cfg.fetch(x[:field], {})[x[:newvalue]]
71
81
  labels.delete(del) if del
72
82
  # labels.add(add) if add
73
- closed = x[:time] if (x[:field] == "status") && (x[:newvalue] == "closed")
83
+ closed_time = x[:time] if x[:field] == "status" && x[:newvalue] == "closed"
74
84
  end
75
85
 
76
86
  # we separate labels from badges
@@ -149,8 +159,12 @@ module Migrator
149
159
  # issue["updated_at"] = format_time(ticket[:changetime])
150
160
  end
151
161
 
152
- if issue["closed"] && closed
153
- # 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
154
168
  end
155
169
 
156
170
  {
@@ -287,7 +301,7 @@ module Migrator
287
301
  name = meta[:filename]
288
302
  body = meta[:description]
289
303
  if @attachurl
290
- url = @uri_parser.escape("#{@attachurl}/#{meta[:id]}/#{name}")
304
+ url = @uri_parser.escape("#{@attachurl}/#{Tractive::Utilities.attachment_path(meta[:id], name, @attachment_options)}")
291
305
  text += "[`#{name}`](#{url})"
292
306
  body += "\n![#{name}](#{url})" if [".png", ".jpg", ".gif"].include? File.extname(name).downcase
293
307
  else
@@ -335,9 +349,9 @@ module Migrator
335
349
  end
336
350
 
337
351
  def trac_ticket_link(ticket)
338
- return "trac:#{ticket[:id]}" unless @tracticketbaseurl
352
+ return "trac:#{ticket[:id]}" unless @trac_ticket_base_url
339
353
 
340
- "[trac:#{ticket[:id]}](#{@tracticketbaseurl}/#{ticket[:id]})"
354
+ "[trac:#{ticket[:id]}](#{@trac_ticket_base_url}/#{ticket[:id]})"
341
355
  end
342
356
  end
343
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)
@@ -95,6 +96,7 @@ module Migrator
95
96
  str.gsub!(/\[changeset:"(\d+)".*\]/) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
96
97
  str.gsub!(/\[changeset:(\d+).*\]/) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
97
98
  str.gsub!(/\[(\d+)\]/) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
99
+ str.gsub!(%r{\[(\d+)/.*\]}) { Tractive::Utilities.map_changeset(Regexp.last_match[1], @revmap, changeset_base_url) }
98
100
  end
99
101
 
100
102
  # Font styles
@@ -126,15 +128,15 @@ module Migrator
126
128
  image_path = if mod == "source"
127
129
  "![#{path.split("/").last}](#{base_url}#{path})"
128
130
  elsif mod == "wiki"
129
- _, file = path.split(":")
130
- 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)}"
131
133
  "![#{file}](#{upload_path})"
132
134
  elsif path.start_with?("http")
133
135
  # [[Image(http://example.org/s.jpg)]]
134
136
  "![#{d[:path]}](#{d[:path]})"
135
137
  else
136
138
  _, id, file = path.split(":")
137
- file_path = "#{attach_url}/#{id}/#{file}"
139
+ file_path = "#{attach_url}/#{Tractive::Utilities.attachment_path(id, file, hashed: @attach_hashed)}"
138
140
  "![#{d[:path]}](#{file_path})"
139
141
  end
140
142
 
@@ -27,7 +27,7 @@ module Migrator
27
27
  input_file_name = args[:opts][:importfromfile]
28
28
 
29
29
  @filter_applied = args[:opts][:filter]
30
- @filter_options = { column_name: args[:opts][:columnname], operator: args[:opts][:operator], column_value: args[:opts][:columnvalue] }
30
+ @filter_options = { column_name: args[:opts][:columnname], operator: args[:opts][:operator], column_value: args[:opts][:columnvalue], include_null: args[:opts][:includenull] }
31
31
 
32
32
  @trac = Tractive::Trac.new(db)
33
33
  @repo = github["repo"]
@@ -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,145 @@
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
+ @attachments_hashed = @config.dig("wiki", "attachments", "hashed")
21
+
22
+ @attachment_options = {
23
+ hashed: @attachments_hashed
24
+ }
25
+
26
+ verify_options
27
+ verify_locations
28
+
29
+ @twf_to_markdown = Migrator::Converter::TwfToMarkdown.new(@tracticketbaseurl, @attachment_options, @changeset_base_url, @wiki_attachments_url, @revmap_path)
30
+ end
31
+
32
+ def migrate_wikis
33
+ $logger.info("Processing the wiki...")
34
+
35
+ Dir.chdir(@options["repo-path"]) do
36
+ # For every version of every file in the wiki...
37
+ Tractive::Wiki.for_migration.each do |wiki|
38
+ next if skip_file(wiki[:name])
39
+
40
+ comment = if wiki[:comment].nil? || wiki[:comment].empty?
41
+ "Initial load of version #{wiki[:version]} of trac-file #{wiki[:name]}"
42
+ else
43
+ wiki[:comment].gsub('"', '\"')
44
+ end
45
+
46
+ file_name = "#{cleanse_filename(wiki[:name])}.md"
47
+ $logger.info("Working with file [#{file_name}]")
48
+ $logger.debug("Object: #{wiki}")
49
+
50
+ wiki_markdown_text = @twf_to_markdown.convert(wiki[:text])
51
+ wiki_markdown_text += wiki_attachments(wiki)
52
+
53
+ # Create file with content
54
+ File.open(file_name, "w") do |f|
55
+ f.puts(wiki_markdown_text)
56
+ end
57
+
58
+ # git-add it
59
+ unless execute_command("git add #{file_name}").success?
60
+ $logger.error("ERROR at git-add #{file_name}!!!")
61
+ exit(1)
62
+ end
63
+
64
+ author = generate_author(wiki[:author])
65
+
66
+ # git-commit it
67
+ commit_command = "git commit --allow-empty -m \"#{comment}\" --author \"#{author}\" --date \"#{wiki[:fixeddate]}\""
68
+ unless execute_command(commit_command).success?
69
+ $logger.error("ERROR at git-commit #{file_name}!!!")
70
+ exit(1)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def verify_options
79
+ $logger.info("Verifying options...")
80
+
81
+ missing_options = []
82
+ missing_options << "attachment-base-url" if @wiki_attachments_url.empty?
83
+ missing_options << "repo-path" if @repo_path.empty?
84
+
85
+ return if missing_options.empty?
86
+
87
+ $logger.error("Following options are missing: #{missing_options} - exiting...")
88
+ exit(1)
89
+ end
90
+
91
+ def verify_locations
92
+ $logger.info("Verifying locations...")
93
+ missing_directories = []
94
+
95
+ # git-root exists?
96
+ missing_directories << "repo-path" unless Dir.exist?(@repo_path)
97
+
98
+ return if missing_directories.empty?
99
+
100
+ $logger.error("Following directories are missing: #{missing_directories} - exiting ...")
101
+ exit(1)
102
+ end
103
+
104
+ def cleanse_filename(name)
105
+ # Get rid of 'magic' characters from potential filename - replace with '_'
106
+ # Magic: [ /<>- ]
107
+ name.gsub(%r{[/<>-]}, "_")
108
+ end
109
+
110
+ def skip_file(file_name)
111
+ file_name.start_with?("Trac") || (file_name.start_with?("Wiki") && !file_name.start_with?("WikiStart"))
112
+ end
113
+
114
+ def generate_author(author)
115
+ return "" if author.nil? || author.empty?
116
+
117
+ author_name = @authors_map[author]&.[]("name") || author.split("@").first
118
+ author_email = @authors_map[author]&.[]("email") || author
119
+
120
+ "#{author_name} <#{author_email}>"
121
+ end
122
+
123
+ def wiki_attachments(wiki)
124
+ attachments = wiki.attachments
125
+ return "" if attachments.count.zero?
126
+
127
+ attachments_list = ["# Attachments\n"]
128
+
129
+ attachments.each do |attachment|
130
+ attachment_path = Tractive::Utilities.attachment_path(
131
+ wiki.name, attachment.filename, hashed: @attachments_hashed
132
+ )
133
+ attachments_list << "- [#{attachment.filename}](#{@wiki_attachments_url}/#{attachment_path})"
134
+ end
135
+
136
+ attachments_list.join("\n")
137
+ end
138
+
139
+ def execute_command(command)
140
+ `#{command}`
141
+ $CHILD_STATUS
142
+ end
143
+ end
144
+ end
145
+ 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
@@ -19,14 +19,18 @@ module Tractive
19
19
  def filter_column(options)
20
20
  return self if options.nil? || options.values.compact.empty?
21
21
 
22
- case options[:operator].downcase
23
- when "like"
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]) }
27
- else
28
- where { Sequel.lit("#{options[:column_name]} #{options[:operator]} '#{options[:column_value]}'") }
29
- end
22
+ query = case options[:operator].downcase
23
+ when "like"
24
+ Sequel.like(options[:column_name].to_sym, options[:column_value])
25
+ when "not like"
26
+ ~Sequel.like(options[:column_name].to_sym, options[:column_value])
27
+ else
28
+ Sequel.lit("#{options[:column_name]} #{options[:operator]} '#{options[:column_value]}'")
29
+ end
30
+
31
+ query = Sequel.|(query, { options[:column_name].to_sym => nil }) if options[:include_null]
32
+
33
+ where { query }
30
34
  end
31
35
  end
32
36
 
@@ -35,5 +39,9 @@ module Tractive
35
39
  change_arr = changes + attachments
36
40
  change_arr.sort_by { |change| change[:time] }
37
41
  end
42
+
43
+ def closed_comments
44
+ changes_dataset.where(field: "status", newvalue: "closed")
45
+ end
38
46
  end
39
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.7"
4
+ VERSION = "1.0.11"
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.7
4
+ version: 1.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-18 00:00:00.000000000 Z
11
+ date: 2021-12-17 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