sub 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+