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 +43 -18
- data/lib/svn-command/subversion.rb +93 -20
- data/lib/svn-command/subversion_extensions.rb +38 -15
- data/lib/svn-command/svn_command.rb +336 -42
- data/test/subversion_test.rb +44 -0
- data/test/svn_command_test.rb +168 -2
- metadata +7 -3
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
|
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>
|
48
|
-
*
|
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
|
-
|
51
|
-
|
52
|
-
* <tt>svn move</tt>
|
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
|
-
|
73
|
+
= Usage / Examples
|
68
74
|
|
69
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
203
|
+
(The _standard_ +svn+ command only accepts a _single_ source and a _single_ destination!)
|
198
204
|
|
199
|
-
|
205
|
+
== <tt>svn revisions</tt> (revisions browser)
|
200
206
|
|
201
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
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:
|
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(/^
|
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/) { $&.
|
29
|
-
gsub(/^ *A\s/) { $&.
|
30
|
-
gsub(/^ *M\s/) { $&.
|
31
|
-
gsub(/^ *D\s/) { $&.
|
32
|
-
gsub(/^ *C\s/) { $&.
|
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
|
129
|
+
# This is just a wrapper for Subversion::diff that adds some color
|
114
130
|
def self.diff(*args)
|
115
|
-
|
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
|
-
|
46
|
-
|
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
|
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 = [
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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 " +
|
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 "
|
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 "
|
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
|
702
|
+
response = $stdin.getc.chr.downcase
|
703
|
+
system command if response == 'y'
|
638
704
|
rescue Interrupt
|
639
|
-
puts "
|
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
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
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
|
-
|
758
|
-
|
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!
|
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
|
data/test/subversion_test.rb
CHANGED
@@ -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
|
data/test/svn_command_test.rb
CHANGED
@@ -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
|
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.
|
7
|
-
date: 2007-04-
|
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:
|