lib/svn-command/subversion.rb in svn-command-0.1.1 vs lib/svn-command/subversion.rb in svn-command-0.2.1

- old
+ new

@@ -19,10 +19,16 @@ #require 'active_support/core_ext/module/attribute_accessors' #require 'facets/core/class/cattr' gem 'qualitysmith_extensions' require 'qualitysmith_extensions/module/attribute_accessors' +# RSCM is used for some of the abstraction, such as for parsing log messages into nice data structures. It seems like overkill, though, to use RSCM for most things... +gem 'rscm' +#require 'rscm' +#require 'rscm/scm/subversion' +require 'rscm/scm/subversion_log_parser' + # Wraps the Subversion shell commands for Ruby. module Subversion # True if you want output from svn to be colorized (useful if output is for human eyes, but not useful if using the output programatically) @@color = false mattr_accessor :color @@ -135,10 +141,13 @@ def self.update(*args) args = ['./'] if args.empty? execute("update #{args.join ' '}") end + # The output from `svn status` is nicely divided into two "sections": the section which pertains to the current working copy (not + # counting externals as part of the working copy) and then the section with status of all of the externals. + # This method returns the first section. def self.status_the_section_before_externals(path = './') status = status(path) || '' status.sub!(/(Performing status.*)/m, '') end @@ -195,65 +204,119 @@ # Set the property puts "About to set propety to: #{lines.inspect}" if $debug self.set_property property, lines.join("\n"), path end + # :todo: Stop assuming the svn: namespace. What's the point of a namespace if you only allow one of them? def self.get_property(property, path = './') execute "propget svn:#{property} #{path}" end + def self.get_revision_property(property_name, rev) + execute("propget --revprop #{property_name} -r #{rev}").chomp + end + def self.delete_property(property, path = './') execute "propdel svn:#{property} #{path}" end + def self.delete_revision_property(property_name, rev) + execute("propdel --revprop #{property_name} -r #{rev}").chomp + end + def self.set_property(property, value, path = './') execute "propset svn:#{property} '#{value}' #{path}" end + def self.set_revision_property(property_name, rev) + execute("propset --revprop #{property_name} -r #{rev}").chomp + end + # Gets raw output of proplist command + def self.proplist(rev) + execute("proplist --revprop -r #{rev}") + end + # Returns an array of the names of all revision properties currently set on the given +rev+ + # Tested by: ../../test/subversion_test.rb:test_revision_properties_names + def self.revision_properties_names(rev) + raw_list = proplist(rev) + raw_list.scan(/^ +([^ ]+)$/).map { |matches| + matches.first.chomp + } + end + # Returns an array of RevisionProperty objects (name, value) for revisions currently set on the given +rev+ + # Tested by: ../../test/subversion_test.rb:test_revision_properties + def self.revision_properties(rev) + revision_properties_names(rev).map { |property_name| + RevisionProperty.new(property_name, get_revision_property(property_name, rev)) + } + end + def self.make_directory(dir) execute "mkdir #{dir}" end def self.help(*args) execute "help #{args.join(' ')}" end + # Returns the raw output from svn log def self.log(*args) args = ['./'] if args.empty? execute "log #{args.join(' ')}" end + # Returns the revision number for head. def self.latest_revision(*args) args = ['./'] if args.empty? matches = /Status against revision:\s+(\d+)/m.match(status_against_server(args)) matches && matches[1] end + # Returns an array of RSCM::Revision objects + def self.revisions(*args) + # Tried using this, but it seems to expect you to pass in a starting date or accept the default starting date of right now, which is silly if you actually just want *all* revisions... + #@rscm = ::RSCM::Subversion.new + #@rscm.revisions + + #log_output = Subversion.log('-v') + log_output = Subversion.log(*(['-v'] + args)) + parser = ::RSCM::SubversionLogParser.new(io = StringIO.new(log_output), url = 'http://ignore.me.com') + revisions = parser.parse_revisions + revisions + end + + def self.info(*args) args = ['./'] if args.empty? execute "info #{args.join(' ')}" end # :todo: needs some serious unit-testing love def self.base_url(path_or_url = './') - base_url = nil # needed so that base_url variable isn't local to if block! - started_using_dot_dots = false - loop do - matches = /URL: (.+)/.match(info(path_or_url)) - if matches && matches[1] - base_url = matches[1] - else - break base_url - end + matches = info(path_or_url).match(/^Repository Root: (.+)/) + matches && matches[1] - # Keep going up the path, one directory at a time, until `svn info` no longer returns a URL (will probably eventually return 'svn: PROPFIND request failed') - if path_or_url.include?('/') && !started_using_dot_dots - path_or_url = File.dirname(path_or_url) - else - started_using_dot_dots = true - path_or_url = File.join(path_or_url, '..') - end - #puts 'going up to ' + path_or_url - end + # It appears that we might need to use this old way (which looks at 'URL'), since there is actually a +# base_url = nil # needed so that base_url variable isn't local to loop block (and reset during next iteration)! +# started_using_dot_dots = false +# loop do +# matches = /^URL: (.+)/.match(info(path_or_url)) +# if matches && matches[1] +# base_url = matches[1] +# else +# break base_url +# end +# +# # Keep going up the path, one directory at a time, until `svn info` no longer returns a URL (will probably eventually return 'svn: PROPFIND request failed') +# if path_or_url.include?('/') && !started_using_dot_dots +# path_or_url = File.dirname(path_or_url) +# else +# started_using_dot_dots = true +# path_or_url = File.join(path_or_url, '..') +# end +# #puts 'going up to ' + path_or_url +# end end + def self.root_url(*args); base_url(*args); end + def self.repository_root(*args); base_url(*args); end # The location of the executable to be used def self.executable @@executable ||= ENV['PATH'].split(':').each do |dir| @@ -292,15 +355,21 @@ p command end valid_options = [:capture, :exec, :popen] case method + when :capture `#{command} 2>&1` + when :exec #Kernel.exec *args Kernel.exec command + + when :system + Kernel.system command + when :popen # This is just an idea of how maybe we could improve the LATENCY. Rather than waiting until the command completes # (which can take quite a while for svn status sometimes since it has to walk the entire directory tree), why not process # the output from /usr/bin/svn *in real-time*?? # @@ -336,11 +405,15 @@ +#Subversion.const_set(:RevisionProperty) = Struct.new(:name, :repository_path) module Subversion + + RevisionProperty = Struct.new(:name, :value) + # Represents an "externals container", which is a directory that has the <tt>svn:externals</tt> property set to something useful. # Each ExternalsContainer contains a set of "entries", which are the actual directories listed in the <tt>svn:externals</tt> # property and are "pulled into" the directory. class ExternalsContainer ExternalItem = Struct.new(:name, :repository_path)