xmigra 1.0.1 → 1.1.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/access_artifact.rb +44 -0
- data/lib/xmigra/access_artifact_collection.rb +50 -0
- data/lib/xmigra/branch_upgrade.rb +62 -0
- data/lib/xmigra/db_support/mssql.rb +1070 -0
- data/lib/xmigra/function.rb +17 -0
- data/lib/xmigra/index.rb +21 -0
- data/lib/xmigra/index_collection.rb +38 -0
- data/lib/xmigra/migration.rb +27 -0
- data/lib/xmigra/migration_chain.rb +63 -0
- data/lib/xmigra/migration_conflict.rb +68 -0
- data/lib/xmigra/new_file.rb +4 -0
- data/lib/xmigra/new_migration_adder.rb +74 -0
- data/lib/xmigra/permission_script_writer.rb +67 -0
- data/lib/xmigra/program.rb +927 -0
- data/lib/xmigra/schema_manipulator.rb +39 -0
- data/lib/xmigra/schema_updater.rb +183 -0
- data/lib/xmigra/stored_procedure.rb +20 -0
- data/lib/xmigra/vcs_support/git.rb +275 -0
- data/lib/xmigra/vcs_support/svn.rb +213 -0
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra/view.rb +17 -0
- data/lib/xmigra.rb +47 -3222
- data/test/runner.rb +53 -4
- metadata +22 -2
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module XMigra
|
3
|
+
class SchemaManipulator
|
4
|
+
DBINFO_FILE = 'database.yaml'
|
5
|
+
PERMISSIONS_FILE = 'permissions.yaml'
|
6
|
+
ACCESS_SUBDIR = 'access'
|
7
|
+
INDEXES_SUBDIR = 'indexes'
|
8
|
+
STRUCTURE_SUBDIR = 'structure'
|
9
|
+
VERINC_FILE = 'branch-upgrade.yaml'
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = Pathname.new(path)
|
13
|
+
@db_info = YAML.load_file(@path + DBINFO_FILE)
|
14
|
+
raise TypeError, "Expected Hash in #{DBINFO_FILE}" unless Hash === @db_info
|
15
|
+
@db_info = Hash.new do |h, k|
|
16
|
+
raise Error, "#{DBINFO_FILE} missing key #{k.inspect}"
|
17
|
+
end.update(@db_info)
|
18
|
+
|
19
|
+
db_system = @db_info['system']
|
20
|
+
extend(
|
21
|
+
@db_specifics = DatabaseSupportModules.find {|m|
|
22
|
+
m::SYSTEM_NAME == db_system
|
23
|
+
} || NoSpecifics
|
24
|
+
)
|
25
|
+
|
26
|
+
extend(
|
27
|
+
@vcs_specifics = VersionControlSupportModules.find {|m|
|
28
|
+
m.manages(path)
|
29
|
+
} || NoSpecifics
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :path
|
34
|
+
|
35
|
+
def branch_upgrade_file
|
36
|
+
@path.join(STRUCTURE_SUBDIR, VERINC_FILE)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
|
2
|
+
require 'xmigra/schema_manipulator'
|
3
|
+
|
4
|
+
module XMigra
|
5
|
+
class SchemaUpdater < SchemaManipulator
|
6
|
+
DEV_SCRIPT_WARNING = <<-"END_OF_TEXT"
|
7
|
+
*********************************************************
|
8
|
+
*** WARNING ***
|
9
|
+
*********************************************************
|
10
|
+
|
11
|
+
THIS SCRIPT IS FOR USE ONLY ON DEVELOPMENT DATABASES.
|
12
|
+
|
13
|
+
IF RUN ON AN EMPTY DATABASE IT WILL CREATE A DEVELOPMENT
|
14
|
+
DATABASE THAT IS NOT GUARANTEED TO FOLLOW ANY COMMITTED
|
15
|
+
MIGRATION PATH.
|
16
|
+
|
17
|
+
RUNNING THIS SCRIPT ON A PRODUCTION DATABASE WILL FAIL.
|
18
|
+
END_OF_TEXT
|
19
|
+
|
20
|
+
def initialize(path)
|
21
|
+
super(path)
|
22
|
+
|
23
|
+
@file_based_groups = []
|
24
|
+
|
25
|
+
begin
|
26
|
+
@file_based_groups << (@access_artifacts = AccessArtifactCollection.new(
|
27
|
+
@path.join(ACCESS_SUBDIR),
|
28
|
+
:db_specifics=>@db_specifics,
|
29
|
+
:filename_metavariable=>@db_info.fetch('filename metavariable', nil)
|
30
|
+
))
|
31
|
+
@file_based_groups << (@indexes = IndexCollection.new(
|
32
|
+
@path.join(INDEXES_SUBDIR),
|
33
|
+
:db_specifics=>@db_specifics
|
34
|
+
))
|
35
|
+
@file_based_groups << (@migrations = MigrationChain.new(
|
36
|
+
@path.join(STRUCTURE_SUBDIR),
|
37
|
+
:db_specifics=>@db_specifics
|
38
|
+
))
|
39
|
+
|
40
|
+
@branch_upgrade = BranchUpgrade.new(branch_upgrade_file)
|
41
|
+
@file_based_groups << [@branch_upgrade] if @branch_upgrade.found?
|
42
|
+
rescue Error
|
43
|
+
raise
|
44
|
+
rescue StandardError
|
45
|
+
raise Error, "Error initializing #{self.class} components"
|
46
|
+
end
|
47
|
+
|
48
|
+
@production = false
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_accessor :production
|
52
|
+
attr_reader :migrations, :access_artifacts, :indexes, :branch_upgrade
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
"<#{self.class.name}: path=#{path.to_s.inspect}, db=#{@db_specifics}, vcs=#{@vcs_specifics}>"
|
56
|
+
end
|
57
|
+
|
58
|
+
def in_ddl_transaction
|
59
|
+
yield
|
60
|
+
end
|
61
|
+
|
62
|
+
def ddl_block_separator; "\n"; end
|
63
|
+
|
64
|
+
def update_sql
|
65
|
+
raise XMigra::Error, "Incomplete migration chain" unless @migrations.complete?
|
66
|
+
raise XMigra::Error, "Unchained migrations exist" unless @migrations.includes_all?
|
67
|
+
if respond_to? :warning
|
68
|
+
@branch_upgrade.warnings.each {|w| warning(w)}
|
69
|
+
if @branch_upgrade.found? && !@branch_upgrade.applicable?(@migrations)
|
70
|
+
warning("#{branch_upgrade.file_path} does not apply to the current migration chain.")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
check_working_copy!
|
75
|
+
|
76
|
+
intro_comment = @db_info.fetch('script comment', '')
|
77
|
+
intro_comment << if production
|
78
|
+
sql_comment_block(vcs_information || "")
|
79
|
+
else
|
80
|
+
sql_comment_block(DEV_SCRIPT_WARNING)
|
81
|
+
end
|
82
|
+
intro_comment << "\n\n"
|
83
|
+
|
84
|
+
# If supported, wrap transactionality around modifications
|
85
|
+
intro_comment + in_ddl_transaction do
|
86
|
+
script_parts = [
|
87
|
+
# Check for blatantly incorrect application of script, e.g. running
|
88
|
+
# on master or template database.
|
89
|
+
:check_execution_environment_sql,
|
90
|
+
|
91
|
+
# Create schema version control (SVC) tables if they don't exist
|
92
|
+
:ensure_version_tables_sql,
|
93
|
+
|
94
|
+
# Create and fill a temporary table with migration IDs known by
|
95
|
+
# the script with order information
|
96
|
+
:create_and_fill_migration_table_sql,
|
97
|
+
|
98
|
+
# Create and fill a temporary table with index information known by
|
99
|
+
# the script
|
100
|
+
:create_and_fill_indexes_table_sql,
|
101
|
+
|
102
|
+
# Check that all migrations applied to the database are known to
|
103
|
+
# the script (as far back as the most recent "version bridge" record)
|
104
|
+
:check_preceding_migrations_sql,
|
105
|
+
|
106
|
+
# Check that there are no "gaps" in the chain of migrations
|
107
|
+
# that have already been applied
|
108
|
+
:check_chain_continuity_sql,
|
109
|
+
|
110
|
+
# Mark migrations in the temporary table that should be installed
|
111
|
+
:select_for_install_sql,
|
112
|
+
|
113
|
+
# Check production configuration of database
|
114
|
+
:production_config_check_sql,
|
115
|
+
|
116
|
+
# Remove all access artifacts
|
117
|
+
:remove_access_artifacts_sql,
|
118
|
+
|
119
|
+
# Remove all undesired indexes
|
120
|
+
:remove_undesired_indexes_sql,
|
121
|
+
|
122
|
+
# Apply a branch upgrade if indicated
|
123
|
+
:branch_upgrade_sql,
|
124
|
+
|
125
|
+
# Apply selected migrations
|
126
|
+
:apply_migration_sql,
|
127
|
+
|
128
|
+
# Create all access artifacts
|
129
|
+
:create_access_artifacts_sql,
|
130
|
+
|
131
|
+
# Create any desired indexes that don't yet exist
|
132
|
+
:create_new_indexes_sql,
|
133
|
+
|
134
|
+
# Any cleanup needed
|
135
|
+
:upgrade_cleanup_sql,
|
136
|
+
]
|
137
|
+
|
138
|
+
amend_script_parts(script_parts)
|
139
|
+
|
140
|
+
script_parts.map {|mn| self.send(mn)}.flatten.compact.join(ddl_block_separator)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def amend_script_parts(parts)
|
145
|
+
end
|
146
|
+
|
147
|
+
def sql_comment_block(text)
|
148
|
+
text.lines.collect {|l| '-- ' + l.chomp + "\n"}.join('')
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_working_copy!
|
152
|
+
raise VersionControlError, "XMigra source not under version control" if production
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_access_artifacts_sql
|
156
|
+
scripts = []
|
157
|
+
@access_artifacts.each_definition_sql {|s| scripts << s}
|
158
|
+
return scripts unless scripts.empty?
|
159
|
+
end
|
160
|
+
|
161
|
+
def apply_migration_sql
|
162
|
+
# Apply selected migrations
|
163
|
+
@migrations.collect do |m|
|
164
|
+
m.migration_application_sql
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def branch_upgrade_sql
|
169
|
+
end
|
170
|
+
|
171
|
+
def upgrade_cleanup_sql
|
172
|
+
end
|
173
|
+
|
174
|
+
def vcs_information
|
175
|
+
end
|
176
|
+
|
177
|
+
def each_file_path
|
178
|
+
@file_based_groups.each do |group|
|
179
|
+
group.each {|item| yield item.file_path}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'xmigra/access_artifact'
|
3
|
+
|
4
|
+
module XMigra
|
5
|
+
class StoredProcedure < AccessArtifact
|
6
|
+
OBJECT_TYPE = "PROCEDURE"
|
7
|
+
|
8
|
+
# Construct with a hash (as if loaded from a stored procedure YAML file)
|
9
|
+
def initialize(sproc_info)
|
10
|
+
@name = sproc_info["name"].dup.freeze
|
11
|
+
@definition = sproc_info["sql"].dup.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
def depends_on
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
|
2
|
+
module XMigra
|
3
|
+
module GitSpecifics
|
4
|
+
VersionControlSupportModules << self
|
5
|
+
|
6
|
+
MASTER_HEAD_ATTRIBUTE = 'xmigra-master'
|
7
|
+
MASTER_BRANCH_SUBDIR = 'xmigra-master'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def manages(path)
|
11
|
+
run_git(:status, :check_exit=>true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_git(subcmd, *args)
|
15
|
+
options = (Hash === args[-1]) ? args.pop : {}
|
16
|
+
check_exit = options.fetch(:check_exit, false)
|
17
|
+
no_result = !options.fetch(:get_result, true)
|
18
|
+
|
19
|
+
cmd_parts = ["git", subcmd.to_s]
|
20
|
+
cmd_parts.concat(
|
21
|
+
args.flatten.collect {|a| '""'.insert(1, a.to_s)}
|
22
|
+
)
|
23
|
+
case PLATFORM
|
24
|
+
when :unix
|
25
|
+
cmd_parts << "2>/dev/null"
|
26
|
+
end if options[:quiet]
|
27
|
+
|
28
|
+
cmd_str = cmd_parts.join(' ')
|
29
|
+
|
30
|
+
output = `#{cmd_str}`
|
31
|
+
return ($?.success? ? output : nil) if options[:get_result] == :on_success
|
32
|
+
return $?.success? if check_exit
|
33
|
+
raise(VersionControlError, "Git command failed with exit code #{$?.exitstatus}") unless $?.success?
|
34
|
+
return output unless no_result
|
35
|
+
end
|
36
|
+
|
37
|
+
def attr_values(attr, path, options={})
|
38
|
+
value_list = run_git('check-attr', attr, '--', path).each_line.map do |line|
|
39
|
+
line.chomp.split(/: /, 3)[2]
|
40
|
+
end
|
41
|
+
return value_list unless options[:single]
|
42
|
+
raise VersionControlError, options[:single] + ' ambiguous' if value_list.length > 1
|
43
|
+
if (value_list.empty? || value_list == ['unspecified']) && options[:required]
|
44
|
+
raise VersionControlError, options[:single] + ' undefined'
|
45
|
+
end
|
46
|
+
return value_list[0]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def git(*args)
|
51
|
+
Dir.chdir(self.path) do |pwd|
|
52
|
+
GitSpecifics.run_git(*args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_working_copy!
|
57
|
+
return unless production
|
58
|
+
|
59
|
+
file_paths = Array.from_generator(method(:each_file_path))
|
60
|
+
unversioned_files = git(
|
61
|
+
'diff-index',
|
62
|
+
%w{-z --no-commit-id --name-only HEAD},
|
63
|
+
'--',
|
64
|
+
self.path
|
65
|
+
).split("\000").collect do |path|
|
66
|
+
File.expand_path(self.path + path)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Check that file_paths and unversioned_files are disjoint
|
70
|
+
unless (file_paths & unversioned_files).empty?
|
71
|
+
raise VersionControlError, "Some source files differ from their committed versions"
|
72
|
+
end
|
73
|
+
|
74
|
+
git_fetch_master_branch
|
75
|
+
migrations.each do |m|
|
76
|
+
# Check that the migration has not changed in the currently checked-out branch
|
77
|
+
fpath = m.file_path
|
78
|
+
|
79
|
+
history = git(:log, %w{--format=%H --}, fpath).split
|
80
|
+
if history[1]
|
81
|
+
raise VersionControlError, "'#{fpath}' has been modified in the current branch of the repository since its introduction"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Since a production script was requested, warn if we are not generating
|
86
|
+
# from a production branch
|
87
|
+
if branch_use != :production
|
88
|
+
raise VersionControlError, "The working tree is not a commit in the master history."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def vcs_information
|
93
|
+
return [
|
94
|
+
"Branch: #{branch_identifier}",
|
95
|
+
"Path: #{git_internal_path}",
|
96
|
+
"Commit: #{git_schema_commit}"
|
97
|
+
].join("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
def branch_identifier
|
101
|
+
return (if self.production
|
102
|
+
self.git_branch_info[0]
|
103
|
+
else
|
104
|
+
return @git_branch_identifier if defined? @git_branch_identifier
|
105
|
+
|
106
|
+
@git_branch_identifier = (
|
107
|
+
self.git_master_head(:required=>false) ||
|
108
|
+
self.git_local_branch_identifier(:note_modifications=>true)
|
109
|
+
)
|
110
|
+
end)
|
111
|
+
end
|
112
|
+
|
113
|
+
def branch_use(commit=nil)
|
114
|
+
if commit
|
115
|
+
self.git_fetch_master_branch
|
116
|
+
|
117
|
+
# If there are no commits between the master head and *commit*, then
|
118
|
+
# *commit* is production-ish
|
119
|
+
return (self.git_commits_in? self.git_master_local_branch..commit) ? :development : :production
|
120
|
+
end
|
121
|
+
|
122
|
+
return nil unless self.git_master_head(:required=>false)
|
123
|
+
|
124
|
+
return self.git_branch_info[1]
|
125
|
+
end
|
126
|
+
|
127
|
+
def vcs_move(old_path, new_path)
|
128
|
+
git(:mv, old_path, new_path, :get_result=>false)
|
129
|
+
end
|
130
|
+
|
131
|
+
def vcs_remove(path)
|
132
|
+
git(:rm, path, :get_result=>false)
|
133
|
+
end
|
134
|
+
|
135
|
+
def production_pattern
|
136
|
+
".+"
|
137
|
+
end
|
138
|
+
|
139
|
+
def production_pattern=(pattern)
|
140
|
+
raise VersionControlError, "Under version control by git, XMigra does not support production patterns."
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_conflict_info
|
144
|
+
structure_dir = Pathname.new(self.path) + SchemaManipulator::STRUCTURE_SUBDIR
|
145
|
+
head_file = structure_dir + MigrationChain::HEAD_FILE
|
146
|
+
stage_numbers = []
|
147
|
+
git('ls-files', '-uz', '--', head_file).split("\000").each {|ref|
|
148
|
+
if m = /[0-7]{6} [0-9a-f]{40} (\d)\t\S*/.match(ref)
|
149
|
+
stage_numbers |= [m[1].to_i]
|
150
|
+
end
|
151
|
+
}
|
152
|
+
return nil unless stage_numbers.sort == [1, 2, 3]
|
153
|
+
|
154
|
+
chain_head = lambda do |stage_number|
|
155
|
+
return YAML.parse(
|
156
|
+
git(:show, ":#{stage_number}:#{head_file}")
|
157
|
+
).transform
|
158
|
+
end
|
159
|
+
|
160
|
+
# Ours (2) before theirs (3)...
|
161
|
+
heads = [2, 3].collect(&chain_head)
|
162
|
+
# ... unless merging from upstream
|
163
|
+
if self.git_merging_from_upstream?
|
164
|
+
heads.reverse!
|
165
|
+
end
|
166
|
+
|
167
|
+
branch_point = chain_head.call(1)[MigrationChain::LATEST_CHANGE]
|
168
|
+
|
169
|
+
conflict = MigrationConflict.new(structure_dir, branch_point, heads)
|
170
|
+
|
171
|
+
# Standard git usage never commits directly to the master branch, and
|
172
|
+
# there is no effective way to tell if this is happening.
|
173
|
+
conflict.branch_use = :development
|
174
|
+
|
175
|
+
tool = self
|
176
|
+
conflict.after_fix = proc {tool.resolve_conflict!(head_file)}
|
177
|
+
|
178
|
+
return conflict
|
179
|
+
end
|
180
|
+
|
181
|
+
def resolve_conflict!(path)
|
182
|
+
git(:add, '--', path, :get_result=>false)
|
183
|
+
end
|
184
|
+
|
185
|
+
def git_master_head(options={})
|
186
|
+
options = {:required=>true}.merge(options)
|
187
|
+
return @git_master_head if defined? @git_master_head
|
188
|
+
master_head = GitSpecifics.attr_values(
|
189
|
+
MASTER_HEAD_ATTRIBUTE,
|
190
|
+
self.path + SchemaManipulator::DBINFO_FILE,
|
191
|
+
:single=>'Master branch',
|
192
|
+
:required=>options[:required]
|
193
|
+
)
|
194
|
+
return nil if master_head.nil?
|
195
|
+
return @git_master_head = master_head
|
196
|
+
end
|
197
|
+
|
198
|
+
def git_branch
|
199
|
+
return @git_branch if defined? @git_branch
|
200
|
+
return @git_branch = git('rev-parse', %w{--abbrev-ref HEAD}).chomp
|
201
|
+
end
|
202
|
+
|
203
|
+
def git_schema_commit
|
204
|
+
return @git_commit if defined? @git_commit
|
205
|
+
reported_commit = git(:log, %w{-n1 --format=%H --}, self.path).chomp
|
206
|
+
raise VersionControlError, "Schema not committed" if reported_commit.empty?
|
207
|
+
return @git_commit = reported_commit
|
208
|
+
end
|
209
|
+
|
210
|
+
def git_branch_info
|
211
|
+
return @git_branch_info if defined? @git_branch_info
|
212
|
+
|
213
|
+
self.git_fetch_master_branch
|
214
|
+
|
215
|
+
# If there are no commits between the master head and HEAD, this working
|
216
|
+
# copy is production-ish
|
217
|
+
return (@git_branch_info = if self.branch_use('HEAD') == :production
|
218
|
+
[self.git_master_head, :production]
|
219
|
+
else
|
220
|
+
[self.git_local_branch_identifier, :development]
|
221
|
+
end)
|
222
|
+
end
|
223
|
+
|
224
|
+
def git_local_branch_identifier(options={})
|
225
|
+
host = `hostname`
|
226
|
+
path = git('rev-parse', '--show-toplevel')
|
227
|
+
return "#{git_branch} of #{path} on #{host} (commit #{git_schema_commit})"
|
228
|
+
end
|
229
|
+
|
230
|
+
def git_fetch_master_branch
|
231
|
+
return if @git_master_branch_fetched
|
232
|
+
master_url, remote_branch = self.git_master_head.split('#', 2)
|
233
|
+
|
234
|
+
git(:fetch, '-f', master_url, "#{remote_branch}:#{git_master_local_branch}", :get_result=>false, :quiet=>true)
|
235
|
+
@git_master_branch_fetched = true
|
236
|
+
end
|
237
|
+
|
238
|
+
def git_master_local_branch
|
239
|
+
"#{MASTER_BRANCH_SUBDIR}/#{git_branch}"
|
240
|
+
end
|
241
|
+
|
242
|
+
def git_internal_path
|
243
|
+
return @git_internal_path if defined? @git_internal_path
|
244
|
+
path_prefix = git('rev-parse', %w{--show-prefix}).chomp[0..-2]
|
245
|
+
internal_path = '.'
|
246
|
+
if path_prefix.length > 0
|
247
|
+
internal_path += '/' + path_prefix
|
248
|
+
end
|
249
|
+
return @git_internal_path = internal_path
|
250
|
+
end
|
251
|
+
|
252
|
+
def git_merging_from_upstream?
|
253
|
+
upstream = git('rev-parse', '@{u}', :get_result=>:on_success, :quiet=>true)
|
254
|
+
return false if upstream.nil?
|
255
|
+
|
256
|
+
# Check if there are any commits in #{upstream}..MERGE_HEAD
|
257
|
+
begin
|
258
|
+
return !(self.git_commits_in? upstream..'MERGE_HEAD')
|
259
|
+
rescue VersionControlError
|
260
|
+
return false
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def git_commits_in?(range, path=nil)
|
265
|
+
git(
|
266
|
+
:log,
|
267
|
+
'--pretty=format:%H',
|
268
|
+
'-1',
|
269
|
+
"#{range.begin.strip}..#{range.end.strip}",
|
270
|
+
'--',
|
271
|
+
path || self.path
|
272
|
+
) != ''
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|