module XMigra::GitSpecifics
Constants
- ATTRIBUTE_UNSPECIFIED
- MASTER_BRANCH_SUBDIR
- MASTER_HEAD_ATTRIBUTE
- PRODUCTION_CHAIN_EXTENSION_COMMAND
Public Class Methods
attr_values(attr, path, options={})
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 97 def attr_values(attr, path, options={}) value_list = run_git('check-attr', attr, '--', path).each_line.map do |line| line.chomp.split(/: /, 3)[2] end return value_list unless options[:single] raise VersionControlError, options[:single] + ' ambiguous' if value_list.length > 1 if (value_list.empty? || value_list == ['unspecified']) && options[:required] raise VersionControlError, options[:single] + ' undefined' end return value_list[0] end
attributes_file_paths(path)
click to toggle source
# 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()
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 69 def run_git(subcmd, *args) options = (Hash === args[-1]) ? args.pop : {} check_exit = options.fetch(:check_exit, false) no_result = !options.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 options[:quiet] cmd_str = cmd_parts.join(' ') output = begin `#{cmd_str}` rescue return false if check_exit raise end return ($?.success? ? output : nil) if options[: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
Public Instance Methods
branch_identifier()
click to toggle source
# 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)
click to toggle source
# 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!()
click to toggle source
# 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.expand_path(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()
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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()
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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()
click to toggle source
# 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={})
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 539 def git_local_branch_identifier(options={}) host = `hostname` path = git('rev-parse', '--show-toplevel') return "#{git_branch} of #{path} on #{host} (commit #{git_schema_commit})" end
git_master_head(options={})
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 500 def git_master_head(options={}) options = {:required=>true}.merge(options) 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=>options[:required] ) return nil if master_head.nil? return @git_master_head = (master_head if master_head != GitSpecifics::ATTRIBUTE_UNSPECIFIED) end
git_master_local_branch()
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 553 def git_master_local_branch "#{MASTER_BRANCH_SUBDIR}/#{git_branch}" end
git_merging_from_master?()
click to toggle source
# 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?()
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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()
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 435 def git_status @git_status ||= git_retrieve_status(file_path) end
production_pattern()
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 449 def production_pattern ".+" end
production_pattern=(pattern)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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={})
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 401 def vcs_comparator(options={}) VersionComparator.new(self, options) end
vcs_contents(path, options={})
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 292 def vcs_contents(path, options={}) args = [] commit = options.fetch(:revision, 'HEAD') args << "#{commit}:./#{path}" git(:show, *args) end
vcs_file_modified?(file_path)
click to toggle source
# 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()
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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()
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 277 def vcs_remove(path) git(:rm, path, :get_result=>false) end
vcs_uncommitted?()
click to toggle source
# File lib/xmigra/vcs_support/git.rb, line 321 def vcs_uncommitted? git_status == '??' || git_status[0] == 'A' end