tractive 1.0.7 → 1.0.11
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 +4 -4
- data/.rubocop.yml +3 -0
- data/README.adoc +80 -16
- data/db/trac-test.db +0 -0
- data/exe/command_base.rb +11 -0
- data/exe/generate.rb +49 -0
- data/exe/tractive +19 -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 -12
- data/lib/tractive/migrator/converter/twf_to_markdown.rb +7 -5
- data/lib/tractive/migrator/engine.rb +4 -3
- data/lib/tractive/migrator/wikis/migrate_from_db.rb +145 -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 +16 -8
- 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: 8ebb8048967ee1732b68d54ed6c10798e0959d533e7ed9744a843f1a129f764d
         | 
| 4 | 
            +
              data.tar.gz: 4799587764dcaa848e76971b50df220e72ffa914a5f1bc7c3cad20ef2e5de83d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0c052a1f936f6e4d4749e4fd9f8ec9fcab3042f4220e2c4f10e6a7ed127f2c1ee8a856f49a58ae8b960a38f12ffdc35d9097a55361142b39bfa605a269ea2d1b
         | 
| 7 | 
            +
              data.tar.gz: 6d0025c7682c74526aa3a0fb8a4d93d4f46fad4cab5e560898e3b2c504e8981cffe10203e59afb7c8d818d3598efe3729b854c2939d7b8ca64105846de55f131
         | 
    
        data/.rubocop.yml
    CHANGED
    
    
    
        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
         | 
| @@ -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 | 
    
        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"],
         | 
| @@ -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 | 
            -
               | 
| 49 | 
            -
              def migrate
         | 
| 50 | 
            +
              def migrate_tickets
         | 
| 50 51 | 
             
                Tractive::Main.new(options).run
         | 
| 51 52 | 
             
              end
         | 
| 52 53 |  | 
| 53 | 
            -
              desc " | 
| 54 | 
            -
              method_option " | 
| 55 | 
            -
             | 
| 56 | 
            -
              method_option " | 
| 57 | 
            -
             | 
| 58 | 
            -
              method_option " | 
| 59 | 
            -
             | 
| 60 | 
            -
               | 
| 61 | 
            -
             | 
| 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 | 
            -
                       | 
| 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,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] | 
| 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( | 
| 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 | 
            -
                     | 
| 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 | 
            -
                       | 
| 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"] | 
| 153 | 
            -
                       | 
| 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] | 
| 304 | 
            +
                        url = @uri_parser.escape("#{@attachurl}/#{Tractive::Utilities.attachment_path(meta[:id], name, @attachment_options)}")
         | 
| 291 305 | 
             
                        text += "[`#{name}`](#{url})"
         | 
| 292 306 | 
             
                        body += "\n" 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 @ | 
| 352 | 
            +
                    return "trac:#{ticket[:id]}" unless @trac_ticket_base_url
         | 
| 339 353 |  | 
| 340 | 
            -
                    "[trac:#{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,  | 
| 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)
         | 
| @@ -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 | 
             
                                   ""
         | 
| 128 130 | 
             
                                 elsif mod == "wiki"
         | 
| 129 | 
            -
                                    | 
| 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 | 
             
                                   ""
         | 
| 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 | 
| 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 | 
            -
                              " | 
| 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
         | 
    
        data/lib/tractive/migrator.rb
    CHANGED
    
    
| @@ -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 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 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
         | 
    
        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.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 | 
            +
            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
         |