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 +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:
|