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 ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-09-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/sub
7
+ lib/sub.rb
8
+ lib/sub/app.rb
9
+ lib/sub/version.rb
10
+ tasks/environment.rake
data/README.txt ADDED
@@ -0,0 +1 @@
1
+ README
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
5
+
6
+ task :default => :spec
data/bin/sub ADDED
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
2
+ require 'sub'
3
+ Sub.from_args(ARGV).execute
data/lib/sub.rb ADDED
@@ -0,0 +1,3 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'sub/app'
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
@@ -0,0 +1,9 @@
1
+ module Pong #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ task :ruby_env do
2
+ RUBY_APP = if RUBY_PLATFORM =~ /java/
3
+ "jruby"
4
+ else
5
+ "ruby"
6
+ end unless defined? RUBY_APP
7
+ end
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
+