Module: XMigra::GitSpecifics
- Defined in:
- lib/xmigra/vcs_support/git.rb
Defined Under Namespace
Classes: AttributesFile, RepoStoredMigrationChain, VersionComparator
Constant Summary collapse
- MASTER_HEAD_ATTRIBUTE =
'xmigra-master'
- MASTER_BRANCH_SUBDIR =
'xmigra-master'
- PRODUCTION_CHAIN_EXTENSION_COMMAND =
'xmigra-on-production-chain-extended'
- ATTRIBUTE_UNSPECIFIED =
'unspecified'
Class Method Summary collapse
- .attr_values(attr, path, options = {}) ⇒ Object
- .attributes_file_paths(path) ⇒ Object
- .get_master_url ⇒ Object
- .init_schema(schema_config) ⇒ Object
- .manages(path) ⇒ Object
- .run_git(subcmd, *args) ⇒ Object
Instance Method Summary collapse
- #branch_identifier ⇒ Object
- #branch_use(commit = nil) ⇒ Object
- #check_working_copy! ⇒ Object
- #get_conflict_info ⇒ Object
- #git(*args) ⇒ Object
- #git_branch ⇒ Object
- #git_branch_info ⇒ Object
- #git_commits_in?(range, path = nil) ⇒ Boolean
- #git_fetch_master_branch ⇒ Object
- #git_internal_path ⇒ Object
- #git_local_branch_identifier(options = {}) ⇒ Object
- #git_master_head(options = {}) ⇒ Object
- #git_master_local_branch ⇒ Object
- #git_merging_from_master? ⇒ Boolean
- #git_merging_from_upstream? ⇒ Boolean
- #git_retrieve_status(a_path) ⇒ Object
- #git_schema_commit ⇒ Object
- #git_status ⇒ Object
- #production_pattern ⇒ Object
- #production_pattern=(pattern) ⇒ Object
- #resolve_conflict!(path) ⇒ Object
- #vcs_changes_from(from_commit, file_path) ⇒ Object
- #vcs_comparator(options = {}) ⇒ Object
- #vcs_contents(path, options = {}) ⇒ Object
- #vcs_file_modified?(file_path) ⇒ Boolean
- #vcs_information ⇒ Object
- #vcs_latest_revision(a_file = nil) ⇒ Object
- #vcs_most_recent_committed_contents(file_path) ⇒ Object
- #vcs_move(old_path, new_path) ⇒ Object
- #vcs_prod_chain_extension_handler ⇒ Object
- #vcs_production_contents(path) ⇒ Object
- #vcs_remove(path) ⇒ Object
- #vcs_uncommitted? ⇒ Boolean
Class Method Details
.attr_values(attr, path, options = {}) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/xmigra/vcs_support/git.rb', line 97 def attr_values(attr, path, ={}) value_list = run_git('check-attr', attr, '--', path).each_line.map do |line| line.chomp.split(/: /, 3)[2] end return value_list unless [:single] raise VersionControlError, [:single] + ' ambiguous' if value_list.length > 1 if (value_list.empty? || value_list == ['unspecified']) && [:required] raise VersionControlError, [:single] + ' undefined' end return value_list[0] end |
.attributes_file_paths(path) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/xmigra/vcs_support/git.rb', line 109 def attributes_file_paths(path) wdroot = Dir.chdir path do Pathname(run_git('rev-parse', '--show-toplevel').strip).realpath end pwd = Pathname.pwd [].tap do |result| path.realpath.ascend do |dirpath| result << AttributesFile.new(dirpath) break if (wdroot <=> dirpath) >= 0 end result << AttributesFile.new(wdroot, :local) end end |
.get_master_url ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/xmigra/vcs_support/git.rb', line 125 def get_master_url print "Master repository URL (empty for none): " master_repo = $stdin.gets.strip return nil if master_repo.empty? Console.validated_input "Master branch name" do |master_branch| if master_branch.empty? raise Console::InvalidInput.new( "Master branch name required to set 'xmigra-master' attribute --" ) end "#{master_repo}##{master_branch}" end end |
.init_schema(schema_config) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/xmigra/vcs_support/git.rb', line 140 def init_schema(schema_config) Console.output_section "Git Integration" do if master_url = get_master_url # Select locations for .gitattributes or .git/info/attributes attribs_file = Console::Menu.new( "Git Attributes Files", attributes_file_paths(schema_config.root_path), "File for storing 'xmigra-master' attribute", :get_name => lambda {|af| af.description} ).get_selection dbinfo_path = schema_config.root_path + SchemaManipulator::DBINFO_FILE attribute_pattern = "/#{dbinfo_path.relative_path_from(attribs_file.effect_root)}" schema_config.after_dbinfo_creation do attribs_file.open('a') do |attribs_io| attribs_io.puts "#{attribute_pattern} xmigra-master=#{master_url}" end schema_config.created_file! attribs_file.file_path end end end end |
.manages(path) ⇒ Object
65 66 67 |
# File 'lib/xmigra/vcs_support/git.rb', line 65 def manages(path) run_git(:status, :check_exit=>true, :quiet=>true) end |
.run_git(subcmd, *args) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/xmigra/vcs_support/git.rb', line 69 def run_git(subcmd, *args) = (Hash === args[-1]) ? args.pop : {} check_exit = .fetch(:check_exit, false) no_result = !.fetch(:get_result, true) cmd_parts = ["git", subcmd.to_s] cmd_parts.concat( args.flatten.collect {|a| '""'.insert(1, a.to_s)} ) case PLATFORM when :unix cmd_parts << "2>#{XMigra::NULL_FILE}" end if [:quiet] cmd_str = cmd_parts.join(' ') output = begin `#{cmd_str}` rescue return false if check_exit raise end return ($?.success? ? output : nil) if [:get_result] == :on_success return $?.success? if check_exit raise(VersionControlError, "Git command failed with exit code #{$?.exitstatus}\n Command: #{cmd_str}") unless $?.success? return output unless no_result end |
Instance Method Details
#branch_identifier ⇒ Object
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/xmigra/vcs_support/git.rb', line 224 def branch_identifier for_production = begin self.production rescue NameError false end return (if for_production self.git_branch_info[0] else return @git_branch_identifier if defined? @git_branch_identifier @git_branch_identifier = ( self.git_master_head(:required=>false) || self.git_local_branch_identifier(:note_modifications=>true) ) end) end |
#branch_use(commit = nil) ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/xmigra/vcs_support/git.rb', line 243 def branch_use(commit=nil) if commit self.git_fetch_master_branch # If there are commits between the master head and *commit*, then # *commit* is not production-ish if self.git_commits_in? self.git_master_local_branch..commit return :development end # Otherwise, look to see if all migrations in the migration chain for # commit are in the master head with no diffs -- the migration chain # is a "prefix" of the chain in the master head: migration_chain = RepoStoredMigrationChain.new( commit, Pathname(path).join(SchemaManipulator::STRUCTURE_SUBDIR), ) return :production if self.git( :diff, '--name-only', self.git_master_local_branch, commit, '--', *migration_chain.map(&:file_path) ).empty? return :development end return nil unless self.git_master_head(:required=>false) return self.git_branch_info[1] end |
#check_working_copy! ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/xmigra/vcs_support/git.rb', line 180 def check_working_copy! return unless production file_paths = Array.from_generator(method(:each_file_path)) unversioned_files = git( 'diff-index', %w{-z --no-commit-id --name-only HEAD}, '--', self.path ).split("\000").collect do |path| File.(self.path + path) end # Check that file_paths and unversioned_files are disjoint unless (file_paths & unversioned_files).empty? raise VersionControlError, "Some source files differ from their committed versions" end git_fetch_master_branch migrations.each do |m| # Check that the migration in the working tree is the same as in head of the central master branch fpath = m.file_path unless git(:diff, '--exit-code', self.git_master_local_branch, '--', fpath, check_exit: true) master_url, remote_branch = self.git_master_head.split('#', 2) raise VersionControlError, "'#{fpath}' is different locally than on '#{remote_branch}' in #{master_url}" end end # Since a production script was requested, warn if we are not generating # from a production branch if branch_use != :production master_url, remote_branch = self.git_master_head.split('#', 2) raise VersionControlError, "The working tree is not a commit in the history of '#{remote_branch}' in #{master_url}" end end |
#get_conflict_info ⇒ Object
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/xmigra/vcs_support/git.rb', line 457 def get_conflict_info structure_dir = Pathname.new(self.path) + SchemaManipulator::STRUCTURE_SUBDIR head_file = structure_dir + MigrationChain::HEAD_FILE stage_numbers = [] git('ls-files', '-uz', '--', head_file).split("\000").each {|ref| if m = /[0-7]{6} [0-9a-f]{40} (\d)\t\S*/.match(ref) stage_numbers |= [m[1].to_i] end } return nil unless stage_numbers.sort == [1, 2, 3] chain_head = lambda do |stage_number| head_file_relative = head_file.relative_path_from(self.path) return YAML.parse( git(:show, ":#{stage_number}:./#{head_file_relative}") ).transform end # Ours (2) before theirs (3)... heads = [2, 3].collect(&chain_head) # ... unless merging from upstream or the master branch if self.git_merging_from_upstream? || self.git_merging_from_master? heads.reverse! end branch_point = chain_head.call(1)[MigrationChain::LATEST_CHANGE] conflict = MigrationConflict.new(structure_dir, branch_point, heads) # Standard git usage never commits directly to the master branch, and # there is no effective way to tell if this is happening. conflict.branch_use = :development tool = self conflict.after_fix = proc {tool.resolve_conflict!(head_file)} return conflict end |
#git(*args) ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/xmigra/vcs_support/git.rb', line 165 def git(*args) _path = begin self.path rescue NameError begin self.schema_dir rescue NameError Pathname(self.file_path).dirname end end Dir.chdir(_path) do |pwd| GitSpecifics.run_git(*args) end end |
#git_branch ⇒ Object
513 514 515 516 |
# File 'lib/xmigra/vcs_support/git.rb', line 513 def git_branch return @git_branch if defined? @git_branch return @git_branch = git('rev-parse', %w{--abbrev-ref HEAD}, :quiet=>true).chomp end |
#git_branch_info ⇒ Object
525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/xmigra/vcs_support/git.rb', line 525 def git_branch_info return @git_branch_info if defined? @git_branch_info self.git_fetch_master_branch # If there are no commits between the master head and HEAD, this working # copy is production-ish return (@git_branch_info = if self.branch_use('HEAD') == :production [self.git_master_head, :production] else [self.git_local_branch_identifier, :development] end) end |
#git_commits_in?(range, path = nil) ⇒ Boolean
586 587 588 589 590 591 592 593 594 595 |
# File 'lib/xmigra/vcs_support/git.rb', line 586 def git_commits_in?(range, path=nil) git( :log, '--pretty=format:%H', '-1', "#{range.begin.strip}..#{range.end.strip}", '--', path || self.path ) != '' end |
#git_fetch_master_branch ⇒ Object
545 546 547 548 549 550 551 |
# File 'lib/xmigra/vcs_support/git.rb', line 545 def git_fetch_master_branch return if @git_master_branch_fetched master_url, remote_branch = self.git_master_head.split('#', 2) git(:fetch, '-f', master_url, "#{remote_branch}:#{git_master_local_branch}", :get_result=>false, :quiet=>true) @git_master_branch_fetched = true end |
#git_internal_path ⇒ Object
557 558 559 560 561 562 563 564 565 |
# File 'lib/xmigra/vcs_support/git.rb', line 557 def git_internal_path return @git_internal_path if defined? @git_internal_path path_prefix = git('rev-parse', %w{--show-prefix}).chomp[0..-2] internal_path = '.' if path_prefix.length > 0 internal_path += '/' + path_prefix end return @git_internal_path = internal_path end |
#git_local_branch_identifier(options = {}) ⇒ Object
539 540 541 542 543 |
# File 'lib/xmigra/vcs_support/git.rb', line 539 def git_local_branch_identifier(={}) host = `hostname` path = git('rev-parse', '--show-toplevel') return "#{git_branch} of #{path} on #{host} (commit #{git_schema_commit})" end |
#git_master_head(options = {}) ⇒ Object
500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/xmigra/vcs_support/git.rb', line 500 def git_master_head(={}) = {:required=>true}.merge() return @git_master_head if defined? @git_master_head master_head = GitSpecifics.attr_values( MASTER_HEAD_ATTRIBUTE, self.path + SchemaManipulator::DBINFO_FILE, :single=>'Master branch', :required=>[:required] ) return nil if master_head.nil? return @git_master_head = (master_head if master_head != GitSpecifics::ATTRIBUTE_UNSPECIFIED) end |
#git_master_local_branch ⇒ Object
553 554 555 |
# File 'lib/xmigra/vcs_support/git.rb', line 553 def git_master_local_branch "#{MASTER_BRANCH_SUBDIR}/#{git_branch}" end |
#git_merging_from_master? ⇒ Boolean
579 580 581 582 583 584 |
# File 'lib/xmigra/vcs_support/git.rb', line 579 def git_merging_from_master? git_fetch_master_branch return !(self.git_commits_in? git_master_local_branch..'MERGE_HEAD') rescue VersionControlError return false end |
#git_merging_from_upstream? ⇒ Boolean
567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/xmigra/vcs_support/git.rb', line 567 def git_merging_from_upstream? upstream = git('rev-parse', '@{u}', :get_result=>:on_success, :quiet=>true) return false if upstream.nil? # Check if there are any commits in #{upstream}..MERGE_HEAD begin return !(self.git_commits_in? upstream..'MERGE_HEAD') rescue VersionControlError return false end end |
#git_retrieve_status(a_path) ⇒ Object
439 440 441 442 443 444 445 446 447 |
# File 'lib/xmigra/vcs_support/git.rb', line 439 def git_retrieve_status(a_path) return nil unless Pathname(a_path).exist? if git('status', '--porcelain', a_path.to_s) =~ /^.+?(?= \S)/ $& else ' ' end end |
#git_schema_commit ⇒ Object
518 519 520 521 522 523 |
# File 'lib/xmigra/vcs_support/git.rb', line 518 def git_schema_commit return @git_commit if defined? @git_commit reported_commit = git(:log, %w{-n1 --format=%H --}, self.path, :quiet=>true).chomp raise VersionControlError, "Schema not committed" if reported_commit.empty? return @git_commit = reported_commit end |
#git_status ⇒ Object
435 436 437 |
# File 'lib/xmigra/vcs_support/git.rb', line 435 def git_status @git_status ||= git_retrieve_status(file_path) end |
#production_pattern ⇒ Object
449 450 451 |
# File 'lib/xmigra/vcs_support/git.rb', line 449 def production_pattern ".+" end |
#production_pattern=(pattern) ⇒ Object
453 454 455 |
# File 'lib/xmigra/vcs_support/git.rb', line 453 def production_pattern=(pattern) raise VersionControlError, "Under version control by git, XMigra does not support production patterns." end |
#resolve_conflict!(path) ⇒ Object
496 497 498 |
# File 'lib/xmigra/vcs_support/git.rb', line 496 def resolve_conflict!(path) git(:add, '--', path, :get_result=>false) end |
#vcs_changes_from(from_commit, file_path) ⇒ Object
422 423 424 |
# File 'lib/xmigra/vcs_support/git.rb', line 422 def vcs_changes_from(from_commit, file_path) git(:diff, from_commit, '--', file_path) end |
#vcs_comparator(options = {}) ⇒ Object
401 402 403 |
# File 'lib/xmigra/vcs_support/git.rb', line 401 def vcs_comparator(={}) VersionComparator.new(self, ) end |
#vcs_contents(path, options = {}) ⇒ Object
292 293 294 295 296 297 298 299 |
# File 'lib/xmigra/vcs_support/git.rb', line 292 def vcs_contents(path, ={}) args = [] commit = .fetch(:revision, 'HEAD') args << "#{commit}:./#{path}" git(:show, *args) end |
#vcs_file_modified?(file_path) ⇒ Boolean
430 431 432 433 |
# File 'lib/xmigra/vcs_support/git.rb', line 430 def vcs_file_modified?(file_path) gstat = git_retrieve_status(file_path) gstat[0] != ' ' end |
#vcs_information ⇒ Object
216 217 218 219 220 221 222 |
# File 'lib/xmigra/vcs_support/git.rb', line 216 def vcs_information return [ "Branch: #{branch_identifier}", "Path: #{git_internal_path}", "Commit: #{git_schema_commit}" ].join("\n") end |
#vcs_latest_revision(a_file = nil) ⇒ Object
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/xmigra/vcs_support/git.rb', line 405 def vcs_latest_revision(a_file=nil) if a_file.nil? && defined? @vcs_latest_revision return @vcs_latest_revision end git( :log, '-n1', '--pretty=format:%H', '--', a_file || file_path, :quiet=>true ).chomp.tap do |val| @vcs_latest_revision = val if a_file.nil? end end |
#vcs_most_recent_committed_contents(file_path) ⇒ Object
426 427 428 |
# File 'lib/xmigra/vcs_support/git.rb', line 426 def vcs_most_recent_committed_contents(file_path) git(:show, "HEAD:#{file_path}", :quiet=>true) end |
#vcs_move(old_path, new_path) ⇒ Object
273 274 275 |
# File 'lib/xmigra/vcs_support/git.rb', line 273 def vcs_move(old_path, new_path) git(:mv, old_path, new_path, :get_result=>false) end |
#vcs_prod_chain_extension_handler ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/xmigra/vcs_support/git.rb', line 301 def vcs_prod_chain_extension_handler attr_val = GitSpecifics.attr_values( PRODUCTION_CHAIN_EXTENSION_COMMAND, self.path + SchemaManipulator::DBINFO_FILE, :required=>false, )[0] # Check for special value return nil if attr_val == 'unspecified' handler_path = Pathname(attr_val) if handler_path.absolute? return handler_path if handler_path.exist? else handler_path = self.path + handler_path return handler_path if handler_path.exist? end return attr_val end |
#vcs_production_contents(path) ⇒ Object
281 282 283 284 285 286 287 288 289 290 |
# File 'lib/xmigra/vcs_support/git.rb', line 281 def vcs_production_contents(path) return nil unless git_master_head(:required => false) git_fetch_master_branch # Skip the first two characters after the join to leave off the "./" prefix, # which makes git consider the current directory target_path = [git_internal_path, Pathname(path).relative_path_from(self.path)].join('/')[2..-1] git(:show, [git_master_local_branch, target_path].join(':'), :quiet=>true) rescue VersionControlError return nil end |
#vcs_remove(path) ⇒ Object
277 278 279 |
# File 'lib/xmigra/vcs_support/git.rb', line 277 def vcs_remove(path) git(:rm, path, :get_result=>false) end |
#vcs_uncommitted? ⇒ Boolean
321 322 323 |
# File 'lib/xmigra/vcs_support/git.rb', line 321 def vcs_uncommitted? git_status == '??' || git_status[0] == 'A' end |