module XMigra::SubversionSpecifics

Constants

PRODUCTION_PATH_PROPERTY

Public Class Methods

init_schema(schema_config) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 40
def init_schema(schema_config)
  Console.output_section "Subversion Integration" do
    puts "Establishing a \"production pattern,\" a regular expression for"
    puts "recognizing branch identifiers of branches used for production"
    puts "script generation, simplifies the process of resolving conflicts"
    puts "in the migration chain, should any arise."
    puts
    puts "No escaping (either shell or Ruby) of the regular expression is"
    puts "necessary when entered here."
    puts
    puts "Common choices are:"
    puts "    ^trunk/"
    puts "    ^version/"
    puts
    print "Production pattern (empty to skip): "
    
    production_pattern = $stdin.gets.chomp
    return if production_pattern.empty?
    schema_config.after_dbinfo_creation do
      tool = SchemaManipulator.new(schema_config.root_path).extend(WarnToStderr)
      tool.production_pattern = production_pattern
    end
  end
end
manages(path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 10
def manages(path)
  begin
    return true if File.directory?(File.join(path, '.svn'))
  rescue TypeError
    return false
  end
  
  `svn info "#{path}" 2>&1`
  return $?.success?
end
run_svn(subcmd, *args) click to toggle source

Run the svn command line client in XML mode and return a REXML::Document

# File lib/xmigra/vcs_support/svn.rb, line 22
def run_svn(subcmd, *args)
  options = (Hash === args[-1]) ? args.pop : {}
  no_result = !options.fetch(:get_result, true)
  raw_result = options.fetch(:raw, false) || subcmd.to_s == 'cat'
  
  cmd_parts = ["svn", subcmd.to_s]
  cmd_parts << "--xml" unless no_result || raw_result
  cmd_parts.concat(
    args.collect {|a| '""'.insert(1, a.to_s)}
  )
  cmd_str = cmd_parts.join(' ')
  
  output = `#{cmd_str} 2>#{XMigra::NULL_FILE}`
  raise(VersionControlError, "Subversion command failed with exit code #{$?.exitstatus}") unless $?.success?
  return output if raw_result && !no_result
  return REXML::Document.new(output) unless no_result
end

Public Instance Methods

branch_identifier() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 206
def branch_identifier
  return @subversion_branch_id if defined? @subversion_branch_id
  dir_info = subversion_info
  return @subversion_branch_id = dir_info.elements['info/entry/url'].text[
    dir_info.elements['info/entry/repository/root'].text.length..-1
  ]
end
branch_use() { |use| ... } click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 192
def branch_use
    # Look for xmigra:production-path on the database directory (self.path)
  return nil unless prod_path_element = subversion(:propget, PRODUCTION_PATH_PROPERTY, self.path).elements['properties/target/property']
  
  prod_path_pattern = Regexp.new(prod_path_element.text)
  
  use = prod_path_pattern.match(branch_identifier) ? :production : :development
  if block_given?
    yield use
  else
    return use
  end
end
check_working_copy!() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 70
    def check_working_copy!
      return unless production
      
      schema_info = subversion_info
      file_paths = Array.from_generator(method(:each_file_path))
      status = subversion(:status, '--no-ignore', path)
      unversioned_files = status.elements.each("status/target/entry/@path")
      unversioned_files = unversioned_files.collect {|a| File.expand_path(a.to_s)}
      
      unless (file_paths & unversioned_files).empty?
        raise VersionControlError, "Some source files are not versions found in the repository"
      end
      status = nil
      
      wc_rev = {}
      working_rev = schema_info.elements["info/entry/@revision"].value.to_i
      file_paths.each do |fp|
        fp_info = subversion(:info, fp)
        wc_rev[fp] = fp_wc_rev = fp_info.elements["info/entry/@revision"].value.to_i
        if working_rev != fp_wc_rev
          raise VersionControlError, "The working copy contains objects at multiple revisions"
        end
      end
      
      migrations.each do |m|
        fpath = m.file_path
        
        log = subversion(:log, "-r#{wc_rev[fpath]}:1", "--stop-on-copy", fpath)
        if log.elements["log/logentry[2]"]
          raise VersionControlError, "'#{fpath}' has been modified in the repository since it was created or copied"
        end
      end
      
      # Since a production script was requested, warn if we are not generating
      # from a production branch
      if branch_use != :production and self.respond_to? :warning
        self.warning(<<END_OF_MESSAGE)
The branch backing the target working copy is not marked as a production branch.
END_OF_MESSAGE
      end
    end
get_conflict_info() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 120
def get_conflict_info
  # Check if the structure head is conflicted
  structure_dir = Pathname.new(self.path) + SchemaManipulator::STRUCTURE_SUBDIR
  status = subversion(:status, structure_dir + MigrationChain::HEAD_FILE)
  return nil if status.elements["status/target/entry/wc-status/@item"].value != "conflicted"
  
  chain_head = lambda do |extension|
    pattern = MigrationChain::HEAD_FILE + extension
    if extension.include? '*'
      files = structure_dir.glob(MigrationChain::HEAD_FILE + extension)
      raise VersionControlError, "Multiple #{pattern} files in structure directory" if files.length > 1
      raise VersionControlError, "#{pattern} file missing from structure directory" if files.length < 1
    else
      files = [structure_dir.join(pattern)]
    end
    
    # Using YAML.parse_file and YAML::Syck::Node#transform rerenders
    # scalars in the same style they were read from the source file:
    return YAML.parse_file(files[0]).transform
  end
  
  if (structure_dir + (MigrationChain::HEAD_FILE + ".working")).exist?
    # This is a merge conflict
    
    # structure/head.yaml.working is from the current branch
    # structure/head.yaml.merge-left.r* is the branch point
    # structure/head.yaml.merge-right.r* is from the merged-in branch
    this_head = chain_head.call(".working")
    other_head = chain_head.call(".merge-right.r*")
    branch_point = chain_head.call(".merge-left.r*")[MigrationChain::LATEST_CHANGE]
    
    conflict = MigrationConflict.new(structure_dir, branch_point, [other_head, this_head])
    
    branch_use {|u| conflict.branch_use = u}
  else
    # This is an update conflict
    
    # structure/head.yaml.mine is from the working copy
    # structure/head.yaml.r<lower> is the common ancestor
    # structure/head.yaml.r<higher> is the newer revision
    working_head = chain_head.call('.mine')
    oldrev, newrev = nil, 0
    structure_dir.glob(MigrationChain::HEAD_FILE + '.r*') do |fn|
      if fn.to_s =~ /.r(\d+)$/
        rev = $1.to_i
        if oldrev.nil? or rev < oldrev
          oldrev = rev
        end
        if newrev < rev
          newrev = rev
        end
      end
    end
    repo_head = chain_head.call(".r#{newrev}")
    branch_point = chain_head.call(".r#{oldrev}")[MigrationChain::LATEST_CHANGE]
    
    conflict = MigrationConflict.new(structure_dir, branch_point, [repo_head, working_head])
    branch_use {|u| conflict.branch_use = u}
    
    fix_target, = conflict.migration_tweak
    fix_target_st = subversion(:status, fix_target)
    if fix_target_st.elements['status/target/entry/wc-status/@item'].value == 'modified'
      conflict.scope = :working_copy
    end
  end
  
  tool = self
  conflict.after_fix = proc {tool.resolve_conflict!(structure_dir + MigrationChain::HEAD_FILE)}
  
  return conflict
end
production_pattern() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 214
def production_pattern
  subversion(:propget, PRODUCTION_PATH_PROPERTY, self.path, :raw=>true)
end
production_pattern=(pattern) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 217
def production_pattern=(pattern)
  subversion(:propset, PRODUCTION_PATH_PROPERTY, pattern, self.path, :get_result=>false)
end
resolve_conflict!(path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 221
def resolve_conflict!(path)
  subversion(:resolve, '--accept=working', path, :get_result=>false)
end
subversion(*args) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 66
def subversion(*args)
  SubversionSpecifics.run_svn(*args)
end
subversion_info() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 313
def subversion_info
  return @subversion_info if defined? @subversion_info
  return @subversion_info = subversion(:info, self.path)
end
subversion_retrieve_status(file_path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 380
def subversion_retrieve_status(file_path)
  subversion(:status, '-v', file_path).elements['status/target']
end
vcs_changes_from(from_revision, file_path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 372
def vcs_changes_from(from_revision, file_path)
  subversion(:diff, '-r', from_revision, file_path, :raw=>true)
end
vcs_comparator(options={}) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 301
def vcs_comparator(options={})
  VersionComparator.new(self, options)
end
vcs_contents(path, options={}) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 347
def vcs_contents(path, options={})
  args = []
  
  if options[:revision]
    args << "-r#{options[:revision]}"
  end
  
  args << path.to_s
  
  subversion(:cat, *args)
end
vcs_file_modified?(file_path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 230
def vcs_file_modified?(file_path)
  status = subversion_retrieve_status(file_path).elements['entry/wc-status']
  !status.nil? && status.attributes['item'] == 'modified'
end
vcs_information() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 112
def vcs_information
  info = subversion_info
  return [
    "Repository URL: #{info.elements["info/entry/url"].text}",
    "Revision: #{info.elements["info/entry/@revision"].value}"
  ].join("\n")
end
vcs_latest_revision(a_file=nil) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 359
def vcs_latest_revision(a_file=nil)
  if a_file.nil? && defined? @vcs_latest_revision
    return @vcs_latest_revision
  end
  
  val = subversion(:status, '-v', a_file || file_path).elements[
    'string(status/target/entry/wc-status/commit/@revision)'
  ]
  (val.nil? ? val : val.to_i).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/svn.rb, line 376
def vcs_most_recent_committed_contents(file_path)
  subversion(:cat, file_path)
end
vcs_move(old_path, new_path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 305
def vcs_move(old_path, new_path)
  subversion(:move, old_path, new_path, :get_result=>false)
end
vcs_production_contents(path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 318
def vcs_production_contents(path)
  path = Pathname(path)
  
  # Check for a production pattern.  If none exists, there is no way to
  # identify which branches are production, so essentially no production
  # content:
  prod_pat = self.production_pattern
  return nil if prod_pat.nil?
  prod_pat = Regexp.compile(prod_pat.chomp)
  
  # Is the current branch a production branch?  If so, cat the committed
  # version:
  if branch_identifier =~ prod_pat
    return svn(:cat, path.to_s)
  end
  
  # Use an SvnHistoryTracer to walk back through the history of self.path
  # looking for a copy from a production branch.
  tracer = SvnHistoryTracer.new(self.path)
  
  while !(match = tracer.earliest_loaded_repopath =~ prod_pat) && tracer.load_parent_commit
    # loop
  end
  
  if match
    subversion(:cat, "-r#{tracer.earliest_loaded_revision}", path.to_s)
  end
end
vcs_remove(path) click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 309
def vcs_remove(path)
  subversion(:remove, path, :get_result=>false)
end
vcs_uncommitted?() click to toggle source
# File lib/xmigra/vcs_support/svn.rb, line 225
def vcs_uncommitted?
  status = subversion_retrieve_status(file_path).elements['entry/wc-status']
  status.nil? || status.attributes['item'] == 'unversioned'
end