svn-command 0.2.7 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
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