xmigra 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/xmigra/db_support/mssql.rb +12 -5
- data/lib/xmigra/db_support/psql.rb +12 -2
- data/lib/xmigra/new_migration_adder.rb +63 -0
- data/lib/xmigra/program.rb +23 -2
- data/lib/xmigra/schema_updater.rb +8 -3
- data/lib/xmigra/vcs_support/git.rb +29 -0
- data/lib/xmigra/vcs_support/svn.rb +103 -1
- data/lib/xmigra/version.rb +1 -1
- data/test/git_vcs.rb +29 -0
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0f93c4d5e00f442d8bd288980b7d3754b5dca202
         | 
| 4 | 
            +
              data.tar.gz: 9485199a710af1bd8f2c0ecbfc5ae9186c5e0097
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2d7104989df02da32dc2adb897e9a4abf986b1422f541431080540ea76b101065bf42617536c7b2f7609da19e82917a98e810fc66d89f935366f53da827ca297
         | 
| 7 | 
            +
              data.tar.gz: a7a66fd0a47c0dca11235f3c8593981fc4fb4ec89f33ef69c0d08b9aa665abba68d0854fffbc149913a9391d30af79c24af6f6704cc045b045e9acacd49b9e7f
         | 
| @@ -57,7 +57,7 @@ module XMigra | |
| 57 57 | 
             
                  return @stats_objs
         | 
| 58 58 | 
             
                end
         | 
| 59 59 |  | 
| 60 | 
            -
                def in_ddl_transaction
         | 
| 60 | 
            +
                def in_ddl_transaction(options={})
         | 
| 61 61 | 
             
                  parts = []
         | 
| 62 62 | 
             
                  parts << <<-"END_OF_SQL"
         | 
| 63 63 | 
             
            SET ANSI_NULLS ON
         | 
| @@ -71,18 +71,25 @@ GO | |
| 71 71 |  | 
| 72 72 | 
             
            SET NOCOUNT ON
         | 
| 73 73 | 
             
            GO
         | 
| 74 | 
            -
             | 
| 74 | 
            +
            -------------------- ERROR REFERENCE LINE --------------------
         | 
| 75 | 
            +
            DECLARE @BATCH_START_OFFSET INTEGER; SET @BATCH_START_OFFSET = 0;
         | 
| 75 76 | 
             
            BEGIN TRY
         | 
| 76 77 | 
             
              BEGIN TRAN;
         | 
| 77 78 | 
             
                  END_OF_SQL
         | 
| 78 79 |  | 
| 80 | 
            +
                  offset_lines = 5
         | 
| 79 81 | 
             
                  each_batch(yield) do |batch|
         | 
| 80 82 | 
             
                    batch_literal = MSSQLSpecifics.string_literal("\n" + batch)
         | 
| 81 | 
            -
                    parts << "EXEC sp_executesql @statement = #{batch_literal};"
         | 
| 83 | 
            +
                    parts << "SET @BATCH_START_OFFSET = #{offset_lines}; EXEC sp_executesql @statement = #{batch_literal}; SET @BATCH_START_OFFSET = 0;"
         | 
| 84 | 
            +
                    offset_lines += parts[-1].count("\n") + 1
         | 
| 82 85 | 
             
                  end
         | 
| 83 86 |  | 
| 87 | 
            +
                  if options[:dry_run]
         | 
| 88 | 
            +
                    parts << "  PRINT N'Dry-run successful.  Rolling back changes.'; ROLLBACK TRAN;"
         | 
| 89 | 
            +
                  else
         | 
| 90 | 
            +
                    parts << "  COMMIT TRAN;"
         | 
| 91 | 
            +
                  end
         | 
| 84 92 | 
             
                  parts << <<-"END_OF_SQL"
         | 
| 85 | 
            -
              COMMIT TRAN;
         | 
| 86 93 | 
             
            END TRY
         | 
| 87 94 | 
             
            BEGIN CATCH
         | 
| 88 95 | 
             
              ROLLBACK TRAN;
         | 
| @@ -93,7 +100,7 @@ BEGIN CATCH | |
| 93 100 |  | 
| 94 101 | 
             
              PRINT N'Update failed: ' + ERROR_MESSAGE();
         | 
| 95 102 | 
             
              PRINT N'    State: ' + CAST(ERROR_STATE() AS NVARCHAR);
         | 
| 96 | 
            -
              PRINT N'    Line: ' + CAST(ERROR_LINE() AS NVARCHAR) | 
| 103 | 
            +
              PRINT N'    Line: ' + CAST(@BATCH_START_OFFSET + ERROR_LINE() - 1 AS NVARCHAR) + N' after ERROR REFERENCE LINE'
         | 
| 97 104 |  | 
| 98 105 | 
             
              SELECT 
         | 
| 99 106 | 
             
                  @ErrorMessage = N'Update failed: ' + ERROR_MESSAGE(),
         | 
| @@ -23,8 +23,18 @@ module XMigra | |
| 23 23 |  | 
| 24 24 | 
             
                def filename_metavariable; "[{filename}]"; end
         | 
| 25 25 |  | 
| 26 | 
            -
                def in_ddl_transaction
         | 
| 27 | 
            -
                   | 
| 26 | 
            +
                def in_ddl_transaction(options={})
         | 
| 27 | 
            +
                  transaction_wrapup = begin
         | 
| 28 | 
            +
                    if options[:dry_run]
         | 
| 29 | 
            +
                      PgSQLSpecifics.in_plpgsql(%Q{
         | 
| 30 | 
            +
                        RAISE NOTICE 'Dry-run successful.  Rolling back changes.';
         | 
| 31 | 
            +
                      }) + "\nROLLBACK;"
         | 
| 32 | 
            +
                    else
         | 
| 33 | 
            +
                      "COMMIT;"
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  
         | 
| 37 | 
            +
                  ["BEGIN;", yield, transaction_wrapup].join("\n")
         | 
| 28 38 | 
             
                end
         | 
| 29 39 |  | 
| 30 40 | 
             
                def check_execution_environment_sql
         | 
| @@ -5,6 +5,11 @@ module XMigra | |
| 5 5 | 
             
              class NewMigrationAdder < SchemaManipulator
         | 
| 6 6 | 
             
                OBSOLETE_VERINC_FILE = 'version-upgrade-obsolete.yaml'
         | 
| 7 7 |  | 
| 8 | 
            +
                # Return this class (not an instance of it) from a Proc yielded by
         | 
| 9 | 
            +
                # each_possible_production_chain_extension_handler to continue through the
         | 
| 10 | 
            +
                # handler chain.
         | 
| 11 | 
            +
                class IgnoreHandler; end
         | 
| 12 | 
            +
                
         | 
| 8 13 | 
             
                def initialize(path)
         | 
| 9 14 | 
             
                  super(path)
         | 
| 10 15 | 
             
                end
         | 
| @@ -23,6 +28,13 @@ module XMigra | |
| 23 28 | 
             
                  end
         | 
| 24 29 | 
             
                  Hash === head_info or raise XMigra::Error, "Invalid #{MigrationChain::HEAD_FILE} format"
         | 
| 25 30 |  | 
| 31 | 
            +
                  if !head_info.empty? && respond_to?(:vcs_production_contents) && (production_head_contents = vcs_production_contents(head_file))
         | 
| 32 | 
            +
                    production_head_info = YAML.load(production_head_contents)
         | 
| 33 | 
            +
                    extending_production = head_info[MigrationChain::LATEST_CHANGE] == production_head_info[MigrationChain::LATEST_CHANGE]
         | 
| 34 | 
            +
                  else
         | 
| 35 | 
            +
                    extending_production = false
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                  
         | 
| 26 38 | 
             
                  new_fpath = struct_dir.join(
         | 
| 27 39 | 
             
                    [Date.today.strftime("%Y-%m-%d"), summary].join(' ') + '.yaml'
         | 
| 28 40 | 
             
                  )
         | 
| @@ -68,7 +80,58 @@ module XMigra | |
| 68 80 | 
             
                    mv_method.call(bufp, obufp)
         | 
| 69 81 | 
             
                  end
         | 
| 70 82 |  | 
| 83 | 
            +
                  production_chain_extended if extending_production
         | 
| 84 | 
            +
                  
         | 
| 71 85 | 
             
                  return new_fpath
         | 
| 72 86 | 
             
                end
         | 
| 87 | 
            +
                
         | 
| 88 | 
            +
                # Called when the chain of migrations in the production/master branch is
         | 
| 89 | 
            +
                # extended with a new migration.
         | 
| 90 | 
            +
                #
         | 
| 91 | 
            +
                # This method calls each_possible_production_chain_extension_handler to
         | 
| 92 | 
            +
                # generate a chain of handlers.
         | 
| 93 | 
            +
                #
         | 
| 94 | 
            +
                def production_chain_extended
         | 
| 95 | 
            +
                  Dir.chdir(self.path) do
         | 
| 96 | 
            +
                    each_possible_production_chain_extension_handler do |handler|
         | 
| 97 | 
            +
                      if handler.kind_of? Proc
         | 
| 98 | 
            +
                        handler_result = handler[]
         | 
| 99 | 
            +
                        break true unless handler_result == IgnoreHandler
         | 
| 100 | 
            +
                      else
         | 
| 101 | 
            +
                        handler_result = system(handler)
         | 
| 102 | 
            +
                        break true unless handler_result.nil?
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
                
         | 
| 108 | 
            +
                # Yield command strings or Proc instances to attempt handling the
         | 
| 109 | 
            +
                # production-chain-extension event
         | 
| 110 | 
            +
                #
         | 
| 111 | 
            +
                # Strings yielded by this method will be executed using Kernel#system.  If
         | 
| 112 | 
            +
                # this results in `nil` (command does not exist), processing will continue
         | 
| 113 | 
            +
                # through the remaining handlers.
         | 
| 114 | 
            +
                #
         | 
| 115 | 
            +
                # Procs yielded by this method will be executed without any parameters.
         | 
| 116 | 
            +
                # Unless the invocation returns IgnoreHandler, event processing will
         | 
| 117 | 
            +
                # terminate after invocation.
         | 
| 118 | 
            +
                #
         | 
| 119 | 
            +
                def each_possible_production_chain_extension_handler
         | 
| 120 | 
            +
                  yield "on-prod-chain-extended-local"
         | 
| 121 | 
            +
                  if respond_to?(:vcs_prod_chain_extension_handler) && (vcs_handler = vcs_prod_chain_extension_handler)
         | 
| 122 | 
            +
                    yield vcs_handler.to_s
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                  yield "on-prod-chain-extended"
         | 
| 125 | 
            +
                  yield Proc.new {
         | 
| 126 | 
            +
                    next unless respond_to? :warning
         | 
| 127 | 
            +
                    warning(<<END_MESSAGE)
         | 
| 128 | 
            +
            This command has extended the production migration chain.
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            Backing up your development database now may be advantageous in case you need
         | 
| 131 | 
            +
            to accept migrations developed in parallel from upstream before merging your
         | 
| 132 | 
            +
            work back to the mainline.
         | 
| 133 | 
            +
            END_MESSAGE
         | 
| 134 | 
            +
                  }
         | 
| 135 | 
            +
                end
         | 
| 73 136 | 
             
              end
         | 
| 74 137 | 
             
            end
         | 
    
        data/lib/xmigra/program.rb
    CHANGED
    
    | @@ -161,6 +161,14 @@ END_OF_HELP | |
| 161 161 | 
             
                        end
         | 
| 162 162 | 
             
                      end
         | 
| 163 163 |  | 
| 164 | 
            +
                      if use[:dry_run]
         | 
| 165 | 
            +
                        options.dry_run = false
         | 
| 166 | 
            +
                        flags.on("--dry-run", "Generated script will test upgrade", 
         | 
| 167 | 
            +
                                 "  commands without committing changes") do
         | 
| 168 | 
            +
                          options.dry_run = true
         | 
| 169 | 
            +
                        end
         | 
| 170 | 
            +
                      end
         | 
| 171 | 
            +
                      
         | 
| 164 172 | 
             
                      options.source_dir = Dir.pwd
         | 
| 165 173 | 
             
                      flags.on("--source=DIR", "Work from/on the schema in DIR") do |dir|
         | 
| 166 174 | 
             
                        options.source_dir = File.expand_path(dir)
         | 
| @@ -349,7 +357,9 @@ migration, and is included in the upgrade metadata stored in the database.  Use | |
| 349 357 | 
             
            of the '%program_cmd new' command is recommended; it handles several tiresome
         | 
| 350 358 | 
             
            and error-prone tasks: creating a new migration file with a conformant name,
         | 
| 351 359 | 
             
            setting the "starting from" section to the correct value, and updating
         | 
| 352 | 
            -
            SCHEMA/structure/head.yaml to reference the newly generated file.
         | 
| 360 | 
            +
            SCHEMA/structure/head.yaml to reference the newly generated file.  It can also
         | 
| 361 | 
            +
            print a warning or run a command when the source condition indicates a database
         | 
| 362 | 
            +
            backup would be a good idea.
         | 
| 353 363 |  | 
| 354 364 | 
             
            The SCHEMA/structure/head.yaml file deserves special note: it contains a
         | 
| 355 365 | 
             
            reference to the last migration to be applied.  Because of this, parallel
         | 
| @@ -672,6 +682,16 @@ This command generates a new migration file and ties it into the current | |
| 672 682 | 
             
            migration chain.  The name of the new file is generated from today's date and
         | 
| 673 683 | 
             
            the given MIGRATION_SUMMARY.  The resulting new file may be opened in an
         | 
| 674 684 | 
             
            editor (see the --[no-]edit option).
         | 
| 685 | 
            +
             | 
| 686 | 
            +
            If this command extends the production migration chain, it will attempt to
         | 
| 687 | 
            +
            locate a handler function ("on-prod-chain-extended-local", a VCS-specified
         | 
| 688 | 
            +
            handler, or "on-prod-chain-extended") executable from the schema root
         | 
| 689 | 
            +
            directory.  If it cannot locate one of these, it will print a message about
         | 
| 690 | 
            +
            the advisability of taking a backup of the development database.
         | 
| 691 | 
            +
             | 
| 692 | 
            +
            Git VCS specifics: The #{GitSpecifics::PRODUCTION_CHAIN_EXTENSION_COMMAND} attribute on the
         | 
| 693 | 
            +
            database.yaml file can be used to specify a handler for the production chain
         | 
| 694 | 
            +
            extension.
         | 
| 675 695 | 
             
            END_OF_HELP
         | 
| 676 696 |  | 
| 677 697 | 
             
                  argument_error_unless(args.length == 1,
         | 
| @@ -689,7 +709,7 @@ END_OF_HELP | |
| 689 709 | 
             
                end
         | 
| 690 710 |  | 
| 691 711 | 
             
                subcommand 'upgrade', "Generate an upgrade script" do |argv|
         | 
| 692 | 
            -
                  args, options = command_line(argv, {:production=>true, :outfile=>true},
         | 
| 712 | 
            +
                  args, options = command_line(argv, {:production=>true, :outfile=>true, :dry_run=>true},
         | 
| 693 713 | 
             
                                               :help=> <<END_OF_HELP)
         | 
| 694 714 | 
             
            Running this command will generate an update script from the source schema.
         | 
| 695 715 | 
             
            Generation of a production script involves more checks on the status of the
         | 
| @@ -705,6 +725,7 @@ END_OF_HELP | |
| 705 725 | 
             
                  sql_gen = SchemaUpdater.new(options.source_dir).extend(WarnToStderr)
         | 
| 706 726 | 
             
                  sql_gen.load_plugin!
         | 
| 707 727 | 
             
                  sql_gen.production = options.production
         | 
| 728 | 
            +
                  sql_gen.dry_run = options.dry_run
         | 
| 708 729 |  | 
| 709 730 | 
             
                  output_to(options.outfile) do |out_stream|
         | 
| 710 731 | 
             
                    out_stream.print(sql_gen.update_sql)
         | 
| @@ -50,16 +50,21 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL. | |
| 50 50 | 
             
                  end
         | 
| 51 51 |  | 
| 52 52 | 
             
                  @production = false
         | 
| 53 | 
            +
                  @dry_run = false
         | 
| 53 54 | 
             
                end
         | 
| 54 55 |  | 
| 55 | 
            -
                attr_accessor :production
         | 
| 56 | 
            +
                attr_accessor :production, :dry_run
         | 
| 56 57 | 
             
                attr_reader :migrations, :access_artifacts, :indexes, :branch_upgrade
         | 
| 57 58 |  | 
| 58 59 | 
             
                def inspect
         | 
| 59 60 | 
             
                  "<#{self.class.name}: path=#{path.to_s.inspect}, db=#{@db_specifics}, vcs=#{@vcs_specifics}>"
         | 
| 60 61 | 
             
                end
         | 
| 61 62 |  | 
| 62 | 
            -
                def in_ddl_transaction
         | 
| 63 | 
            +
                def in_ddl_transaction(options = {})
         | 
| 64 | 
            +
                  if options[:dry_run]
         | 
| 65 | 
            +
                    raise(XMigra::Error, 'DDL transaction not supported; dry-run unavailable.')
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                  
         | 
| 63 68 | 
             
                  yield
         | 
| 64 69 | 
             
                end
         | 
| 65 70 |  | 
| @@ -90,7 +95,7 @@ RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL. | |
| 90 95 | 
             
                  intro_comment << "\n\n"
         | 
| 91 96 |  | 
| 92 97 | 
             
                  # If supported, wrap transactionality around modifications
         | 
| 93 | 
            -
                  intro_comment + in_ddl_transaction do
         | 
| 98 | 
            +
                  intro_comment + in_ddl_transaction(:dry_run => @dry_run) do
         | 
| 94 99 | 
             
                    script_parts = [
         | 
| 95 100 | 
             
                      # Check for blatantly incorrect application of script, e.g. running
         | 
| 96 101 | 
             
                      # on master or template database.
         | 
| @@ -6,6 +6,7 @@ module XMigra | |
| 6 6 |  | 
| 7 7 | 
             
                MASTER_HEAD_ATTRIBUTE = 'xmigra-master'
         | 
| 8 8 | 
             
                MASTER_BRANCH_SUBDIR = 'xmigra-master'
         | 
| 9 | 
            +
                PRODUCTION_CHAIN_EXTENSION_COMMAND = 'xmigra-on-production-chain-extended'
         | 
| 9 10 |  | 
| 10 11 | 
             
                class AttributesFile
         | 
| 11 12 | 
             
                  def initialize(effect_root, access=:shared)
         | 
| @@ -244,6 +245,34 @@ module XMigra | |
| 244 245 | 
             
                  git(:rm, path, :get_result=>false)
         | 
| 245 246 | 
             
                end
         | 
| 246 247 |  | 
| 248 | 
            +
                def vcs_production_contents(path)
         | 
| 249 | 
            +
                  return nil unless git_master_head(:required => false)
         | 
| 250 | 
            +
                  git_fetch_master_branch
         | 
| 251 | 
            +
                  git(:show, [git_master_local_branch, git_internal_path].join(':'), :quiet=>true)
         | 
| 252 | 
            +
                rescue VersionControlError
         | 
| 253 | 
            +
                  return nil
         | 
| 254 | 
            +
                end
         | 
| 255 | 
            +
                
         | 
| 256 | 
            +
                def vcs_prod_chain_extension_handler
         | 
| 257 | 
            +
                  attr_val = GitSpecifics.attr_values(
         | 
| 258 | 
            +
                    PRODUCTION_CHAIN_EXTENSION_COMMAND,
         | 
| 259 | 
            +
                    self.path + SchemaManipulator::DBINFO_FILE,
         | 
| 260 | 
            +
                    :required=>false,
         | 
| 261 | 
            +
                  )[0]
         | 
| 262 | 
            +
                  
         | 
| 263 | 
            +
                  # Check for special value
         | 
| 264 | 
            +
                  return nil if attr_val == 'unspecified'
         | 
| 265 | 
            +
                  
         | 
| 266 | 
            +
                  handler_path = Pathname(attr_val)
         | 
| 267 | 
            +
                  if handler_path.absolute?
         | 
| 268 | 
            +
                    return handler_path if handler_path.exist?
         | 
| 269 | 
            +
                  else
         | 
| 270 | 
            +
                    handler_path = self.path + handler_path
         | 
| 271 | 
            +
                    return handler_path if handler_path.exist?
         | 
| 272 | 
            +
                  end
         | 
| 273 | 
            +
                  return attr_val
         | 
| 274 | 
            +
                end
         | 
| 275 | 
            +
                
         | 
| 247 276 | 
             
                def production_pattern
         | 
| 248 277 | 
             
                  ".+"
         | 
| 249 278 | 
             
                end
         | 
| @@ -22,7 +22,7 @@ module XMigra | |
| 22 22 | 
             
                  def run_svn(subcmd, *args)
         | 
| 23 23 | 
             
                    options = (Hash === args[-1]) ? args.pop : {}
         | 
| 24 24 | 
             
                    no_result = !options.fetch(:get_result, true)
         | 
| 25 | 
            -
                    raw_result = options.fetch(:raw, false)
         | 
| 25 | 
            +
                    raw_result = options.fetch(:raw, false) || subcmd.to_s == 'cat'
         | 
| 26 26 |  | 
| 27 27 | 
             
                    cmd_parts = ["svn", subcmd.to_s]
         | 
| 28 28 | 
             
                    cmd_parts << "--xml" unless no_result || raw_result
         | 
| @@ -235,5 +235,107 @@ END_OF_MESSAGE | |
| 235 235 | 
             
                  return @subversion_info if defined? @subversion_info
         | 
| 236 236 | 
             
                  return @subversion_info = subversion(:info, self.path)
         | 
| 237 237 | 
             
                end
         | 
| 238 | 
            +
                
         | 
| 239 | 
            +
                def vcs_production_contents(path)
         | 
| 240 | 
            +
                  path = Pathname(path)
         | 
| 241 | 
            +
                  
         | 
| 242 | 
            +
                  # Check for a production pattern.  If none exists, there is no way to
         | 
| 243 | 
            +
                  # identify which branches are production, so essentially no production
         | 
| 244 | 
            +
                  # content:
         | 
| 245 | 
            +
                  prod_pat = self.production_pattern
         | 
| 246 | 
            +
                  return nil if prod_pat.nil?
         | 
| 247 | 
            +
                  prod_pat = Regexp.compile(prod_pat.chomp)
         | 
| 248 | 
            +
                  
         | 
| 249 | 
            +
                  # Is the current branch a production branch?  If so, cat the committed
         | 
| 250 | 
            +
                  # version:
         | 
| 251 | 
            +
                  if branch_identifier =~ prod_pat
         | 
| 252 | 
            +
                    return svn(:cat, path.to_s)
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
                  
         | 
| 255 | 
            +
                  # Use an SvnHistoryTracer to walk back through the history of self.path
         | 
| 256 | 
            +
                  # looking for a copy from a production branch.
         | 
| 257 | 
            +
                  tracer = SvnHistoryTracer.new(self.path)
         | 
| 258 | 
            +
                  
         | 
| 259 | 
            +
                  while !(match = tracer.earliest_loaded_repopath =~ prod_pat) && tracer.load_parent_commit
         | 
| 260 | 
            +
                    # loop
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
                  
         | 
| 263 | 
            +
                  if match
         | 
| 264 | 
            +
                    subversion(:cat, "-r#{tracer.earliest_loaded_revision}", path.to_s)
         | 
| 265 | 
            +
                  end
         | 
| 266 | 
            +
                end
         | 
| 267 | 
            +
              end
         | 
| 268 | 
            +
              
         | 
| 269 | 
            +
              class SvnHistoryTracer
         | 
| 270 | 
            +
                include SubversionSpecifics
         | 
| 271 | 
            +
                
         | 
| 272 | 
            +
                def initialize(path)
         | 
| 273 | 
            +
                  @path = Pathname(path)
         | 
| 274 | 
            +
                  info_doc = subversion(:info, path.to_s)
         | 
| 275 | 
            +
                  @root_url = info_doc.elements['string(info/entry/repository/root)']
         | 
| 276 | 
            +
                  @most_recent_commit = info_doc.elements['string(info/entry/@revision)'].to_i
         | 
| 277 | 
            +
                  @history = []
         | 
| 278 | 
            +
                  @next_query = [branch_identifier, @most_recent_commit]
         | 
| 279 | 
            +
                  @history.unshift(@next_query.dup)
         | 
| 280 | 
            +
                end
         | 
| 281 | 
            +
                
         | 
| 282 | 
            +
                attr_reader :path, :most_recent_commit, :history
         | 
| 283 | 
            +
                
         | 
| 284 | 
            +
                def load_parent_commit
         | 
| 285 | 
            +
                  log_doc = next_earlier_log
         | 
| 286 | 
            +
                  if copy_elt = copying_element(log_doc)
         | 
| 287 | 
            +
                    trailing_part = branch_identifier[copy_elt.text.length..-1]
         | 
| 288 | 
            +
                    @next_query = [
         | 
| 289 | 
            +
                      copy_elt.attributes['copyfrom-path'] + trailing_part,
         | 
| 290 | 
            +
                      copy_elt.attributes['copyfrom-rev'].to_i
         | 
| 291 | 
            +
                    ]
         | 
| 292 | 
            +
                    @history.unshift(@next_query)
         | 
| 293 | 
            +
                    @next_query.dup
         | 
| 294 | 
            +
                  elsif change_elt = log_doc.elements['/log/logentry']
         | 
| 295 | 
            +
                    @next_query[1] = change_elt.attributes['revision'].to_i - 1
         | 
| 296 | 
            +
                    @next_query.dup if @next_query[1] > 0
         | 
| 297 | 
            +
                  else
         | 
| 298 | 
            +
                    @next_query[1] -= 1
         | 
| 299 | 
            +
                    @next_query.dup if @next_query[1] > 0
         | 
| 300 | 
            +
                  end
         | 
| 301 | 
            +
                end
         | 
| 302 | 
            +
                
         | 
| 303 | 
            +
                def history_exhausted?
         | 
| 304 | 
            +
                  @next_query[1] <= 0
         | 
| 305 | 
            +
                end
         | 
| 306 | 
            +
                
         | 
| 307 | 
            +
                def earliest_loaded_repopath
         | 
| 308 | 
            +
                  history[0][0]
         | 
| 309 | 
            +
                end
         | 
| 310 | 
            +
                
         | 
| 311 | 
            +
                def earliest_loaded_url
         | 
| 312 | 
            +
                  @root_url + history[0][0]
         | 
| 313 | 
            +
                end
         | 
| 314 | 
            +
                
         | 
| 315 | 
            +
                def earliest_loaded_revision
         | 
| 316 | 
            +
                  history[0][1]
         | 
| 317 | 
            +
                end
         | 
| 318 | 
            +
                
         | 
| 319 | 
            +
                def earliest_loaded_pinned_url(rel_path=nil)
         | 
| 320 | 
            +
                  pin_rev = @history[0][1]
         | 
| 321 | 
            +
                  if rel_path.nil?
         | 
| 322 | 
            +
                    [earliest_loaded_url, pin_rev.to_s].join('@')
         | 
| 323 | 
            +
                  else
         | 
| 324 | 
            +
                    rel_path = Pathname(rel_path)
         | 
| 325 | 
            +
                    "#{earliest_loaded_url}/#{rel_path}@#{pin_rev}"
         | 
| 326 | 
            +
                  end
         | 
| 327 | 
            +
                end
         | 
| 328 | 
            +
                
         | 
| 329 | 
            +
                def copying_element(log_doc)
         | 
| 330 | 
            +
                  log_doc.each_element %Q{/log/logentry/paths/path[@copyfrom-path]} do |elt|
         | 
| 331 | 
            +
                    return elt if elt.text == @next_query[0]
         | 
| 332 | 
            +
                    return elt if @next_query[0].start_with? (elt.text + '/')
         | 
| 333 | 
            +
                  end
         | 
| 334 | 
            +
                  return nil
         | 
| 335 | 
            +
                end
         | 
| 336 | 
            +
                
         | 
| 337 | 
            +
                def next_earlier_log
         | 
| 338 | 
            +
                  subversion(:log, '-l1', '-v', "-r#{@next_query[1]}:1", self.path)
         | 
| 339 | 
            +
                end
         | 
| 238 340 | 
             
              end
         | 
| 239 341 | 
             
            end
         | 
    
        data/lib/xmigra/version.rb
    CHANGED
    
    
    
        data/test/git_vcs.rb
    CHANGED
    
    | @@ -239,4 +239,33 @@ if GIT_PRESENT | |
| 239 239 | 
             
                  end
         | 
| 240 240 | 
             
                end
         | 
| 241 241 | 
             
              end
         | 
| 242 | 
            +
              
         | 
| 243 | 
            +
              run_test "XMigra detects extension of the production migration chain" do
         | 
| 244 | 
            +
                capture_chain_extension = Module.new do
         | 
| 245 | 
            +
                  def production_chain_extended(*args)
         | 
| 246 | 
            +
                    (@captured_call_args ||= []) << args
         | 
| 247 | 
            +
                  end
         | 
| 248 | 
            +
                  
         | 
| 249 | 
            +
                  attr_reader :captured_call_args
         | 
| 250 | 
            +
                end
         | 
| 251 | 
            +
                
         | 
| 252 | 
            +
                2.temp_dirs do |upstream, repo|
         | 
| 253 | 
            +
                  initialize_git_repo(upstream)
         | 
| 254 | 
            +
                  
         | 
| 255 | 
            +
                  Dir.chdir(upstream) do
         | 
| 256 | 
            +
                    commit_a_migration "first table"
         | 
| 257 | 
            +
                    make_this_branch_master
         | 
| 258 | 
            +
                  end
         | 
| 259 | 
            +
                  
         | 
| 260 | 
            +
                  `git clone "#{upstream.expand_path}" "#{repo}" 2>/dev/null`
         | 
| 261 | 
            +
                  
         | 
| 262 | 
            +
                  Dir.chdir(repo) do
         | 
| 263 | 
            +
                    XMigra::NewMigrationAdder.new('.') do |tool|
         | 
| 264 | 
            +
                      tool.extend(capture_chain_extension)
         | 
| 265 | 
            +
                      tool.add_migration('Create foo table')
         | 
| 266 | 
            +
                      assert_eq tool.captured_call_args, [[]]
         | 
| 267 | 
            +
                    end
         | 
| 268 | 
            +
                  end
         | 
| 269 | 
            +
                end
         | 
| 270 | 
            +
              end
         | 
| 242 271 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: xmigra
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.5.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Next IT Corporation
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2015- | 
| 12 | 
            +
            date: 2015-07-24 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: bundler
         |