nandi 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +477 -0
- data/Rakefile +8 -0
- data/exe/nandi-enforce +36 -0
- data/lib/generators/nandi/check_constraint/USAGE +19 -0
- data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +52 -0
- data/lib/generators/nandi/check_constraint/templates/add_check_constraint.rb +15 -0
- data/lib/generators/nandi/check_constraint/templates/validate_check_constraint.rb +9 -0
- data/lib/generators/nandi/compile/USAGE +19 -0
- data/lib/generators/nandi/compile/compile_generator.rb +62 -0
- data/lib/generators/nandi/foreign_key/USAGE +47 -0
- data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +91 -0
- data/lib/generators/nandi/foreign_key/templates/add_foreign_key.rb +13 -0
- data/lib/generators/nandi/foreign_key/templates/add_reference.rb +11 -0
- data/lib/generators/nandi/foreign_key/templates/validate_foreign_key.rb +9 -0
- data/lib/generators/nandi/migration/USAGE +9 -0
- data/lib/generators/nandi/migration/migration_generator.rb +24 -0
- data/lib/generators/nandi/migration/templates/migration.rb +13 -0
- data/lib/generators/nandi/not_null_check/USAGE +19 -0
- data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +56 -0
- data/lib/generators/nandi/not_null_check/templates/add_not_null_check.rb +11 -0
- data/lib/generators/nandi/not_null_check/templates/validate_not_null_check.rb +9 -0
- data/lib/nandi.rb +35 -0
- data/lib/nandi/compiled_migration.rb +86 -0
- data/lib/nandi/config.rb +126 -0
- data/lib/nandi/file_diff.rb +32 -0
- data/lib/nandi/file_matcher.rb +72 -0
- data/lib/nandi/formatting.rb +79 -0
- data/lib/nandi/instructions.rb +19 -0
- data/lib/nandi/instructions/add_check_constraint.rb +23 -0
- data/lib/nandi/instructions/add_column.rb +24 -0
- data/lib/nandi/instructions/add_foreign_key.rb +40 -0
- data/lib/nandi/instructions/add_index.rb +50 -0
- data/lib/nandi/instructions/change_column_default.rb +23 -0
- data/lib/nandi/instructions/create_table.rb +83 -0
- data/lib/nandi/instructions/drop_constraint.rb +22 -0
- data/lib/nandi/instructions/drop_table.rb +21 -0
- data/lib/nandi/instructions/irreversible_migration.rb +15 -0
- data/lib/nandi/instructions/remove_column.rb +23 -0
- data/lib/nandi/instructions/remove_index.rb +41 -0
- data/lib/nandi/instructions/remove_not_null_constraint.rb +22 -0
- data/lib/nandi/instructions/validate_constraint.rb +22 -0
- data/lib/nandi/lockfile.rb +58 -0
- data/lib/nandi/migration.rb +363 -0
- data/lib/nandi/renderers.rb +7 -0
- data/lib/nandi/renderers/active_record.rb +13 -0
- data/lib/nandi/renderers/active_record/generate.rb +59 -0
- data/lib/nandi/renderers/active_record/instructions.rb +134 -0
- data/lib/nandi/safe_migration_enforcer.rb +143 -0
- data/lib/nandi/timeout_policies.rb +38 -0
- data/lib/nandi/timeout_policies/access_exclusive.rb +54 -0
- data/lib/nandi/timeout_policies/concurrent.rb +64 -0
- data/lib/nandi/validation.rb +10 -0
- data/lib/nandi/validation/add_column_validator.rb +43 -0
- data/lib/nandi/validation/each_validator.rb +32 -0
- data/lib/nandi/validation/failure_helpers.rb +35 -0
- data/lib/nandi/validation/remove_index_validator.rb +30 -0
- data/lib/nandi/validation/result.rb +30 -0
- data/lib/nandi/validation/timeout_validator.rb +37 -0
- data/lib/nandi/validator.rb +102 -0
- data/lib/nandi/version.rb +5 -0
- data/lib/templates/nandi/renderers/active_record/generate/show.rb.erb +27 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_check_constraint/show.rb.erb +7 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb +6 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb +8 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_table/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/irreversible_migration/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb +3 -0
- metadata +320 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= class_name %> < Nandi::Migration
|
4
|
+
def up
|
5
|
+
# Migration instructions go here, eg:
|
6
|
+
# add_column :widgets, :size, :integer
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
# Reverse migration instructions go here, eg:
|
11
|
+
# remove_column :widgets, :size
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Description:
|
2
|
+
Generates two new database migrations which will safely add a check that
|
3
|
+
a column is not null, and validate it separately.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
rails generate nandi:not_null_check foos bar
|
7
|
+
|
8
|
+
This will create:
|
9
|
+
db/safe_migrations/20190424123727_add_not_null_check_on_bar_to_foos.rb
|
10
|
+
db/safe_migrations/20190424123728_validate_not_null_check_on_bar_to_foos.rb
|
11
|
+
|
12
|
+
Example:
|
13
|
+
rails generate nandi:not_null_check foos bar --validation-timeout 20000
|
14
|
+
|
15
|
+
This will create:
|
16
|
+
db/safe_migrations/20190424123727_add_not_null_check_on_bar_to_foos.rb
|
17
|
+
db/safe_migrations/20190424123728_validate_not_null_check_on_bar_to_foos.rb
|
18
|
+
|
19
|
+
The statement timeout in the second migration will be set to 20,000ms.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "nandi/formatting"
|
5
|
+
|
6
|
+
module Nandi
|
7
|
+
class NotNullCheckGenerator < Rails::Generators::Base
|
8
|
+
include Nandi::Formatting
|
9
|
+
|
10
|
+
argument :table, type: :string
|
11
|
+
argument :column, type: :string
|
12
|
+
class_option :validation_timeout, type: :numeric, default: 15 * 60 * 1000
|
13
|
+
|
14
|
+
source_root File.expand_path("templates", __dir__)
|
15
|
+
|
16
|
+
attr_reader :add_not_null_check_name, :validate_not_null_check_name
|
17
|
+
|
18
|
+
def add_not_null_check
|
19
|
+
self.table = table.to_sym
|
20
|
+
self.column = column.to_sym
|
21
|
+
|
22
|
+
@add_not_null_check_name = "add_not_null_check_on_#{column}_to_#{table}"
|
23
|
+
|
24
|
+
template(
|
25
|
+
"add_not_null_check.rb",
|
26
|
+
"#{base_path}/#{timestamp}_#{add_not_null_check_name}.rb",
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_not_null_check
|
31
|
+
self.table = table.to_sym
|
32
|
+
self.column = column.to_sym
|
33
|
+
|
34
|
+
@validate_not_null_check_name = "validate_not_null_check_on_#{column}_to_#{table}"
|
35
|
+
|
36
|
+
template(
|
37
|
+
"validate_not_null_check.rb",
|
38
|
+
"#{base_path}/#{timestamp(1)}_#{validate_not_null_check_name}.rb",
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def base_path
|
45
|
+
Nandi.config.migration_directory || "db/safe_migrations"
|
46
|
+
end
|
47
|
+
|
48
|
+
def timestamp(offset = 0)
|
49
|
+
(Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
|
50
|
+
end
|
51
|
+
|
52
|
+
def name
|
53
|
+
"#{table}_check_#{column}_not_null"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= add_not_null_check_name.camelize %> < Nandi::Migration
|
4
|
+
def up
|
5
|
+
add_check_constraint <%= format_value(table) %>, <%= format_value(name) %>, "<%= column %> IS NOT NULL"
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
drop_constraint <%= format_value(table) %>, <%= format_value(name) %>
|
10
|
+
end
|
11
|
+
end
|
data/lib/nandi.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/config"
|
4
|
+
require "nandi/renderers"
|
5
|
+
require "nandi/compiled_migration"
|
6
|
+
require "active_support/core_ext/string/inflections"
|
7
|
+
|
8
|
+
module Nandi
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def compile(files:)
|
13
|
+
compiled = files.
|
14
|
+
map { |f| CompiledMigration.build(f) }
|
15
|
+
|
16
|
+
yield compiled
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure
|
20
|
+
yield config
|
21
|
+
end
|
22
|
+
|
23
|
+
def validator
|
24
|
+
Nandi::Validator
|
25
|
+
end
|
26
|
+
|
27
|
+
def config
|
28
|
+
@config ||= Config.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def compiled_output_directory
|
32
|
+
Nandi.config.output_directory || "db/migrate"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/file_diff"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
class CompiledMigration
|
7
|
+
class InvalidMigrationError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :file_name, :source_file_path, :class_name
|
10
|
+
|
11
|
+
def self.build(source_file_path)
|
12
|
+
new(source_file_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(source_file_path)
|
16
|
+
@source_file_path = source_file_path
|
17
|
+
require source_file_path
|
18
|
+
|
19
|
+
@file_name, @class_name = /\d+_([a-z0-9_]+)\.rb\z/.match(source_file_path)[0..1]
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate!
|
23
|
+
validation = migration.validate
|
24
|
+
|
25
|
+
unless validation.valid?
|
26
|
+
raise InvalidMigrationError, "Migration #{source_file_path} " \
|
27
|
+
"is not valid:\n#{validation.error_list}"
|
28
|
+
end
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def body
|
34
|
+
@body ||= if migration_unchanged?
|
35
|
+
File.read(output_path)
|
36
|
+
else
|
37
|
+
validate!
|
38
|
+
compiled_body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def output_path
|
43
|
+
"#{Nandi.compiled_output_directory}/#{file_name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def migration
|
47
|
+
@migration ||= class_name.camelize.constantize.new(Nandi.validator)
|
48
|
+
end
|
49
|
+
|
50
|
+
def compiled_digest
|
51
|
+
Digest::SHA256.hexdigest(body)
|
52
|
+
end
|
53
|
+
|
54
|
+
def source_digest
|
55
|
+
Digest::SHA256.hexdigest(File.read(source_file_path))
|
56
|
+
end
|
57
|
+
|
58
|
+
def migration_unchanged?
|
59
|
+
return false unless File.exist?(output_path)
|
60
|
+
|
61
|
+
source_migration_diff = Nandi::FileDiff.new(
|
62
|
+
file_path: source_file_path,
|
63
|
+
known_digest: Nandi::Lockfile.get(file_name).fetch(:source_digest),
|
64
|
+
)
|
65
|
+
|
66
|
+
compiled_migration_diff = Nandi::FileDiff.new(
|
67
|
+
file_path: output_path,
|
68
|
+
known_digest: Nandi::Lockfile.get(file_name).fetch(:compiled_digest),
|
69
|
+
)
|
70
|
+
|
71
|
+
source_migration_diff.unchanged? && compiled_migration_diff.unchanged?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def compiled_body
|
77
|
+
output = Nandi.config.renderer.generate(migration)
|
78
|
+
|
79
|
+
if Nandi.config.post_processor
|
80
|
+
Nandi.config.post_processor.call(output)
|
81
|
+
else
|
82
|
+
output
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/nandi/config.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/renderers"
|
4
|
+
require "nandi/lockfile"
|
5
|
+
|
6
|
+
module Nandi
|
7
|
+
class Config
|
8
|
+
# Most DDL changes take a very strict lock, but execute very quickly. For these
|
9
|
+
# the statement timeout should be very tight, so that if there's an unexpected
|
10
|
+
# delay the query queue does not back up.
|
11
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT = 1_500
|
12
|
+
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT = 5_000
|
13
|
+
|
14
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT_LIMIT =
|
15
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT
|
16
|
+
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT_LIMIT =
|
17
|
+
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT
|
18
|
+
DEFAULT_LOCKFILE_DIRECTORY = File.join(Dir.pwd, "db")
|
19
|
+
DEFAULT_CONCURRENT_TIMEOUT_LIMIT = 3_600_000
|
20
|
+
DEFAULT_COMPILE_FILES = "all"
|
21
|
+
# The rendering backend used to produce output. The only supported option
|
22
|
+
# at current is Nandi::Renderers::ActiveRecord, which produces ActiveRecord
|
23
|
+
# migrations.
|
24
|
+
# @return [Class]
|
25
|
+
attr_accessor :renderer
|
26
|
+
|
27
|
+
# The default lock timeout for migrations that take ACCESS EXCLUSIVE
|
28
|
+
# locks. Can be overridden by way of the `set_lock_timeout` class
|
29
|
+
# method in a given migration. Default: 1500ms.
|
30
|
+
# @return [Integer]
|
31
|
+
attr_accessor :access_exclusive_lock_timeout
|
32
|
+
|
33
|
+
# The default statement timeout for migrations that take ACCESS EXCLUSIVE
|
34
|
+
# locks. Can be overridden by way of the `set_statement_timeout` class
|
35
|
+
# method in a given migration. Default: 1500ms.
|
36
|
+
# @return [Integer]
|
37
|
+
attr_accessor :access_exclusive_statement_timeout
|
38
|
+
|
39
|
+
# The maximum lock timeout for migrations that take an ACCESS EXCLUSIVE
|
40
|
+
# lock and therefore block all reads and writes. Default: 5,000ms.
|
41
|
+
# @return [Integer]
|
42
|
+
attr_accessor :access_exclusive_statement_timeout_limit
|
43
|
+
|
44
|
+
# The maximum statement timeout for migrations that take an ACCESS
|
45
|
+
# EXCLUSIVE lock and therefore block all reads and writes. Default: 1500ms.
|
46
|
+
# @return [Integer]
|
47
|
+
attr_accessor :access_exclusive_lock_timeout_limit
|
48
|
+
|
49
|
+
# The minimum statement timeout for migrations that take place concurrently.
|
50
|
+
# Default: 3,600,000ms (ie, 3 hours).
|
51
|
+
# @return [Integer]
|
52
|
+
attr_accessor :concurrent_statement_timeout_limit
|
53
|
+
|
54
|
+
# The minimum lock timeout for migrations that take place concurrently.
|
55
|
+
# Default: 3,600,000ms (ie, 3 hours).
|
56
|
+
# @return [Integer]
|
57
|
+
attr_accessor :concurrent_lock_timeout_limit
|
58
|
+
|
59
|
+
# The directory for Nandi migrations. Default: `db/safe_migrations`
|
60
|
+
# @return [String]
|
61
|
+
attr_accessor :migration_directory
|
62
|
+
|
63
|
+
# The directory for output files. Default: `db/migrate`
|
64
|
+
# @return [String]
|
65
|
+
attr_accessor :output_directory
|
66
|
+
|
67
|
+
# The files to compile when the compile generator is run. Default: `all`
|
68
|
+
# May be one of the following:
|
69
|
+
# - 'all' compiles all files
|
70
|
+
# - 'git-diff' only files changed since last commit
|
71
|
+
# - a full or partial version timestamp, eg '20190101010101', '20190101'
|
72
|
+
# - a timestamp range , eg '>=20190101010101'
|
73
|
+
# @return [String]
|
74
|
+
attr_accessor :compile_files
|
75
|
+
#
|
76
|
+
# Directory where .nandilock.yml will be stored
|
77
|
+
# Defaults to project root
|
78
|
+
# @return [String]
|
79
|
+
attr_writer :lockfile_directory
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
attr_reader :post_processor, :custom_methods
|
83
|
+
|
84
|
+
def initialize(renderer: Renderers::ActiveRecord)
|
85
|
+
@renderer = renderer
|
86
|
+
@access_exclusive_statement_timeout = DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT
|
87
|
+
@concurrent_lock_timeout_limit =
|
88
|
+
@concurrent_statement_timeout_limit =
|
89
|
+
DEFAULT_CONCURRENT_TIMEOUT_LIMIT
|
90
|
+
@custom_methods = {}
|
91
|
+
@access_exclusive_lock_timeout =
|
92
|
+
DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT
|
93
|
+
@access_exclusive_statement_timeout =
|
94
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT
|
95
|
+
@access_exclusive_statement_timeout_limit =
|
96
|
+
DEFAULT_ACCESS_EXCLUSIVE_STATEMENT_TIMEOUT_LIMIT
|
97
|
+
@access_exclusive_lock_timeout_limit = DEFAULT_ACCESS_EXCLUSIVE_LOCK_TIMEOUT_LIMIT
|
98
|
+
@compile_files = DEFAULT_COMPILE_FILES
|
99
|
+
@lockfile_directory = DEFAULT_LOCKFILE_DIRECTORY
|
100
|
+
end
|
101
|
+
|
102
|
+
# Register a block to be called on output, for example a code formatter. Whatever is
|
103
|
+
# returned will be written to the output file.
|
104
|
+
# @yieldparam migration [string] The text of a compiled migration.
|
105
|
+
def post_process(&block)
|
106
|
+
@post_processor = block
|
107
|
+
end
|
108
|
+
|
109
|
+
# Register a custom DDL method.
|
110
|
+
# @param name [Symbol] The name of the method to create. This will be monkey-patched
|
111
|
+
# into Nandi::Migration.
|
112
|
+
# @param klass [Class] The class to initialise with the arguments to the
|
113
|
+
# method. It should define a `template` instance method which will return a
|
114
|
+
# subclass of Cell::ViewModel from the Cells templating library and a
|
115
|
+
# `procedure` method that returns the name of the method. It may optionally
|
116
|
+
# define a `mixins` method, which will return an array of `Module`s to be
|
117
|
+
# mixed into any migration that uses this method.
|
118
|
+
def register_method(name, klass)
|
119
|
+
custom_methods[name] = klass
|
120
|
+
end
|
121
|
+
|
122
|
+
def lockfile_directory
|
123
|
+
@lockfile_directory ||= Pathname.new(@lockfile_directory)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nandi
|
4
|
+
class FileDiff
|
5
|
+
attr_reader :file_path, :known_digest
|
6
|
+
|
7
|
+
def initialize(file_path:, known_digest:)
|
8
|
+
@file_path = file_path
|
9
|
+
@known_digest = known_digest
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_name
|
13
|
+
File.basename(file_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def body
|
17
|
+
File.read(file_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def digest
|
21
|
+
Digest::SHA256.hexdigest(body)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unchanged?
|
25
|
+
!changed?
|
26
|
+
end
|
27
|
+
|
28
|
+
def changed?
|
29
|
+
known_digest != digest
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nandi
|
4
|
+
class FileMatcher
|
5
|
+
TIMESTAMP_REGEX = /\A(?<operator>>|>=)?(?<timestamp>\d+)\z/.freeze
|
6
|
+
|
7
|
+
def self.call(*args, **kwargs)
|
8
|
+
new(*args, **kwargs).call
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(files:, spec:)
|
12
|
+
@files = Set.new(files)
|
13
|
+
@spec = spec
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
case spec
|
18
|
+
when "all"
|
19
|
+
Set.new(
|
20
|
+
files.reject { |f| ignored_filenames.include?(File.basename(f)) },
|
21
|
+
)
|
22
|
+
when "git-diff"
|
23
|
+
files.intersection(files_from_git_status)
|
24
|
+
when TIMESTAMP_REGEX
|
25
|
+
match_timestamp
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def ignored_files
|
32
|
+
@ignored_files ||= if File.exist?(".nandiignore")
|
33
|
+
File.read(".nandiignore").lines.map(&:strip)
|
34
|
+
else
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ignored_filenames
|
40
|
+
ignored_files.map(&File.method(:basename))
|
41
|
+
end
|
42
|
+
|
43
|
+
def match_timestamp
|
44
|
+
match = TIMESTAMP_REGEX.match(spec)
|
45
|
+
|
46
|
+
case match[:operator]
|
47
|
+
when nil
|
48
|
+
files.select do |file|
|
49
|
+
file.start_with?(match[:timestamp])
|
50
|
+
end
|
51
|
+
when ">"
|
52
|
+
migrations_after((Integer(match[:timestamp]) + 1).to_s)
|
53
|
+
when ">="
|
54
|
+
migrations_after(match[:timestamp])
|
55
|
+
end.to_set
|
56
|
+
end
|
57
|
+
|
58
|
+
def migrations_after(minimum)
|
59
|
+
files.select { |file| file >= minimum }
|
60
|
+
end
|
61
|
+
|
62
|
+
def files_from_git_status
|
63
|
+
`
|
64
|
+
git status --porcelain --short --untracked-files=all |
|
65
|
+
cut -c4- |
|
66
|
+
xargs -n1 basename
|
67
|
+
`.lines.map(&:strip)
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :files, :spec
|
71
|
+
end
|
72
|
+
end
|