sub 0.1.0
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/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +10 -0
- data/README.txt +1 -0
- data/Rakefile +6 -0
- data/bin/sub +3 -0
- data/lib/sub.rb +3 -0
- data/lib/sub/app.rb +356 -0
- data/lib/sub/version.rb +9 -0
- data/tasks/environment.rake +7 -0
- metadata +59 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Alex Chaffee
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
README
|
data/Rakefile
ADDED
data/bin/sub
ADDED
data/lib/sub.rb
ADDED
data/lib/sub/app.rb
ADDED
@@ -0,0 +1,356 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
require 'benchmark'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
QUIET = 0
|
6
|
+
NORMAL = 1
|
7
|
+
VERBOSE = 2
|
8
|
+
|
9
|
+
class External
|
10
|
+
|
11
|
+
def self.externals_in_directory(parent)
|
12
|
+
exts = []
|
13
|
+
prop = `svn propget svn:externals #{parent}`
|
14
|
+
prop.split("\n").each do |prop_line|
|
15
|
+
next if prop_line.strip.empty?
|
16
|
+
exts << External.new(parent, prop_line)
|
17
|
+
end
|
18
|
+
exts
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name, :path, :url, :revision
|
22
|
+
|
23
|
+
def initialize(parent, property)
|
24
|
+
match = /^(\S+)\s+(-r\s*\d*\s+)?(\S+)\s*$/.match(property)
|
25
|
+
@name = match[1]
|
26
|
+
@revision = match[2] ? match[2].gsub(/\D/,'').to_i : nil
|
27
|
+
@url = match[3]
|
28
|
+
@path = "#{parent}/#{@name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
(path == other.path and url == other.url)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"External[name=#{name}, path=#{path}, url=#{url}, revision=#{revision}]"
|
37
|
+
end
|
38
|
+
|
39
|
+
def revision_actual
|
40
|
+
info = `svn info #{@path}`
|
41
|
+
info.grep(/^Revision:/).first.gsub(/Revision: /, '').to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def should_update?
|
45
|
+
revision == nil || revision_actual != revision
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class Status
|
51
|
+
class Line
|
52
|
+
def initialize(text)
|
53
|
+
text.strip!
|
54
|
+
@modified = /^M/.match(text) ? true : false
|
55
|
+
@external = /^X/.match(text) ? true : false
|
56
|
+
@unversioned = /^\?/.match(text) ? true : false
|
57
|
+
@modified_properties = /^.M/.match(text) ? true : false
|
58
|
+
@path = text.gsub(/^...... /, '')
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :path
|
62
|
+
|
63
|
+
def modified?
|
64
|
+
@modified
|
65
|
+
end
|
66
|
+
def external?
|
67
|
+
@external
|
68
|
+
end
|
69
|
+
def unversioned?
|
70
|
+
@unversioned
|
71
|
+
end
|
72
|
+
def modified_properties?
|
73
|
+
@modified_properties
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def initialize(text)
|
79
|
+
@lines = text.split("\n").collect do |line|
|
80
|
+
Status::Line.new(line)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def modified
|
85
|
+
@lines.select { |line| line.modified? }.collect {|line| line.path}
|
86
|
+
end
|
87
|
+
|
88
|
+
def externals
|
89
|
+
@lines.select { |line| line.external? }.collect {|line| line.path}
|
90
|
+
end
|
91
|
+
|
92
|
+
def unversioned
|
93
|
+
@lines.select { |line| line.unversioned? }.collect {|line| line.path}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Sub
|
98
|
+
DEFAULT_BASE_URL = "svn+ssh://rubyforge.org/var/svn"
|
99
|
+
|
100
|
+
def self.from_args(args)
|
101
|
+
verbosity = NORMAL
|
102
|
+
still_parsing_options = true
|
103
|
+
command = :up
|
104
|
+
options = {}
|
105
|
+
while still_parsing_options
|
106
|
+
case args[0]
|
107
|
+
when 'up', 'co', 'help'
|
108
|
+
options[:command] = args[0]
|
109
|
+
when '-v', '--verbose'
|
110
|
+
options[:verbosity] = VERBOSE
|
111
|
+
when '-q', '--quiet'
|
112
|
+
options[:verbosity] = QUIET
|
113
|
+
when '-h', '--help'
|
114
|
+
options[:command] = 'help'
|
115
|
+
still_parsing_options = false
|
116
|
+
when '-c', '--clean'
|
117
|
+
options[:clean] = true
|
118
|
+
when '--url'
|
119
|
+
args.shift
|
120
|
+
options[:url] = args.shift
|
121
|
+
else
|
122
|
+
still_parsing_options = false
|
123
|
+
end
|
124
|
+
args.shift if still_parsing_options
|
125
|
+
end
|
126
|
+
|
127
|
+
Sub.new(options, args)
|
128
|
+
end
|
129
|
+
|
130
|
+
attr_reader :verbosity, :clean, :command, :args, :url
|
131
|
+
|
132
|
+
def initialize(options = {:verbosity => NORMAL}, args = [])
|
133
|
+
options = defaults.merge(options)
|
134
|
+
@verbosity = options[:verbosity]
|
135
|
+
@clean = options[:clean]
|
136
|
+
@command = options[:command]
|
137
|
+
@url = options[:url] || ENV['SUB_BASE_URL'] || DEFAULT_BASE_URL
|
138
|
+
@args = args
|
139
|
+
|
140
|
+
@status = {}
|
141
|
+
end
|
142
|
+
|
143
|
+
def defaults
|
144
|
+
{
|
145
|
+
:verbosity => NORMAL,
|
146
|
+
:command => :up,
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def execute
|
151
|
+
self.send(@command)
|
152
|
+
end
|
153
|
+
|
154
|
+
# commands
|
155
|
+
def up
|
156
|
+
if @args.empty?
|
157
|
+
@args = [`pwd`.chomp]
|
158
|
+
end
|
159
|
+
update_many(@args)
|
160
|
+
end
|
161
|
+
|
162
|
+
def co
|
163
|
+
if @args.empty?
|
164
|
+
raise "Please specify a project to check out"
|
165
|
+
end
|
166
|
+
|
167
|
+
project = args.shift
|
168
|
+
dir_name = args.shift || project
|
169
|
+
svn("co #{url}/#{project}/trunk #{dir_name}")
|
170
|
+
end
|
171
|
+
|
172
|
+
def help
|
173
|
+
puts """
|
174
|
+
sub - Alex's wrapper for subversion
|
175
|
+
|
176
|
+
Usage:
|
177
|
+
sub co project_name [dir_name]
|
178
|
+
checks out [base_url]/project_name/trunk into ./project_name (or dir_name if specified)
|
179
|
+
sub up [dir]*
|
180
|
+
fast update (default command, so 'sub dir...' or just 'sub' work too)
|
181
|
+
sub help
|
182
|
+
prints this message
|
183
|
+
|
184
|
+
Options:
|
185
|
+
--verbose, -v
|
186
|
+
lots of output
|
187
|
+
--quiet, -q
|
188
|
+
no output at all except for errors
|
189
|
+
--help, -h
|
190
|
+
prints this message
|
191
|
+
--clean, -c
|
192
|
+
'up' command removes all unversioned files and directories
|
193
|
+
--url [base_url]
|
194
|
+
sets base repository url for 'co' command
|
195
|
+
(default is ENV['SUB_BASE_URL'] or #{DEFAULT_BASE_URL})
|
196
|
+
"""
|
197
|
+
end
|
198
|
+
|
199
|
+
# methods
|
200
|
+
|
201
|
+
def update_many(roots)
|
202
|
+
roots.each do |root|
|
203
|
+
say "Updating #{root}"
|
204
|
+
b = Benchmark.measure { update(root) }
|
205
|
+
say "Updated %s in %.2f sec" % [root, b.real]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def update(root)
|
210
|
+
if @clean
|
211
|
+
remove_unversioned(root)
|
212
|
+
end
|
213
|
+
|
214
|
+
externals_before = externals(root)
|
215
|
+
svn("up --ignore-externals #{root}")
|
216
|
+
externals = externals(root)
|
217
|
+
# for some reason (array - array) doesn't work right here
|
218
|
+
# so i had to write my own subtract method
|
219
|
+
removed_externals = externals_before.subtract(externals)
|
220
|
+
removed_externals.each do |ext|
|
221
|
+
say "Removed external #{ext}"
|
222
|
+
FileUtils.rm_rf(ext.path)
|
223
|
+
end
|
224
|
+
|
225
|
+
added_externals = externals.subtract(externals_before)
|
226
|
+
existing_externals = externals.subtract(added_externals)
|
227
|
+
|
228
|
+
# todo: extract Processes
|
229
|
+
processes = []
|
230
|
+
def update_external(processes, external)
|
231
|
+
pid = fork do
|
232
|
+
say "Updating external #{external.path}"
|
233
|
+
run("svn cleanup #{external.path}") if File.exists?(external.path)
|
234
|
+
rev = external.revision.nil? ? '' : "-r#{external.revision}"
|
235
|
+
svn("up #{rev} #{external.path} | grep -v 'At revision'")
|
236
|
+
end
|
237
|
+
processes << {:pid => pid, :external => external}
|
238
|
+
end
|
239
|
+
|
240
|
+
def checkout_external(processes, external)
|
241
|
+
pid = fork do
|
242
|
+
say "Checking out external #{external.path}"
|
243
|
+
rev = external.revision.nil? ? '' : "-r#{external.revision}"
|
244
|
+
svn("co #{rev} #{external.url} #{external.path}")
|
245
|
+
end
|
246
|
+
processes << {:pid => pid, :external => external}
|
247
|
+
end
|
248
|
+
|
249
|
+
added_externals.each do |external|
|
250
|
+
checkout_external(processes, external)
|
251
|
+
end
|
252
|
+
|
253
|
+
already_up_to_date = []
|
254
|
+
existing_externals.each do |external|
|
255
|
+
if external.should_update?
|
256
|
+
update_external(processes, external)
|
257
|
+
else
|
258
|
+
already_up_to_date << external
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
unless already_up_to_date.empty?
|
263
|
+
say("External#{'s' if already_up_to_date.size > 1} " +
|
264
|
+
already_up_to_date.collect {|external| external.name}.join(", ") +
|
265
|
+
" already up to date")
|
266
|
+
end
|
267
|
+
|
268
|
+
processes.each do |process|
|
269
|
+
Process.waitpid(process[:pid], 0)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def remove_unversioned(root)
|
274
|
+
status(root).unversioned.each do |path|
|
275
|
+
if File.directory?(path)
|
276
|
+
say "Removing unversioned directory #{path}"
|
277
|
+
else
|
278
|
+
say "Removing unversioned file #{path}"
|
279
|
+
end
|
280
|
+
FileUtils.rm_rf(path)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def status(root)
|
285
|
+
@status[root] ||= Status.new(run("svn st #{root}", true))
|
286
|
+
end
|
287
|
+
|
288
|
+
def externals(root)
|
289
|
+
exts = []
|
290
|
+
directories_containing_externals(root).collect do |parent|
|
291
|
+
exts += External.externals_in_directory(parent)
|
292
|
+
end
|
293
|
+
exts
|
294
|
+
end
|
295
|
+
|
296
|
+
def directories_containing_externals(root)
|
297
|
+
status(root).externals.collect do |path|
|
298
|
+
if (path !~ /\//)
|
299
|
+
"."
|
300
|
+
else
|
301
|
+
path.gsub(/\/[^\/]*$/, '')
|
302
|
+
end
|
303
|
+
end.uniq
|
304
|
+
end
|
305
|
+
|
306
|
+
def parse_externals(st)
|
307
|
+
exts = []
|
308
|
+
st.split("\n").select do |line|
|
309
|
+
line =~ /^X/
|
310
|
+
end.collect do |line|
|
311
|
+
line.gsub(/^X */, '').gsub(/\/[^\/]*$/, '')
|
312
|
+
end.uniq.collect do |parent|
|
313
|
+
prop = `svn propget svn:externals #{parent}`
|
314
|
+
prop.split("\n").each do |external|
|
315
|
+
next if external.strip.empty?
|
316
|
+
exts << External.new(parent, external)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
exts
|
320
|
+
end
|
321
|
+
|
322
|
+
def say(msg)
|
323
|
+
puts msg if verbosity > QUIET
|
324
|
+
end
|
325
|
+
|
326
|
+
def svn(cmd)
|
327
|
+
svncmd = "svn"
|
328
|
+
svncmd += " --quiet" if (cmd =~ /^(up|co)/ && verbosity == QUIET)
|
329
|
+
run("#{svncmd} #{cmd}")
|
330
|
+
end
|
331
|
+
|
332
|
+
def run(cmd, return_output = false)
|
333
|
+
say("\t#{cmd}") if verbosity == VERBOSE
|
334
|
+
if (return_output)
|
335
|
+
`#{cmd}`
|
336
|
+
else
|
337
|
+
cmd += ">/dev/null"if verbosity == QUIET
|
338
|
+
system(cmd)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
class Array
|
344
|
+
def subtract(other)
|
345
|
+
self.select do |item|
|
346
|
+
!other.has?(item)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def has?(something)
|
351
|
+
self.each do |item|
|
352
|
+
return true if item == something
|
353
|
+
end
|
354
|
+
false
|
355
|
+
end
|
356
|
+
end
|
data/lib/sub/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: sub
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-09-12 00:00:00 -07:00
|
8
|
+
summary: sub is a wrapper for svn that adds faster/safer updates, easy trunk checkout, etc.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: alex@pivotallabs.com
|
12
|
+
homepage: http://pivotalrb.rubyforge.org
|
13
|
+
rubyforge_project: pivotalrb
|
14
|
+
description: sub is a wrapper for svn that adds faster/safer updates, easy trunk checkout, etc.
|
15
|
+
autorequire:
|
16
|
+
default_executable: sub
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Alex Chaffee
|
31
|
+
files:
|
32
|
+
- History.txt
|
33
|
+
- License.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
- Rakefile
|
37
|
+
- bin/sub
|
38
|
+
- lib/sub.rb
|
39
|
+
- lib/sub/app.rb
|
40
|
+
- lib/sub/version.rb
|
41
|
+
- tasks/environment.rake
|
42
|
+
test_files: []
|
43
|
+
|
44
|
+
rdoc_options:
|
45
|
+
- --main
|
46
|
+
- README.txt
|
47
|
+
extra_rdoc_files:
|
48
|
+
- History.txt
|
49
|
+
- License.txt
|
50
|
+
- Manifest.txt
|
51
|
+
- README.txt
|
52
|
+
executables:
|
53
|
+
- sub
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
dependencies: []
|
59
|
+
|