svn-command 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/Readme CHANGED
@@ -5,10 +5,12 @@
5
5
  [<b>Project site</b>:] http://rubyforge.org/projects/svn-command
6
6
  [<b>Wiki</b>:] http://wiki.qualitysmith.com/svn-command
7
7
  [<b>Author</b>:] Tyler Rick
8
+ [<b>Copyright</b>:] 2007 QualitySmith, Inc.
9
+ [<b>License</b>:] {GNU General Public License}[http://www.gnu.org/copyleft/gpl.html]
8
10
 
9
11
  == Introduction
10
12
 
11
- This is a replacement <tt>svn</tt> <b>command-line client</b> meant to be used instead of the standard +svn+ command. Actually, it's a /wrapper/, not a replacement, because it still uses <tt>/usr/bin/svn</tt> to do all the dirty work.
13
+ This is a replacement <tt>svn</tt> <b>command-line client</b> meant to be used instead of the standard +svn+ command. Actually, it's a _wrapper_, not a replacement, because it still uses <tt>/usr/bin/svn</tt> to do all the dirty work.
12
14
 
13
15
  == Installation
14
16
 
@@ -44,29 +46,33 @@ You'll know it's working by way of two signs:
44
46
  == Features
45
47
 
46
48
  Changes to existing subcommands:
47
- * <tt>svn diff</tt> output is in _color_* (requires +colordiff+, see below)
48
- * <tt>svn diff</tt> includes the differences from your *externals* too (consistent with how <tt>svn status</tt> includes them) so that you don't forget to commit those changes too!
49
+ * <tt>svn diff</tt>
50
+ * output is in _color_* (requires +colordiff+, see below)
51
+ * <tt>svn diff</tt> includes the differences from your *externals* too (consistent with how <tt>svn status</tt> includes them) so that you don't forget to commit those changes too! (pass <tt>--ignore-externals</tt> if you _don't_ want a diff of externals)
49
52
  * <tt>svn status</tt>
50
- ** filters out distracting, useless output about externals (don't worry -- it still shows which files were _modified_)
51
- ** the flags (?, M, C, etc.) are in *color*!
52
- * <tt>svn move</tt> -- it will let you move multiple source files to a destination directory with a single command
53
+ * filters out distracting, useless output about externals (don't worry -- it still shows which files were _modified_)
54
+ * the flags (?, M, C, etc.) are in *color*!
55
+ * <tt>svn move</tt> it will let you move multiple source files to a destination directory with a single command
53
56
 
54
57
  (* You can pass --no-color to disable colors for a single command...useful if you want to pipe the output to another command or something. Eventually maybe we could make this a per-user option via .svn-command?)
55
58
 
56
59
  New subcommands:
57
60
  * <tt>svn each_unadded</tt> (+eu+, +unadded+) -- goes through each unadded (<tt>?</tt>) file reported by <tt>svn status</tt> and asks you what to do with them (add, delete, ignore).
58
61
  * <tt>svn externals</tt> -- lists all externals
62
+ * <tt>svn revisions</tt> -- lists all revisions with log messages and lets you browse through them interactively
59
63
  * <tt>svn edit_externals</tt> (+ee+)
60
64
  * <tt>svn externalize</tt>
61
65
  * <tt>svn set_message</tt> / <tt>svn get_message</tt> / <tt>svn edit_message</tt> -- shortcuts for accessing <tt>--revprop svn:log</tt>
62
66
  * <tt>svn ignore</tt> -- shortcut for accessing <tt>svn:ignore</tt> property
63
67
  * <tt>svn view_commits</tt> -- gives you output from both <tt>svn log</tt> and from <tt>svn diff</tt> for the given changesets (useful for code reviews)
68
+ * <tt>svn repository_root</tt> -- prints out the root repository URL of the working copy you are in
69
+ * <tt>svn delete_svn</tt> -- causes the current directory (recursively) to no longer be a working copy
64
70
 
65
71
  (RDoc question: how do I make the identifiers like Subversion::SvnCommand#externalize into links??)
66
72
 
67
- == Usage
73
+ = Usage / Examples
68
74
 
69
- === <tt>st</tt>
75
+ == <tt>svn st</tt>
70
76
 
71
77
  _Without_ this gem installed (really long):
72
78
 
@@ -106,7 +112,7 @@ _Without_ this gem installed (really long):
106
112
  A gemables/subversion/bin/svn
107
113
  M applications/underlord/vendor/plugins/rails_smith/tasks/shared/base.rake
108
114
 
109
- === <tt>each_unadded</tt>
115
+ == <tt>svn each_unadded</tt>
110
116
 
111
117
  My personal favorite. This command is useful for keeping your working copies clean -- getting rid of all those accumulated temp files (or *ignoring* or *adding* them if they're something that _all_ users of this repository should be aware of).
112
118
 
@@ -130,9 +136,9 @@ It simply goes through each "unadded" file (each file reporting a status of <tt>
130
136
  Are you pretty much *SURE* you want to 'rm -rf applications/underlord/vendor/plugins/exception_notification'? (y)es, (n)o > y
131
137
  Deleting...
132
138
 
133
- For *files*, it will show a preview of the _contents_ of that file (limited to the first 3000 characters); for *directories*, it will show a _directory_ _listing_. By looking at the preview, you should hopefully be able to decide whether you want to _keep_ the file or _junk_ it.
139
+ For *files*, it will show a preview of the _contents_ of that file (limited to the first 55 lines); for *directories*, it will show a _directory_ _listing_. By looking at the preview, you should hopefully be able to decide whether you want to _keep_ the file or _junk_ it.
134
140
 
135
- ===<tt>externalize</tt> / <tt>externals</tt> / <tt>edit_externals</tt>
141
+ ==<tt>svn externalize</tt> / <tt>externals</tt> / <tt>edit_externals</tt>
136
142
 
137
143
  Shortcut for creating an svn:external...
138
144
 
@@ -168,7 +174,7 @@ You can also pass a directory name to edit_externals to edit the svn:externals p
168
174
 
169
175
  > svn edit-externals vendor/plugins
170
176
 
171
- ===<tt>get_message</tt> / <tt>set_message</tt> / <tt>edit_message</tt>
177
+ ==<tt>svn get_message</tt> / <tt>set_message</tt> / <tt>edit_message</tt>
172
178
 
173
179
  <b>Pre-requisite for set_message/edit_message</b>: Your repository must have a <tt>pre-revprop-change</tt> hook file.
174
180
 
@@ -187,18 +193,29 @@ You can do this:
187
193
  or just this:
188
194
  svn edit_message
189
195
 
190
- === <tt>move</tt>
196
+ == <tt>svn move</tt>
191
197
 
192
198
  You can now do commands like this:
193
199
 
194
200
  svn mv file1 file2 dir
195
201
  svn mv dir1/* dir
196
202
 
197
- (The standard svn command only accepts a single source and a single destination.)
203
+ (The _standard_ +svn+ command only accepts a _single_ source and a _single_ destination!)
198
204
 
199
- === <tt>commit</tt>
205
+ == <tt>svn revisions</tt> (revisions browser)
200
206
 
201
- ==== --skip-notification / --covert
207
+ Lets you interactively step through all revisions of a file/directory/repository.
208
+
209
+
210
+ Note: You may have to svn update your working copy in order for svn log (and hence svn revisions) to be able to see the revisions you just committed.
211
+
212
+
213
+ Use the <tt>--forwards</tt> flag if you want to <u>start at the oldest revision</u> and step forwards through time rather than starting at the latest revision and stepping backwards (the default).
214
+
215
+
216
+ == <tt>svn commit</tt>
217
+
218
+ === --skip-notification / --covert
202
219
 
203
220
  Added a --skip-notification / --covert option which (assuming you have your post-commit hook set up to do this), will suppress the sending out of a commit notification e-mail.
204
221
 
@@ -249,11 +266,11 @@ For this option to have any effect, you will need to set up your repository simi
249
266
 
250
267
 
251
268
 
252
- ===Help
269
+ ==Help
253
270
 
254
271
  You can, of course, get a lits of the custom commands that have been added by using <tt>svn help</tt>. They will be listed at the end.
255
272
 
256
- ===Global options
273
+ ==Global options
257
274
 
258
275
  * --no-color (since color is on by default)
259
276
  * --dry-run (see what /usr/bin/svn command it _would_ have executed if you weren't just doing a dry run -- useful for debugging if nothing else)
@@ -283,6 +300,14 @@ If you want command completion for the svn subcommands (and I don't blame you if
283
300
 
284
301
  It's really rudimentary right now and could be much improved, but at least it's a start.
285
302
 
303
+ ==Support for code reviews, commit notification, and continuous integration systems
304
+
305
+ The <tt>svn revisions</tt> command lets you browse through recent changes to a project or directory and then, for each revision that you review, you can simply press R and it will mark that revision as reviewed.
306
+
307
+ <tt>svn commit</tt> accepts two custom flags, <tt>--skip-notification / --covert</tt> (don't send commit notification) and <tt>--broken</tt> (tell the continuous integration system to expect failure).
308
+
309
+ =Other
310
+
286
311
  ==Known problems
287
312
 
288
313
  It doesn't support options that are given in this format:
@@ -21,6 +21,12 @@ gem 'qualitysmith_extensions', '>=0.0.7'
21
21
  gem 'qualitysmith_extensions'
22
22
  require 'qualitysmith_extensions/module/attribute_accessors'
23
23
 
24
+ # RSCM is used for some of the abstraction, such as for parsing log messages into nice data structures. It seems like overkill, though, to use RSCM for most things...
25
+ gem 'rscm'
26
+ #require 'rscm'
27
+ #require 'rscm/scm/subversion'
28
+ require 'rscm/scm/subversion_log_parser'
29
+
24
30
  # Wraps the Subversion shell commands for Ruby.
25
31
  module Subversion
26
32
  # True if you want output from svn to be colorized (useful if output is for human eyes, but not useful if using the output programatically)
@@ -137,6 +143,9 @@ module Subversion
137
143
  execute("update #{args.join ' '}")
138
144
  end
139
145
 
146
+ # The output from `svn status` is nicely divided into two "sections": the section which pertains to the current working copy (not
147
+ # counting externals as part of the working copy) and then the section with status of all of the externals.
148
+ # This method returns the first section.
140
149
  def self.status_the_section_before_externals(path = './')
141
150
  status = status(path) || ''
142
151
  status.sub!(/(Performing status.*)/m, '')
@@ -197,15 +206,47 @@ module Subversion
197
206
  self.set_property property, lines.join("\n"), path
198
207
  end
199
208
 
209
+ # :todo: Stop assuming the svn: namespace. What's the point of a namespace if you only allow one of them?
200
210
  def self.get_property(property, path = './')
201
211
  execute "propget svn:#{property} #{path}"
202
212
  end
213
+ def self.get_revision_property(property_name, rev)
214
+ execute("propget --revprop #{property_name} -r #{rev}").chomp
215
+ end
216
+
203
217
  def self.delete_property(property, path = './')
204
218
  execute "propdel svn:#{property} #{path}"
205
219
  end
220
+ def self.delete_revision_property(property_name, rev)
221
+ execute("propdel --revprop #{property_name} -r #{rev}").chomp
222
+ end
223
+
206
224
  def self.set_property(property, value, path = './')
207
225
  execute "propset svn:#{property} '#{value}' #{path}"
208
226
  end
227
+ def self.set_revision_property(property_name, rev)
228
+ execute("propset --revprop #{property_name} -r #{rev}").chomp
229
+ end
230
+
231
+ # Gets raw output of proplist command
232
+ def self.proplist(rev)
233
+ execute("proplist --revprop -r #{rev}")
234
+ end
235
+ # Returns an array of the names of all revision properties currently set on the given +rev+
236
+ # Tested by: ../../test/subversion_test.rb:test_revision_properties_names
237
+ def self.revision_properties_names(rev)
238
+ raw_list = proplist(rev)
239
+ raw_list.scan(/^ +([^ ]+)$/).map { |matches|
240
+ matches.first.chomp
241
+ }
242
+ end
243
+ # Returns an array of RevisionProperty objects (name, value) for revisions currently set on the given +rev+
244
+ # Tested by: ../../test/subversion_test.rb:test_revision_properties
245
+ def self.revision_properties(rev)
246
+ revision_properties_names(rev).map { |property_name|
247
+ RevisionProperty.new(property_name, get_revision_property(property_name, rev))
248
+ }
249
+ end
209
250
 
210
251
  def self.make_directory(dir)
211
252
  execute "mkdir #{dir}"
@@ -215,16 +256,32 @@ module Subversion
215
256
  execute "help #{args.join(' ')}"
216
257
  end
217
258
 
259
+ # Returns the raw output from svn log
218
260
  def self.log(*args)
219
261
  args = ['./'] if args.empty?
220
262
  execute "log #{args.join(' ')}"
221
263
  end
264
+ # Returns the revision number for head.
222
265
  def self.latest_revision(*args)
223
266
  args = ['./'] if args.empty?
224
267
  matches = /Status against revision:\s+(\d+)/m.match(status_against_server(args))
225
268
  matches && matches[1]
226
269
  end
227
270
 
271
+ # Returns an array of RSCM::Revision objects
272
+ def self.revisions(*args)
273
+ # Tried using this, but it seems to expect you to pass in a starting date or accept the default starting date of right now, which is silly if you actually just want *all* revisions...
274
+ #@rscm = ::RSCM::Subversion.new
275
+ #@rscm.revisions
276
+
277
+ #log_output = Subversion.log('-v')
278
+ log_output = Subversion.log(*(['-v'] + args))
279
+ parser = ::RSCM::SubversionLogParser.new(io = StringIO.new(log_output), url = 'http://ignore.me.com')
280
+ revisions = parser.parse_revisions
281
+ revisions
282
+ end
283
+
284
+
228
285
  def self.info(*args)
229
286
  args = ['./'] if args.empty?
230
287
  execute "info #{args.join(' ')}"
@@ -232,26 +289,32 @@ module Subversion
232
289
 
233
290
  # :todo: needs some serious unit-testing love
234
291
  def self.base_url(path_or_url = './')
235
- base_url = nil # needed so that base_url variable isn't local to if block!
236
- started_using_dot_dots = false
237
- loop do
238
- matches = /URL: (.+)/.match(info(path_or_url))
239
- if matches && matches[1]
240
- base_url = matches[1]
241
- else
242
- break base_url
243
- end
244
-
245
- # Keep going up the path, one directory at a time, until `svn info` no longer returns a URL (will probably eventually return 'svn: PROPFIND request failed')
246
- if path_or_url.include?('/') && !started_using_dot_dots
247
- path_or_url = File.dirname(path_or_url)
248
- else
249
- started_using_dot_dots = true
250
- path_or_url = File.join(path_or_url, '..')
251
- end
252
- #puts 'going up to ' + path_or_url
253
- end
254
- end
292
+ matches = info(path_or_url).match(/^Repository Root: (.+)/)
293
+ matches && matches[1]
294
+
295
+ # It appears that we might need to use this old way (which looks at 'URL'), since there is actually a
296
+ # base_url = nil # needed so that base_url variable isn't local to loop block (and reset during next iteration)!
297
+ # started_using_dot_dots = false
298
+ # loop do
299
+ # matches = /^URL: (.+)/.match(info(path_or_url))
300
+ # if matches && matches[1]
301
+ # base_url = matches[1]
302
+ # else
303
+ # break base_url
304
+ # end
305
+ #
306
+ # # Keep going up the path, one directory at a time, until `svn info` no longer returns a URL (will probably eventually return 'svn: PROPFIND request failed')
307
+ # if path_or_url.include?('/') && !started_using_dot_dots
308
+ # path_or_url = File.dirname(path_or_url)
309
+ # else
310
+ # started_using_dot_dots = true
311
+ # path_or_url = File.join(path_or_url, '..')
312
+ # end
313
+ # #puts 'going up to ' + path_or_url
314
+ # end
315
+ end
316
+ def self.root_url(*args); base_url(*args); end
317
+ def self.repository_root(*args); base_url(*args); end
255
318
 
256
319
  # The location of the executable to be used
257
320
  def self.executable
@@ -294,11 +357,17 @@ protected
294
357
 
295
358
  valid_options = [:capture, :exec, :popen]
296
359
  case method
360
+
297
361
  when :capture
298
362
  `#{command} 2>&1`
363
+
299
364
  when :exec
300
365
  #Kernel.exec *args
301
366
  Kernel.exec command
367
+
368
+ when :system
369
+ Kernel.system command
370
+
302
371
  when :popen
303
372
  # This is just an idea of how maybe we could improve the LATENCY. Rather than waiting until the command completes
304
373
  # (which can take quite a while for svn status sometimes since it has to walk the entire directory tree), why not process
@@ -338,8 +407,12 @@ end
338
407
 
339
408
 
340
409
 
410
+ #Subversion.const_set(:RevisionProperty) = Struct.new(:name, :repository_path)
341
411
 
342
412
  module Subversion
413
+
414
+ RevisionProperty = Struct.new(:name, :value)
415
+
343
416
  # Represents an "externals container", which is a directory that has the <tt>svn:externals</tt> property set to something useful.
344
417
  # Each ExternalsContainer contains a set of "entries", which are the actual directories listed in the <tt>svn:externals</tt>
345
418
  # property and are "pulled into" the directory.
@@ -1,4 +1,4 @@
1
- # Tested by: ../test/subversion_extensions_test.rb
1
+ # Tested by: ../../test/subversion_extensions_test.rb
2
2
 
3
3
  gem 'colored'
4
4
  require 'colored'
@@ -10,26 +10,42 @@ class Array
10
10
  end
11
11
 
12
12
  class String
13
+ def colorize_svn_question_mark; self.yellow.bold; end
14
+ def colorize_svn_add; self.green.bold; end
15
+ def colorize_svn_modified; self.cyan.bold; end
16
+ def colorize_svn_updated; self.yellow.bold; end
17
+ def colorize_svn_deleted; self.magenta.bold; end
18
+ def colorize_svn_conflict; self.red.bold; end
19
+ def colorize_svn_tilde; self.red.bold; end
20
+ def colorize_svn_exclamation; self.red.bold; end
21
+
22
+ def colorize_svn_status_code
23
+ if Subversion.color
24
+ self.gsub('?') { $&.colorize_svn_question_mark }.
25
+ gsub('A') { $&.colorize_svn_add }.
26
+ gsub('M') { $&.colorize_svn_modified }.
27
+ gsub('D') { $&.colorize_svn_deleted }.
28
+ gsub('C') { $&.colorize_svn_conflict }.
29
+ gsub('~') { $&.colorize_svn_tilde }.
30
+ gsub('!') { $&.colorize_svn_exclamation }
31
+ else
32
+ self
33
+ end
34
+ end
13
35
  def colorize_svn_status_lines
14
36
  if Subversion.color
15
- self.gsub(/^ *\?/) { $&.yellow.bold}.
16
- gsub(/^ *A/) { $&.green.bold}.
17
- gsub(/^ *M/) { $&.green.bold}.
18
- gsub(/^ *D/) { $&.magenta.bold}.
19
- gsub(/^ *C/) { $&.red.bold}.
20
- gsub(/^ *~/) { $&.red.bold}.
21
- gsub(/^ *!/) { $&.red.bold}
37
+ self.gsub(/^ *([^ ])\s/) { $&.colorize_svn_status_code }
22
38
  else
23
39
  self
24
40
  end
25
41
  end
26
42
  def colorize_svn_update_lines
27
43
  if Subversion.color
28
- self.gsub(/^ *U\s/) { $&.yellow.bold}.
29
- gsub(/^ *A\s/) { $&.green.bold}.
30
- gsub(/^ *M\s/) { $&.green.bold}.
31
- gsub(/^ *D\s/) { $&.magenta.bold}.
32
- gsub(/^ *C\s/) { $&.red.bold}
44
+ self.gsub(/^ *U\s/) { $&.colorize_svn_updated }.
45
+ gsub(/^ *A\s/) { $&.colorize_svn_add }.
46
+ gsub(/^ *M\s/) { $&.colorize_svn_modified }.
47
+ gsub(/^ *D\s/) { $&.colorize_svn_deleted }.
48
+ gsub(/^ *C\s/) { $&.colorize_svn_conflict }
33
49
  else
34
50
  self
35
51
  end
@@ -110,9 +126,16 @@ module Subversion
110
126
  }
111
127
  end
112
128
 
113
- # This is just a wrapper for Subversion.diff that adds some color
129
+ # This is just a wrapper for Subversion::diff that adds some color
114
130
  def self.diff(*args)
115
- output = Subversion.diff(*args).colorize_svn_diff.add_exit_code_error
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")
116
139
  end
117
140
 
118
141
  end
@@ -7,6 +7,7 @@ require 'facets/core/kernel/require_local'
7
7
  require 'facets/core/array/select' # select!
8
8
  require 'facets/core/kernel/with' # returning
9
9
  require 'facets/core/string/lines'
10
+ require 'facets/core/string/index_all'
10
11
 
11
12
  gem 'qualitysmith_extensions', '>=0.0.3'
12
13
  require 'qualitysmith_extensions/enumerable/enum'
@@ -22,6 +23,8 @@ require 'termios'
22
23
  require 'stringio'
23
24
  gem 'colored'
24
25
  require 'colored' # Lets us do "a".white.bold instead of "\033[1ma\033[0m"
26
+
27
+ require_local '../../ProjectInfo'
25
28
  require_local 'subversion'
26
29
  require_local 'subversion_extensions'
27
30
 
@@ -42,8 +45,14 @@ end
42
45
 
43
46
  class String
44
47
  # Makes the first character bold and underlined. Makes the whole string of the given color.
45
- def menu_item(color = :white)
46
- self[0..0].send(color).bold.underline + self[1..-1].send(color)
48
+ # :todo: Move out to extensions/console/menu_item
49
+ def menu_item(color = :white, letter = self[0..0], which_occurence = 0)
50
+ index = index_all(/#{letter}/)[which_occurence]
51
+ raise "Could not find a #{which_occurence}th occurence of '#{letter}' in string '#{self}'" if index.nil?
52
+ before = self[0..index-1].send(color) unless index == 0
53
+ middle = self[index..index].send(color).bold.underline
54
+ after = self[index+1..-1].send(color)
55
+ before.to_s + middle + after
47
56
  end
48
57
  def add_exit_code_error
49
58
  self << "Exited with error!".bold.red if !$?.success?
@@ -74,8 +83,12 @@ module Subversion
74
83
  'each_unadded',
75
84
  'externals_items', 'externals_outline', 'externals_containers', 'edit_externals', 'externalize',
76
85
  'ignore',
86
+ 'revisions',
77
87
  'get_message', 'set_message', 'edit_message',
78
- 'view_commits'
88
+ 'view_commits',
89
+ 'repository_root',
90
+ 'latest_revision',
91
+ 'delete_svn'
79
92
  ]
80
93
  mattr_reader :subcommand_list
81
94
 
@@ -87,7 +100,7 @@ module Subversion
87
100
  #-----------------------------------------------------------------------------------------------------------------------------
88
101
  # Global options
89
102
 
90
- global_option :__no_color, :__dry_run, :__debug
103
+ global_option :__no_color, :__dry_run, :__debug, :__print_commands
91
104
  def __no_color
92
105
  Subversion::color = false
93
106
  end
@@ -101,10 +114,20 @@ module Subversion
101
114
  def __print_commands
102
115
  Subversion::print_commands = true
103
116
  end
104
- # Don't want to hide svn's own -v/--verbose flag
105
- alias_method :__Verbose, :__print_commands
106
117
  alias_method :__show_commands, :__print_commands
118
+ # Don't want to hide/conflict with svn's own -v/--verbose flag, so using capital initial letter
107
119
  alias_method :_V, :__print_commands
120
+ alias_method :__Verbose, :__print_commands
121
+
122
+ # Usually most Subversion commands are recursive and all-inclusive. This option adds file *exclusion* to most of Subversion's commands.
123
+ # Use this if you want to commit (/add/etc.) everything *but* a certain file or set of files
124
+ # svn commit dir1 dir2 --except dir1/not_ready_yet.rb
125
+ def __except
126
+ # We'll have to use a FileList to do this. This option will remove all file arguments, put them into a FileList as inclusions,
127
+ # add the exclusions, and then pass the resulting list of files on to the *actual* svn command.
128
+ # :todo:
129
+ end
130
+ alias_method :__exclude, :__except
108
131
 
109
132
  #-----------------------------------------------------------------------------------------------------------------------------
110
133
  # Default/dynamic behavior
@@ -188,20 +211,44 @@ module Subversion
188
211
  [:__encoding] => 1,
189
212
  }.merge(SvnCommand::standard_remote_command_options), self
190
213
  )
214
+
215
+ # Use this flag if you don't want a commit notification to be sent out.
191
216
  def __skip_notification
192
217
  @skip_notification = true
193
218
  end
194
219
  alias_method :__covert, :__skip_notification
220
+
221
+ # Use this flag if you are about to commit some code for which you know the tests aren't or (probaby won't) pass.
222
+ # This *may* cause your continuous integration system to either skip tests for this revision or at least be a little more
223
+ # *leniant* towards you (a slap on the wrist instead of a public flogging, perhaps) when it runs the tests and finds that
224
+ # they *are* failing.
225
+ # You should probably only do this if you are planning on making multiple commits in rapid succession (sometimes Subversion
226
+ # forces you to do an intermediate commit in order to move something that's already been scheduled for a move or somethhing,
227
+ # for example). If things will be broken for a while, consider starting a branch for your changes and merging the branch
228
+ # back into trunk only when you've gotten the code stable/working again.
229
+ # (See http://svn.collab.net/repos/svn/trunk/doc/user/svn-best-practices.html)
230
+ def __broken
231
+ @broken = true
232
+ end
233
+ alias_method :__expect_to_break_tests, :__broken
234
+ alias_method :__knowingly_committing_broken_code, :__broken
195
235
  end
196
236
  def commit(*args)
197
237
  Subversion.print_commands_for do
198
- svn :capture, "propset svn:skip_commit_notification_for_next_commit true --revprop -r #{Subversion.latest_revision}", :prepare_args => false
238
+ puts svn(:capture, "propset svn:skip_commit_notification_for_next_commit true --revprop -r #{Subversion.latest_revision}", :prepare_args => false)
199
239
  end if @skip_notification
200
- svn :exec, 'commit', *(['--force-log'] + args)
201
-
202
240
  # :todo:
203
241
  # Add some logic to automatically skip the commit e-mail if the size of the files to be committed exceeds a threshold of __ MB.
204
242
  # (Performance idea: Only check the size of the files if svn st includes (bin)?)
243
+
244
+ puts output = svn(:capture, 'commit', *(['--force-log'] + args))
245
+
246
+ just_committed = (matches = output.match(/Committed revision (\d+)\./)) && matches[1]
247
+
248
+ Subversion.print_commands_for do
249
+ puts svn(:capture, "propset code:broken true --revprop -r #{just_committed}", :prepare_args => false)
250
+ end if @broken
251
+
205
252
  end
206
253
 
207
254
  # Ideas:
@@ -222,17 +269,20 @@ module Subversion
222
269
  [:__force] => 1,
223
270
  }.merge(SvnCommand::standard_remote_command_options), self
224
271
  )
272
+ def __ignore_externals
273
+ @ignore_externals = true
274
+ end
225
275
  end
226
276
 
227
277
  def diff(*directories)
228
- directories = ["./"] if directories.empty?
278
+ directories = ['./'] if directories.empty?
229
279
  puts Extensions.diff(*(directories + @passthrough_options))
230
280
 
231
- # Show diff for externals (if there are any)
232
- output = StringIO.new
233
- #paths = args.reject{|arg| arg =~ /^-/} || ['./']
234
- directories.each do |path|
235
- (Subversion.externals_items(path) || []).each do |item|
281
+ begin # Show diff for externals (if there *are* any and the user didn't tell us to ignore them)
282
+ output = StringIO.new
283
+ #paths = args.reject{|arg| arg =~ /^-/} || ['./']
284
+ directories.each do |path|
285
+ (Subversion.externals_items(path) || []).each do |item|
236
286
  diff_output = Extensions.diff(item).strip
237
287
  unless diff_output == ""
238
288
  #output.puts '-'*100
@@ -240,14 +290,15 @@ module Subversion
240
290
  output.puts item.black_on_white.bold
241
291
  output.puts diff_output
242
292
  end
293
+ end
243
294
  end
244
- end
245
- unless output.string == ""
246
- #puts '='*100
247
- puts (' '*100).yellow.underline
248
- puts " Diff of externals (**don't forget to commit these too!**):".ljust(100, ' ').yellow_on_red.bold.underline
249
- puts output.string
250
- end
295
+ unless output.string == ""
296
+ #puts '='*100
297
+ puts (' '*100).yellow.underline
298
+ puts " Diff of externals (**don't forget to commit these too!**):".ljust(100, ' ').yellow_on_red.bold.underline
299
+ puts output.string
300
+ end
301
+ end unless @ignore_externals
251
302
  end
252
303
 
253
304
  #-----------------------------------------------------------------------------------------------------------------------------
@@ -268,9 +319,11 @@ module Subversion
268
319
  # :todo: Finish...
269
320
 
270
321
  when nil
271
- puts "You are using " + 's'.green.bold + 'v'.cyan.bold + 'n'.magenta.bold + '-' + 'c'.red.bold + 'o'.cyan.bold + 'm'.blue.bold + 'm'.yellow.bold + 'a'.green.bold + 'n'.white.bold + 'd'.green.bold + ", a colorful, useful replacement/wrapper for the standard svn command."
322
+ puts "You are using " +
323
+ 's'.green.bold + 'v'.cyan.bold + 'n'.magenta.bold + '-' + 'c'.red.bold + 'o'.cyan.bold + 'm'.blue.bold + 'm'.yellow.bold + 'a'.green.bold + 'n'.white.bold + 'd'.green.bold + ' version ' + Project::Version
324
+ ", a colorful, useful replacement/wrapper for the standard svn command."
272
325
  puts "svn-command is installed at: " + $0.bold
273
- puts "Use the full path to bypass this wrapper: " + Subversion.executable.bold
326
+ puts "You may bypass this wrapper by using the full path to svn: " + Subversion.executable.bold
274
327
  puts
275
328
  puts Subversion.help(subcommand).gsub(<<End, '')
276
329
 
@@ -278,7 +331,8 @@ Subversion is a tool for version control.
278
331
  For additional information, see http://subversion.tigris.org/
279
332
  End
280
333
 
281
- puts ' ' + '--------------'
334
+ puts
335
+ puts 'Subcommands added by svn-command (refer to '.green.underline + 'http://svn-command.rubyforge.org/'.white.underline + ' for usage details):'.green.underline
282
336
  @@subcommand_list.each do |subcommand|
283
337
  aliases_list = subcommand_aliases_list(subcommand.option_methodize.to_sym)
284
338
  aliases_list = aliases_list.empty? ? '' : ' (' + aliases_list.join(', ') + ')'
@@ -395,6 +449,20 @@ End
395
449
  # Custom subcommands
396
450
  #-----------------------------------------------------------------------------------------------------------------------------
397
451
 
452
+ #-----------------------------------------------------------------------------------------------------------------------------
453
+ def repository_root(*args)
454
+ puts Subversion.repository_root(*args)
455
+ end
456
+ alias_subcommand :base_url => :repository_root
457
+ alias_subcommand :root_url => :repository_root
458
+
459
+ #-----------------------------------------------------------------------------------------------------------------------------
460
+ def latest_revision(*args)
461
+ puts Subversion.latest_revision
462
+ end
463
+ alias_subcommand :last_revision => :latest_revision
464
+ alias_subcommand :head => :latest_revision
465
+
398
466
  #-----------------------------------------------------------------------------------------------------------------------------
399
467
 
400
468
  # *Experimental*
@@ -460,8 +528,6 @@ End
460
528
  Subversion::Extensions.each_unadded( Subversion.status(*args) ) do |file|
461
529
  $ignore_dry_run_option = false
462
530
  begin
463
- response = ""
464
-
465
531
  puts( ('-'*100).green )
466
532
  puts "What do you want to do with '#{file.white.underline}'?".white.bold
467
533
  begin
@@ -495,7 +561,7 @@ End
495
561
  "or " + "any other key".white.bold + " to do nothing > "
496
562
  )
497
563
  response = ""
498
- response = $stdin.getc.chr # while !['a', 'd', 'i', "\n"].include?(begin response.downcase!; response end)
564
+ response = $stdin.getc.chr.downcase # while !['a', 'd', 'i', "\n"].include?(begin response.downcase!; response end)
499
565
 
500
566
 
501
567
  case response
@@ -512,7 +578,7 @@ End
512
578
  "Yes".menu_item(:red) + ", " +
513
579
  "No".menu_item(:green) +
514
580
  " > "
515
- response = $stdin.getc.chr while !['y', 'n', "\n"].include?(begin response.downcase!; response end)
581
+ response = $stdin.getc.chr.downcase while !['y', 'n', "\n"].include?(begin response.downcase!; response end)
516
582
  else
517
583
  response = "y"
518
584
  end
@@ -530,10 +596,10 @@ End
530
596
  puts
531
597
  else
532
598
  # Skip / Do nothing with this file
533
- puts
599
+ puts " (Skipping...)"
534
600
  end
535
601
  rescue Interrupt
536
- puts "Goodbye"
602
+ puts "\nGoodbye!"
537
603
  throw :exit
538
604
  end
539
605
  end # each_unadded
@@ -633,10 +699,10 @@ End
633
699
  begin
634
700
  #print "Press Ctrl-C to skip, any other key to continue. (This will start up your default editor.) "
635
701
  print "Do you want to edit svn:externals for this directory?".black_on_white + ' ' + 'yes'.menu_item(:white) + '/' + 'No'.menu_item(:white) + " > "
636
- response = $stdin.getc.chr
637
- system command if response.downcase == 'y'
702
+ response = $stdin.getc.chr.downcase
703
+ system command if response == 'y'
638
704
  rescue Interrupt
639
- puts "Goodbye"
705
+ puts "\nGoodbye!"
640
706
  throw :exit
641
707
  ensure
642
708
  puts
@@ -727,17 +793,62 @@ End
727
793
  svn :exec, *args
728
794
  end
729
795
 
796
+ # Lets you edit it with your default editor
797
+ module EditRevisionProperty
798
+ def _r(revision)
799
+ @revision = revision
800
+ end
801
+ end
802
+ def edit_revision_property(property_name, directory = './')
803
+ args = ['propedit', '--revprop', property_name, directory]
804
+ rev = @revision ? @revision : 'head'
805
+ args.concat ['-r', rev]
806
+ Subversion.print_commands_for do
807
+ svn :system, *args
808
+ end
809
+
810
+ value = Subversion::get_revision_property(property_name, rev)
811
+ p value
812
+
813
+ # Currently there is no seperate option to *delete* a revision property (propdel)... That would be useful for those
814
+ # properties that are just boolean *flags* (set or not set).
815
+ # I'm assuming most people will very rarely if ever actually want to set a property to the empty string (''), so
816
+ # we can use the empty string as a way to trigger a propdel...
817
+ if value == ''
818
+ puts
819
+ print "Are you sure you want to delete property #{property_name}".red.bold + "'? " +
820
+ "Yes".menu_item(:red) + ", " +
821
+ "No".menu_item(:green) +
822
+ " > "
823
+ response = ''
824
+ response = $stdin.getc.chr.downcase while !['y', 'n', "\n"].include?(begin response.downcase!; response end)
825
+ puts
826
+ if response == 'y'
827
+ Subversion.print_commands_for do
828
+ Subversion::delete_revision_property(property_name, rev)
829
+ end
830
+ end
831
+ end
832
+ end
833
+
730
834
  # Lets you edit it with your default editor
731
835
  module EditMessage
732
836
  def _r(revision)
733
837
  @revision = revision
734
838
  end
735
839
  end
736
- def edit_message()
737
- #svn propedit --revprop -r 25 svn:log
738
- args = ['propedit', '--revprop', 'svn:log']
739
- args.concat ['-r', @revision ? @revision : 'head']
740
- svn :exec, *args
840
+ def edit_message(directory = './')
841
+ edit_revision_property('svn:log', directory)
842
+ end
843
+
844
+ def edit_property(property_name, directory = './')
845
+ end
846
+
847
+ #-----------------------------------------------------------------------------------------------------------------------------
848
+ # Cause a working copy to cease being a working copy
849
+ def delete_svn
850
+ system('find -name .svn | xargs -n1 echo')
851
+ system('find -name .svn | xargs -n1 rm -r')
741
852
  end
742
853
 
743
854
  #-----------------------------------------------------------------------------------------------------------------------------
@@ -754,9 +865,191 @@ End
754
865
  def grep_log
755
866
  raise NotImplementedError
756
867
  end
757
- def browse
758
- raise NotImplementedError
868
+
869
+ #-----------------------------------------------------------------------------------------------------------------------------
870
+ module Revisions
871
+ # Start at earlier revision and go forwards rather than starting at the latest revision
872
+ #def __reverse
873
+ def __forward
874
+ @reverse = true
875
+ end
876
+ def __forwards
877
+ @reverse = true
878
+ end
879
+
880
+ # Only show revisions that are in need of a code review
881
+ # :todo:
882
+ def __unreviewed_only
883
+ @unreviewed_only
884
+ end
885
+ end
886
+ # :todo: document in readme! test! release! annouce!
887
+ def revisions(directory = './')
888
+ puts "Getting list of revisions for '#{directory.white.bold}' ..."
889
+
890
+ head = Subversion.latest_revision
891
+ revisions = Subversion.revisions(directory)
892
+
893
+ puts "#{revisions.length.to_s.bold} revisions found. Starting with #{@reverse ? 'oldest' : 'most recent'} revision and #{@reverse ? 'going forward in time' : 'going backward in time'}..."
894
+ revisions.instance_variable_get(:@revisions).reverse! if @reverse
895
+ revision_ids = revisions.map(&:identifier)
896
+
897
+ target_rev = nil # revision_ids.first
898
+ show_revision_again = true
899
+ revisions.each do |revision|
900
+ rev = revision.identifier
901
+ other_rev = rev-1
902
+ if target_rev
903
+ if rev == target_rev
904
+ target_rev = nil # We have arrived.
905
+ else
906
+ next # Keep going (hopefully in the right direction!)
907
+ end
908
+ end
909
+
910
+ # Display the revision
911
+ if show_revision_again
912
+ puts((' '*100).green.underline)
913
+ puts "#{revisions.length - revision_ids.index(rev)}. ".green.bold +
914
+ "r#{rev}".magenta.bold + (rev == head ? ' (head)'.bold : '') +
915
+ " | #{revision.developer} | #{revision.time.strftime('%Y-%m-%d %H:%M:%S')}".magenta.bold
916
+ puts revision.message
917
+ puts
918
+ #pp revision
919
+ puts revision.map {|a|
920
+ (a.status ? a.status[0..0].colorize_svn_status_code : ' ') + # This check is necessary because RSCM doesn't recognize several Subversion status flags, including 'R', and status will return nil in these cases.
921
+ ' ' + a.path
922
+ }.join("\n")
923
+ else
924
+ show_revision_again = true
925
+ end
926
+
927
+ # Display the menu
928
+ print(
929
+ 'View this changeset'.menu_item(:cyan) + ', ' +
930
+ 'Diff against specific revision'.menu_item(:cyan, 'D') + ', ' +
931
+ 'Grep the changeset'.menu_item(:cyan, 'G') + ', ' +
932
+ 'List or '.menu_item(:magenta, 'L') + '' +
933
+ 'Edit revision properties'.menu_item(:magenta, 'E') + ', ' +
934
+ 'svn Cat all files from revision'.menu_item(:cyan, 'C') + ', ' +
935
+ 'grep the cat'.menu_item(:cyan, 'a') + ', ' + "\n " +
936
+ 'mark as Reviewed'.menu_item(:green, 'R') + ', ' +
937
+ 'edit log Message'.menu_item(:yellow, 'M') + ', ' +
938
+ 'or ' + 'browse using ' + 'Up/Down/Enter'.white.bold + ' keys > '
939
+ )
940
+
941
+ # Get response from user and then act on it
942
+ begin # rescue
943
+ response = ""
944
+ response = $stdin.getc.chr.downcase
945
+
946
+ # Escape sequence such as the up arrow key ("\e[A")
947
+ if response == "\e"
948
+ response << (next_char = $stdin.getc.chr)
949
+ if next_char == '['
950
+ response << (next_char = $stdin.getc.chr)
951
+ end
952
+ end
953
+
954
+ if response == 'd' # diff against Other revision
955
+ response = 'v'
956
+ puts
957
+ print 'All right, which revision shall it be then? '.bold + ' (backspace not currently supported)? '
958
+ other_rev = $stdin.gets.chomp.to_i
959
+ end
960
+
961
+ case response
962
+ when 'v' # Diff
963
+ revs_to_compare = [other_rev, rev]
964
+ puts "\n"*10
965
+ puts((' '*100).green.underline)
966
+ print "Diffing #{revs_to_compare.min}:#{revs_to_compare.max}... ".bold
967
+ puts
968
+ #Subversion.repository_root
969
+ SvnCommand.execute("diff #{directory} --ignore-externals -r #{revs_to_compare.min}:#{revs_to_compare.max}")
970
+
971
+ when 'g' # Grep the changeset
972
+ revs_to_compare = [other_rev, rev]
973
+ puts
974
+ puts((' '*100).green.underline)
975
+ print "Diffing #{revs_to_compare.min}:#{revs_to_compare.max}... ".bold
976
+ puts
977
+ puts Extensions.diff(directory, '-r', "#{revs_to_compare.min}:#{revs_to_compare.max}")
978
+
979
+ when 'l' # List revision properties
980
+ puts
981
+ puts Subversion::Extensions::printable_revision_properties(rev)
982
+ show_revision_again = false
983
+
984
+ when 'e' # Edit revision property
985
+ puts
986
+ puts Subversion::Extensions::printable_revision_properties(rev)
987
+ puts "Warning: These properties are *not* under version control! Try not to permanently destroy anything *too* important...".red.bold
988
+ puts "Note: If you want to *delete* a property, simply set its value to '' and it will be deleted (propdel) for you."
989
+ print 'Which property would you like to edit'.bold + ' (backspace not currently supported)? '
990
+ property_name = $stdin.gets.chomp
991
+ unless property_name == ''
992
+ Subversion.print_commands_for do
993
+ @revision = rev
994
+ edit_revision_property(property_name, directory)
995
+ end
996
+ end
997
+
998
+ show_revision_again = false
999
+
1000
+ when 'r' # Mark as reviewed
1001
+ puts
1002
+ your_name = ENV['USER'] # I would use the same username that Subversion itself would use if you committed
1003
+ # something (since it is sometimes different from your system username), but I don't know
1004
+ # how to retrieve that (except by poking around in your ~/.subversion/ directory, but
1005
+ # that seems kind of rude...).
1006
+ puts "Marking as reviewed by '#{your_name}'..."
1007
+ Subversion.print_commands_for do
1008
+ puts svn(:capture, "propset code:reviewed '#{your_name}' --revprop -r #{rev}", :prepare_args => false)
1009
+ # :todo: Maybe *append* to code:reviewed (,-delimited) rather than overwriting it?, in case there is a policy of requiring 2 reviewers or something
1010
+ end
1011
+
1012
+ when 'm' # Edit log message
1013
+ puts
1014
+ Subversion.print_commands_for do
1015
+ SvnCommand.execute("edit_message -r #{rev}")
1016
+ end
1017
+
1018
+ when "\e[A" # Up
1019
+ i = revision_ids.index(rev)
1020
+ target_rev = revision_ids[i - 1]
1021
+ puts " Previous..."
1022
+ retry
1023
+
1024
+
1025
+ when /\n|\e\[B/ # Enter or Down
1026
+ # Skip / Do nothing with this file
1027
+ puts " Next..."
1028
+ next
1029
+
1030
+ else
1031
+ # Invalid option. Do nothing.
1032
+ #puts response.inspect
1033
+ puts
1034
+ show_revision_again = false
1035
+
1036
+ end # case response
1037
+
1038
+ redo # Until they tell us they're ready to move on...
1039
+
1040
+ rescue Interrupt
1041
+ puts "\nGoodbye!"
1042
+ return
1043
+ end # rescue
1044
+ end
759
1045
  end
1046
+ alias_subcommand :changesets => :revisions
1047
+ alias_subcommand :browse => :revisions
1048
+ alias_subcommand :browse_log => :revisions
1049
+ alias_subcommand :browse_revisions => :revisions
1050
+ alias_subcommand :browse_changesets => :revisions
1051
+ # See also the implementation of revisions() in /usr/lib/ruby/gems/1.8/gems/rscm-0.5.1/lib/rscm/scm/subversion.rb
1052
+ # Other name ideas: browse, list_commits, changeset_browser, log_browser, interactive_log
760
1053
 
761
1054
  #-----------------------------------------------------------------------------------------------------------------------------
762
1055
  # Aliases
@@ -794,7 +1087,8 @@ End
794
1087
  end
795
1088
 
796
1089
  def prepare_args(args)
797
- args.compact! # nil elements spell trouble
1090
+ args.compact! # nil elements spell trouble
1091
+ args.map!(&:to_s) # shell_escape doesn't like Fixnums either
798
1092
  @passthrough_options + args.shell_escape
799
1093
  end
800
1094
  # To allow testing/stubbing
@@ -96,4 +96,48 @@ class SubversionTest < Test::Unit::TestCase
96
96
  assert_equal "svn status foo", Subversion.executed.first
97
97
  end
98
98
 
99
+ def test_revisions
100
+ Subversion.stubs(:log).returns(<<End)
101
+ ------------------------------------------------------------------------
102
+ r407119 | moo | 2006-05-16 20:27:28 -0500 (Tue, 16 May 2006) | 5 lines
103
+ Changed paths:
104
+ M /trunk/cow/black.rb
105
+ M /trunk/cow/brown.rb
106
+
107
+ Commit message...
108
+ ------------------------------------------------------------------------
109
+ End
110
+ assert_equal 1, Subversion.revisions.length
111
+ assert_equal RSCM::Revision, Subversion.revisions[0].class
112
+ assert_equal RSCM::RevisionFile, Subversion.revisions[0][0].class
113
+ assert_equal 'modified', Subversion.revisions[0][0].status.downcase
114
+ assert_equal 'trunk/cow/black.rb', Subversion.revisions[0][0].path
115
+ assert_equal 'trunk/cow/brown.rb', Subversion.revisions[0][1].path
116
+ assert_equal 'Commit message...', Subversion.revisions[0].message
117
+ end
118
+
119
+ def test_revision_properties_names
120
+ Subversion.stubs(:proplist).returns(<<End)
121
+ Unversioned properties on revision 2819:
122
+ svn:log
123
+ svn:author
124
+ svn:date
125
+ End
126
+ assert_equal ['svn:log', 'svn:author', 'svn:date'], Subversion.revision_properties_names(rev = 14)
127
+ end
128
+ def test_revision_properties
129
+ Subversion.stubs(:proplist).returns(<<End)
130
+ Unversioned properties on revision 2819:
131
+ svn:log
132
+ svn:author
133
+ svn:date
134
+ End
135
+ Subversion.stubs(:get_revision_property).returns('a value')
136
+
137
+ assert_equal [
138
+ Subversion::RevisionProperty.new('svn:log', 'a value'),
139
+ Subversion::RevisionProperty.new('svn:author', 'a value'),
140
+ Subversion::RevisionProperty.new('svn:date', 'a value'),
141
+ ], Subversion.revision_properties(rev = 14)
142
+ end
99
143
  end
@@ -4,12 +4,62 @@ require_local '../lib/svn-command/svn_command.rb'
4
4
  require 'facets/core/string/to_re'
5
5
  require 'yaml'
6
6
 
7
+
8
+ require 'facets/core/module/alias_method_chain'
9
+ require 'qualitysmith_extensions/string/each_char_with_index'
10
+ module Test
11
+ module Unit
12
+ module Assertions
13
+ # Rather than showing the expected and the actual and asking the user to figure out the commonalities and differences
14
+ # himself, this method will highlight the differences for the user (in color), so that they can be spotted in less than
15
+ # an instant!
16
+ # Strings: show common characters in a plain color and highlight the characters that differ (at that index) in yellow.
17
+ # Strings: show common elements in a plain color and highlight the elements that differ (at that index) in yellow.
18
+ def assert_equal_with_difference_highlighting(expected, actual, message=nil)
19
+ String.class_eval do
20
+ alias_method :colorize, :colorize_without_no_color
21
+ end
22
+ if String===expected && String===actual
23
+ expected_with_highlighting = ''
24
+ actual_with_highlighting = ''
25
+ expected.each_char_with_index do |i, c|
26
+ if c != actual[i].chr
27
+ expected_with_highlighting << c.yellow
28
+ actual_with_highlighting << actual[i].chr.yellow
29
+ else
30
+ expected_with_highlighting << c
31
+ actual_with_highlighting << c
32
+ end
33
+
34
+ end
35
+ full_message = build_message(message, <<End, expected_with_highlighting, actual_with_highlighting)
36
+ <?>
37
+ expected but was
38
+ <?>.
39
+ End
40
+ puts actual_with_highlighting if expected != actual
41
+ assert_block(full_message) { expected == actual }
42
+ else
43
+ assert_equal_without_difference_highlighting(expected, actual, message)
44
+ end
45
+ String.class_eval do
46
+ alias_method :colorize, :colorize_with_no_color
47
+ end
48
+ end # def assert_equal_with_difference_highlighting
49
+
50
+ alias_method_chain :assert_equal, :difference_highlighting
51
+ end
52
+ end
53
+ end
54
+
55
+
7
56
  Subversion.color = false
8
57
  # Makes testing simpler. We can test all the *colorization* features via *manual* testing (since they're not as critical).
9
58
  class String
10
- def colorize(string, options = {})
59
+ def colorize_with_no_color(string, options = {})
11
60
  string
12
61
  end
62
+ alias_method_chain :colorize, :no_color
13
63
  end
14
64
 
15
65
  module Subversion
@@ -443,10 +493,28 @@ end
443
493
  class SvnEditMessageTest < BaseSvnCommandTest
444
494
  def test_1
445
495
  Subversion.stubs(:status_against_server).returns("Status against revision: 56")
496
+ Subversion.stubs(:get_revision_property).returns("The value I just set it to using vim, my favorite editor")
446
497
  output = simulate_input('i') do
447
498
  capture_output { SvnCommand.execute('edit_message') }
448
499
  end
449
- assert_equal ["svn propedit --revprop svn:log -r head"], Subversion.executed
500
+ assert_equal ["svn propedit --revprop svn:log ./ -r head"], Subversion.executed
501
+ end
502
+ end
503
+
504
+ class SvnEditMessageTest < BaseSvnCommandTest
505
+ def test_can_actually_delete_property_too
506
+ Subversion.stubs(:status_against_server).returns("Status against revision: 56")
507
+ Subversion.stubs(:get_revision_property).returns("")
508
+ output = simulate_input(
509
+ 'y' # Yes I'm sure I want to delete the svn:fooo property for this revision.
510
+ ) do
511
+ capture_output { SvnCommand.execute('edit_revision_property svn:foo') }
512
+ end
513
+ assert_match /Are you sure you want to delete/, output
514
+ assert_equal [
515
+ "svn propedit --revprop svn:foo ./ -r head",
516
+ "svn propdel --revprop svn:foo -r head"
517
+ ], Subversion.executed
450
518
  end
451
519
  end
452
520
 
@@ -493,6 +561,104 @@ class SvnViewCommitsTest < BaseSvnCommandTest
493
561
  end
494
562
  end
495
563
 
564
+ #-----------------------------------------------------------------------------------------------------------------------------
565
+ # Changeset/commit/log Browser
566
+
567
+ class SvnRevisionsTest < BaseSvnCommandTest
568
+ def test_1
569
+ Subversion.stubs(:revisions).returns(
570
+ begin
571
+ RSCM::Revisions.class_eval do
572
+ attr_accessor :revisions
573
+ end
574
+ RSCM::Revision.class_eval do
575
+ attr_accessor :files
576
+ end
577
+
578
+ file1 = RSCM::RevisionFile.new
579
+ file1.status = 'added'.upcase
580
+ file1.path = 'dir/file1'
581
+ file2 = RSCM::RevisionFile.new
582
+ file2.status = 'modified'.upcase
583
+ file2.path = 'dir/file2'
584
+
585
+ revision1 = RSCM::Revision.new
586
+ revision1.identifier = 1800
587
+ revision1.developer = 'tyler'
588
+ revision1.time = Time.utc(2007, 12, 01)
589
+ revision1.message = 'I say! Quite the storm, what!'
590
+ revision1.files = [file1, file2]
591
+
592
+ revision2 = RSCM::Revision.new
593
+ revision2.identifier = 1801
594
+ revision2.developer = 'tyler'
595
+ revision2.time = Time.utc(2007, 12, 02)
596
+ revision2.message = 'These Romans are crazy!'
597
+ revision2.files = [file2]
598
+
599
+ revisions = RSCM::Revisions.new
600
+ revisions.revisions = [revision1, revision2]
601
+ end
602
+ )
603
+ Subversion.stubs(:diff).returns("the diff")
604
+
605
+ output = simulate_input(
606
+ 'v' + # View this changeset
607
+ "\n" + # Continue to revision 1800
608
+ "\n" # Try to continue, but of course there won't be any more revisions, so it will exit.
609
+ ) do
610
+ capture_output { SvnCommand.execute('revisions') }
611
+ end
612
+ puts output
613
+ require 'unroller'
614
+ #Unroller::trace :exclude_classes => /PP|PrettyPrint/ do
615
+
616
+ assert_equal <<-End, output
617
+ Getting list of revisions for './' ...
618
+ 2 revisions found. Starting with most recent revision and going backward in time...
619
+
620
+ 2. r1800 | tyler | 2007-12-01 00:00:00
621
+ I say! Quite the storm, what!
622
+
623
+ A dir/file1
624
+ M dir/file2
625
+ View this changeset, Diff against specific revision, Grep the changeset, List or Edit revision properties, svn Cat all files from revision, grep the cat,
626
+ mark as Reviewed, edit log Message, or browse using Up/Down/Enter keys >
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+ Diffing 1799:1800...
638
+ the diff
639
+
640
+ 2. r1800 | tyler | 2007-12-01 00:00:00
641
+ I say! Quite the storm, what!
642
+
643
+ A dir/file1
644
+ M dir/file2
645
+ View this changeset, Diff against specific revision, Grep the changeset, List or Edit revision properties, svn Cat all files from revision, grep the cat,
646
+ mark as Reviewed, edit log Message, or browse using Up/Down/Enter keys > Next...
647
+
648
+ 1. r1801 | tyler | 2007-12-02 00:00:00
649
+ These Romans are crazy!
650
+
651
+ M dir/file2
652
+ View this changeset, Diff against specific revision, Grep the changeset, List or Edit revision properties, svn Cat all files from revision, grep the cat,
653
+ mark as Reviewed, edit log Message, or browse using Up/Down/Enter keys > Next...
654
+ End
655
+ #end
656
+ assert_equal [
657
+ "svn status -u ./" # To find head
658
+ ], Subversion.executed
659
+ end
660
+ end
661
+
496
662
 
497
663
  #-----------------------------------------------------------------------------------------------------------------------------
498
664
  end #module Subversion
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: svn-command
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2007-04-05 00:00:00 -07:00
6
+ version: 0.2.1
7
+ date: 2007-04-26 00:00:00 -07:00
8
8
  summary: A nifty wrapper command for Subversion's command-line svn client
9
9
  require_paths:
10
10
  - lib
@@ -25,7 +25,11 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
- post_install_message:
28
+ post_install_message: |
29
+ ---------------------------------------------------------------------------------------------------
30
+ Please run _svn_command_post_install to finalize the installation.
31
+ ---------------------------------------------------------------------------------------------------
32
+
29
33
  authors:
30
34
  - Tyler Rick
31
35
  files: