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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +24 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +47 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +14 -0
  8. data/LICENSE.md +69 -0
  9. data/README.adoc +742 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/config.example.yaml +73 -0
  14. data/db/trac-test.db +0 -0
  15. data/docker/Dockerfile +19 -0
  16. data/docker/docker-compose.yml +68 -0
  17. data/exe/tractive +111 -0
  18. data/lib/tractive/attachment_exporter.rb +62 -0
  19. data/lib/tractive/github_api/client/issues.rb +78 -0
  20. data/lib/tractive/github_api/client/milestones.rb +35 -0
  21. data/lib/tractive/github_api/client.rb +16 -0
  22. data/lib/tractive/github_api.rb +3 -0
  23. data/lib/tractive/graceful_quit.rb +30 -0
  24. data/lib/tractive/info.rb +46 -0
  25. data/lib/tractive/main.rb +81 -0
  26. data/lib/tractive/migrator/converter/trac_to_github.rb +307 -0
  27. data/lib/tractive/migrator/converter/twf_to_markdown.rb +125 -0
  28. data/lib/tractive/migrator/converter.rb +3 -0
  29. data/lib/tractive/migrator/engine/migrate_from_db.rb +95 -0
  30. data/lib/tractive/migrator/engine/migrate_from_file.rb +100 -0
  31. data/lib/tractive/migrator/engine/migrate_to_file.rb +68 -0
  32. data/lib/tractive/migrator/engine.rb +131 -0
  33. data/lib/tractive/migrator.rb +3 -0
  34. data/lib/tractive/models/attachment.rb +10 -0
  35. data/lib/tractive/models/milestone.rb +6 -0
  36. data/lib/tractive/models/report.rb +6 -0
  37. data/lib/tractive/models/revision.rb +6 -0
  38. data/lib/tractive/models/session.rb +6 -0
  39. data/lib/tractive/models/ticket.rb +36 -0
  40. data/lib/tractive/models/ticket_change.rb +7 -0
  41. data/lib/tractive/revmap_generator.rb +111 -0
  42. data/lib/tractive/trac.rb +16 -0
  43. data/lib/tractive/utilities.rb +68 -0
  44. data/lib/tractive/version.rb +5 -0
  45. data/lib/tractive.rb +29 -0
  46. data/tractive.gemspec +37 -0
  47. metadata +189 -0
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "github_api/client"
@@ -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