svn-command 0.2.7 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ProjectInfo.rb CHANGED
@@ -5,7 +5,7 @@ module Project
5
5
  PrettyName = "Enhanced Subversion Command"
6
6
  Name = "svn-command"
7
7
  RubyForgeName = "svn-command"
8
- Version = "0.2.7"
8
+ Version = "0.2.12"
9
9
  Specification = Gem::Specification.new do |s|
10
10
  s.name = Project::Name
11
11
  s.summary = "A nifty wrapper command for Subversion's command-line svn client"
@@ -22,7 +22,6 @@ module Project
22
22
  s.add_dependency("colored")
23
23
  s.add_dependency("escape")
24
24
  s.add_dependency("facets")
25
- s.add_dependency("extensions")
26
25
  s.add_dependency("qualitysmith_extensions")
27
26
  s.add_dependency("rscm")
28
27
  s.post_install_message = <<-End
data/Readme CHANGED
@@ -1,9 +1,9 @@
1
1
  = <i>Enhanced Subversion command</i> -- an +svn+ command wrapper
2
2
 
3
- [*Environment*:] Command line
3
+ [<b>Environment</b>:] Command line (text based)
4
4
  [<b>Home page</b>:] http://svn-command.rubyforge.org/
5
5
  [<b>Project site</b>:] http://rubyforge.org/projects/svn-command
6
- [<b>Wiki</b>:] http://wiki.qualitysmith.com/svn-command
6
+ [<b>Gem install</b>:] <tt>gem install svn-command</tt>
7
7
  [<b>Author</b>:] Tyler Rick
8
8
  [<b>Copyright</b>:] 2007 QualitySmith, Inc.
9
9
  [<b>License</b>:] {GNU General Public License}[http://www.gnu.org/copyleft/gpl.html]
@@ -127,7 +127,7 @@ Here are a couple things you might use it for:
127
127
  * Rather than looking at <tt>svn log -v</tt> (which can be _huge_) directly and then manually calculating revision numbers and doing things like <tt>svn diff -r1492:1493</tt> over and over, you can simply start up <tt>svn revisions</tt>, browse to the revision you're interested in using the Up/Down arrow keys, and press D to get a diff for the selected changeset.
128
128
  * <b>See what's been committed since the last public release</b>. So that you can list it in your release notes, for example...
129
129
  * <b>Review other people's code</b>. (There's even a mark-as-reviewed feature*, if you want to keep track of which revisions have been reviewed...)
130
- * <b>Searching for a change you know you've made</b> but don't remember what revision that was it. (Hint: Use the "grep this changeset" feature.)
130
+ * <b>Search for a change you know you've _made_</b> but just don't remember what revision it was in. (Hint: Use the "grep this changeset" feature.)
131
131
  * Figure out what the <b>difference is between two branches</b>.
132
132
 
133
133
  Defaults to latest-first, but you can pass it the <tt>--forwards</tt> flag to browse from the other direction (start at the <i>oldest revision</i> and step forwards through time).
@@ -229,6 +229,14 @@ You can do this:
229
229
  or just this:
230
230
  svn edit_message
231
231
 
232
+ == <tt>svn ignore</tt>
233
+
234
+ If you want to add '*' to the list of ignored files for a directory, be sure to enclose the argument in single quotes (<tt>'</tt>) so that the shell doesn't expand the <tt>*</tt> symbol for you.
235
+
236
+ Example:
237
+
238
+ svn ignore 'tmp/sessions/*'
239
+
232
240
  == <tt>svn move</tt>
233
241
 
234
242
  You can now do commands like this:
@@ -391,6 +399,19 @@ But... as with most things written in Ruby, it's all more about *productivity* t
391
399
 
392
400
  ==To do
393
401
 
402
+ Calling "Extensions.anything" is stupid. Can't we just merge/extend/include the methods from Extensions into the Subversion module itself?
403
+
404
+ Say you just did `svn mv base-suffix base-new_suffix`. Now say you want to commit that move without committing anything else in that dir.
405
+ You'd think you could just do `svn ci base-*`, but no. That doesn't get base-suffix because it has been removed from the file system (and scheduled for deletion).
406
+ Can we make it so a '*' (or any glob) (escaped so shell doesn't get it) actually looks at all files returned by `svn st` (which includes those scheduled for deletion, D) that match that glob rather than all files, rather than the glob that the *shell* would do?
407
+
408
+ Say you cp'd a file A to B. You make some modifications to it and later decide to add it. Wouldn't it be nice if you could retroactively cause B to inherit the ancestry of A (as if you had svn cp'd it instead of cp'd it to begin with)?
409
+ I propose a copy_ancestry_from / imbue_with_ancestry_from command, so that you can do svn copy_ancestry_from B A that does just that.
410
+ Then you could also svn rm A and it would be (I think) completely equivalent to having done an svn mv A B in the first place.
411
+
412
+ svn list_conflicts instead of:
413
+ svn st --no-color | grep "^C"
414
+
394
415
  Take the best ideas from these and incorporate:
395
416
  * /usr/lib/ruby/gems/1.8/gems/rscm-0.5.1/lib/rscm/scm/subversion.rb
396
417
  * /usr/lib/ruby/gems/1.8/gems/lazysvn-0.1.3/lib/subversion.rb
@@ -399,13 +420,33 @@ Possibly switch to LazySvn.
399
420
 
400
421
  After you save/edit/set an svn:externals, it should try to automatically pretty up the margins/alignment for you.
401
422
 
402
- Done, I think:
403
- Make sure to show errors!
404
-
405
- If there is an error during an update, such as this one:
406
- Fetching external item into 'glass/rails_backend/vendor/plugins/our_extensions'
407
- svn: REPORT request failed on '/!svn/vcc/default'
408
- svn: Cannot replace a directory from within
409
- it will not be displayed on screen. At least if it's an external that had the error.
423
+ /usr/lib/ruby/gems/1.8/gems/piston-1.3.3/lib/piston/commands/import.rb has interesting way of parsing output from `svn info`
424
+ my_info = YAML::load(svn(:info, File.join(dir, '..')))
425
+ my_revision = YAML::load(svn(:info, my_info['URL']))['Revision']
410
426
 
411
427
  More at: http://wiki.qualitysmith.com/svn-command
428
+
429
+ ===Ideas from TortoiseSvn
430
+
431
+ When you drag and drop one or more files to a WC directory, it prompts you with a context menu with these options:
432
+ * svn move versioned files here
433
+ * svn copy versioned files here
434
+ * svn copy and rename versioned files here
435
+ * svn add files to this WC
436
+ * svn export to here
437
+ * svn export all to here
438
+
439
+ ===Pick a better name!
440
+
441
+ svn-command doesn't say anything to differentiate it from the built-in svn command. In fact, like I did just there, you can call the original 'svn' "the svn command" too, which makes the current name highly confusing.
442
+
443
+ Word ideas to possibly incorporate:
444
+ * improved
445
+ * enhanced
446
+ * wrapper
447
+ * color
448
+ * plus
449
+ * more
450
+
451
+ A play on the word sub?
452
+ * 'subwrap'? Both a sub and wrap can be considered food items.
@@ -43,6 +43,7 @@ module Subversion
43
43
  # If true, will print all commands to the screen before executing them.
44
44
  @@print_commands = false
45
45
  mattr_accessor :print_commands
46
+ mguard_method :print_commands!, :@@print_commands
46
47
 
47
48
  # Adds the given items to the repository. Items may contain wildcards.
48
49
  def self.add(*args)
@@ -324,7 +325,7 @@ module Subversion
324
325
  matches = info(path_or_url).match(/^Repository Root: (.+)/)
325
326
  matches && matches[1]
326
327
 
327
- # It appears that we might need to use this old way (which looks at 'URL'), since there is actually a
328
+ # It appears that we might need to use this old way (which looks at 'URL'), since there is actually a handy property called "Repository Root" that we can look at.
328
329
  # base_url = nil # needed so that base_url variable isn't local to loop block (and reset during next iteration)!
329
330
  # started_using_dot_dots = false
330
331
  # loop do
@@ -348,6 +349,39 @@ module Subversion
348
349
  def self.root_url(*args); base_url(*args); end
349
350
  def self.repository_root(*args); base_url(*args); end
350
351
 
352
+
353
+ def self.repository_uuid(path_or_url = './')
354
+ matches = info(path_or_url).match(/^Repository UUID: (.+)/)
355
+ matches && matches[1]
356
+ end
357
+
358
+ # By default, if you query a directory that is scheduled for addition but hasn't been committed yet (node doesn't have a UUID),
359
+ # then we will still return true, because it is *scheduled* to be under version control. If you want a stricter definition,
360
+ # and only want it to return true if the file exists in the *repository* (has a UUID)@ then pass strict = true
361
+ def self.under_version_control?(file = './', strict = false)
362
+ if strict
363
+ !!repository_uuid(file)
364
+ else # (scheduled_for_addition_counts_as_true)
365
+ !!url(file)
366
+ end
367
+ end
368
+ def self.working_copy_root(directory = './')
369
+ uuid = repository_uuid(directory)
370
+ return nil if uuid.nil?
371
+
372
+ loop do
373
+ # Keep going up, one level at a time, ...
374
+ new_directory = File.expand_path(File.join(directory, '..'))
375
+ new_uuid = repository_uuid(new_directory)
376
+
377
+ # Until we get back a uuid that is nil (it's not a working copy at all) or different (you can have a working copy A inside of a different WC B)...
378
+ break if new_uuid.nil? or new_uuid != uuid
379
+
380
+ directory = new_directory
381
+ end
382
+ directory
383
+ end
384
+
351
385
  # The location of the executable to be used
352
386
  def self.executable
353
387
  @@executable ||=
@@ -363,12 +397,6 @@ module Subversion
363
397
  #
364
398
  end
365
399
 
366
- def self.print_commands_for(&block)
367
- old_print_commands, @@print_commands = @@print_commands, true
368
- yield
369
- @@print_commands = old_print_commands
370
- end
371
-
372
400
  protected
373
401
  def self.execute(*args)
374
402
  options = args.last.is_a?(Hash) ? args.pop : {}
@@ -3,6 +3,8 @@
3
3
  gem 'colored'
4
4
  require 'colored'
5
5
 
6
+ require 'facets/core/module/class_extension'
7
+
6
8
  class Array
7
9
  def to_regexp_char_class
8
10
  "[#{join('')}]"
@@ -70,73 +72,77 @@ module Subversion
70
72
  Uninteresting_status_flags = ["X", "W"]
71
73
  Status_flags = Interesting_status_flags | Uninteresting_status_flags
72
74
 
73
- def self.status_lines_filter(input)
74
- input = (input || "").reject { |line|
75
- line =~ /^$/ # Blank lines
76
- }.reject { |line|
77
- line =~ /^#{Uninteresting_status_flags.to_regexp_char_class}/
78
- }.join
79
-
80
- before_externals, *externals = input.split(/^Performing status on external item at.*$/)
81
-
82
- before_externals ||= ''
83
- before_externals = before_externals.strip.colorize_svn_status_lines + "\n" if before_externals != ""
75
+ class_extension do # These are actually class methods, but we have to do it this way so that the Subversion.extend(Subversion::Extensions) will also add these class methods to Subversion.
84
76
 
85
- externals = externals.join.strip
86
- externals =
87
- '_'*40 + ' externals '.underline + '_'*40 + "\n" +
88
- externals.reject { |line|
89
- line =~ /^Performing status on external item at/
77
+ def status_lines_filter(input)
78
+ input = (input || "").reject { |line|
79
+ line =~ /^$/ # Blank lines
90
80
  }.reject { |line|
81
+ line =~ /^#{Uninteresting_status_flags.to_regexp_char_class}/
82
+ }.join
83
+
84
+ before_externals, *externals = input.split(/^Performing status on external item at.*$/)
85
+
86
+ before_externals ||= ''
87
+ before_externals = before_externals.strip.colorize_svn_status_lines + "\n" if before_externals != ""
88
+
89
+ externals = externals.join.strip
90
+ externals =
91
+ '_'*40 + ' externals '.underline + '_'*40 + "\n" +
92
+ externals.reject { |line|
93
+ line =~ /^Performing status on external item at/
94
+ }.reject { |line|
95
+ line =~ /^$/ # Blank lines
96
+ }.join.strip.colorize_svn_status_lines + "\n" if externals != ""
97
+
98
+ before_externals +
99
+ externals
100
+ end
101
+
102
+ def update_lines_filter(input)
103
+ input.reject { |line|
91
104
  line =~ /^$/ # Blank lines
92
- }.join.strip.colorize_svn_status_lines + "\n" if externals != ""
93
-
94
- before_externals +
95
- externals
96
- end
97
-
98
- def self.update_lines_filter(input)
99
- input.reject { |line|
100
- line =~ /^$/ # Blank lines
101
- }.reject { |line|
102
- line =~ /^Fetching external item into/
103
- # Eventually we may want it to include this whole block, but only iff there is something updated for this external.
104
- }.reject { |line|
105
- line =~ /^External at revision/
106
- }.join.colorize_svn_update_lines
107
- # Also get rid of all but one "At revision _."?
108
- end
109
-
110
- def self.unadded_lines_filter(input)
111
- input.select { |line|
112
- line =~ /^\?/
113
- }.join
114
- end
115
- def self.unadded_filter(input)
116
- unadded_lines_filter(input).map { |line|
117
- # Just keep the filename part
118
- line =~ /^\?\s+(.+)/
119
- $1
120
- }
121
- end
122
-
123
- def self.each_unadded(input)
124
- unadded_filter(input).each { |line|
125
- yield line
126
- }
127
- end
128
-
129
- # This is just a wrapper for Subversion::diff that adds some color
130
- def self.diff(*args)
131
- Subversion::diff(*args).colorize_svn_diff.add_exit_code_error
132
- end
133
-
134
- # A wrapper for Subversion::revision_properties that formats it for display on srceen
135
- def self.printable_revision_properties(rev)
136
- Subversion::revision_properties(rev).map do |property|
137
- "#{property.name.ljust(20)} = '#{property.value}'"
138
- end.join("\n")
139
- end
140
-
141
- end
142
- end
105
+ }.reject { |line|
106
+ line =~ /^Fetching external item into/
107
+ # Eventually we may want it to include this whole block, but only iff there is something updated for this external.
108
+ }.reject { |line|
109
+ line =~ /^External at revision/
110
+ }.join.colorize_svn_update_lines
111
+ # Also get rid of all but one "At revision _."?
112
+ end
113
+
114
+ def unadded_lines_filter(input)
115
+ input.select { |line|
116
+ line =~ /^\?/
117
+ }.join
118
+ end
119
+ def unadded_filter(input)
120
+ unadded_lines_filter(input).map { |line|
121
+ # Just keep the filename part
122
+ line =~ /^\?\s+(.+)/
123
+ $1
124
+ }
125
+ end
126
+
127
+ def each_unadded(input)
128
+ unadded_filter(input).each { |line|
129
+ yield line
130
+ }
131
+ end
132
+
133
+ # This is just a wrapper for Subversion::diff that adds some color
134
+ def colorized_diff(*args)
135
+ Subversion::diff(*args).colorize_svn_diff.add_exit_code_error
136
+ end
137
+
138
+ # A wrapper for Subversion::revision_properties that formats it for display on srceen
139
+ def printable_revision_properties(rev)
140
+ Subversion::revision_properties(rev).map do |property|
141
+ "#{property.name.ljust(20)} = '#{property.value}'"
142
+ end.join("\n")
143
+ end
144
+
145
+ end # class_extension
146
+
147
+ end # module Extensions
148
+ end # module Subversion
@@ -1,15 +1,19 @@
1
+ # Tested by: ../../test/svn_command_test.rb
2
+
1
3
  require 'rubygems'
2
4
 
3
5
  gem 'facets', '>=1.8.51'
4
6
  #require 'facets/more/command' # Not until they include my changes
5
- require 'facets/core/string/margin'
6
7
  require 'facets/core/kernel/require_local'
7
- require 'facets/core/array/select' # select!
8
8
  require 'facets/core/kernel/with' # returning
9
+ require 'facets/core/enumerable/every'
10
+ require 'facets/core/array/select' # select!
11
+ require 'facets/core/string/margin'
9
12
  require 'facets/core/string/lines'
10
13
  require 'facets/core/string/index_all'
11
14
  require 'facets/core/string/to_re'
12
15
  require 'facets/core/string/to_rx'
16
+ require 'facets/core/symbol/to_proc'
13
17
 
14
18
  gem 'qualitysmith_extensions', '>=0.0.3'
15
19
  require 'qualitysmith_extensions/enumerable/enum'
@@ -17,12 +21,14 @@ require 'qualitysmith_extensions/array/expand_ranges'
17
21
  require 'qualitysmith_extensions/array/shell_escape'
18
22
  require 'qualitysmith_extensions/file_test/binary_file'
19
23
  require 'qualitysmith_extensions/console/command'
24
+ require 'qualitysmith_extensions/module/attribute_accessors'
25
+ #require 'qualitysmith_extensions/module/class_methods'
20
26
 
21
- require 'extensions/symbol' # to_proc
22
27
  require 'English'
23
28
  require 'pp'
24
- require 'termios'
25
29
  require 'stringio'
30
+ gem 'termios'
31
+ require 'termios'
26
32
  gem 'colored'
27
33
  require 'colored' # Lets us do "a".white.bold instead of "\033[1ma\033[0m"
28
34
 
@@ -34,8 +40,9 @@ begin
34
40
  # Set up termios so that it returns immediately when you press a key.
35
41
  # (http://blog.rezra.com/articles/2005/12/05/single-character-input)
36
42
  t = Termios.tcgetattr(STDIN)
43
+ save_terminal_attributes = t.dup
37
44
  t.lflag &= ~Termios::ICANON
38
- Termios.tcsetattr(STDIN,0,t)
45
+ Termios.tcsetattr(STDIN, 0, t)
39
46
  rescue RuntimeError => exception # Necessary for automated testing.
40
47
  if exception.message =~ /can't get terminal parameters/
41
48
  puts 'Warning: Terminal not found.'
@@ -45,6 +52,20 @@ rescue RuntimeError => exception # Necessary for automated testing.
45
52
  end
46
53
  end
47
54
 
55
+ begin # Reset terminal_attributes
56
+
57
+ module Kernel
58
+ # Simply to allow us to override it
59
+ # Should be renamed to exit_status maybe since it returns a Process::Status
60
+ def exit_code
61
+ $?
62
+ end
63
+ end
64
+
65
+ class Object
66
+ def nonnil?; !nil?; end
67
+ end
68
+
48
69
  class String
49
70
  # Makes the first character bold and underlined. Makes the whole string of the given color.
50
71
  # :todo: Move out to extensions/console/menu_item
@@ -56,8 +77,9 @@ class String
56
77
  after = self[index+1..-1].send(color)
57
78
  before.to_s + middle + after
58
79
  end
80
+ # Extracted so that we can override it for tests. Otherwise it will have a NoMethodError because $? will be nil because it will not have actually executed any commands.
59
81
  def add_exit_code_error
60
- self << "Exited with error!".bold.red if !$?.success?
82
+ self << "Exited with error!".bold.red if !exit_code.success?
61
83
  self
62
84
  end
63
85
  def relativize_path
@@ -78,33 +100,60 @@ def confirm(question, options = ['Yes', 'No'])
78
100
  response
79
101
  end
80
102
 
81
- Subversion.extend(Subversion::Extensions)
103
+ #Subversion.extend(Subversion::Extensions)
104
+ module Subversion
105
+ include(Subversion::Extensions)
106
+ end
82
107
  Subversion::color = true
83
108
 
84
109
  module Subversion
85
- # Tested by: ../../test/svn_command_test.rb
86
110
  class SvnCommand < Console::Command
87
- @@standard_remote_command_options = {
111
+ end
112
+ end
113
+
114
+ # Handle user preferences
115
+ module Subversion
116
+ class SvnCommand
117
+ @@user_preferences = {}
118
+ mattr_accessor :user_preferences
119
+ end
120
+ end
121
+ if File.exists?(user_preference_file = "#{ENV['HOME']}/.svn-command.yml")
122
+ Subversion::SvnCommand.user_preferences = YAML::load(IO.read(user_preference_file)) || {}
123
+ end
124
+
125
+ module Subversion
126
+ class SvnCommand
127
+
128
+ # Constants
129
+ C_standard_remote_command_options = {
88
130
  [:__username] => 1,
89
131
  [:__password] => 1,
90
132
  [:__no_auth_cache] => 0,
91
133
  [:__non_interactive] => 0,
92
134
  [:__config_dir] => 1,
93
135
  }
94
- mattr_reader :standard_remote_command_options
136
+ C_standard_commitable_command_options = {
137
+ [:_m, :__message] => 1,
138
+ [:_F, :__file] => 1,
139
+ [:__force_log] => 0,
140
+ [:__editor_cmd] => 1,
141
+ [:__encoding] => 1,
142
+ }
95
143
 
96
144
  # This shouldn't be necessary. Console::Command should allow introspection. But until such time...
97
145
  @@subcommand_list = [
98
146
  'each_unadded',
99
147
  'externals_items', 'externals_outline', 'externals_containers', 'edit_externals', 'externalize',
100
- 'ignore',
148
+ 'ignore', 'edit_ignores',
101
149
  'revisions',
102
150
  'get_message', 'set_message', 'edit_message',
103
151
  'view_commits',
104
152
  'url',
105
- 'repository_root',
153
+ 'repository_root', 'working_copy_root', 'repository_uuid',
106
154
  'latest_revision',
107
- 'delete_svn'
155
+ 'delete_svn',
156
+ 'fix_out_of_date_commit_state'
108
157
  ]
109
158
  mattr_reader :subcommand_list
110
159
 
@@ -186,8 +235,12 @@ module Subversion
186
235
  # * 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*.
187
236
  # 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.
188
237
 
189
- @passthrough_options << "#{option_name}" << args
190
- @passthrough_options.flatten!
238
+ # The args will look like this, for example:
239
+ # option_missing(-m, ["a multi-word message", "--something-else", "something else"])
240
+ # , so we need to be sure we wrap multi-word args in quotes as necessary. That's what the args.shell_escape does.
241
+
242
+ @passthrough_options << "#{option_name}" << args.shell_escape
243
+ @passthrough_options.flatten! # necessary now that we have args.shell_escape ?
191
244
 
192
245
  return arity = args.size # All of 'em
193
246
  end
@@ -220,19 +273,18 @@ module Subversion
220
273
  [:_N, :__non_recursive] => 1,
221
274
  [:__targets] => 1,
222
275
  [:__no_unlock] => 0,
223
- [:_m, :__message] => 1,
224
- [:_F, :__file] => 1,
225
- [:__force_log] => 0,
226
- [:__editor_cmd] => 1,
227
- [:__encoding] => 1,
228
- }.merge(SvnCommand::standard_remote_command_options), self
276
+ }.
277
+ merge(SvnCommand::C_standard_remote_command_options).
278
+ merge(SvnCommand::C_standard_commitable_command_options), self
229
279
  )
230
280
 
231
281
  # Use this flag if you don't want a commit notification to be sent out.
232
282
  def __skip_notification
233
283
  @skip_notification = true
234
284
  end
235
- alias_method :__covert, :__skip_notification
285
+ alias_method :__covert, :__skip_notification
286
+ alias_method :__minor_edit, :__skip_notification # Like in MediaWiki. If you're just fixing a typo or something, then most people probably don't want to hear about it.
287
+ alias_method :__minor, :__skip_notification
236
288
 
237
289
  # Use this flag if you are about to commit some code for which you know the tests aren't or (probaby won't) pass.
238
290
  # This *may* cause your continuous integration system to either skip tests for this revision or at least be a little more
@@ -248,28 +300,81 @@ module Subversion
248
300
  end
249
301
  alias_method :__expect_to_break_tests, :__broken
250
302
  alias_method :__knowingly_committing_broken_code, :__broken
251
- end
303
+
304
+ # Skips e-mail and marks reviewed=true
305
+ # Similar to the 'reviewed' command, which just marks reviewed=true
306
+ def __doesnt_need_review
307
+ @__doesnt_need_review = true
308
+ end
309
+
310
+ # :todo: svn doesn't allow you to commit changes to externals in the same transaction as your "main working copy", but we
311
+ # can provide the illusion that this is possible, by doing multiple commits, one for each working copy/external.
312
+ #
313
+ # When this option is used, the same commit message is used for all commits.
314
+ #
315
+ # Of course, this may not be what the user wants; the user may wish to specify a different commit message for the externals
316
+ # than for the "main working copy", in which case the user should not be using this option!
317
+ def __include_externals
318
+ @include_externals = true
319
+ end
320
+
321
+ # Causes blame/author for this commit/file to stay the same as previous revision of the file
322
+ # Useful to workaround bug where fixing indent and other inconsequential changes causes you to be displayed as the author if you do a blame, [hiding] the real author
323
+ def __shirk_blame
324
+ @shirk_blame = true
325
+ end
326
+
327
+ end #module Commit
328
+
252
329
  def commit(*args)
253
- latest_rev_before_commit = Subversion.latest_revision if @broken || @skip_notification
330
+ directory = args.first || './' # We can only pass one path to .latest_revision and .repository_root, so we'll just arbitrarily choose the first path. They should all be paths within the same repository anyway, so it shouldn't matter.
331
+ if @broken || @skip_notification
332
+ latest_rev_before_commit = Subversion.latest_revision(directory)
333
+ repository_root = Subversion.repository_root(directory)
334
+ end
254
335
 
255
- Subversion.print_commands_for do
256
- puts svn(:capture, "propset svn:skip_commit_notification_for_next_commit true --revprop -r #{latest_rev_before_commit}", :prepare_args => false)
336
+ Subversion.print_commands! do
337
+ puts svn(:capture, "propset svn:skip_commit_notification_for_next_commit true --revprop -r #{latest_rev_before_commit} #{repository_root}", :prepare_args => false)
257
338
  end if @skip_notification
258
339
  # :todo:
259
340
  # Add some logic to automatically skip the commit e-mail if the size of the files to be committed exceeds a threshold of __ MB.
260
341
  # (Performance idea: Only check the size of the files if svn st includes (bin)?)
261
342
 
343
+ # Have to use :system rather than :capture because they may not have specified a commit message, in which case it will open up an editor...
262
344
  svn(:system, 'commit', *(['--force-log'] + args))
263
345
 
346
+ puts ''.add_exit_code_error
347
+ return if !exit_code.success?
348
+
264
349
  # The following only works if we do :capture (`svn`), but that doesn't work so well (at all) if svn tries to open up an editor (vim),
265
350
  # which is what happens if you don't specify a message.:
266
351
  # puts output = svn(:capture, 'commit', *(['--force-log'] + args))
267
352
  # just_committed = (matches = output.match(/Committed revision (\d+)\./)) && matches[1]
268
353
 
269
- Subversion.print_commands_for do
354
+ Subversion.print_commands! do
270
355
  puts svn(:capture, "propset code:broken true --revprop -r #{latest_rev_before_commit + 1}", :prepare_args => false)
271
356
  end if @broken
272
357
 
358
+ if @include_externals
359
+ #:todo:
360
+ #externals.each do |external|
361
+ #svn(:system, 'commit', *(['--force-log'] + args + external))
362
+ #end
363
+ end
364
+
365
+
366
+ # This should be disableable! ~/.svn-command ?
367
+ # http://svn.collab.net/repos/svn/trunk/doc/user/svn-best-practices.html:
368
+ # After every svn commit, your working copy has mixed revisions. The things you just committed are now at the HEAD revision, and everything else is at an older revision.
369
+ #puts "Whenever you commit something, strangely, your working copy becomes out of date (as you can observe if you run svn info and look at the revision number). This is a problem for svn log, and piston, to name two applications. So we will now update '#{(args.every + '/..').join(' ').white.bold}' just to make sure they're not out of date..."
370
+ print ''.bold # Clear the bold flag that svn annoyingly sets
371
+ working_copy_root = Subversion.working_copy_root(directory).to_s
372
+ #response = confirm("Do you want to update #{working_copy_root.bold} now? (Any key other than y to skip) ")
373
+ #if response == 'y'
374
+ puts "Updating #{working_copy_root}..."
375
+ #end
376
+
377
+ puts Subversion.update_lines_filter( Subversion.update(*args) )
273
378
  end
274
379
 
275
380
  # Ideas:
@@ -284,11 +389,11 @@ module Subversion
284
389
 
285
390
  puts Subversion.export("#{dir}", "#{dir}.new"). # Exports (copies) the contents of working copy 'dir' (including your uncommitted changes, don't worry! ... and you'll get a chance to confirm before anything is deleted; but sometimes although it exports files that are scheduled for addition, they are no longer scheduled for addition in the new working copy, so you have to re-add them) to non-working-copy 'dir.new'
286
391
  add_exit_code_error
287
- return if !$?.success?
392
+ return if !exit_code.success?
288
393
 
289
394
  system("mv #{dir} #{dir}.backup") # Just in case something goes ary
290
395
  puts ''.add_exit_code_error
291
- return if !$?.success?
396
+ return if !exit_code.success?
292
397
 
293
398
  puts "Restoring #{dir}..."
294
399
  Subversion.update dir # Restore the directory to a pristine state so we will no longer get that annoying error
@@ -301,21 +406,26 @@ module Subversion
301
406
  system("cp -R #{dir}.new/. #{dir}/")
302
407
 
303
408
  # Assure the user one more time
304
- puts Extensions.diff(dir)
409
+ puts Subversion.colorized_diff(dir)
410
+
411
+ puts "Please check the output of " + "svn st #{dir}.backup".blue.bold + " to check if any files were scheduled for addition. You will need to manually re-add these, as the export will have caused those files to lost their scheduling."
412
+ Subversion.print_commands! do
413
+ print Subversion.status_lines_filter( Subversion.status("#{dir}.backup") )
414
+ print Subversion.status_lines_filter( Subversion.status("#{dir}") )
415
+ end
305
416
 
306
417
  # Actually commit
307
418
  puts
308
419
  response = confirm("Are you ready to try the commit again now?")
309
420
  puts
310
421
  if response == 'y'
311
- puts "Great. Go for it."
312
- #Subversion.commit dir
422
+ puts "Great! Go for it. (I'd do it for you but I don't know what commit command you were trying to execute when the problem occurred.)"
313
423
  end
314
424
 
315
425
  # Clean up
316
426
  #puts
317
427
  #response = confirm("Do you want to delete array.backup array.new now?")
318
- puts "Don't forget to delete array.backup array.new now!"
428
+ puts "Don't forget to " + "rm -rf #{dir}.backup #{dir}.new".blue.bold + " when you are done!"
319
429
  #rm_rf array.backup, array.new
320
430
  puts
321
431
  end
@@ -326,33 +436,42 @@ module Subversion
326
436
  [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
327
437
  [:__old] => 1,
328
438
  [:__new] => 0,
329
- [:_N, :__non_recursive] => 1,
439
+ #[:_N, :__non_recursive] => 0,
330
440
  [:__diff_cmd] => 1,
331
441
  [:_x, :__extensions] => 1, # :todo: should support any number of args??
332
442
  [:__no_diff_deleted] => 0,
333
443
  [:__notice_ancestry] => 1,
334
444
  [:__force] => 1,
335
- }.merge(SvnCommand::standard_remote_command_options), self
445
+ }.merge(SvnCommand::C_standard_remote_command_options), self
336
446
  )
447
+
448
+ def __non_recursive
449
+ @non_recursive = true
450
+ @passthrough_options << '--non-recursive'
451
+ end
452
+ alias_method :_N, :__non_recursive
453
+
337
454
  def __ignore_externals
338
455
  @ignore_externals = true
339
456
  end
457
+ alias_method :_ie, :__ignore_externals
458
+ alias_method :_skip_externals, :__ignore_externals
340
459
  end
341
460
 
342
461
  def diff(*directories)
343
462
  directories = ['./'] if directories.empty?
344
- puts Extensions.diff(*(directories + @passthrough_options))
463
+ puts Subversion.colorized_diff(*(prepare_args(directories)))
345
464
 
346
465
  begin # Show diff for externals (if there *are* any and the user didn't tell us to ignore them)
347
466
  output = StringIO.new
348
467
  #paths = args.reject{|arg| arg =~ /^-/} || ['./']
349
468
  directories.each do |path|
350
469
  (Subversion.externals_items(path) || []).each do |item|
351
- diff_output = Extensions.diff(item).strip
470
+ diff_output = Subversion.colorized_diff(item).strip
352
471
  unless diff_output == ""
353
472
  #output.puts '-'*100
354
473
  #output.puts item.ljust(100, ' ').black_on_white.bold.underline
355
- output.puts item.ljust(100).black_on_white.bold
474
+ output.puts item.ljust(100).yellow_on_red.bold
356
475
  output.puts diff_output
357
476
  end
358
477
  end
@@ -363,7 +482,8 @@ module Subversion
363
482
  puts " Diff of externals (**don't forget to commit these too!**):".ljust(100, ' ').yellow_on_red.bold.underline
364
483
  puts output.string
365
484
  end
366
- end unless @ignore_externals
485
+ end unless @ignore_externals || @non_recursive
486
+ # :todo: or @non_recursive (but that's currently a passthrough option that doesn't set @non_recursive)
367
487
  end
368
488
 
369
489
  #-----------------------------------------------------------------------------------------------------------------------------
@@ -422,16 +542,56 @@ End
422
542
  [:__incremental] => 0,
423
543
  [:__xml] => 0,
424
544
  [:__limit] => 1,
425
- }.merge(SvnCommand::standard_remote_command_options), self
545
+ }.merge(SvnCommand::C_standard_remote_command_options), self
426
546
  )
427
547
  end
428
548
  def log(*args)
429
- puts Subversion.log( @passthrough_options + args )
549
+ puts Subversion.log( prepare_args(args) )
430
550
  #svn :exec, *args
431
551
  end
432
552
 
433
553
  # Ideas:
434
554
  # Just pass a number (5) and it will be treated as --limit 5 (unless File.exists?('5'))
555
+
556
+ #-----------------------------------------------------------------------------------------------------------------------------
557
+ module Mkdir
558
+ Console::Command.pass_through({
559
+ [:_q, :__quiet] => 0,
560
+ }.
561
+ merge(SvnCommand::C_standard_remote_command_options).
562
+ merge(SvnCommand::C_standard_commitable_command_options), self
563
+ )
564
+
565
+ # Make parent directories as needed. (Like the --parents option of GNU mkdir.)
566
+ def __parents; @create_parents = true; end
567
+ alias_method :_p, :__parents
568
+ end
569
+
570
+ def mkdir(*directories)
571
+ if @create_parents
572
+ directories.each do |directory|
573
+
574
+ # :todo: change this so that it's guaranteed to have an exit condition; currently, can get into infinite loop
575
+ loop do
576
+ puts "Creating '#{directory}'"
577
+ FileUtils.mkdir_p directory # Create it if it doesn't already exist
578
+ if Subversion.under_version_control?(File.dirname(directory))
579
+ # Yay, we found a working copy. Now we can issue an add command, from that directory, which will recursively add the
580
+ # (non-working copy) directories we've been creating along the way.
581
+
582
+ #puts Subversion.add prepare_args([directory])
583
+ svn :system, 'add', *directory
584
+ break
585
+ else
586
+ directory = File.dirname(directory)
587
+ end
588
+ end
589
+ end
590
+ else
591
+ # Preserve default behavior.
592
+ svn :system, 'mkdir', *directories
593
+ end
594
+ end
435
595
 
436
596
  #-----------------------------------------------------------------------------------------------------------------------------
437
597
  module Move
@@ -439,13 +599,20 @@ End
439
599
  [:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
440
600
  [:_q, :__quiet] => 0,
441
601
  [:__force] => 0,
442
- [:_m, :__message] => 1,
443
- [:_F, :__file] => 1,
444
- [:__force_log] => 0,
445
- [:__editor_cmd] => 1,
446
- [:__encoding] => 1,
447
- }.merge(SvnCommand::standard_remote_command_options), self
602
+ }.
603
+ merge(SvnCommand::C_standard_remote_command_options).
604
+ merge(SvnCommand::C_standard_commitable_command_options), self
448
605
  )
606
+
607
+ # If the directory specified by the destination path does not exist, it will `svn mkdir --parents` the directory for you to
608
+ # save you the trouble (and to save you from getting an error message!).
609
+ #
610
+ # For example, if you try to move file_name to dir1/dir2/new_file_name and dir1/dir2 is not under version control, then it
611
+ # will effectively do these commands:
612
+ # svn mkdir --parents dir1/dir2
613
+ # svn mv a dir1/dir2/new_file_name # The command you were originally trying to do
614
+ def __parents; @create_parents = true; end
615
+ alias_method :_p, :__parents
449
616
  end
450
617
 
451
618
  # Unlike the built-in move, this one lets you list multiple source files
@@ -453,19 +620,64 @@ End
453
620
  # or
454
621
  # Source Destination
455
622
  def move(*args)
456
- if args.length >= 3
457
- destination = args.pop
623
+ destination = args.pop
624
+
625
+ # If the last character is a '/', then they obviously expect the destination to be a *directory*. Yet when I do this:
626
+ # svn mv a b/
627
+ # and b doesn't exist,
628
+ # it moves a (a file) to b as a file, rather than creating directory b/ and moving a to b/a.
629
+ # I find this default behavior less than intuitive, so I have "fixed" it here...
630
+ # So instead of seeing this:
631
+ # A b
632
+ # D a
633
+ # You should see this:
634
+ # A b
635
+ # A b/a
636
+ # D a
637
+ if destination[-1..-1] == '/'
638
+ if !File.exist?(destination[0..-2])
639
+ puts "Notice: It appears that the '" + destination.bold + "' directory doesn't exist. Would you like to create it now? Good..."
640
+ self.mkdir destination # @create_parents flag will be reused there
641
+ elsif !File.directory?(destination[0..-2])
642
+ puts "Error".red.bold + ": It appears that '" + destination.bold + "' already exists but is not actually a directory. " +
643
+ "The " + 'destination'.bold + " must either be the path to a " + 'file'.underline + " that does " + 'not'.underline + " yet exist or the path to a " + 'directory'.underline + " (which may or may not yet exist)."
644
+ return
645
+ end
646
+ end
647
+
648
+ if @create_parents and !Subversion.under_version_control?(destination_dir = File.dirname(destination))
649
+ puts "Creating parent directory '#{destination_dir}'..."
650
+ self.mkdir destination_dir # @create_parents flag will be reused there
651
+ end
652
+ if args.length >= 2
458
653
  sources = args
459
654
 
460
655
  sources.each do |source|
461
656
  puts filtered_svn('move', source, destination)
462
657
  end
463
658
  else
464
- svn :exec, 'move', *args
659
+ svn :exec, 'move', *(args + [destination])
465
660
  end
466
661
  end
467
662
  alias_subcommand :mv => :move
468
663
 
664
+ #-----------------------------------------------------------------------------------------------------------------------------
665
+ module Import
666
+ Console::Command.pass_through({
667
+ [:_N, :__non_recursive] => 0,
668
+ [:_q, :__quiet] => 0,
669
+ [:__auto_props] => 0,
670
+ [:__no_auto_props] => 0,
671
+ }.
672
+ merge(SvnCommand::C_standard_remote_command_options).
673
+ merge(SvnCommand::C_standard_commitable_command_options), self
674
+ )
675
+ end
676
+ def import(*args)
677
+ p args
678
+ svn :exec, 'import', *(args)
679
+ end
680
+
469
681
  #-----------------------------------------------------------------------------------------------------------------------------
470
682
  module Status
471
683
  Console::Command.pass_through({
@@ -477,14 +689,11 @@ End
477
689
  [:__incremental] => 0,
478
690
  [:__xml] => 0,
479
691
  [:__ignore_externals] => 0,
480
- }.merge(SvnCommand::standard_remote_command_options), self
692
+ }.merge(SvnCommand::C_standard_remote_command_options), self
481
693
  )
482
694
  end
483
695
  def status(*args)
484
- #puts "in status(#{args.inspect})"
485
- #puts "#{self}.@passthrough_options == #{@passthrough_options.inspect}"
486
- #print Subversion::Extensions.status_lines_filter( Subversion.status(*(@passthrough_options + args)) )
487
- print Subversion::Extensions.status_lines_filter( Subversion.status(*(@passthrough_options + args)) )
696
+ print Subversion.status_lines_filter( Subversion.status(*(prepare_args(args))) )
488
697
  end
489
698
 
490
699
  #-----------------------------------------------------------------------------------------------------------------------------
@@ -494,12 +703,24 @@ End
494
703
  [:_N, :__non_recursive] => 0,
495
704
  [:_q, :__quiet] => 0,
496
705
  [:__diff3_cmd] => 1,
497
- [:__ignore_externals] => 0,
498
- }.merge(SvnCommand::standard_remote_command_options), self
706
+ #[:__ignore_externals] => 0,
707
+ }.merge(SvnCommand::C_standard_remote_command_options), self
499
708
  )
709
+
710
+ def __ignore_externals; @ignore_externals = true; end
711
+ def __include_externals; @ignore_externals = false; end
712
+ def __with_externals; @ignore_externals = false; end
713
+
714
+ def ignore_externals?
715
+ @ignore_externals.nonnil? ?
716
+ @ignore_externals :
717
+ (user_preferences['update'] && user_preferences['update']['ignore_externals'])
718
+ end
500
719
  end
720
+
501
721
  def update(*args)
502
- puts Subversion::Extensions.update_lines_filter( Subversion.update(*prepare_args(args)) )
722
+ @passthrough_options << '--non-recursive' if ignore_externals?
723
+ puts Subversion.update_lines_filter( Subversion.update(*prepare_args(args)) )
503
724
  end
504
725
 
505
726
 
@@ -520,6 +741,25 @@ End
520
741
  end
521
742
 
522
743
  #-----------------------------------------------------------------------------------------------------------------------------
744
+
745
+ def under_version_control(*args)
746
+ puts Subversion.under_version_control?(*args)
747
+ end
748
+ alias_subcommand :under_version_control? => :under_version_control
749
+
750
+ # Returns root/base *path* for a working copy
751
+ def working_copy_root(*args)
752
+ puts Subversion.working_copy_root(*args)
753
+ end
754
+ alias_subcommand :root => :working_copy_root
755
+
756
+ # Returns the UUID for a working copy/URL
757
+ def repository_uuid(*args)
758
+ puts Subversion.repository_uuid(*args)
759
+ end
760
+ alias_subcommand :uuid => :repository_uuid
761
+
762
+ # Returns root repository *URL* for a working copy
523
763
  def repository_root(*args)
524
764
  puts Subversion.repository_root(*args)
525
765
  end
@@ -595,7 +835,7 @@ End
595
835
  catch :exit do
596
836
 
597
837
  $ignore_dry_run_option = true
598
- Subversion::Extensions.each_unadded( Subversion.status(*args) ) do |file|
838
+ Subversion.each_unadded( Subversion.status(*args) ) do |file|
599
839
  $ignore_dry_run_option = false
600
840
  begin
601
841
  puts( ('-'*100).green )
@@ -628,6 +868,7 @@ End
628
868
  "Add".menu_item(:green) + ", " +
629
869
  "Delete".menu_item(:red) + ", " +
630
870
  "add to " + "svn:".yellow + "Ignore".menu_item(:yellow) + " property, " +
871
+ "ignore ".yellow + "Contents".menu_item(:yellow) + " of directory, " +
631
872
  "or " + "any other key".white.bold + " to do nothing > "
632
873
  )
633
874
  response = ""
@@ -761,11 +1002,10 @@ End
761
1002
  end
762
1003
  Subversion.externals_containers(directory).each do |external|
763
1004
  puts external.to_s
764
- command = "#{Subversion.executable} propedit svn:externals #{external.container_dir}"
765
- #puts command
1005
+ command = "propedit svn:externals #{external.container_dir}"
766
1006
  begin
767
1007
  response = confirm("Do you want to edit svn:externals for this directory?".black_on_white)
768
- system command if response == 'y'
1008
+ svn :system, command if response == 'y'
769
1009
  rescue Interrupt
770
1010
  puts "\nGoodbye!"
771
1011
  throw :exit
@@ -775,7 +1015,8 @@ End
775
1015
  end
776
1016
  puts 'Done'
777
1017
  else
778
- system "#{Subversion.executable} propedit svn:externals #{directory}"
1018
+ #system "#{Subversion.executable} propedit svn:externals #{directory}"
1019
+ svn :system, "propedit svn:externals #{directory}"
779
1020
  end
780
1021
  end # catch :exit
781
1022
  end
@@ -811,6 +1052,14 @@ End
811
1052
  Subversion.ignore(file)
812
1053
  end
813
1054
 
1055
+ # Example:
1056
+ # svn edit_ignores tmp/sessions/
1057
+ def edit_ignores(directory = './')
1058
+ #puts Subversion.get_property("ignore", directory)
1059
+ # If it's empty, ask them if they want to edit it anyway??
1060
+
1061
+ svn :system, "propedit svn:ignore #{directory}"
1062
+ end
814
1063
 
815
1064
  #-----------------------------------------------------------------------------------------------------------------------------
816
1065
  # Commit message retrieving/editing
@@ -868,7 +1117,7 @@ End
868
1117
  args = ['propedit', '--revprop', property_name, directory]
869
1118
  rev = @revision ? @revision : 'head'
870
1119
  args.concat ['-r', rev]
871
- Subversion.print_commands_for do
1120
+ Subversion.print_commands! do
872
1121
  svn :system, *args
873
1122
  end
874
1123
 
@@ -884,7 +1133,7 @@ End
884
1133
  response = confirm("Are you sure you want to delete property #{property_name}".red.bold + "'? ")
885
1134
  puts
886
1135
  if response == 'y'
887
- Subversion.print_commands_for do
1136
+ Subversion.print_commands! do
888
1137
  Subversion::delete_revision_property(property_name, rev)
889
1138
  end
890
1139
  end
@@ -906,9 +1155,14 @@ End
906
1155
 
907
1156
  #-----------------------------------------------------------------------------------------------------------------------------
908
1157
  # Cause a working copy to cease being a working copy
909
- def delete_svn
910
- system('find -name .svn | xargs -n1 echo')
911
- system('find -name .svn | xargs -n1 rm -r')
1158
+ def delete_svn(directory = './')
1159
+ puts "If you continue, all of the following directories/files will be deleted:"
1160
+ system("find #{directory} -name .svn | xargs -n1 echo")
1161
+ response = confirm("Do you wish to continue?")
1162
+ puts
1163
+ if response == 'y'
1164
+ system("find #{directory} -name .svn | xargs -n1 rm -r")
1165
+ end
912
1166
  end
913
1167
 
914
1168
  #-----------------------------------------------------------------------------------------------------------------------------
@@ -1004,12 +1258,13 @@ End
1004
1258
 
1005
1259
  # Display the menu
1006
1260
  print(
1261
+ "r#{rev}".magenta.on_blue.bold + ': ' +
1007
1262
  'View this changeset'.menu_item(:cyan) + ', ' +
1008
1263
  'Diff against specific revision'.menu_item(:cyan, 'D') + ', ' +
1009
1264
  'Grep the changeset'.menu_item(:cyan, 'G') + ', ' +
1010
1265
  'List or '.menu_item(:magenta, 'L') + '' +
1011
1266
  'Edit revision properties'.menu_item(:magenta, 'E') + ', ' +
1012
- 'svn Cat all files from revision'.menu_item(:cyan, 'C') + ', ' +
1267
+ 'svn Cat all files'.menu_item(:cyan, 'C') + ', ' +
1013
1268
  'grep the cat'.menu_item(:cyan, 'a') + ', ' + "\n " +
1014
1269
  'mark as Reviewed'.menu_item(:green, 'R') + ', ' +
1015
1270
  'edit log Message'.menu_item(:yellow, 'M') + ', ' +
@@ -1037,20 +1292,23 @@ End
1037
1292
  end
1038
1293
 
1039
1294
  case response
1040
- when 'v' # Diff
1295
+ when 'v' # View this changeset
1041
1296
  revs_to_compare = [other_rev, rev]
1042
1297
  puts "\n"*10
1043
1298
  puts((' '*100).green.underline)
1044
1299
  print "Diffing #{revs_to_compare.min}:#{revs_to_compare.max}... ".bold
1045
1300
  puts
1046
1301
  #Subversion.repository_root
1047
- SvnCommand.execute("diff #{directory} --ignore-externals -r #{revs_to_compare.min}:#{revs_to_compare.max}")
1302
+ Subversion.print_commands! do
1303
+ SvnCommand.execute("diff #{directory} --ignore-externals -r #{revs_to_compare.min}:#{revs_to_compare.max}")
1304
+ end
1305
+ show_revision_again = false
1048
1306
 
1049
1307
  when 'g' # Grep the changeset
1050
1308
  # :todo; make it accept regexpes like /like.*this/im so you can make it case insensitive or multi-line
1051
1309
  revs_to_compare = [other_rev, rev]
1052
1310
  puts
1053
- print 'Grep for'.bold + ' (Regular expressions ' + 'like.*this'.bold.blue + ' are allowed, but not ' + '/like.*this/im'.bold.blue + '): '
1311
+ print 'Grep for'.bold + ' (Case sensitive; Regular expressions ' + 'like.*this'.bold.blue + ' allowed, but not ' + '/like.*this/im'.bold.blue + ') (backspace not currently supported): '
1054
1312
  search_pattern = $stdin.gets.chomp.to_rx
1055
1313
  puts((' '*100).green.underline)
1056
1314
  puts "Searching `svn diff #{revs_to_compare.min}:#{revs_to_compare.max}` for #{search_pattern.to_s}... ".bold
@@ -1076,18 +1334,18 @@ End
1076
1334
 
1077
1335
  when 'l' # List revision properties
1078
1336
  puts
1079
- puts Subversion::Extensions::printable_revision_properties(rev)
1337
+ puts Subversion::printable_revision_properties(rev)
1080
1338
  show_revision_again = false
1081
1339
 
1082
1340
  when 'e' # Edit revision property
1083
1341
  puts
1084
- puts Subversion::Extensions::printable_revision_properties(rev)
1342
+ puts Subversion::printable_revision_properties(rev)
1085
1343
  puts "Warning: These properties are *not* under version control! Try not to permanently destroy anything *too* important...".red.bold
1086
1344
  puts "Note: If you want to *delete* a property, simply set its value to '' and it will be deleted (propdel) for you."
1087
1345
  print 'Which property would you like to edit'.bold + ' (backspace not currently supported)? '
1088
1346
  property_name = $stdin.gets.chomp
1089
1347
  unless property_name == ''
1090
- Subversion.print_commands_for do
1348
+ Subversion.print_commands! do
1091
1349
  @revision = rev
1092
1350
  edit_revision_property(property_name, directory)
1093
1351
  end
@@ -1110,16 +1368,18 @@ End
1110
1368
  # how to retrieve that (except by poking around in your ~/.subversion/ directory, but
1111
1369
  # that seems kind of rude...).
1112
1370
  puts "Marking as reviewed by '#{your_name}'..."
1113
- Subversion.print_commands_for do
1371
+ Subversion.print_commands! do
1114
1372
  puts svn(:capture, "propset code:reviewed '#{your_name}' --revprop -r #{rev}", :prepare_args => false)
1115
1373
  # :todo: Maybe *append* to code:reviewed (,-delimited) rather than overwriting it?, in case there is a policy of requiring 2 reviewers or something
1116
1374
  end
1375
+ show_revision_again = false
1117
1376
 
1118
1377
  when 'm' # Edit log message
1119
1378
  puts
1120
- Subversion.print_commands_for do
1379
+ Subversion.print_commands! do
1121
1380
  SvnCommand.execute("edit_message -r #{rev}")
1122
1381
  end
1382
+ show_revision_again = false
1123
1383
 
1124
1384
  when "\e[A" # Up
1125
1385
  i = revision_ids.index(rev)
@@ -1192,6 +1452,7 @@ End
1192
1452
  svn(:capture, *args).add_exit_code_error
1193
1453
  end
1194
1454
 
1455
+ # Removes nil elements, converts to strings, and adds any pass-through args that may have been provided.
1195
1456
  def prepare_args(args)
1196
1457
  args.compact! # nil elements spell trouble
1197
1458
  args.map!(&:to_s) # shell_escape doesn't like Fixnums either
@@ -1203,3 +1464,8 @@ End
1203
1464
  end
1204
1465
  end
1205
1466
  end
1467
+
1468
+ ensure
1469
+ # Set terminal_attributes back to how we found them...
1470
+ Termios.tcsetattr(STDIN, 0, save_terminal_attributes)
1471
+ end