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 +4 -4
- data/README.adoc +75 -16
- data/exe/command_base.rb +11 -0
- data/exe/generate.rb +49 -0
- data/exe/tractive +17 -51
- data/lib/tractive/github_api/client/issues.rb +6 -6
- data/lib/tractive/github_api/client/labels.rb +2 -2
- data/lib/tractive/github_api/client/milestones.rb +2 -2
- data/lib/tractive/github_api/client.rb +2 -0
- data/lib/tractive/http/client/request.rb +59 -0
- data/lib/tractive/http/client.rb +3 -0
- data/lib/tractive/main.rb +5 -1
- data/lib/tractive/migrator/converter/trac_to_github.rb +26 -24
- data/lib/tractive/migrator/converter/twf_to_markdown.rb +6 -5
- data/lib/tractive/migrator/engine.rb +3 -2
- data/lib/tractive/migrator/wikis/migrate_from_db.rb +127 -0
- data/lib/tractive/migrator/wikis.rb +3 -0
- data/lib/tractive/migrator.rb +1 -0
- data/lib/tractive/models/attachment.rb +1 -0
- data/lib/tractive/models/ticket.rb +4 -0
- data/lib/tractive/models/wiki.rb +18 -0
- data/lib/tractive/trac.rb +2 -1
- data/lib/tractive/utilities.rb +14 -2
- data/lib/tractive/version.rb +1 -1
- data/lib/tractive.rb +1 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d048cfbc81f42620ae076e33691d303559fb14b097048e2d4dcfb9574ed5bae
|
4
|
+
data.tar.gz: 3015ae96353d4e65c10569115d3c7b43d08474cfdf9223e82f8c2710e1663efc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
-
|
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:
|
363
|
-
|
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
|
-
`
|
511
|
+
`url:`:::: URL to obtain Trac attachments from
|
504
512
|
|
505
|
-
`url
|
513
|
+
`hashed:`:::: Whether the url has hased or plain image names and ids
|
506
514
|
|
507
|
-
`export_folder
|
515
|
+
`export_folder:`:::: folder where the attachments will be downloaded to from Trac.
|
508
516
|
|
509
|
-
`export_script
|
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
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
data/exe/command_base.rb
ADDED
@@ -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 "
|
4
|
+
require_relative "./command_base"
|
5
|
+
require_relative "./generate"
|
5
6
|
|
6
|
-
class TractiveCommand <
|
7
|
-
default_command :
|
8
|
-
|
9
|
-
|
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
|
-
|
51
|
-
def migrate
|
50
|
+
def migrate_tickets
|
52
51
|
Tractive::Main.new(options).run
|
53
52
|
end
|
54
53
|
|
55
|
-
desc "
|
56
|
-
method_option "
|
57
|
-
|
58
|
-
method_option "
|
59
|
-
|
60
|
-
method_option "
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
22
|
+
Http::Client::Request.post(
|
23
23
|
"https://api.github.com/repos/#{repo}/milestones",
|
24
24
|
params.to_json,
|
25
25
|
{
|
@@ -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
|
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
|
-
@
|
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]
|
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 = {
|
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(
|
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
|
-
|
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
|
-
|
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"]
|
154
|
-
|
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 @
|
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
|
-
"
|
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,
|
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 =
|
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
|
-
|
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
|
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
|
-
"
|
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
|
data/lib/tractive/migrator.rb
CHANGED
@@ -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
|
data/lib/tractive/utilities.rb
CHANGED
@@ -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}"
|
data/lib/tractive/version.rb
CHANGED
data/lib/tractive.rb
CHANGED
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.
|
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-
|
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
|