Module: Subversion

Extended by:
Extensions
Defined in:
lib/svn-command/subversion.rb,
lib/svn-command/subversion.rb,
lib/svn-command/svn_command.rb,
lib/svn-command/subversion_extensions.rb

Overview

These are methods used by the SvnCommand for filtering and whatever else it needs… It could probably be moved into SvnCommand, but I thought it might be good to at least make it possible to use them apart from SvnCommand. Rename to Subversion::Filters ? Then each_unadded would be an odd man out.

Defined Under Namespace

Modules: Extensions Classes: ExternalsContainer, RevisionProperty, SvnCommand

Constant Summary collapse

@@color =

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)

false
@@dry_run =

If true, will only output which command would have been executed but will not actually execute it.

false
false

Constants included from Extensions

Extensions::Interesting_status_flags, Extensions::Status_flags, Extensions::Uninteresting_status_flags

Class Method Summary collapse

Methods included from Extensions

each_unadded, printable_revision_properties, status_lines_filter, unadded_filter, unadded_lines_filter, update_lines_filter

Class Method Details

.add(*args) ⇒ Object

Adds the given items to the repository. Items may contain wildcards.



45
46
47
# File 'lib/svn-command/subversion.rb', line 45

def self.add(*args)
  execute "add #{args.join ' '}"
end

.add_to_property(property, path, *new_lines) ⇒ Object

It’s easy to get/set properties, but less easy to add to a property. This method uses get/set to simulate add. It will uniquify lines, removing duplicates. (:todo: what if we want to set a property to have some duplicate lines?)



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/svn-command/subversion.rb', line 192

def self.add_to_property(property, path, *new_lines)
  # :todo: I think it's possible to have properties other than svn:* ... so if property contains a prefix (something:), use it; else default to 'svn:'
  
  # Get the current properties
  lines = self.get_property(property, path).split "\n"
  puts "Existing lines: #{lines.inspect}" if $debug

  # Add the new lines, delete empty lines, and uniqueify all elements
  lines.concat(new_lines).uniq!
  puts "After concat(new_lines).uniq!: #{lines.inspect}" if $debug

  lines.delete ''
  # Set the property
  puts "About to set propety to: #{lines.inspect}" if $debug
  self.set_property property, lines.join("\n"), path
end

.base_url(path_or_url = './') ⇒ Object

:todo: needs some serious unit-testing love



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/svn-command/subversion.rb', line 291

def self.base_url(path_or_url = './')
   matches = info(path_or_url).match(/^Repository Root: (.+)/)
   matches && matches[1]

   # 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

.delete_property(property, path = './') ⇒ Object



217
218
219
# File 'lib/svn-command/subversion.rb', line 217

def self.delete_property(property, path = './')
  execute "propdel svn:#{property} #{path}"
end

.delete_revision_property(property_name, rev) ⇒ Object



220
221
222
# File 'lib/svn-command/subversion.rb', line 220

def self.delete_revision_property(property_name, rev)
  execute("propdel --revprop #{property_name} -r #{rev}").chomp
end

.diff(*args) ⇒ Object

Returns the local modifications to the working directory specified by path.



184
185
186
187
188
# File 'lib/svn-command/subversion.rb', line 184

def self.diff(*args)
  args = ['./'] if args.empty?
  #puts("diff #{"--diff-cmd colordiff" if color} #{args.join ' '}")
  execute("diff #{"--diff-cmd colordiff" if color} #{args.join ' '}")
end

.executableObject

The location of the executable to be used



320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/svn-command/subversion.rb', line 320

def self.executable
  @@executable ||=
    ENV['PATH'].split(':').each do |dir|
#        if File.exist?(executable = "#{dir}/svn")
#          puts executable
#        end
      if File.exist?(executable = "#{dir}/svn") and   #
         `file #{executable}` !~ /ruby/               # We want to wrap the svn command provided by Subversion, not our custom replacement for that.
        return executable
      end
    end
  #
end

.externalize(repo_url, options = {}) ⇒ Object

Adds the given repo URL (svn.yourcompany.com/path/to/something) as an svn:externals.

Options may include:

  • :as - overrides the default behavior of naming the checkout based on the last component of the repo path

  • :local_path - specifies where to set the externals property. Defaults to ‘./’



74
75
76
77
78
79
# File 'lib/svn-command/subversion.rb', line 74

def self.externalize(repo_url, options = {})
  options[:local_path] ||= './'
  options[:as] ||= File.basename(repo_url)
  options[:as] = options[:as].ljust(29)
  add_to_property 'externals', options[:local_path], "#{options[:as]} #{repo_url}"
end

.externals_containers(path = './') ⇒ Object

Returns an array of ExternalsContainer objects representing all externals containers in the working directory specified by path.



172
173
174
175
176
177
178
179
180
181
# File 'lib/svn-command/subversion.rb', line 172

def self.externals_containers(path = './')
  # Using self.externals_items is kind of a cheap way to do this, and it results in some redundancy that we have to filter out
  # (using uniq_by), but it seemed more efficient than the alternative (traversing the entire directory tree and querying for
  # `svn prepget svn:externals` at each stop to see if the directory is an externals container).
  self.externals_items(path).map { |external_dir|
    ExternalsContainer.new(external_dir + '/..')
  }.uniq_by { |external|
    external.container_dir
  }
end

.externals_items(path = './') ⇒ Object

Returns an array of externals items. These are the actual externals listed in an svn:externals property. Example:

vendor/a
vendor/b

Where ‘vendor’ is an ExternalsContainer containing external items ‘a’ and ‘b’.



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/svn-command/subversion.rb', line 159

def self.externals_items(path = './')
  status = status_the_section_before_externals(path)
  return [] if status.nil?
  status.select { |line|
    line =~ /^X/
  }.map { |line|
    # Just keep the filename part
    line =~ /^X\s+(.+)/
    $1
  }
end

.get_property(property, path = './') ⇒ Object

:todo: Stop assuming the svn: namespace. What’s the point of a namespace if you only allow one of them?



210
211
212
# File 'lib/svn-command/subversion.rb', line 210

def self.get_property(property, path = './')
  execute "propget svn:#{property} #{path}"
end

.get_revision_property(property_name, rev) ⇒ Object



213
214
215
# File 'lib/svn-command/subversion.rb', line 213

def self.get_revision_property(property_name, rev)
  execute("propget --revprop #{property_name} -r #{rev}").chomp
end

.help(*args) ⇒ Object



255
256
257
# File 'lib/svn-command/subversion.rb', line 255

def self.help(*args)
  execute "help #{args.join(' ')}"
end

.ignore(*patterns) ⇒ Object

Sets the svn:ignore property based on the given patterns. Each pattern is both the path (where the property gets set) and the property itself. For instance:

"log/*.log" would add "*.log" to the svn:ignore property on the log/ directory.
"log" would add "log" to the svn:ignore property on the ./ directory.


54
55
56
57
58
59
60
61
62
63
# File 'lib/svn-command/subversion.rb', line 54

def self.ignore(*patterns)
  
  patterns.each do |pattern|
    path = File.dirname(pattern)
    path += '/' if path == '.'
    pattern = File.basename(pattern)
    add_to_property 'ignore', path, pattern
  end
  nil
end

.info(*args) ⇒ Object



285
286
287
288
# File 'lib/svn-command/subversion.rb', line 285

def self.info(*args)
  args = ['./'] if args.empty?
  execute "info #{args.join(' ')}"
end

.latest_revision(*args) ⇒ Object

Returns the revision number for head.



265
266
267
268
269
# File 'lib/svn-command/subversion.rb', line 265

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

.log(*args) ⇒ Object

Returns the raw output from svn log



260
261
262
263
# File 'lib/svn-command/subversion.rb', line 260

def self.log(*args)
  args = ['./'] if args.empty?
  execute "log #{args.join(' ')}"
end

.make_directory(dir) ⇒ Object



251
252
253
# File 'lib/svn-command/subversion.rb', line 251

def self.make_directory(dir)
  execute "mkdir #{dir}"
end

.make_executable(*paths) ⇒ Object

Marks the given items as being executable. Items may not contain wildcards.



119
120
121
122
123
# File 'lib/svn-command/subversion.rb', line 119

def self.make_executable(*paths)
  paths.each do |path|
    self.set_property 'executable', '', path
  end
end

.make_not_executable(*paths) ⇒ Object



124
125
126
127
128
# File 'lib/svn-command/subversion.rb', line 124

def self.make_not_executable(*paths)
  paths.each do |path|
    self.delete_property 'executable', path
  end
end


334
335
336
337
338
# File 'lib/svn-command/subversion.rb', line 334

def self.print_commands_for(&block)
  old_print_commands, @@print_commands = @@print_commands, true
  yield
  @@print_commands = old_print_commands
end

.proplist(rev) ⇒ Object

Gets raw output of proplist command



232
233
234
# File 'lib/svn-command/subversion.rb', line 232

def self.proplist(rev)
  execute("proplist --revprop -r #{rev}")
end

.remove(*args) ⇒ Object

Removes the given items from the repository and the disk. Items may contain wildcards.



82
83
84
# File 'lib/svn-command/subversion.rb', line 82

def self.remove(*args)
  execute "rm #{args.join ' '}"
end

.remove_force(*args) ⇒ Object

Removes the given items from the repository and the disk. Items may contain wildcards. To do: add a :force => true option to remove



88
89
90
# File 'lib/svn-command/subversion.rb', line 88

def self.remove_force(*args)
  execute "rm --force #{args.join ' '}"
end

.remove_without_delete(*args) ⇒ Object

Removes the given items from the repository BUT NOT THE DISK. Items may contain wildcards.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/svn-command/subversion.rb', line 93

def self.remove_without_delete(*args)
  # resolve the wildcards before iterating
  args.collect {|path| Dir[path]}.flatten.each do |path|
    entries_file = "#{File.dirname(path)}/.svn/entries"
    File.chmod(0644, entries_file)

    xmldoc = REXML::Document.new(IO.read(entries_file))
    # first attempt to delete a matching entry with schedule == add
    unless xmldoc.root.elements.delete "//entry[@name='#{File.basename(path)}'][@schedule='add']"
      # then attempt to alter a missing schedule to schedule=delete
      entry = REXML::XPath.first(xmldoc, "//entry[@name='#{File.basename(path)}']")
      entry.attributes['schedule'] ||= 'delete' if entry
    end
    # write back to the file
    File.open(entries_file, 'w') { |f| xmldoc.write f, 0 }

    File.chmod(0444, entries_file)
  end
end

.repository_root(*args) ⇒ Object



317
# File 'lib/svn-command/subversion.rb', line 317

def self.repository_root(*args); base_url(*args); end

.revert(*args) ⇒ Object

Reverts the given items in the working copy. Items may contain wildcards.



114
115
116
# File 'lib/svn-command/subversion.rb', line 114

def self.revert(*args)
  execute "revert #{args.join ' '}"
end

.revision_properties(rev) ⇒ Object

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



245
246
247
248
249
# File 'lib/svn-command/subversion.rb', line 245

def self.revision_properties(rev)
  revision_properties_names(rev).map { |property_name|
    RevisionProperty.new(property_name, get_revision_property(property_name, rev))
  }
end

.revision_properties_names(rev) ⇒ Object

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



237
238
239
240
241
242
# File 'lib/svn-command/subversion.rb', line 237

def self.revision_properties_names(rev)
  raw_list = proplist(rev)
  raw_list.scan(/^ +([^ ]+)$/).map { |matches|
    matches.first.chomp
  }
end

.revisions(*args) ⇒ Object

Returns an array of RSCM::Revision objects



272
273
274
275
276
277
278
279
280
281
282
# File 'lib/svn-command/subversion.rb', line 272

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

.root_url(*args) ⇒ Object

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


316
# File 'lib/svn-command/subversion.rb', line 316

def self.root_url(*args);        base_url(*args); end

.set_property(property, value, path = './') ⇒ Object



224
225
226
# File 'lib/svn-command/subversion.rb', line 224

def self.set_property(property, value, path = './')
  execute "propset svn:#{property} '#{value}' #{path}"
end

.set_revision_property(property_name, rev) ⇒ Object



227
228
229
# File 'lib/svn-command/subversion.rb', line 227

def self.set_revision_property(property_name, rev)
  execute("propset --revprop #{property_name} -r #{rev}").chomp
end

.status(*args) ⇒ Object

Returns the status of items in the working directories paths. Returns the raw output from svn (use split("\n") if you want an array).



131
132
133
134
# File 'lib/svn-command/subversion.rb', line 131

def self.status(*args)
  args = ['./'] if args.empty?
  execute("status #{args.join ' '}")
end

.status_against_server(*args) ⇒ Object



136
137
138
139
# File 'lib/svn-command/subversion.rb', line 136

def self.status_against_server(*args)
  args = ['./'] if args.empty?
  self.status('-u', *args)
end

.status_the_section_before_externals(path = './') ⇒ Object

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.



149
150
151
152
# File 'lib/svn-command/subversion.rb', line 149

def self.status_the_section_before_externals(path = './')
  status = status(path) || ''
  status.sub!(/(Performing status.*)/m, '')
end

.unignore(*patterns) ⇒ Object

Raises:

  • (NotImplementedError)


64
65
66
# File 'lib/svn-command/subversion.rb', line 64

def self.unignore(*patterns)
  raise NotImplementedError
end

.update(*args) ⇒ Object



141
142
143
144
# File 'lib/svn-command/subversion.rb', line 141

def self.update(*args)
  args = ['./'] if args.empty?
  execute("update #{args.join ' '}")
end