svn-command 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ # Tested by: ../test/subversion_extensions_test.rb
2
+
3
+ class Array
4
+ def to_regexp_char_class
5
+ "[#{join('')}]"
6
+ end
7
+ end
8
+
9
+ # These are methods used by the SvnCommand for filtering and whatever else it needs...
10
+ # 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.
11
+ # Rename to Subversion::Filters ? Then would each_unadded fit?
12
+ module Subversion
13
+ module Extensions
14
+ Interesting_status_flags = ["M", "A", "D", "?"]
15
+ Uninteresting_status_flags = ["X", "W"]
16
+ Status_flags = Interesting_status_flags | Uninteresting_status_flags
17
+
18
+ def self.status_lines_filter(input)
19
+ (input || "").reject { |line|
20
+ line =~ /^$/ # Blank lines
21
+ }.reject { |line|
22
+ line =~ /^Performing status on external item at/
23
+ }.reject { |line|
24
+ line =~ /^#{Uninteresting_status_flags.to_regexp_char_class}/
25
+ }.join
26
+ end
27
+
28
+ def self.update_lines_filter(input)
29
+ input.reject { |line|
30
+ line =~ /^$/ # Blank lines
31
+ }.reject { |line|
32
+ line =~ /^Fetching external item into/
33
+ # Eventually we may want it to include this whole block, but only iff there is something updated for this external.
34
+ }.reject { |line|
35
+ line =~ /^External at revision/
36
+ }.join
37
+ # Also get rid of all but one "At revision _."?
38
+ end
39
+
40
+ def self.unadded_lines_filter(input)
41
+ input.select { |line|
42
+ line =~ /^\?/
43
+ }.join
44
+ end
45
+ def self.unadded_filter(input)
46
+ unadded_lines_filter(input).map { |line|
47
+ # Just keep the filename part
48
+ line =~ /^\?\s+(.+)/
49
+ $1
50
+ }
51
+ end
52
+
53
+ def self.each_unadded(input)
54
+ unadded_filter(input).each { |line|
55
+ yield line
56
+ }
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,627 @@
1
+ require 'rubygems'
2
+
3
+ require_gem 'facets', '>=1.8.51'
4
+ require 'facets/more/command'
5
+ require 'facets/core/string/margin'
6
+ require 'facets/core/kernel/require_local'
7
+ require 'facets/core/array/select' # select!
8
+
9
+ require_gem 'our_extensions', '>=0.0.2'
10
+ require 'enumerable/enum'
11
+ require 'array/expand_ranges'
12
+ require 'array/shell_escape'
13
+
14
+ require 'extensions/symbol' # to_proc
15
+ require 'pp'
16
+ require 'termios'
17
+ require 'stringio'
18
+ require_local '../lib/subversion'
19
+ require_local '../lib/subversion_extensions'
20
+
21
+ begin
22
+ # Set up termios so that it returns immediately when you press a key.
23
+ # (http://blog.rezra.com/articles/2005/12/05/single-character-input)
24
+ t = Termios.tcgetattr(STDIN)
25
+ t.lflag &= ~Termios::ICANON
26
+ Termios.tcsetattr(STDIN,0,t)
27
+ rescue RuntimeError => exception # Necessary for automated testing.
28
+ if exception.message =~ /can't get terminal parameters/
29
+ puts 'Warning: Terminal not found.'
30
+ $interactive = false
31
+ else
32
+ raise
33
+ end
34
+ end
35
+
36
+ Subversion.extend(Subversion::Extensions)
37
+ Subversion::color = true
38
+
39
+ module Subversion
40
+ # Tested by: ../test/svn_command_test.rb
41
+ class SvnCommand < Console::Command
42
+ @@standard_remote_command_options = {
43
+ [:__username] => 1,
44
+ [:__password] => 1,
45
+ [:__no_auth_cache] => 0,
46
+ [:__non_interactive] => 0,
47
+ [:__config_dir] => 1,
48
+ }
49
+ mattr_reader :standard_remote_command_options
50
+
51
+ # This shouldn't be necessary. Console::Command should allow introspection. But until such time...
52
+ @@subcommand_list = [
53
+ 'each_unadded',
54
+ 'externals', 'externals_containers', 'edit_externals', 'externalize',
55
+ 'ignore',
56
+ 'get_message', 'set_message', 'edit_message',
57
+ 'view_commits'
58
+ ]
59
+ mattr_reader :subcommand_list
60
+
61
+ def initialize(*args)
62
+ @passthrough_options = []
63
+ super
64
+ end
65
+
66
+ #-----------------------------------------------------------------------------------------------------------------------------
67
+ # Global options
68
+
69
+ global_option :__no_color, :__dry_run, :__debug
70
+ def __no_color
71
+ Subversion::color = false
72
+ end
73
+ def __debug
74
+ $debug = true
75
+ end
76
+ def __dry_run
77
+ Subversion::dry_run = true
78
+ end
79
+ def __print
80
+ Subversion::print_commands = true
81
+ end
82
+
83
+ #-----------------------------------------------------------------------------------------------------------------------------
84
+ # Default/dynamic behavior
85
+
86
+ # Any subcommands that we haven't implemented here will simply be passed on to the built-in svn command.
87
+ # :todo: Distinguish between subcommand_missing and method_missing !
88
+ # Currently, for example, if as isn't defined, this: puts Subversion.externalize(repo_path, {:as => as })
89
+ # will call method_missing and try to run `svn as`, which of course will fail (without a sensible relevant error)...
90
+ # I think we should probably just have a separate subcommand_missing, like we already have a separate option_missing !!!
91
+ # Even a simple type (sss instead of ss) causes trouble... *this* was causing a call to "/usr/bin/svn new_messsage" -- what huh??
92
+ # def set_message(new_message = nil)
93
+ # args << new_messsage if new_message
94
+ def method_missing(subcommand, *args)
95
+ #puts "method_missing(#{subcommand}, #{args.inspect})"
96
+ svn :exec, subcommand, *args
97
+ end
98
+
99
+ def option_missing(option_name, args)
100
+ #puts "#{@subcommand} defined? #{@subcommand_is_defined}"
101
+ if !@subcommand_is_defined
102
+ # It's okay to use this for pass-through subcommands, because we just pass all options/arguments verbatim anyway...
103
+ #puts "option_missing(#{option_name}, #{args.inspect})"
104
+ else
105
+ # But for subcommands that are defined here, we should know better! All valid options should be explicitly listed!
106
+ raise UnknownOptionError.new(option_name)
107
+ end
108
+
109
+ # The following is necessary because we really don't know the arity (how many subsequent tokens it should eat) of the option -- we don't know anything about the options, in fact; that's why we've landed in option_missing.
110
+ # This is kind of a hokey solution, but for any unrecognized options/args (which will be *all* of them unless we list the available options in the subcommand module), we just eat all of the args, store them in @passthrough_options, and later we will add them back on.
111
+ # What's annoying about it this solution is that *everything* after the first unrecognized option comes in as args, even if they are args for the subcommand and not for the *option*!
112
+ # But...it seems to work to just pretend they're options.
113
+ # It seems like this is mostly a problem for *wrappers* that try to use Console::Command. Sometimes you just want to *pass through all args and options* unchanged and just filter the output somehow.
114
+ # Command doesn't make that super-easy though. If an option (--whatever) isn't defined, then the only way to catch it is in option_missing. And since we can't the arity unless we enumerate all options, we have to hokily treat the first option as having unlimited arity.
115
+ # Alternatives considered:
116
+ # * Assume arity of 0. Then I'm afraid it would extract out all the option flags and leave the args that were meant for the args dangling there out of order ("-r 1 -m 'hi'" => "-r -m", "1 'hi'")
117
+ # * Assume arity of 1. Then if it was really 0, it would pick up an extra arg that really wasn't supposed to be an arg for the *option*.
118
+ # Ideally, we wouldn't be using option_missing at all because all options would be listed in the respective subcommand module...but for subcommands handled through method_missing, we don't have that option.
119
+
120
+ @passthrough_options << "#{option_name}" << args
121
+ @passthrough_options.flatten!
122
+
123
+ return arity = args.size # All of 'em
124
+ end
125
+
126
+ #-----------------------------------------------------------------------------------------------------------------------------
127
+ # Built-in commands (in alphabetical order)
128
+
129
+ #-----------------------------------------------------------------------------------------------------------------------------
130
+ module Add
131
+ Console::Command.pass_through({
132
+ [:__targets] => 1,
133
+ [:_N, :__non_recursive] => 0,
134
+ [:_q, :__quiet] => 0,
135
+ [:__config_dir] => 1,
136
+ [:__force] => 0,
137
+ [:__no_ignore] => 0,
138
+ [:__auto_props] => 0,
139
+ [:__no_auto_props] => 0,
140
+ }, self)
141
+ end
142
+ def add(*args)
143
+ #puts "add #{args.inspect}"
144
+ svn :exec, 'add', *args
145
+ end
146
+
147
+ #-----------------------------------------------------------------------------------------------------------------------------
148
+ module Commit
149
+ Console::Command.pass_through({
150
+ [:_q, :__quiet] => 0,
151
+ [:_N, :__non_recursive] => 1,
152
+ [:__targets] => 1,
153
+ [:__no_unlock] => 0,
154
+ [:_m, :__message] => 1,
155
+ [:_F, :__file] => 1,
156
+ [:__force_log] => 0,
157
+ [:__editor_cmd] => 1,
158
+ [:__encoding] => 1,
159
+ }.merge(SvnCommand::standard_remote_command_options), self
160
+ )
161
+ end
162
+ def commit(*args)
163
+ svn :exec, 'commit', *(['--force-log'] + args)
164
+ end
165
+
166
+ # Ideas:
167
+ # * look for .svn-commit files within current tree and if one is found, show what's in it and ask
168
+ # "Found a commit message from a previous failed commit. {preview} Do you want to (u)se this message for the current commit, or (d)elete it?"
169
+
170
+ #-----------------------------------------------------------------------------------------------------------------------------
171
+ module Diff
172
+ Console::Command.pass_through({
173
+ [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
174
+ [:__old] => 1,
175
+ [:__new] => 0,
176
+ [:_N, :__non_recursive] => 1,
177
+ [:__diff_cmd] => 1,
178
+ [:_x, :__extensions] => 1, # :todo: should support any number of args??
179
+ [:__no_diff_deleted] => 0,
180
+ [:__notice_ancestry] => 1,
181
+ [:__force] => 1,
182
+ }.merge(SvnCommand::standard_remote_command_options), self
183
+ )
184
+ end
185
+
186
+ #def diff(*args)
187
+ def diff(directory = "./")
188
+ print Subversion.diff(*([directory] + @passthrough_options))
189
+
190
+ # Show diff for externals (if there are any)
191
+ output = StringIO.new
192
+ #paths = args.reject{|arg| arg =~ /^-/} || ['./']
193
+ [directory].each do |path|
194
+ (Subversion.externals_containers(path) || []).each do |external|
195
+ #puts external.to_s
196
+ external.entries.each do |entry|
197
+ dir = entry[/^[^ ]+/]
198
+ #puts "#{external.container_dir} + #{dir}"
199
+ path = File.join(external.container_dir, dir)
200
+ diff_output = Subversion.diff(path).strip
201
+ unless diff_output == ""
202
+ output.puts '-'*100
203
+ output.puts path
204
+ output.puts diff_output
205
+ end
206
+ end
207
+ end
208
+ end
209
+ unless output.string == ""
210
+ puts '='*100
211
+ puts "Diff of externals (**don't forget to commit these too!**):"
212
+ puts output.string
213
+ end
214
+ end
215
+
216
+ #-----------------------------------------------------------------------------------------------------------------------------
217
+ module Help
218
+ Console::Command.pass_through({
219
+ [:__version] => 0,
220
+ [:_q, :__quiet] => 0,
221
+ [:__config_dir] => 1,
222
+ }, self)
223
+ end
224
+ def help(subcommand = nil)
225
+ case subcommand
226
+ when "externals"
227
+ puts %Q{
228
+ | externals (ext): Lists all externals in the given working directory.
229
+ | usage: externals [PATH]
230
+ }.margin
231
+ # :todo: Finish...
232
+
233
+ when nil
234
+ puts "You are using svn-command, a replacement/wrapper for the standard svn command."
235
+ puts "svn-command is installed at: #{$0}"
236
+ puts "Wrapping svn executable at: #{Subversion.executable} (use the full path to bypass this wrapper)"
237
+ puts
238
+ puts Subversion.help(subcommand).gsub(<<End, '')
239
+
240
+ Subversion is a tool for version control.
241
+ For additional information, see http://subversion.tigris.org/
242
+ End
243
+
244
+ puts ' ' + '--------------'
245
+ @@subcommand_list.each do |subcommand|
246
+ aliases_list = subcommand_aliases_list(subcommand.option_methodize.to_sym)
247
+ aliases_list = aliases_list.empty? ? '' : ' (' + aliases_list.join(', ') + ')'
248
+ puts ' ' + subcommand + aliases_list
249
+ end
250
+ #p subcommand_aliases_list(:edit_externals)
251
+
252
+ else
253
+ #puts "help #{subcommand}"
254
+ puts Subversion.help(subcommand)
255
+ end
256
+ end
257
+
258
+ #-----------------------------------------------------------------------------------------------------------------------------
259
+ module Log
260
+ Console::Command.pass_through({
261
+ [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
262
+ [:_q, :__quiet] => 0,
263
+ [:_v, :__verbose] => 0,
264
+ [:__targets] => 1,
265
+ [:__stop_on_copy] => 0,
266
+ [:__incremental] => 0,
267
+ [:__xml] => 0,
268
+ [:__limit] => 1,
269
+ }.merge(SvnCommand::standard_remote_command_options), self
270
+ )
271
+ end
272
+ def log(*args)
273
+ puts Subversion.log( @passthrough_options + args )
274
+ #svn :exec, *args
275
+ end
276
+
277
+ # Ideas:
278
+ # Just pass a number (5) and it will be treated as --limit 5 (unless File.exists?('5'))
279
+
280
+ #-----------------------------------------------------------------------------------------------------------------------------
281
+ module Status
282
+ Console::Command.pass_through({
283
+ [:_u, :__show_updates] => 0,
284
+ [:_v, :__verbose] => 0,
285
+ [:_N, :__non_recursive] => 0,
286
+ [:_q, :__quiet] => 0,
287
+ [:__no_ignore] => 0,
288
+ [:__incremental] => 0,
289
+ [:__xml] => 0,
290
+ [:__ignore_externals] => 0,
291
+ }.merge(SvnCommand::standard_remote_command_options), self
292
+ )
293
+ end
294
+ def status(*args)
295
+ #puts "in status(#{args.inspect})"
296
+ #puts "#{self}.@passthrough_options == #{@passthrough_options.inspect}"
297
+ print Subversion::Extensions.status_lines_filter( Subversion.status(*(@passthrough_options + args)) )
298
+ end
299
+
300
+ #-----------------------------------------------------------------------------------------------------------------------------
301
+ module Update
302
+ Console::Command.pass_through({
303
+ [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
304
+ [:_N, :__non_recursive] => 0,
305
+ [:_q, :__quiet] => 0,
306
+ [:__diff3_cmd] => 1,
307
+ [:__ignore_externals] => 0,
308
+ }.merge(SvnCommand::standard_remote_command_options), self
309
+ )
310
+ end
311
+ def update(*args)
312
+ print Subversion::Extensions.update_lines_filter( Subversion.update(*prepare_args(args)) )
313
+ end
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+ #-----------------------------------------------------------------------------------------------------------------------------
324
+ # Custom subcommands
325
+ #-----------------------------------------------------------------------------------------------------------------------------
326
+
327
+ #-----------------------------------------------------------------------------------------------------------------------------
328
+
329
+ # *Experimental*
330
+ #
331
+ # Combine commit messages / diffs for the given range for the given files. Gives one aggregate diff for the range instead of many individual diffs.
332
+ #
333
+ # Could be useful for code reviews?
334
+ #
335
+ # Pass in a list of revisions/revision ranges ("134", "134:136", "134-136", and "134-136 139" are all valid)
336
+ #
337
+ module ViewCommits
338
+ def _r(*revisions)
339
+ # This is necessary so that the -r option doesn't accidentally eat up an arg that wasn't meant to be a revision (a filename, for instance). The only problem with this is if there's actully a filename that matches these patterns! (But then we could just re-order ars.)
340
+ revisions.select! do |revision|
341
+ revision =~ /\d+|\d+:\d+/
342
+ end
343
+ @revisions = revisions
344
+ @revisions.size
345
+ end
346
+ end
347
+ def view_commits(path = "./")
348
+ if @revisions.nil?
349
+ raise "-r (revisions) option is mandatory"
350
+ end
351
+ $ignore_dry_run_option = true
352
+ base_url = Subversion.base_url(path)
353
+ $ignore_dry_run_option = false
354
+ #puts "Base URL: #{base_url}"
355
+ revisions = self.class.parse_revision_ranges(@revisions)
356
+ revisions.each do |revision|
357
+ puts Subversion.log("-r #{revision} -v #{base_url}")
358
+ end
359
+
360
+ puts Subversion.diff("-r #{revisions.first}:#{revisions.last} #{path}")
361
+ #/usr/bin/svn diff http://code.qualitysmith.com/gemables/subversion@2279 http://code.qualitysmith.com/gemables/svn-command@2349 --diff-cmd colordiff
362
+
363
+ end
364
+ alias_subcommand :code_review => :view_commits
365
+
366
+ def SvnCommand.parse_revision_ranges(revisions_array)
367
+ revisions_array.map do |item|
368
+ case item
369
+ when /(\d+):(\d+)/
370
+ ($1.to_i .. $2.to_i)
371
+ when /(\d+)-(\d+)/
372
+ ($1.to_i .. $2.to_i)
373
+ when /(\d+)\.\.(\d+)/
374
+ ($1.to_i .. $2.to_i)
375
+ when /\d+/
376
+ item.to_i
377
+ else
378
+ raise "Item in revisions_array had an unrecognized format: #{item}"
379
+ end
380
+ end.expand_ranges
381
+ end
382
+
383
+ #-----------------------------------------------------------------------------------------------------------------------------
384
+ # Goes through each "unadded" file (each file reporting a status of <tt>?</tt>) reported by <tt>svn status</tt> and asks you what you want to do with them (add, delete, or ignore)
385
+ def each_unadded(*args)
386
+ catch :exit do
387
+
388
+ $ignore_dry_run_option = true
389
+ Subversion::Extensions.each_unadded( Subversion.status(*args) ) do |file|
390
+ $ignore_dry_run_option = false
391
+ begin
392
+ response = ""
393
+ loop do
394
+ puts '-'*100
395
+ puts "What do you want to do with '#{file}'?"
396
+ begin
397
+ if File.file?(file)
398
+ # Only show the first x bytes so that we don't accidentally dump the contens of some 20 GB log file to screen...
399
+ puts File.read(file, 3000) || ''
400
+ elsif File.directory?(file)
401
+ puts "Directory contains:"
402
+ Dir.new(file).reject {|f| ['.','..'].include? f}.each do |f|
403
+ puts f
404
+ end
405
+ else
406
+ raise "#{file} is not a file or directory -- what *is* it??"
407
+ end
408
+ end
409
+ print "(a)dd, (d)elete, add to svn:(i)ignore property, or [Enter] to do nothing > "
410
+ response = ""
411
+ response = $stdin.getc.chr while !['a', 'd', 'i', "\n"].include?(begin response.downcase!; response end)
412
+ break
413
+ end
414
+
415
+ case response
416
+ when 'a'
417
+ print "\nAdding... "
418
+ Subversion.add file
419
+ puts
420
+ when 'd'
421
+ puts
422
+
423
+ print "Are you pretty much *SURE* you want to 'rm -rf #{file}'? (y)es, (n)o > "
424
+ response = ""
425
+ response = $stdin.getc.chr while !['y', 'n', "\n"].include?(begin response.downcase!; response end)
426
+
427
+ if response == 'y'
428
+ print "\nDeleting... "
429
+ FileUtils.rm_rf file
430
+ puts
431
+ else
432
+ puts "\nI figured as much!"
433
+ end
434
+ when 'i'
435
+ print "\nIgnoring... "
436
+ Subversion.ignore file
437
+ puts
438
+ end
439
+ rescue Interrupt
440
+ puts "Goodbye"
441
+ throw :exit
442
+ end
443
+ end # each_unadded
444
+
445
+ end # catch :exit
446
+ end
447
+ alias_subcommand :eu => :each_unadded
448
+
449
+
450
+
451
+
452
+ #-----------------------------------------------------------------------------------------------------------------------------
453
+ # Externals-related commands
454
+
455
+ # For every directory that has the svn:externals property set, this lists the contents of the svn:externals property (dir, URL)
456
+ def externals(directory = "./")
457
+ puts Subversion.externals_containers(directory).map { |external|
458
+ external.to_s.
459
+ gsub(File.expand_path(Dir.pwd) + '/', '')
460
+ }
461
+ end
462
+ alias_subcommand :ext => :externals
463
+
464
+ # Lists *directories* that have the svn:externals property set.
465
+ def externals_containers(directory = "./")
466
+ puts Subversion.externals_containers(directory).map { |external|
467
+ external.container_dir
468
+ }
469
+ end
470
+
471
+ def edit_externals(directory = nil)
472
+ if directory.nil?
473
+ puts "No directory specified. Editing externals for *all* externals dirs..."
474
+ Subversion.externals_containers('.').each do |external|
475
+ puts external.to_s
476
+ command = "#{Subversion.executable} propedit svn:externals #{external.container_dir}"
477
+ #puts command
478
+ begin
479
+ #print "Press Ctrl-C to skip, any other key to continue. (This will start up your default editor.) "
480
+ print "Do you want to edit svn:externals for this directory? y/N > "
481
+ response = $stdin.getc.chr
482
+ system command if response.downcase == 'y'
483
+ rescue Interrupt
484
+ ensure
485
+ puts
486
+ end
487
+ end
488
+ else
489
+ system "#{Subversion.executable} propedit svn:externals #{directory}"
490
+ end
491
+ puts 'Done'
492
+ end
493
+ alias_subcommand :edit_ext => :edit_externals
494
+ alias_subcommand :ee => :edit_externals
495
+ alias_subcommand :edit_external => :edit_externals
496
+
497
+ module Externalize
498
+ # :todo: shortcut to create both __whatever method that sets instance variable
499
+ # *and* accessor method 'whatever' for reading it (and ||= initializing it)
500
+ # Console::Command.option({
501
+ # :as => 1 # 1 is arity
502
+ # :as => [1, nil] # 1 is arity, nil is default?
503
+ # )
504
+
505
+ def __as(as); @as = as; end
506
+ def as; @as; end
507
+ end
508
+ def externalize(repo_path)
509
+ # :todo: let them pass in local_path as well -- then we would need to accept 2 args, the first one poylmorphic, the second optional
510
+
511
+ Subversion.externalize(repo_path, {:as => as })
512
+ end
513
+
514
+
515
+ #-----------------------------------------------------------------------------------------------------------------------------
516
+
517
+ def ignore(file)
518
+ Subversion.ignore(file)
519
+ end
520
+
521
+
522
+ #-----------------------------------------------------------------------------------------------------------------------------
523
+ # Commit message retrieving/editing
524
+
525
+ module GetMessage
526
+ def _r(revision)
527
+ @revision = revision
528
+ end
529
+ end
530
+ def get_message()
531
+ #svn propget --revprop svn:log -r2325
532
+ args = ['propget', '--revprop', 'svn:log']
533
+ #args.concat ['-r', @revision ? @revision : Subversion.latest_revision]
534
+ args.concat ['-r', (revision = @revision ? @revision : 'head')]
535
+ puts "Message for r#{Subversion.latest_revision} :" if revision == 'head'
536
+
537
+ $ignore_dry_run_option = true
538
+ puts svn(:capture, *args)
539
+ $ignore_dry_run_option = false
540
+ end
541
+
542
+ module SetMessage
543
+ def _r(revision)
544
+ @revision = revision
545
+ end
546
+ def __file(filename)
547
+ @filename = filename
548
+ end
549
+ end
550
+ def set_message(new_message)
551
+ #svn propset --revprop -r 25 svn:log "Journaled about trip to New York."
552
+ puts "Message before changing:"
553
+ get_message
554
+
555
+ args = ['propset', '--revprop', 'svn:log']
556
+ args.concat ['-r', @revision ? @revision : 'head']
557
+ args << new_message if new_message
558
+ if @filename
559
+ contents = File.readlines(@filename).join.strip
560
+ puts "Read file '#{@filename}':"
561
+ print contents
562
+ puts
563
+ args << contents
564
+ end
565
+ svn :exec, *args
566
+ end
567
+
568
+ # Lets you edit it with your default editor
569
+ module EditMessage
570
+ def _r(revision)
571
+ @revision = revision
572
+ end
573
+ end
574
+ def edit_message()
575
+ #svn propedit --revprop -r 25 svn:log
576
+ args = ['propedit', '--revprop', 'svn:log']
577
+ args.concat ['-r', @revision ? @revision : 'head']
578
+ svn :exec, *args
579
+ end
580
+
581
+ #-----------------------------------------------------------------------------------------------------------------------------
582
+
583
+ def add_all_unadded
584
+ raise NotImplementedError
585
+ end
586
+ def grep
587
+ raise NotImplementedError
588
+ end
589
+ def grep_externals
590
+ raise NotImplementedError
591
+ end
592
+ def grep_log
593
+ raise NotImplementedError
594
+ end
595
+ def browse
596
+ raise NotImplementedError
597
+ end
598
+
599
+ #-----------------------------------------------------------------------------------------------------------------------------
600
+ # Aliases
601
+ #:stopdoc:
602
+ alias_subcommand :st => :status
603
+ alias_subcommand :up => :update
604
+ alias_subcommand :ci => :commit
605
+ #:startdoc:
606
+
607
+
608
+ #-----------------------------------------------------------------------------------------------------------------------------
609
+ # Helpers
610
+
611
+ private
612
+ def svn(method, *args)
613
+ subcommand = args[0]
614
+ args = ([subcommand] + prepare_args(args[1..-1]) + [:method => method])
615
+ # puts "in svn(): about to call Subversion#execute(#{args.inspect})"
616
+ Subversion.send :execute, *args
617
+ end
618
+ def prepare_args(args)
619
+ args.compact! # nil elements spell trouble
620
+ @passthrough_options + args.shell_escape
621
+ end
622
+ # To allow testing/stubbing
623
+ def system(*args)
624
+ Kernel.system *args
625
+ end
626
+ end
627
+ end