tractive 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +24 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +47 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +14 -0
- data/LICENSE.md +69 -0
- data/README.adoc +742 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config.example.yaml +73 -0
- data/db/trac-test.db +0 -0
- data/docker/Dockerfile +19 -0
- data/docker/docker-compose.yml +68 -0
- data/exe/tractive +111 -0
- data/lib/tractive/attachment_exporter.rb +62 -0
- data/lib/tractive/github_api/client/issues.rb +78 -0
- data/lib/tractive/github_api/client/milestones.rb +35 -0
- data/lib/tractive/github_api/client.rb +16 -0
- data/lib/tractive/github_api.rb +3 -0
- data/lib/tractive/graceful_quit.rb +30 -0
- data/lib/tractive/info.rb +46 -0
- data/lib/tractive/main.rb +81 -0
- data/lib/tractive/migrator/converter/trac_to_github.rb +307 -0
- data/lib/tractive/migrator/converter/twf_to_markdown.rb +125 -0
- data/lib/tractive/migrator/converter.rb +3 -0
- data/lib/tractive/migrator/engine/migrate_from_db.rb +95 -0
- data/lib/tractive/migrator/engine/migrate_from_file.rb +100 -0
- data/lib/tractive/migrator/engine/migrate_to_file.rb +68 -0
- data/lib/tractive/migrator/engine.rb +131 -0
- data/lib/tractive/migrator.rb +3 -0
- data/lib/tractive/models/attachment.rb +10 -0
- data/lib/tractive/models/milestone.rb +6 -0
- data/lib/tractive/models/report.rb +6 -0
- data/lib/tractive/models/revision.rb +6 -0
- data/lib/tractive/models/session.rb +6 -0
- data/lib/tractive/models/ticket.rb +36 -0
- data/lib/tractive/models/ticket_change.rb +7 -0
- data/lib/tractive/revmap_generator.rb +111 -0
- data/lib/tractive/trac.rb +16 -0
- data/lib/tractive/utilities.rb +68 -0
- data/lib/tractive/version.rb +5 -0
- data/lib/tractive.rb +29 -0
- data/tractive.gemspec +37 -0
- metadata +189 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "tractive"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config.example.yaml
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Trac-specific information
|
2
|
+
trac:
|
3
|
+
# Trac database location
|
4
|
+
database: sqlite://db/trac.db
|
5
|
+
# database: mysql2://user:password@host:port/database
|
6
|
+
# database: mysql2://root:password@mysql:3306/foobar
|
7
|
+
|
8
|
+
# URL of the Trac "tickets" interface
|
9
|
+
ticketbaseurl: https://example.org/trac/foobar/ticket
|
10
|
+
|
11
|
+
# GitHub-specific information
|
12
|
+
github:
|
13
|
+
# Target GitHub organization and repo name
|
14
|
+
repo: 'example-org/target-repository'
|
15
|
+
|
16
|
+
# GitHub user Personal Access Token
|
17
|
+
token: 'ghp_fpsc4de1f0c46e01576810740c9242097cba4619486'
|
18
|
+
|
19
|
+
# RevMap file to use for migration
|
20
|
+
revmap_path: ./example-revmap.txt
|
21
|
+
|
22
|
+
# User map: Trac username to GitHub username
|
23
|
+
# - Please ensure the GitHub usernames are correct and valid, otherwise
|
24
|
+
# the issue migration process will fail if the GitHub user specified as owner
|
25
|
+
# (assignee) does not exist.
|
26
|
+
users:
|
27
|
+
# <Trac email or username>: <username on GitHub>
|
28
|
+
matthew@example.org: example-matt
|
29
|
+
valencia: example-vale
|
30
|
+
|
31
|
+
# Label mapping from Trac ticket to GitHub label
|
32
|
+
labels:
|
33
|
+
type:
|
34
|
+
Feature Request: feature request
|
35
|
+
Defect: defect
|
36
|
+
Task: task
|
37
|
+
component:
|
38
|
+
configuration: conf
|
39
|
+
documentation: doc
|
40
|
+
resolution:
|
41
|
+
Invalid: invalid
|
42
|
+
Duplicate: duplicate
|
43
|
+
Wontfix: wontfix
|
44
|
+
platform:
|
45
|
+
Linux: Linux
|
46
|
+
Windows: Windows
|
47
|
+
|
48
|
+
# less useful, but also possible:
|
49
|
+
priority:
|
50
|
+
Low: '@low'
|
51
|
+
High: '@high'
|
52
|
+
severity:
|
53
|
+
blocker: '#high'
|
54
|
+
critical: '#critical'
|
55
|
+
major: '#major'
|
56
|
+
minor: '#minor'
|
57
|
+
trivial: '#trivial'
|
58
|
+
version:
|
59
|
+
'1.3': v1.3
|
60
|
+
'1.4': v1.4
|
61
|
+
|
62
|
+
milestones:
|
63
|
+
'2021_02':
|
64
|
+
name: '2021_02'
|
65
|
+
due: 1392595200000000
|
66
|
+
completed: 1415959156000000
|
67
|
+
description: ''
|
68
|
+
|
69
|
+
attachments:
|
70
|
+
url: "https://github.com/example-org/raw/main/from_tractive"
|
71
|
+
# export-folder is relative to export-script
|
72
|
+
export_folder: "./attachments"
|
73
|
+
export_script: ./attachments.sh
|
data/db/trac-test.db
ADDED
Binary file
|
data/docker/Dockerfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
FROM ruby:2.5
|
2
|
+
|
3
|
+
WORKDIR /usr/src/app
|
4
|
+
|
5
|
+
COPY Gemfile Gemfile.lock ./
|
6
|
+
RUN bundle install
|
7
|
+
|
8
|
+
|
9
|
+
# throw errors if Gemfile has been modified since Gemfile.lock
|
10
|
+
RUN bundle config --global frozen 1
|
11
|
+
|
12
|
+
COPY . .
|
13
|
+
|
14
|
+
WORKDIR /usr/src/myapp/trac-hub
|
15
|
+
|
16
|
+
ENTRYPOINT ./trac-hub
|
17
|
+
CMD -h
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
version: "2"
|
2
|
+
|
3
|
+
services:
|
4
|
+
tracd:
|
5
|
+
image: solsson/trac:engine
|
6
|
+
|
7
|
+
restart: unless-stopped
|
8
|
+
volumes:
|
9
|
+
- ../trac-env/:/trac
|
10
|
+
ports:
|
11
|
+
- 81:80
|
12
|
+
links:
|
13
|
+
- mysql
|
14
|
+
depends_on:
|
15
|
+
- mysql
|
16
|
+
command: ["-s", "/trac", "--auth", "*,/trac/conf/digest_passwd,pjtadmin"]
|
17
|
+
|
18
|
+
mysql:
|
19
|
+
image: mysql:5.7
|
20
|
+
restart: unless-stopped
|
21
|
+
environment:
|
22
|
+
- MYSQL_ROOT_PASSWORD=password
|
23
|
+
ports:
|
24
|
+
- 3306:3306
|
25
|
+
volumes:
|
26
|
+
- mysql:/var/lib/mysql
|
27
|
+
|
28
|
+
gitlab:
|
29
|
+
image: gitlab/gitlab-ce:latest
|
30
|
+
ports:
|
31
|
+
- 443:443
|
32
|
+
- 80:80
|
33
|
+
- 22:22
|
34
|
+
container_name: gitlab
|
35
|
+
|
36
|
+
restart: unless-stopped
|
37
|
+
volumes:
|
38
|
+
- /Users/beweiche/gitlab/config:/etc/gitlab
|
39
|
+
- /Users/beweiche/gitlab/logs:/var/log/gitlab
|
40
|
+
- /Users/beweiche/gitlab/data:/var/opt/gitlab
|
41
|
+
|
42
|
+
trac-hub:
|
43
|
+
image: bwl21/trac-hub
|
44
|
+
build: .
|
45
|
+
|
46
|
+
restart: unless-stopped
|
47
|
+
|
48
|
+
volumes:
|
49
|
+
- ..:/usr/src/myapp
|
50
|
+
- /Users/beweiche/gitlab/data:/var/opt/gitlab
|
51
|
+
links:
|
52
|
+
- mysql
|
53
|
+
depends_on:
|
54
|
+
- mysql
|
55
|
+
|
56
|
+
working_dir: /usr/src/myapp/trac-hub
|
57
|
+
entrypoint: ./trac-hub
|
58
|
+
command: -h
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
volumes:
|
64
|
+
mysql: {
|
65
|
+
|
66
|
+
}
|
67
|
+
www: {}
|
68
|
+
log: {}
|
data/exe/tractive
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/tractive"
|
5
|
+
|
6
|
+
class TractiveCommand < Thor
|
7
|
+
default_command :migrate
|
8
|
+
|
9
|
+
desc "<OPTIONS>", "Migrate Trac instances to modern Git management platforms like GitHub and GitLab"
|
10
|
+
method_option "attachmentexporter", type: :string, aliases: ["-A", "--attachment-exporter"],
|
11
|
+
desc: "Generate an attachment exporter script according to config.yaml"
|
12
|
+
method_option "attachurl", type: :string, aliases: ["-a", "--attachment-url"], banner: "<URL>",
|
13
|
+
desc: "If attachment files are reachable via a URL we reference this here"
|
14
|
+
method_option "config", type: :string, default: "tractive.config.yaml", banner: "<PATH>", aliases: "-c",
|
15
|
+
desc: "Set the configuration file"
|
16
|
+
method_option "dryrun", type: :boolean, aliases: ["-d", "--dry-run"],
|
17
|
+
desc: "Write data to a file instead of pushing it to github"
|
18
|
+
method_option "exportattachments", type: :string, aliases: ["-e", "--export-attachments"],
|
19
|
+
desc: "Export attachments from the database according to config.yaml"
|
20
|
+
method_option "fast", type: :boolean, aliases: ["-F", "--fast-import"],
|
21
|
+
desc: "Import without safety-checking issue numbers."
|
22
|
+
|
23
|
+
method_option "filter", type: :boolean, aliases: ["-f", "--filter"],
|
24
|
+
desc: "Filter records that you want to import."
|
25
|
+
method_option "columnname", type: :string, aliases: ["--column-name"],
|
26
|
+
desc: "Name of the column to filter."
|
27
|
+
method_option "operator", type: :string, aliases: ["--operator"],
|
28
|
+
desc: "Operator for filter."
|
29
|
+
method_option "columnvalue", type: :string, aliases: ["--column-value"],
|
30
|
+
desc: "Value of the column to filter."
|
31
|
+
|
32
|
+
method_option "importfromfile", type: :string, aliases: ["-I", "--import-from-file"],
|
33
|
+
desc: "Import issues from a json file"
|
34
|
+
method_option "info", type: :boolean, aliases: ["-i", "--info"],
|
35
|
+
desc: "Reports existing labels and users in the database"
|
36
|
+
method_option "logfile", type: :string, aliases: ["-L", "--log-file"],
|
37
|
+
desc: "Name of the logfile to output logs to."
|
38
|
+
method_option "mockdeleted", type: :boolean, aliases: ["-M", "--mockup"],
|
39
|
+
desc: "Import from 0 and mocking tickets deleted on trac"
|
40
|
+
method_option "openedonly", type: :boolean, aliases: ["-o", "--opened-only"],
|
41
|
+
desc: "Skips the import of closed tickets"
|
42
|
+
method_option "revmapfile", type: :string, aliases: ["-r", "--rev-map-file"], banner: "<PATH>",
|
43
|
+
desc: "Allows to specify a commit revision mapping FILE"
|
44
|
+
method_option "singlepost", type: :boolean, aliases: ["-S", "--single-post"],
|
45
|
+
desc: "Put all issue comments in the first message."
|
46
|
+
method_option "start", type: :numeric, aliases: ["-s", "--start-at"], banner: "<ID>",
|
47
|
+
desc: "Start migration from ticket with number <ID>"
|
48
|
+
method_option "verbose", type: :boolean, aliases: ["-v", "--verbose"], desc: "Verbose mode"
|
49
|
+
def migrate
|
50
|
+
Tractive::Main.new(options).run
|
51
|
+
end
|
52
|
+
|
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::RevmapGenerator.new(
|
68
|
+
options["revtimestampfile"],
|
69
|
+
options["svnurl"],
|
70
|
+
options["svnlocalpath"],
|
71
|
+
options["gitlocalrepopath"],
|
72
|
+
options["revoutputfile"]
|
73
|
+
).generate
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.exit_on_failure?
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def method_missing(*args)
|
81
|
+
warn "No method found named: #{args[0]}"
|
82
|
+
warn "Run with `--help` or `-h` to see available options"
|
83
|
+
exit 1
|
84
|
+
end
|
85
|
+
|
86
|
+
def respond_to_missing?
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
no_commands do
|
91
|
+
def verify_revmap_generator_options!(options)
|
92
|
+
required_options = {}
|
93
|
+
required_options["--svn-url OR --svn-local-path"] = options["svnurl"] || options["svnlocalpath"]
|
94
|
+
required_options["--git-local-repo-path"] = options["gitlocalrepopath"]
|
95
|
+
required_options["--rev-timestamp-file"] = options["revtimestampfile"]
|
96
|
+
required_options["--revmap-output-file"] = options["revoutputfile"]
|
97
|
+
|
98
|
+
missing_options = {}
|
99
|
+
required_options.each do |key, value|
|
100
|
+
missing_options[key] = value if value.nil? || value.strip.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
return if missing_options.empty?
|
104
|
+
|
105
|
+
warn("missing revmap generator options (#{missing_options.keys}).\nRun with `--help` or `-h` to see available options")
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
TractiveCommand.start(ARGV)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module Tractive
|
7
|
+
class AttachmentExporter
|
8
|
+
def initialize(cfg, db)
|
9
|
+
@cfg = cfg
|
10
|
+
@db = db
|
11
|
+
end
|
12
|
+
|
13
|
+
# Produce a shell script to export attachments, to be executed on the Trac
|
14
|
+
# instance
|
15
|
+
def generate_script
|
16
|
+
outfile = @cfg.dig("attachments", "export_script")
|
17
|
+
outfolder = @cfg.dig("attachments", "export_folder")
|
18
|
+
|
19
|
+
raise("missing attachments.export_script entry in configuration") unless outfile
|
20
|
+
raise("missing attachments.export_folder entry in configuration") unless outfolder
|
21
|
+
|
22
|
+
attachments = Attachment.tickets_attachments.for_export
|
23
|
+
export_commands = attachments.map do |attachment|
|
24
|
+
%(mkdir -p #{outfolder}/#{attachment[:id]}
|
25
|
+
trac-admin /trac attachment export ticket:#{attachment[:id]} '#{attachment[:filename]}' > '#{outfolder}/#{attachment[:id]}/#{attachment[:filename]}')
|
26
|
+
end
|
27
|
+
|
28
|
+
File.open(outfile, "w") do |f|
|
29
|
+
f.puts("mkdir -p #{outfolder}")
|
30
|
+
f.puts(export_commands.join("\n"))
|
31
|
+
end
|
32
|
+
|
33
|
+
$logger.info "created attachment exporter in #{outfile}"
|
34
|
+
rescue StandardError => e
|
35
|
+
$logger.error(e.message)
|
36
|
+
exit(1)
|
37
|
+
end
|
38
|
+
|
39
|
+
# export the images from the database into a folder
|
40
|
+
def export
|
41
|
+
output_dir = @cfg.dig("attachments", "export_folder") || "#{Dir.pwd}/tmp/trac"
|
42
|
+
trac_url = @cfg.dig("attachments", "url")
|
43
|
+
|
44
|
+
raise("attachments url is required in config.yaml to export attachments.") unless trac_url
|
45
|
+
|
46
|
+
FileUtils.mkdir_p output_dir
|
47
|
+
attachments = Attachment.tickets_attachments.for_export
|
48
|
+
|
49
|
+
# using URI::Parser.new because URI.encode raise warning: URI.escape is obsolete
|
50
|
+
uri_parser = URI::Parser.new
|
51
|
+
|
52
|
+
attachments.each do |attachment|
|
53
|
+
$logger.info "Saving attachments of ticket #{attachment.id}... "
|
54
|
+
FileUtils.mkdir_p "#{output_dir}/#{attachment.id}"
|
55
|
+
|
56
|
+
File.open("#{output_dir}/#{attachment.id}/#{attachment.filename}", "wb") do |file|
|
57
|
+
file.write URI.open(uri_parser.escape("#{trac_url}/#{attachment.id}/#{attachment.filename}")).read
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubApi
|
4
|
+
class Client
|
5
|
+
# Methods for the Issues API
|
6
|
+
module Issues
|
7
|
+
def create_issue(repo, params)
|
8
|
+
JSON.parse(
|
9
|
+
RestClient.post(
|
10
|
+
"https://api.github.com/repos/#{repo}/import/issues",
|
11
|
+
params.to_json,
|
12
|
+
{
|
13
|
+
"Authorization" => "token #{@token}",
|
14
|
+
"Content-Type" => "application/json",
|
15
|
+
"Accept" => "application/vnd.github.golden-comet-preview+json"
|
16
|
+
}
|
17
|
+
)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_issues(repo, params)
|
22
|
+
JSON.parse(
|
23
|
+
RestClient.get(
|
24
|
+
"https://api.github.com/repos/#{repo}/issues",
|
25
|
+
{
|
26
|
+
"Authorization" => "token #{@token}",
|
27
|
+
params: params
|
28
|
+
}
|
29
|
+
)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
alias issues list_issues
|
33
|
+
|
34
|
+
def issue(repo, number)
|
35
|
+
JSON.parse(
|
36
|
+
RestClient.get(
|
37
|
+
"https://api.github.com/repos/#{repo}/issues/#{number}",
|
38
|
+
{ "Authorization" => "token #{@token}" }
|
39
|
+
)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def issue_import_status(repo, id)
|
44
|
+
JSON.parse(
|
45
|
+
RestClient.get(
|
46
|
+
"https://api.github.com/repos/#{repo}/import/issues/#{id}",
|
47
|
+
{
|
48
|
+
"Authorization" => "token #{@token}",
|
49
|
+
"Accept" => "application/vnd.github.golden-comet-preview+json"
|
50
|
+
}
|
51
|
+
)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def issue_comments(repo, issue_id)
|
56
|
+
JSON.parse(
|
57
|
+
RestClient.get(
|
58
|
+
"https://api.github.com/repos/#{repo}/issues/#{issue_id}/comments",
|
59
|
+
{
|
60
|
+
"Authorization" => "token #{@token}",
|
61
|
+
"Accept" => "application/vnd.github.golden-comet-preview+json"
|
62
|
+
}
|
63
|
+
)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_issue_comment(repo, comment_id, comment_body)
|
68
|
+
JSON.parse(
|
69
|
+
RestClient.patch(
|
70
|
+
"https://api.github.com/repos/#{repo}/issues/comments/#{comment_id}",
|
71
|
+
{ body: comment_body }.to_json,
|
72
|
+
{ "Authorization" => "token #{@token}" }
|
73
|
+
)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GithubApi
|
4
|
+
class Client
|
5
|
+
# Methods for the Issues API
|
6
|
+
module Milestones
|
7
|
+
def list_milestones(repo, params)
|
8
|
+
JSON.parse(
|
9
|
+
RestClient.get(
|
10
|
+
"https://api.github.com/repos/#{repo}/milestones?per_page=100",
|
11
|
+
{
|
12
|
+
"Authorization" => "token #{@token}",
|
13
|
+
params: params
|
14
|
+
}
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
alias milestones list_milestones
|
19
|
+
|
20
|
+
def create_milestone(repo, params)
|
21
|
+
JSON.parse(
|
22
|
+
RestClient.post(
|
23
|
+
"https://api.github.com/repos/#{repo}/milestones",
|
24
|
+
params.to_json,
|
25
|
+
{
|
26
|
+
"Authorization" => "token #{@token}",
|
27
|
+
"Content-Type" => "application/json",
|
28
|
+
"Accept" => "application/vnd.github.golden-comet-preview+json"
|
29
|
+
}
|
30
|
+
)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "client/issues"
|
4
|
+
require_relative "client/milestones"
|
5
|
+
|
6
|
+
# Service to perform github actions
|
7
|
+
module GithubApi
|
8
|
+
class Client
|
9
|
+
include GithubApi::Client::Issues
|
10
|
+
include GithubApi::Client::Milestones
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@token = options[:access_token]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Tractive
|
6
|
+
class GracefulQuit
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_accessor :breaker
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
self.breaker = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.enable
|
16
|
+
trap("INT") do
|
17
|
+
yield if block_given?
|
18
|
+
instance.breaker = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.check(message = "Quitting")
|
23
|
+
return unless instance.breaker
|
24
|
+
|
25
|
+
yield if block_given?
|
26
|
+
$logger.info message
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tractive
|
4
|
+
class Info
|
5
|
+
def print
|
6
|
+
$logger.info result_hash.to_yaml
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def result_hash
|
12
|
+
users = [
|
13
|
+
Ticket.distinct.select(:reporter).select_map(:reporter),
|
14
|
+
Ticket.distinct.select(:owner).select_map(:owner),
|
15
|
+
TicketChange.distinct.select(:author).select_map(:author),
|
16
|
+
TicketChange.distinct.select(:newvalue).where(field: "reporter").select_map(:newvalue),
|
17
|
+
Revision.distinct.select(:author).select_map(:author),
|
18
|
+
Report.distinct.select(:author).select_map(:author),
|
19
|
+
Attachment.distinct.select(:author).select_map(:author)
|
20
|
+
].flatten.uniq.compact
|
21
|
+
|
22
|
+
milestones = {}
|
23
|
+
Milestone.each { |r| milestones[r.name] = r.to_hash }
|
24
|
+
|
25
|
+
types = Ticket.distinct.select(:type).select_map(:type).compact
|
26
|
+
components = Ticket.distinct.select(:component).select_map(:component).compact
|
27
|
+
resolutions = Ticket.distinct.select(:resolution).select_map(:resolution).compact
|
28
|
+
severity = Ticket.distinct.select(:severity).select_map(:severity).compact
|
29
|
+
priorities = Ticket.distinct.select(:priority).select_map(:priority).compact
|
30
|
+
tracstates = Ticket.distinct.select(:status).select_map(:status).compact
|
31
|
+
|
32
|
+
{
|
33
|
+
"users" => Utilities.make_hash("", users),
|
34
|
+
"milestones" => milestones,
|
35
|
+
"labels" => {
|
36
|
+
"type" => Utilities.make_hash("type_", types),
|
37
|
+
"component" => Utilities.make_hash("component_", components),
|
38
|
+
"resolution" => Utilities.make_hash("resolution_", resolutions),
|
39
|
+
"severity" => Utilities.make_hash("severity", severity),
|
40
|
+
"priority" => Utilities.make_hash("priority_", priorities),
|
41
|
+
"tracstate" => Utilities.make_hash("tracstate_", tracstates)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tractive
|
4
|
+
class Main
|
5
|
+
def initialize(opts)
|
6
|
+
verify_options!(opts)
|
7
|
+
|
8
|
+
@opts = opts
|
9
|
+
@cfg = YAML.load_file(@opts[:config])
|
10
|
+
|
11
|
+
Tractive::Utilities.setup_logger(output_stream: @opts[:log_file] || $stderr, verbose: @opts[:verbose])
|
12
|
+
@db = Tractive::Utilities.setup_db!(@cfg["trac"]["database"])
|
13
|
+
rescue Sequel::DatabaseConnectionError, Sequel::AdapterNotFound, URI::InvalidURIError, Sequel::DatabaseError => e
|
14
|
+
$logger.error e.message
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
if @opts[:info]
|
20
|
+
info
|
21
|
+
elsif @opts[:attachmentexporter]
|
22
|
+
create_attachment_exporter_script
|
23
|
+
elsif @opts[:exportattachments]
|
24
|
+
export_attachments
|
25
|
+
elsif @opts[:generaterevmap]
|
26
|
+
generate_revmap_file
|
27
|
+
else
|
28
|
+
migrate
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def migrate
|
33
|
+
Migrator::Engine.new(opts: @opts, cfg: @cfg, db: @db).migrate
|
34
|
+
end
|
35
|
+
|
36
|
+
def info
|
37
|
+
Tractive::Info.new.print
|
38
|
+
end
|
39
|
+
|
40
|
+
def export_attachments
|
41
|
+
Tractive::AttachmentExporter.new(@cfg, @db).export
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_attachment_exporter_script
|
45
|
+
Tractive::AttachmentExporter.new(@cfg, @db).generate_script
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def verify_options!(options)
|
51
|
+
verify_config_options!(options)
|
52
|
+
verify_filter_options!(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_config_options!(options)
|
56
|
+
return if File.exist?(options[:config])
|
57
|
+
|
58
|
+
warn_and_exit("missing configuration file (#{options[:config]})", 1)
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify_filter_options!(options)
|
62
|
+
required_options = { columnname: "--column-name",
|
63
|
+
operator: "--operator",
|
64
|
+
columnvalue: "--column-value" }
|
65
|
+
missing_options = {}
|
66
|
+
required_options.each do |key, value|
|
67
|
+
missing_options[key] = value if !options[key] || options[key].strip.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
return if !options[:filter] || missing_options.empty?
|
71
|
+
|
72
|
+
warn_and_exit("missing filter options #{missing_options.values}", 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
def warn_and_exit(message, exit_code)
|
76
|
+
warn message
|
77
|
+
warn "Run with `--help` or `-h` to see available options"
|
78
|
+
exit exit_code
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|