svn-command 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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: