class RSCM::Subversion

RSCM implementation for Subversion.

You need the svn/svnadmin executable on the PATH in order for it to work.

NOTE: On Cygwin these have to be the win32 builds of svn/svnadmin and not the Cygwin ones.

Attributes

password[RW]
path[RW]

Must be specified in order to create repo and install triggers. Must also be specified as “” if the url represents the root of the repository, or files in revisions will not be detected.

url[RW]
username[RW]

Public Class Methods

new(url="", path="") click to toggle source
# File lib/rscm/scm/subversion.rb, line 26
def initialize(url="", path="")
  @url, @path = url, path
  @username = ""
  @password = ""
end

Public Instance Methods

add(relative_filename, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 49
def add(relative_filename, options={})
  svn("add #{checkout_dir}/#{relative_filename}", options)
end
can_create_central?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 95
def can_create_central?
  local? && !path.nil? && !(path == "")
end
central_exists?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 107
def central_exists?
  if(local?)
    File.exists?("#{svnrootdir}/db")
  else
    # Do a simple command over the network
    # If the repo/path doesn't exist, we'll get zero output
    # on stdout (and an error msg on std err).
    exists = false
    cmd = "svn log #{url} -r HEAD"
    execute(cmd) do |stdout|
      stdout.each_line do |line|
        exists = true
      end
    end
    exists
  end
end
checked_out?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 203
def checked_out?
  rootentries = File.expand_path("#{checkout_dir}/.svn/entries")
  result = File.exists?(rootentries)
  result
end
commit(message, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 70
def commit(message, options={})
  svn(commit_command(message), options)
  # We have to do an update to get the local revision right
  checkout_silent(nil, options)
end
create_central(options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 136
def create_central(options={})
  options = options.dup.merge({:dir => svnrootdir})
  native_path = PathConverter.filepath_to_nativepath(svnrootdir, false)
  mkdir_p(PathConverter.nativepath_to_filepath(native_path))
  svnadmin("create #{native_path}", options)
  if(path != "")
    options = options.dup.merge({:dir => "."})
    # create the directories
    paths = path.split("/")
    paths.each_with_index do |p,i|
      p = paths[0..i]
      u = "#{repourl}/#{p.join('/')}"
      svn("mkdir #{u} -m \"Adding directories\"", options)
    end
  end
end
destroy_central() click to toggle source
# File lib/rscm/scm/subversion.rb, line 99
def destroy_central
  if(File.exist?(svnrootdir) && local?)
    FileUtils.rm_rf(svnrootdir)
  else
    raise "Cannot destroy central repository. '#{svnrootdir}' doesn't exist or central repo isn't local to this machine"
  end
end
diff(path, from, to, options={}, &block) click to toggle source
# File lib/rscm/scm/subversion.rb, line 80
def diff(path, from, to, options={}, &block)
  cmd = "svn diff --revision #{from}:#{to} \"#{url}/#{path}\""
  execute(cmd, options) do |io|
    return(block.call(io))
  end
end
import_central(dir, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 171
def import_central(dir, options={})
  import_cmd = "import #{dir} #{url} -m \"#{options[:message]}\""
  svn(import_cmd, options)
end
install_trigger(trigger_command, trigger_files_checkout_dir, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 153
def install_trigger(trigger_command, trigger_files_checkout_dir, options={})
  if (WINDOWS)
    install_win_trigger(trigger_command, trigger_files_checkout_dir, options)
  else
    install_unix_trigger(trigger_command, trigger_files_checkout_dir, options)
  end
end
installed?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 36
def installed?
  begin
    svn("--version", {}) 
    true
  rescue
    false
  end
end
label() click to toggle source
# File lib/rscm/scm/subversion.rb, line 76
def label
  local_revision_identifier.to_s
end
move(relative_src, relative_dest, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 53
def move(relative_src, relative_dest, options={})
  svn("mv #{checkout_dir}/#{relative_src} #{checkout_dir}/#{relative_dest}", options)
end
open(path, native_revision_identifier, options={}, &block) click to toggle source
# File lib/rscm/scm/subversion.rb, line 87
def open(path, native_revision_identifier, options={}, &block)
  raise "native_revision_identifier cannot be nil" if native_revision_identifier.nil?
  cmd = "svn cat #{url}/#{path}@#{native_revision_identifier}"
  execute(cmd, options) do |io|
    return(block.call(io))
  end
end
repourl() click to toggle source

url pointing to the root of the repo

# File lib/rscm/scm/subversion.rb, line 198
def repourl
  last = (path.nil? || path == "") ? -1 : -(path.length)-2
  url[0..last]
end
revisions(from_identifier=Time.new.utc, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 176
def revisions(from_identifier=Time.new.utc, options={})
  raise "from_identifer cannot be nil" if from_identifier.nil?
  options = {
    :from_identifier => from_identifier,
    :to_identifier => Time.infinity, 
    :relative_path => "",
    :dir => Dir.pwd
  }.merge(options)
  
  checkout_dir = PathConverter.filepath_to_nativepath(@checkout_dir, false)
  revisions = nil
  command = "svn #{changes_command(options[:from_identifier], options[:to_identifier], options[:relative_path])}"
  execute(command, options) do |stdout|
    stdout = StringIO.new(stdout.read)
    parser = SubversionLogParser.new(stdout, @url, options[:from_identifier], options[:to_identifier], path)
    revisions = parser.parse_revisions
  end
  revisions.cmd = command if store_revisions_command?
  revisions
end
supports_trigger?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 125
def supports_trigger?
  true
  # we'll assume it supports trigger even if not local. this is to ensure user interfaces
  # can display appropriate options, even if the object is not 'fully initialised'
  # local?
end
to_identifier(raw_identifier) click to toggle source
# File lib/rscm/scm/subversion.rb, line 45
def to_identifier(raw_identifier)
  raw_identifier.to_i
end
transactional?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 57
def transactional?
  true
end
trigger_installed?(trigger_command, trigger_files_checkout_dir, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 165
def trigger_installed?(trigger_command, trigger_files_checkout_dir, options={})
  return false unless File.exist?(post_commit_file)
  not_already_commented = LineEditor.comment_out(File.new(post_commit_file), /#{Regexp.escape(trigger_command)}/, "# ", "")
  not_already_commented
end
trigger_mechanism() click to toggle source
# File lib/rscm/scm/subversion.rb, line 132
def trigger_mechanism
  "hooks/post-commit"
end
uninstall_trigger(trigger_command, trigger_files_checkout_dir, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 161
def uninstall_trigger(trigger_command, trigger_files_checkout_dir, options={})
  File.comment_out(post_commit_file, /#{Regexp.escape(trigger_command)}/, nil)
end
uptodate?(identifier, options={}) click to toggle source
# File lib/rscm/scm/subversion.rb, line 61
def uptodate?(identifier, options={})
  if(!checked_out?)
    false
  else
    rev = identifier.nil? ? head_revision_identifier(options) : identifier 
    local_revision_identifier(options) == rev
  end
end

Protected Instance Methods

checkout_silent(to_identifier, options) click to toggle source
# File lib/rscm/scm/subversion.rb, line 211
def checkout_silent(to_identifier, options)
  checkout_dir = PathConverter.filepath_to_nativepath(@checkout_dir, false)
  mkdir_p(@checkout_dir)
  if(checked_out?)
    svn(update_command(to_identifier), options)
  else
    svn(checkout_command(to_identifier), options)
  end
end
ignore_paths() click to toggle source
# File lib/rscm/scm/subversion.rb, line 221
def ignore_paths
  [/\.svn\/.*/]
end

Private Instance Methods

changes_command(from_identifier, to_identifier, relative_path) click to toggle source
# File lib/rscm/scm/subversion.rb, line 315
def changes_command(from_identifier, to_identifier, relative_path)
  # http://svnbook.red-bean.com/svnbook-1.1/svn-book.html#svn-ch-3-sect-3.3
  # file_list = files.join('\n')
  "log --verbose #{login_options} #{revision_option(from_identifier, to_identifier)} #{@url}"
end
checkout_command(to_identifier) click to toggle source
# File lib/rscm/scm/subversion.rb, line 307
def checkout_command(to_identifier)
  "checkout #{login_options} #{url} #{revision_option(nil,to_identifier)} #{quoted_checkout_dir}"
end
commit_command(message) click to toggle source
# File lib/rscm/scm/subversion.rb, line 368
def commit_command(message)
  "commit #{login_options} --force-log -m \"#{message}\" #{quoted_checkout_dir}"
end
head_revision_identifier(options) click to toggle source
# File lib/rscm/scm/subversion.rb, line 236
def head_revision_identifier(options)
  # This command only seems to yield any changesets if the url is the root of
  # the repo, which we don't know in the case where path is not specified (likely)
  # We therefore don't specify it and get the latest revision from the full url instead.
  # cmd = "svn log #{login_options} #{repourl} -r HEAD"
  cmd = "svn log #{login_options} #{url}"
  execute(cmd, options) do |stdout|
    parser = SubversionLogParser.new(stdout, @url)
    revisions = parser.parse_revisions
    revisions[0].identifier
  end
end
install_unix_trigger(trigger_command, damagecontrol_install_dir, options) click to toggle source
# File lib/rscm/scm/subversion.rb, line 249
def install_unix_trigger(trigger_command, damagecontrol_install_dir, options)
  post_commit_exists = File.exists?(post_commit_file)
  mode = post_commit_exists ? File::APPEND|File::WRONLY : File::CREAT|File::WRONLY
  begin
    File.open(post_commit_file, mode) do |file|
      file.puts("#!/bin/sh") unless post_commit_exists 
      file.puts("#{trigger_command}\n" )
    end
    File.chmod(0744, post_commit_file)
  rescue
    raise ["Didn't have permission to write to #{post_commit_file}.",
          "Try to manually add the following line:",
          trigger_command,
          "Finally make it executable with chmod g+x #{post_commit_file}"]
  end
end
install_win_trigger(trigger_command, damagecontrol_install_dir, options) click to toggle source
# File lib/rscm/scm/subversion.rb, line 266
def install_win_trigger(trigger_command, damagecontrol_install_dir, options)
  post_commit_exists = File.exists?(post_commit_file)
  mode = post_commit_exists ? File::APPEND|File::WRONLY : File::CREAT|File::WRONLY
  begin
    File.open(post_commit_file, mode) do |file|
      file.puts("#{trigger_command}\n" )
    end
  rescue
    raise ["Didn't have permission to write to #{post_commit_file}.",
          "Try to manually add the following line:",
          trigger_command]
  end
end
local?() click to toggle source
# File lib/rscm/scm/subversion.rb, line 378
def local?
  if(url =~ /^file:/)
    return true
  else
    return false
  end
end
local_revision_identifier(options) click to toggle source
# File lib/rscm/scm/subversion.rb, line 227
def local_revision_identifier(options)
  local_revision_identifier = nil
  svn("info #{quoted_checkout_dir}", options) do |line|
    if(line =~ /Revision: ([0-9]*)/)
      return $1.to_i
    end
  end
end
login_options() click to toggle source
# File lib/rscm/scm/subversion.rb, line 321
def login_options
  result = ""
  u = @username ? @username.strip : ""
  p = @password ? @password.strip : ""
  result << "--username #{u} " unless u == ""
  result << "--password #{p} " unless p == ""
  result
end
post_commit_file() click to toggle source
# File lib/rscm/scm/subversion.rb, line 386
def post_commit_file
  # We actualy need to use the .cmd when on cygwin. The cygwin svn post-commit
  # hook is hosed. We'll be relying on native windows
  if(local?)
    WINDOWS ? "#{svnrootdir}/hooks/post-commit.cmd" : "#{svnrootdir}/hooks/post-commit"
  else
    raise "The repository is not local. Cannot install or uninstall trigger."
  end
end
quoted_checkout_dir() click to toggle source
# File lib/rscm/scm/subversion.rb, line 372
def quoted_checkout_dir
  cd = '"' + PathConverter.filepath_to_nativepath(checkout_dir, false) + '"'
  raise "checkout_dir not set" if cd == ""
  cd
end
revision_option(from_identifier, to_identifier) click to toggle source
# File lib/rscm/scm/subversion.rb, line 330
def revision_option(from_identifier, to_identifier)
  # The inclusive start
  from = nil
  if(from_identifier.is_a?(Time))
    from = svndate(from_identifier)
  elsif(from_identifier.is_a?(Numeric))
    from = from_identifier
  elsif(!from_identifier.nil?)
    raise "from_identifier must be Numeric, Time or nil. Was: #{from_identifier} (#{from_identifier.class.name})"
  end

  to = nil
  if(to_identifier.is_a?(Time))
    to = svndate(to_identifier)
  elsif(to_identifier.is_a?(Numeric))
    to = to_identifier
  elsif(!from_identifier.nil?)
    raise "to_identifier must be Numeric, Time or nil. Was: #{to_identifier} (#{to_identifier.class.name})"
  end

  revision_option = nil
  if(from && to.nil?)
    revision_option = "--revision #{from}:HEAD"
  elsif(from.nil? && to)
    revision_option = "--revision #{to}"
  elsif(from.nil? && to.nil?)
    revision_option = ""
  elsif(from && to)
    revision_option = "--revision #{from}:#{to}"
  end
  revision_option
end
svn(cmd, options={}, &proc) click to toggle source
# File lib/rscm/scm/subversion.rb, line 294
def svn(cmd, options={}, &proc)
  svncommand("svn", cmd, options, &proc)
end
svnadmin(cmd, options={}, &proc) click to toggle source
# File lib/rscm/scm/subversion.rb, line 290
def svnadmin(cmd, options={}, &proc)
  svncommand("svnadmin", cmd, options, &proc)
end
svncommand(executable, cmd, options) { |line| ... } click to toggle source
# File lib/rscm/scm/subversion.rb, line 298
def svncommand(executable, cmd, options, &proc)
  command_line = "#{executable} #{cmd}"
  execute(command_line, options) do |stdout|
    stdout.each_line do |line|
      yield line if block_given?
    end
  end
end
svndate(time) click to toggle source
# File lib/rscm/scm/subversion.rb, line 363
def svndate(time)
  return nil unless time
  time.utc.strftime("{\"%Y-%m-%d %H:%M:%S\"}")
end
svnrootdir() click to toggle source
# File lib/rscm/scm/subversion.rb, line 280
def svnrootdir
  last = (path.nil? || path == "") ? -1 : -(path.length)-2
  result = url["file://".length..last]
  # for windows, turn /c:/blabla into c:/blabla"
  if(result =~ /^\/[a-zA-Z]:/)
    result = result[1..-1]
  end
  result
end
update_command(to_identifier) click to toggle source
# File lib/rscm/scm/subversion.rb, line 311
def update_command(to_identifier)
  "update #{login_options} #{revision_option(nil,to_identifier)} #{quoted_checkout_dir}"
end