svn-command 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme +251 -0
- data/bin/command_completion_for_svn_command +20 -0
- data/bin/rscm_test +19 -0
- data/bin/svn +7 -0
- data/lib/attribute_accessors.rb +44 -0
- data/lib/my_wrapper.rb +72 -0
- data/lib/subversion.rb +335 -0
- data/lib/subversion_extensions.rb +60 -0
- data/lib/svn_command.rb +627 -0
- data/test/shared/test_helper.rb +3 -0
- data/test/shared/test_helpers/assertions.rb +56 -0
- data/test/shared/test_helpers/test_colorizer.rb +106 -0
- data/test/subversion_extensions_test.rb +66 -0
- data/test/subversion_test.rb +99 -0
- data/test/svn_command_test.rb +455 -0
- data/test/test_helper.rb +31 -0
- metadata +75 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# Tested by: ../test/subversion_extensions_test.rb
|
2
|
+
|
3
|
+
class Array
|
4
|
+
def to_regexp_char_class
|
5
|
+
"[#{join('')}]"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# These are methods used by the SvnCommand for filtering and whatever else it needs...
|
10
|
+
# It could probably be moved into SvnCommand, but I thought it might be good to at least make it *possible* to use them apart from SvnCommand.
|
11
|
+
# Rename to Subversion::Filters ? Then would each_unadded fit?
|
12
|
+
module Subversion
|
13
|
+
module Extensions
|
14
|
+
Interesting_status_flags = ["M", "A", "D", "?"]
|
15
|
+
Uninteresting_status_flags = ["X", "W"]
|
16
|
+
Status_flags = Interesting_status_flags | Uninteresting_status_flags
|
17
|
+
|
18
|
+
def self.status_lines_filter(input)
|
19
|
+
(input || "").reject { |line|
|
20
|
+
line =~ /^$/ # Blank lines
|
21
|
+
}.reject { |line|
|
22
|
+
line =~ /^Performing status on external item at/
|
23
|
+
}.reject { |line|
|
24
|
+
line =~ /^#{Uninteresting_status_flags.to_regexp_char_class}/
|
25
|
+
}.join
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.update_lines_filter(input)
|
29
|
+
input.reject { |line|
|
30
|
+
line =~ /^$/ # Blank lines
|
31
|
+
}.reject { |line|
|
32
|
+
line =~ /^Fetching external item into/
|
33
|
+
# Eventually we may want it to include this whole block, but only iff there is something updated for this external.
|
34
|
+
}.reject { |line|
|
35
|
+
line =~ /^External at revision/
|
36
|
+
}.join
|
37
|
+
# Also get rid of all but one "At revision _."?
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.unadded_lines_filter(input)
|
41
|
+
input.select { |line|
|
42
|
+
line =~ /^\?/
|
43
|
+
}.join
|
44
|
+
end
|
45
|
+
def self.unadded_filter(input)
|
46
|
+
unadded_lines_filter(input).map { |line|
|
47
|
+
# Just keep the filename part
|
48
|
+
line =~ /^\?\s+(.+)/
|
49
|
+
$1
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.each_unadded(input)
|
54
|
+
unadded_filter(input).each { |line|
|
55
|
+
yield line
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/svn_command.rb
ADDED
@@ -0,0 +1,627 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require_gem 'facets', '>=1.8.51'
|
4
|
+
require 'facets/more/command'
|
5
|
+
require 'facets/core/string/margin'
|
6
|
+
require 'facets/core/kernel/require_local'
|
7
|
+
require 'facets/core/array/select' # select!
|
8
|
+
|
9
|
+
require_gem 'our_extensions', '>=0.0.2'
|
10
|
+
require 'enumerable/enum'
|
11
|
+
require 'array/expand_ranges'
|
12
|
+
require 'array/shell_escape'
|
13
|
+
|
14
|
+
require 'extensions/symbol' # to_proc
|
15
|
+
require 'pp'
|
16
|
+
require 'termios'
|
17
|
+
require 'stringio'
|
18
|
+
require_local '../lib/subversion'
|
19
|
+
require_local '../lib/subversion_extensions'
|
20
|
+
|
21
|
+
begin
|
22
|
+
# Set up termios so that it returns immediately when you press a key.
|
23
|
+
# (http://blog.rezra.com/articles/2005/12/05/single-character-input)
|
24
|
+
t = Termios.tcgetattr(STDIN)
|
25
|
+
t.lflag &= ~Termios::ICANON
|
26
|
+
Termios.tcsetattr(STDIN,0,t)
|
27
|
+
rescue RuntimeError => exception # Necessary for automated testing.
|
28
|
+
if exception.message =~ /can't get terminal parameters/
|
29
|
+
puts 'Warning: Terminal not found.'
|
30
|
+
$interactive = false
|
31
|
+
else
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Subversion.extend(Subversion::Extensions)
|
37
|
+
Subversion::color = true
|
38
|
+
|
39
|
+
module Subversion
|
40
|
+
# Tested by: ../test/svn_command_test.rb
|
41
|
+
class SvnCommand < Console::Command
|
42
|
+
@@standard_remote_command_options = {
|
43
|
+
[:__username] => 1,
|
44
|
+
[:__password] => 1,
|
45
|
+
[:__no_auth_cache] => 0,
|
46
|
+
[:__non_interactive] => 0,
|
47
|
+
[:__config_dir] => 1,
|
48
|
+
}
|
49
|
+
mattr_reader :standard_remote_command_options
|
50
|
+
|
51
|
+
# This shouldn't be necessary. Console::Command should allow introspection. But until such time...
|
52
|
+
@@subcommand_list = [
|
53
|
+
'each_unadded',
|
54
|
+
'externals', 'externals_containers', 'edit_externals', 'externalize',
|
55
|
+
'ignore',
|
56
|
+
'get_message', 'set_message', 'edit_message',
|
57
|
+
'view_commits'
|
58
|
+
]
|
59
|
+
mattr_reader :subcommand_list
|
60
|
+
|
61
|
+
def initialize(*args)
|
62
|
+
@passthrough_options = []
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
67
|
+
# Global options
|
68
|
+
|
69
|
+
global_option :__no_color, :__dry_run, :__debug
|
70
|
+
def __no_color
|
71
|
+
Subversion::color = false
|
72
|
+
end
|
73
|
+
def __debug
|
74
|
+
$debug = true
|
75
|
+
end
|
76
|
+
def __dry_run
|
77
|
+
Subversion::dry_run = true
|
78
|
+
end
|
79
|
+
def __print
|
80
|
+
Subversion::print_commands = true
|
81
|
+
end
|
82
|
+
|
83
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
84
|
+
# Default/dynamic behavior
|
85
|
+
|
86
|
+
# Any subcommands that we haven't implemented here will simply be passed on to the built-in svn command.
|
87
|
+
# :todo: Distinguish between subcommand_missing and method_missing !
|
88
|
+
# Currently, for example, if as isn't defined, this: puts Subversion.externalize(repo_path, {:as => as })
|
89
|
+
# will call method_missing and try to run `svn as`, which of course will fail (without a sensible relevant error)...
|
90
|
+
# I think we should probably just have a separate subcommand_missing, like we already have a separate option_missing !!!
|
91
|
+
# Even a simple type (sss instead of ss) causes trouble... *this* was causing a call to "/usr/bin/svn new_messsage" -- what huh??
|
92
|
+
# def set_message(new_message = nil)
|
93
|
+
# args << new_messsage if new_message
|
94
|
+
def method_missing(subcommand, *args)
|
95
|
+
#puts "method_missing(#{subcommand}, #{args.inspect})"
|
96
|
+
svn :exec, subcommand, *args
|
97
|
+
end
|
98
|
+
|
99
|
+
def option_missing(option_name, args)
|
100
|
+
#puts "#{@subcommand} defined? #{@subcommand_is_defined}"
|
101
|
+
if !@subcommand_is_defined
|
102
|
+
# It's okay to use this for pass-through subcommands, because we just pass all options/arguments verbatim anyway...
|
103
|
+
#puts "option_missing(#{option_name}, #{args.inspect})"
|
104
|
+
else
|
105
|
+
# But for subcommands that are defined here, we should know better! All valid options should be explicitly listed!
|
106
|
+
raise UnknownOptionError.new(option_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
# The following is necessary because we really don't know the arity (how many subsequent tokens it should eat) of the option -- we don't know anything about the options, in fact; that's why we've landed in option_missing.
|
110
|
+
# This is kind of a hokey solution, but for any unrecognized options/args (which will be *all* of them unless we list the available options in the subcommand module), we just eat all of the args, store them in @passthrough_options, and later we will add them back on.
|
111
|
+
# What's annoying about it this solution is that *everything* after the first unrecognized option comes in as args, even if they are args for the subcommand and not for the *option*!
|
112
|
+
# But...it seems to work to just pretend they're options.
|
113
|
+
# It seems like this is mostly a problem for *wrappers* that try to use Console::Command. Sometimes you just want to *pass through all args and options* unchanged and just filter the output somehow.
|
114
|
+
# Command doesn't make that super-easy though. If an option (--whatever) isn't defined, then the only way to catch it is in option_missing. And since we can't the arity unless we enumerate all options, we have to hokily treat the first option as having unlimited arity.
|
115
|
+
# Alternatives considered:
|
116
|
+
# * Assume arity of 0. Then I'm afraid it would extract out all the option flags and leave the args that were meant for the args dangling there out of order ("-r 1 -m 'hi'" => "-r -m", "1 'hi'")
|
117
|
+
# * Assume arity of 1. Then if it was really 0, it would pick up an extra arg that really wasn't supposed to be an arg for the *option*.
|
118
|
+
# Ideally, we wouldn't be using option_missing at all because all options would be listed in the respective subcommand module...but for subcommands handled through method_missing, we don't have that option.
|
119
|
+
|
120
|
+
@passthrough_options << "#{option_name}" << args
|
121
|
+
@passthrough_options.flatten!
|
122
|
+
|
123
|
+
return arity = args.size # All of 'em
|
124
|
+
end
|
125
|
+
|
126
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
127
|
+
# Built-in commands (in alphabetical order)
|
128
|
+
|
129
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
130
|
+
module Add
|
131
|
+
Console::Command.pass_through({
|
132
|
+
[:__targets] => 1,
|
133
|
+
[:_N, :__non_recursive] => 0,
|
134
|
+
[:_q, :__quiet] => 0,
|
135
|
+
[:__config_dir] => 1,
|
136
|
+
[:__force] => 0,
|
137
|
+
[:__no_ignore] => 0,
|
138
|
+
[:__auto_props] => 0,
|
139
|
+
[:__no_auto_props] => 0,
|
140
|
+
}, self)
|
141
|
+
end
|
142
|
+
def add(*args)
|
143
|
+
#puts "add #{args.inspect}"
|
144
|
+
svn :exec, 'add', *args
|
145
|
+
end
|
146
|
+
|
147
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
148
|
+
module Commit
|
149
|
+
Console::Command.pass_through({
|
150
|
+
[:_q, :__quiet] => 0,
|
151
|
+
[:_N, :__non_recursive] => 1,
|
152
|
+
[:__targets] => 1,
|
153
|
+
[:__no_unlock] => 0,
|
154
|
+
[:_m, :__message] => 1,
|
155
|
+
[:_F, :__file] => 1,
|
156
|
+
[:__force_log] => 0,
|
157
|
+
[:__editor_cmd] => 1,
|
158
|
+
[:__encoding] => 1,
|
159
|
+
}.merge(SvnCommand::standard_remote_command_options), self
|
160
|
+
)
|
161
|
+
end
|
162
|
+
def commit(*args)
|
163
|
+
svn :exec, 'commit', *(['--force-log'] + args)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Ideas:
|
167
|
+
# * look for .svn-commit files within current tree and if one is found, show what's in it and ask
|
168
|
+
# "Found a commit message from a previous failed commit. {preview} Do you want to (u)se this message for the current commit, or (d)elete it?"
|
169
|
+
|
170
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
171
|
+
module Diff
|
172
|
+
Console::Command.pass_through({
|
173
|
+
[:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
|
174
|
+
[:__old] => 1,
|
175
|
+
[:__new] => 0,
|
176
|
+
[:_N, :__non_recursive] => 1,
|
177
|
+
[:__diff_cmd] => 1,
|
178
|
+
[:_x, :__extensions] => 1, # :todo: should support any number of args??
|
179
|
+
[:__no_diff_deleted] => 0,
|
180
|
+
[:__notice_ancestry] => 1,
|
181
|
+
[:__force] => 1,
|
182
|
+
}.merge(SvnCommand::standard_remote_command_options), self
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
#def diff(*args)
|
187
|
+
def diff(directory = "./")
|
188
|
+
print Subversion.diff(*([directory] + @passthrough_options))
|
189
|
+
|
190
|
+
# Show diff for externals (if there are any)
|
191
|
+
output = StringIO.new
|
192
|
+
#paths = args.reject{|arg| arg =~ /^-/} || ['./']
|
193
|
+
[directory].each do |path|
|
194
|
+
(Subversion.externals_containers(path) || []).each do |external|
|
195
|
+
#puts external.to_s
|
196
|
+
external.entries.each do |entry|
|
197
|
+
dir = entry[/^[^ ]+/]
|
198
|
+
#puts "#{external.container_dir} + #{dir}"
|
199
|
+
path = File.join(external.container_dir, dir)
|
200
|
+
diff_output = Subversion.diff(path).strip
|
201
|
+
unless diff_output == ""
|
202
|
+
output.puts '-'*100
|
203
|
+
output.puts path
|
204
|
+
output.puts diff_output
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
unless output.string == ""
|
210
|
+
puts '='*100
|
211
|
+
puts "Diff of externals (**don't forget to commit these too!**):"
|
212
|
+
puts output.string
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
217
|
+
module Help
|
218
|
+
Console::Command.pass_through({
|
219
|
+
[:__version] => 0,
|
220
|
+
[:_q, :__quiet] => 0,
|
221
|
+
[:__config_dir] => 1,
|
222
|
+
}, self)
|
223
|
+
end
|
224
|
+
def help(subcommand = nil)
|
225
|
+
case subcommand
|
226
|
+
when "externals"
|
227
|
+
puts %Q{
|
228
|
+
| externals (ext): Lists all externals in the given working directory.
|
229
|
+
| usage: externals [PATH]
|
230
|
+
}.margin
|
231
|
+
# :todo: Finish...
|
232
|
+
|
233
|
+
when nil
|
234
|
+
puts "You are using svn-command, a replacement/wrapper for the standard svn command."
|
235
|
+
puts "svn-command is installed at: #{$0}"
|
236
|
+
puts "Wrapping svn executable at: #{Subversion.executable} (use the full path to bypass this wrapper)"
|
237
|
+
puts
|
238
|
+
puts Subversion.help(subcommand).gsub(<<End, '')
|
239
|
+
|
240
|
+
Subversion is a tool for version control.
|
241
|
+
For additional information, see http://subversion.tigris.org/
|
242
|
+
End
|
243
|
+
|
244
|
+
puts ' ' + '--------------'
|
245
|
+
@@subcommand_list.each do |subcommand|
|
246
|
+
aliases_list = subcommand_aliases_list(subcommand.option_methodize.to_sym)
|
247
|
+
aliases_list = aliases_list.empty? ? '' : ' (' + aliases_list.join(', ') + ')'
|
248
|
+
puts ' ' + subcommand + aliases_list
|
249
|
+
end
|
250
|
+
#p subcommand_aliases_list(:edit_externals)
|
251
|
+
|
252
|
+
else
|
253
|
+
#puts "help #{subcommand}"
|
254
|
+
puts Subversion.help(subcommand)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
259
|
+
module Log
|
260
|
+
Console::Command.pass_through({
|
261
|
+
[:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
|
262
|
+
[:_q, :__quiet] => 0,
|
263
|
+
[:_v, :__verbose] => 0,
|
264
|
+
[:__targets] => 1,
|
265
|
+
[:__stop_on_copy] => 0,
|
266
|
+
[:__incremental] => 0,
|
267
|
+
[:__xml] => 0,
|
268
|
+
[:__limit] => 1,
|
269
|
+
}.merge(SvnCommand::standard_remote_command_options), self
|
270
|
+
)
|
271
|
+
end
|
272
|
+
def log(*args)
|
273
|
+
puts Subversion.log( @passthrough_options + args )
|
274
|
+
#svn :exec, *args
|
275
|
+
end
|
276
|
+
|
277
|
+
# Ideas:
|
278
|
+
# Just pass a number (5) and it will be treated as --limit 5 (unless File.exists?('5'))
|
279
|
+
|
280
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
281
|
+
module Status
|
282
|
+
Console::Command.pass_through({
|
283
|
+
[:_u, :__show_updates] => 0,
|
284
|
+
[:_v, :__verbose] => 0,
|
285
|
+
[:_N, :__non_recursive] => 0,
|
286
|
+
[:_q, :__quiet] => 0,
|
287
|
+
[:__no_ignore] => 0,
|
288
|
+
[:__incremental] => 0,
|
289
|
+
[:__xml] => 0,
|
290
|
+
[:__ignore_externals] => 0,
|
291
|
+
}.merge(SvnCommand::standard_remote_command_options), self
|
292
|
+
)
|
293
|
+
end
|
294
|
+
def status(*args)
|
295
|
+
#puts "in status(#{args.inspect})"
|
296
|
+
#puts "#{self}.@passthrough_options == #{@passthrough_options.inspect}"
|
297
|
+
print Subversion::Extensions.status_lines_filter( Subversion.status(*(@passthrough_options + args)) )
|
298
|
+
end
|
299
|
+
|
300
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
301
|
+
module Update
|
302
|
+
Console::Command.pass_through({
|
303
|
+
[:_r, :__revision] => 1, # :todo: support "{" DATE "}" format
|
304
|
+
[:_N, :__non_recursive] => 0,
|
305
|
+
[:_q, :__quiet] => 0,
|
306
|
+
[:__diff3_cmd] => 1,
|
307
|
+
[:__ignore_externals] => 0,
|
308
|
+
}.merge(SvnCommand::standard_remote_command_options), self
|
309
|
+
)
|
310
|
+
end
|
311
|
+
def update(*args)
|
312
|
+
print Subversion::Extensions.update_lines_filter( Subversion.update(*prepare_args(args)) )
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
|
317
|
+
|
318
|
+
|
319
|
+
|
320
|
+
|
321
|
+
|
322
|
+
|
323
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
324
|
+
# Custom subcommands
|
325
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
326
|
+
|
327
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
328
|
+
|
329
|
+
# *Experimental*
|
330
|
+
#
|
331
|
+
# Combine commit messages / diffs for the given range for the given files. Gives one aggregate diff for the range instead of many individual diffs.
|
332
|
+
#
|
333
|
+
# Could be useful for code reviews?
|
334
|
+
#
|
335
|
+
# Pass in a list of revisions/revision ranges ("134", "134:136", "134-136", and "134-136 139" are all valid)
|
336
|
+
#
|
337
|
+
module ViewCommits
|
338
|
+
def _r(*revisions)
|
339
|
+
# This is necessary so that the -r option doesn't accidentally eat up an arg that wasn't meant to be a revision (a filename, for instance). The only problem with this is if there's actully a filename that matches these patterns! (But then we could just re-order ars.)
|
340
|
+
revisions.select! do |revision|
|
341
|
+
revision =~ /\d+|\d+:\d+/
|
342
|
+
end
|
343
|
+
@revisions = revisions
|
344
|
+
@revisions.size
|
345
|
+
end
|
346
|
+
end
|
347
|
+
def view_commits(path = "./")
|
348
|
+
if @revisions.nil?
|
349
|
+
raise "-r (revisions) option is mandatory"
|
350
|
+
end
|
351
|
+
$ignore_dry_run_option = true
|
352
|
+
base_url = Subversion.base_url(path)
|
353
|
+
$ignore_dry_run_option = false
|
354
|
+
#puts "Base URL: #{base_url}"
|
355
|
+
revisions = self.class.parse_revision_ranges(@revisions)
|
356
|
+
revisions.each do |revision|
|
357
|
+
puts Subversion.log("-r #{revision} -v #{base_url}")
|
358
|
+
end
|
359
|
+
|
360
|
+
puts Subversion.diff("-r #{revisions.first}:#{revisions.last} #{path}")
|
361
|
+
#/usr/bin/svn diff http://code.qualitysmith.com/gemables/subversion@2279 http://code.qualitysmith.com/gemables/svn-command@2349 --diff-cmd colordiff
|
362
|
+
|
363
|
+
end
|
364
|
+
alias_subcommand :code_review => :view_commits
|
365
|
+
|
366
|
+
def SvnCommand.parse_revision_ranges(revisions_array)
|
367
|
+
revisions_array.map do |item|
|
368
|
+
case item
|
369
|
+
when /(\d+):(\d+)/
|
370
|
+
($1.to_i .. $2.to_i)
|
371
|
+
when /(\d+)-(\d+)/
|
372
|
+
($1.to_i .. $2.to_i)
|
373
|
+
when /(\d+)\.\.(\d+)/
|
374
|
+
($1.to_i .. $2.to_i)
|
375
|
+
when /\d+/
|
376
|
+
item.to_i
|
377
|
+
else
|
378
|
+
raise "Item in revisions_array had an unrecognized format: #{item}"
|
379
|
+
end
|
380
|
+
end.expand_ranges
|
381
|
+
end
|
382
|
+
|
383
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
384
|
+
# Goes through each "unadded" file (each file reporting a status of <tt>?</tt>) reported by <tt>svn status</tt> and asks you what you want to do with them (add, delete, or ignore)
|
385
|
+
def each_unadded(*args)
|
386
|
+
catch :exit do
|
387
|
+
|
388
|
+
$ignore_dry_run_option = true
|
389
|
+
Subversion::Extensions.each_unadded( Subversion.status(*args) ) do |file|
|
390
|
+
$ignore_dry_run_option = false
|
391
|
+
begin
|
392
|
+
response = ""
|
393
|
+
loop do
|
394
|
+
puts '-'*100
|
395
|
+
puts "What do you want to do with '#{file}'?"
|
396
|
+
begin
|
397
|
+
if File.file?(file)
|
398
|
+
# Only show the first x bytes so that we don't accidentally dump the contens of some 20 GB log file to screen...
|
399
|
+
puts File.read(file, 3000) || ''
|
400
|
+
elsif File.directory?(file)
|
401
|
+
puts "Directory contains:"
|
402
|
+
Dir.new(file).reject {|f| ['.','..'].include? f}.each do |f|
|
403
|
+
puts f
|
404
|
+
end
|
405
|
+
else
|
406
|
+
raise "#{file} is not a file or directory -- what *is* it??"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
print "(a)dd, (d)elete, add to svn:(i)ignore property, or [Enter] to do nothing > "
|
410
|
+
response = ""
|
411
|
+
response = $stdin.getc.chr while !['a', 'd', 'i', "\n"].include?(begin response.downcase!; response end)
|
412
|
+
break
|
413
|
+
end
|
414
|
+
|
415
|
+
case response
|
416
|
+
when 'a'
|
417
|
+
print "\nAdding... "
|
418
|
+
Subversion.add file
|
419
|
+
puts
|
420
|
+
when 'd'
|
421
|
+
puts
|
422
|
+
|
423
|
+
print "Are you pretty much *SURE* you want to 'rm -rf #{file}'? (y)es, (n)o > "
|
424
|
+
response = ""
|
425
|
+
response = $stdin.getc.chr while !['y', 'n', "\n"].include?(begin response.downcase!; response end)
|
426
|
+
|
427
|
+
if response == 'y'
|
428
|
+
print "\nDeleting... "
|
429
|
+
FileUtils.rm_rf file
|
430
|
+
puts
|
431
|
+
else
|
432
|
+
puts "\nI figured as much!"
|
433
|
+
end
|
434
|
+
when 'i'
|
435
|
+
print "\nIgnoring... "
|
436
|
+
Subversion.ignore file
|
437
|
+
puts
|
438
|
+
end
|
439
|
+
rescue Interrupt
|
440
|
+
puts "Goodbye"
|
441
|
+
throw :exit
|
442
|
+
end
|
443
|
+
end # each_unadded
|
444
|
+
|
445
|
+
end # catch :exit
|
446
|
+
end
|
447
|
+
alias_subcommand :eu => :each_unadded
|
448
|
+
|
449
|
+
|
450
|
+
|
451
|
+
|
452
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
453
|
+
# Externals-related commands
|
454
|
+
|
455
|
+
# For every directory that has the svn:externals property set, this lists the contents of the svn:externals property (dir, URL)
|
456
|
+
def externals(directory = "./")
|
457
|
+
puts Subversion.externals_containers(directory).map { |external|
|
458
|
+
external.to_s.
|
459
|
+
gsub(File.expand_path(Dir.pwd) + '/', '')
|
460
|
+
}
|
461
|
+
end
|
462
|
+
alias_subcommand :ext => :externals
|
463
|
+
|
464
|
+
# Lists *directories* that have the svn:externals property set.
|
465
|
+
def externals_containers(directory = "./")
|
466
|
+
puts Subversion.externals_containers(directory).map { |external|
|
467
|
+
external.container_dir
|
468
|
+
}
|
469
|
+
end
|
470
|
+
|
471
|
+
def edit_externals(directory = nil)
|
472
|
+
if directory.nil?
|
473
|
+
puts "No directory specified. Editing externals for *all* externals dirs..."
|
474
|
+
Subversion.externals_containers('.').each do |external|
|
475
|
+
puts external.to_s
|
476
|
+
command = "#{Subversion.executable} propedit svn:externals #{external.container_dir}"
|
477
|
+
#puts command
|
478
|
+
begin
|
479
|
+
#print "Press Ctrl-C to skip, any other key to continue. (This will start up your default editor.) "
|
480
|
+
print "Do you want to edit svn:externals for this directory? y/N > "
|
481
|
+
response = $stdin.getc.chr
|
482
|
+
system command if response.downcase == 'y'
|
483
|
+
rescue Interrupt
|
484
|
+
ensure
|
485
|
+
puts
|
486
|
+
end
|
487
|
+
end
|
488
|
+
else
|
489
|
+
system "#{Subversion.executable} propedit svn:externals #{directory}"
|
490
|
+
end
|
491
|
+
puts 'Done'
|
492
|
+
end
|
493
|
+
alias_subcommand :edit_ext => :edit_externals
|
494
|
+
alias_subcommand :ee => :edit_externals
|
495
|
+
alias_subcommand :edit_external => :edit_externals
|
496
|
+
|
497
|
+
module Externalize
|
498
|
+
# :todo: shortcut to create both __whatever method that sets instance variable
|
499
|
+
# *and* accessor method 'whatever' for reading it (and ||= initializing it)
|
500
|
+
# Console::Command.option({
|
501
|
+
# :as => 1 # 1 is arity
|
502
|
+
# :as => [1, nil] # 1 is arity, nil is default?
|
503
|
+
# )
|
504
|
+
|
505
|
+
def __as(as); @as = as; end
|
506
|
+
def as; @as; end
|
507
|
+
end
|
508
|
+
def externalize(repo_path)
|
509
|
+
# :todo: let them pass in local_path as well -- then we would need to accept 2 args, the first one poylmorphic, the second optional
|
510
|
+
|
511
|
+
Subversion.externalize(repo_path, {:as => as })
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
516
|
+
|
517
|
+
def ignore(file)
|
518
|
+
Subversion.ignore(file)
|
519
|
+
end
|
520
|
+
|
521
|
+
|
522
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
523
|
+
# Commit message retrieving/editing
|
524
|
+
|
525
|
+
module GetMessage
|
526
|
+
def _r(revision)
|
527
|
+
@revision = revision
|
528
|
+
end
|
529
|
+
end
|
530
|
+
def get_message()
|
531
|
+
#svn propget --revprop svn:log -r2325
|
532
|
+
args = ['propget', '--revprop', 'svn:log']
|
533
|
+
#args.concat ['-r', @revision ? @revision : Subversion.latest_revision]
|
534
|
+
args.concat ['-r', (revision = @revision ? @revision : 'head')]
|
535
|
+
puts "Message for r#{Subversion.latest_revision} :" if revision == 'head'
|
536
|
+
|
537
|
+
$ignore_dry_run_option = true
|
538
|
+
puts svn(:capture, *args)
|
539
|
+
$ignore_dry_run_option = false
|
540
|
+
end
|
541
|
+
|
542
|
+
module SetMessage
|
543
|
+
def _r(revision)
|
544
|
+
@revision = revision
|
545
|
+
end
|
546
|
+
def __file(filename)
|
547
|
+
@filename = filename
|
548
|
+
end
|
549
|
+
end
|
550
|
+
def set_message(new_message)
|
551
|
+
#svn propset --revprop -r 25 svn:log "Journaled about trip to New York."
|
552
|
+
puts "Message before changing:"
|
553
|
+
get_message
|
554
|
+
|
555
|
+
args = ['propset', '--revprop', 'svn:log']
|
556
|
+
args.concat ['-r', @revision ? @revision : 'head']
|
557
|
+
args << new_message if new_message
|
558
|
+
if @filename
|
559
|
+
contents = File.readlines(@filename).join.strip
|
560
|
+
puts "Read file '#{@filename}':"
|
561
|
+
print contents
|
562
|
+
puts
|
563
|
+
args << contents
|
564
|
+
end
|
565
|
+
svn :exec, *args
|
566
|
+
end
|
567
|
+
|
568
|
+
# Lets you edit it with your default editor
|
569
|
+
module EditMessage
|
570
|
+
def _r(revision)
|
571
|
+
@revision = revision
|
572
|
+
end
|
573
|
+
end
|
574
|
+
def edit_message()
|
575
|
+
#svn propedit --revprop -r 25 svn:log
|
576
|
+
args = ['propedit', '--revprop', 'svn:log']
|
577
|
+
args.concat ['-r', @revision ? @revision : 'head']
|
578
|
+
svn :exec, *args
|
579
|
+
end
|
580
|
+
|
581
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
582
|
+
|
583
|
+
def add_all_unadded
|
584
|
+
raise NotImplementedError
|
585
|
+
end
|
586
|
+
def grep
|
587
|
+
raise NotImplementedError
|
588
|
+
end
|
589
|
+
def grep_externals
|
590
|
+
raise NotImplementedError
|
591
|
+
end
|
592
|
+
def grep_log
|
593
|
+
raise NotImplementedError
|
594
|
+
end
|
595
|
+
def browse
|
596
|
+
raise NotImplementedError
|
597
|
+
end
|
598
|
+
|
599
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
600
|
+
# Aliases
|
601
|
+
#:stopdoc:
|
602
|
+
alias_subcommand :st => :status
|
603
|
+
alias_subcommand :up => :update
|
604
|
+
alias_subcommand :ci => :commit
|
605
|
+
#:startdoc:
|
606
|
+
|
607
|
+
|
608
|
+
#-----------------------------------------------------------------------------------------------------------------------------
|
609
|
+
# Helpers
|
610
|
+
|
611
|
+
private
|
612
|
+
def svn(method, *args)
|
613
|
+
subcommand = args[0]
|
614
|
+
args = ([subcommand] + prepare_args(args[1..-1]) + [:method => method])
|
615
|
+
# puts "in svn(): about to call Subversion#execute(#{args.inspect})"
|
616
|
+
Subversion.send :execute, *args
|
617
|
+
end
|
618
|
+
def prepare_args(args)
|
619
|
+
args.compact! # nil elements spell trouble
|
620
|
+
@passthrough_options + args.shell_escape
|
621
|
+
end
|
622
|
+
# To allow testing/stubbing
|
623
|
+
def system(*args)
|
624
|
+
Kernel.system *args
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|