sub 0.3.0 → 0.4.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 +10 -0
- data/Manifest.txt +1 -0
- data/lib/sub.rb +2 -1
- data/lib/sub/app.rb +113 -64
- data/lib/sub/external.rb +22 -24
- data/lib/sub/root.rb +44 -22
- data/lib/sub/status.rb +0 -1
- data/lib/sub/sys.rb +73 -0
- data/lib/sub/version.rb +1 -1
- metadata +4 -3
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 0.4.0
|
2
|
+
|
3
|
+
* "sub ext" command to deal with svn:externals property (see "sub help")
|
4
|
+
* "sub ignore" command to either print or add to a directory's svn:ignore
|
5
|
+
property
|
6
|
+
* If you replace an external with a "real" directory, then use "sub up" in
|
7
|
+
another workspace, it will now do the right thing and remove the external
|
8
|
+
before updating the directory. (This solves what is, IMHO, a bug in svn --
|
9
|
+
it gets confused and attempts to grab its own lock and aborts.)
|
10
|
+
|
1
11
|
== 0.3.0 2007-10-02
|
2
12
|
|
3
13
|
* Bugfix: checks out externals that were added since last update
|
data/Manifest.txt
CHANGED
data/lib/sub.rb
CHANGED
data/lib/sub/app.rb
CHANGED
@@ -1,23 +1,29 @@
|
|
1
1
|
#!/usr/local/bin/ruby
|
2
2
|
require 'benchmark'
|
3
3
|
require 'fileutils'
|
4
|
-
|
5
|
-
QUIET = 0
|
6
|
-
NORMAL = 1
|
7
|
-
VERBOSE = 2
|
4
|
+
require 'sub/sys'
|
8
5
|
|
9
6
|
class Sub
|
7
|
+
include Sys::Client
|
8
|
+
|
10
9
|
DEFAULT_BASE_URL = "svn+ssh://rubyforge.org/var/svn"
|
11
10
|
|
12
11
|
def self.from_args(args)
|
13
|
-
verbosity = NORMAL
|
14
12
|
still_parsing_options = true
|
15
|
-
command = :
|
13
|
+
command = defaults[:command]
|
16
14
|
options = {}
|
17
|
-
|
15
|
+
real_args = []
|
16
|
+
while !args.empty?
|
18
17
|
case args[0]
|
19
|
-
|
18
|
+
|
19
|
+
# commands
|
20
|
+
when 'up', 'co', 'help', 'ext', 'ignore'
|
20
21
|
options[:command] = args[0]
|
22
|
+
|
23
|
+
# no-param options
|
24
|
+
when '--version'
|
25
|
+
puts "Sub version #{Sub::VERSION::STRING}"
|
26
|
+
exit 0
|
21
27
|
when '-v', '--verbose'
|
22
28
|
options[:verbosity] = VERBOSE
|
23
29
|
when '-q', '--quiet'
|
@@ -27,34 +33,41 @@ class Sub
|
|
27
33
|
still_parsing_options = false
|
28
34
|
when '-c', '--clean'
|
29
35
|
options[:clean] = true
|
36
|
+
|
37
|
+
# one-param options
|
30
38
|
when '--url'
|
31
39
|
args.shift
|
32
40
|
options[:url] = args[0]
|
33
|
-
when '--
|
34
|
-
|
35
|
-
|
41
|
+
when '--revision', '-r'
|
42
|
+
args.shift
|
43
|
+
options[:revision] = args[0]
|
44
|
+
when '--directory', '--dir', '-d'
|
45
|
+
args.shift
|
46
|
+
options[:dir] = args[0]
|
47
|
+
|
36
48
|
else
|
37
|
-
|
49
|
+
real_args << args[0]
|
38
50
|
end
|
39
|
-
args.shift
|
51
|
+
args.shift
|
40
52
|
end
|
41
53
|
|
42
|
-
Sub.new(options,
|
54
|
+
Sub.new(options, real_args)
|
43
55
|
end
|
44
56
|
|
45
|
-
attr_reader :clean, :command, :args, :url
|
46
|
-
attr_accessor :verbosity
|
57
|
+
attr_reader :clean, :command, :args, :url, :revision, :dir
|
47
58
|
|
48
59
|
def initialize(options = {:verbosity => NORMAL}, args = [])
|
49
|
-
options = defaults.merge(options)
|
50
|
-
|
60
|
+
options = Sub.defaults.merge(options)
|
61
|
+
sys.verbosity = options[:verbosity]
|
51
62
|
@clean = options[:clean]
|
52
63
|
@command = options[:command]
|
53
|
-
@url = options[:url]
|
64
|
+
@url = options[:url]
|
65
|
+
@revision = options[:revision]
|
66
|
+
@dir = options[:dir] || '.'
|
54
67
|
@args = args
|
55
68
|
end
|
56
69
|
|
57
|
-
def defaults
|
70
|
+
def self.defaults
|
58
71
|
{
|
59
72
|
:verbosity => NORMAL,
|
60
73
|
:command => :up,
|
@@ -62,7 +75,7 @@ class Sub
|
|
62
75
|
end
|
63
76
|
|
64
77
|
def execute
|
65
|
-
self.send(@command)
|
78
|
+
puts self.send(@command)
|
66
79
|
end
|
67
80
|
|
68
81
|
# commands
|
@@ -71,6 +84,7 @@ class Sub
|
|
71
84
|
@args = [`pwd`.chomp]
|
72
85
|
end
|
73
86
|
update_many(@args)
|
87
|
+
"Update complete."
|
74
88
|
end
|
75
89
|
|
76
90
|
def co
|
@@ -80,7 +94,37 @@ class Sub
|
|
80
94
|
|
81
95
|
project = args.shift
|
82
96
|
dir_name = args.shift || project
|
83
|
-
svn("co #{
|
97
|
+
svn("co #{base}/#{project}/trunk #{dir_name}")
|
98
|
+
"Checkout complete."
|
99
|
+
end
|
100
|
+
|
101
|
+
def base
|
102
|
+
url || ENV['SUB_BASE_URL'] || ENV['SVN'] || DEFAULT_BASE_URL
|
103
|
+
end
|
104
|
+
|
105
|
+
#todo: test
|
106
|
+
def ext
|
107
|
+
external_name = args.shift
|
108
|
+
if external_name.nil?
|
109
|
+
External.externals_of(dir).join("\n")
|
110
|
+
else
|
111
|
+
old_external = get_external(dir, external_name)
|
112
|
+
if url || revision
|
113
|
+
revision_to_set = revision == 'HEAD' ? nil : revision
|
114
|
+
set_external(dir, external_name, url || old_external.url, revision_to_set)
|
115
|
+
else
|
116
|
+
old_external
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def ignore
|
122
|
+
if args.empty?
|
123
|
+
svn("propget svn:ignore #{dir}", true).strip
|
124
|
+
else
|
125
|
+
svn("propset svn:ignore '#{args.join("\n")}' #{dir}")
|
126
|
+
"#{dir} now ignoring #{args.join(", ")}"
|
127
|
+
end
|
84
128
|
end
|
85
129
|
|
86
130
|
def help
|
@@ -90,13 +134,31 @@ sub - Alex's wrapper for subversion
|
|
90
134
|
Version: #{Sub::VERSION::STRING}
|
91
135
|
|
92
136
|
Usage:
|
93
|
-
|
94
|
-
|
95
|
-
|
137
|
+
|
138
|
+
sub co [--url base_url] project [dir]
|
139
|
+
checks out [base_url]/project/trunk into ./project (or dir if specified)
|
140
|
+
default base_url is ENV['SUB_BASE_URL'] or ENV['SVN']
|
141
|
+
or #{DEFAULT_BASE_URL}
|
142
|
+
|
143
|
+
sub up [--clean] [dir]*
|
96
144
|
fast update (default command, so 'sub dir...' or just 'sub' work too)
|
145
|
+
if --clean is specified, removes all unversioned files and directories
|
146
|
+
|
97
147
|
sub help
|
98
148
|
prints this message
|
99
149
|
|
150
|
+
sub ext [--dir directory] [external_name [--revision rev] [--url url]]]
|
151
|
+
prints the value of the named svn:external property for directory dir
|
152
|
+
if no dir is specified, uses '.'
|
153
|
+
if no external_name is specified, prints all externals in that directory
|
154
|
+
if --url or --revision is specified, sets the value and commits the change
|
155
|
+
if rev is HEAD then the revision is removed from that external
|
156
|
+
|
157
|
+
sub ignore [--dir directory] [pattern]
|
158
|
+
prints the value of the svn:ignore property for directory dir
|
159
|
+
if pattern is specified, ignores that pattern
|
160
|
+
if no dir is specified, uses '.'
|
161
|
+
|
100
162
|
Options:
|
101
163
|
--verbose, -v
|
102
164
|
lots of output
|
@@ -104,15 +166,11 @@ Options:
|
|
104
166
|
no output at all except for errors
|
105
167
|
--help, -h
|
106
168
|
prints this message
|
107
|
-
--clean, -c
|
108
|
-
'up' command removes all unversioned files and directories
|
109
|
-
--url [base_url]
|
110
|
-
sets base repository url for 'co' command
|
111
|
-
(default is ENV['SUB_BASE_URL'] or ENV['SVN'] or #{DEFAULT_BASE_URL})
|
112
169
|
--version
|
113
170
|
prints release version
|
114
171
|
"""
|
115
172
|
end
|
173
|
+
|
116
174
|
|
117
175
|
# methods
|
118
176
|
|
@@ -125,47 +183,38 @@ Options:
|
|
125
183
|
end
|
126
184
|
|
127
185
|
def update(root)
|
128
|
-
Root.new(root,
|
186
|
+
Root.new(root, clean).update
|
129
187
|
end
|
130
188
|
|
131
|
-
def
|
132
|
-
Root.new(root,
|
189
|
+
def all_externals(root)
|
190
|
+
Root.new(root, clean).all_externals
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_external(dir, external_name)
|
194
|
+
External.externals_of(dir).select do |external|
|
195
|
+
external.name == external_name
|
196
|
+
end.first
|
133
197
|
end
|
134
198
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
line =~ /^X/
|
139
|
-
end.collect do |line|
|
140
|
-
line.gsub(/^X */, '').gsub(/\/[^\/]*$/, '')
|
141
|
-
end.uniq.collect do |parent|
|
142
|
-
prop = `svn propget svn:externals #{parent}`
|
143
|
-
prop.split("\n").each do |external|
|
144
|
-
next if external.strip.empty?
|
145
|
-
exts << External.new(parent, external, self)
|
146
|
-
end
|
199
|
+
def set_external(dir, external_name, url, revision = nil)
|
200
|
+
if get_external(dir, external_name)
|
201
|
+
remove_external(dir, external_name)
|
147
202
|
end
|
148
|
-
|
203
|
+
new_external = External.new(dir, external_name, url, revision)
|
204
|
+
externals = External.externals_of(dir) << new_external
|
205
|
+
set_externals(dir, externals)
|
206
|
+
new_external
|
149
207
|
end
|
150
|
-
|
151
|
-
def
|
152
|
-
|
208
|
+
|
209
|
+
def set_externals(externals_path, externals)
|
210
|
+
property_lines = externals.collect { |x| x.to_s }
|
211
|
+
svn "propset svn:externals '#{property_lines.join("\n")}' #{externals_path}"
|
153
212
|
end
|
154
|
-
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
213
|
+
|
214
|
+
def remove_external(dir, name)
|
215
|
+
set_externals(dir, External.externals_of(dir).select { |x| x.name != name })
|
216
|
+
svn("ci --non-recursive -m 'removing external #{name}' #{dir}")
|
217
|
+
FileUtils.rm_rf("#{dir}/#{name}") # TODO: unless modified
|
159
218
|
end
|
160
219
|
|
161
|
-
def run(cmd, return_output = false)
|
162
|
-
say("\t#{cmd}") if verbosity == VERBOSE
|
163
|
-
if (return_output)
|
164
|
-
`#{cmd}`
|
165
|
-
else
|
166
|
-
cmd += ">/dev/null"if verbosity == QUIET
|
167
|
-
system(cmd)
|
168
|
-
end
|
169
|
-
end
|
170
220
|
end
|
171
|
-
|
data/lib/sub/external.rb
CHANGED
@@ -1,36 +1,31 @@
|
|
1
1
|
class External
|
2
|
+
include Sys::Client
|
2
3
|
|
3
|
-
def self.
|
4
|
+
def self.externals_of(parent, prop = `svn propget svn:externals #{parent}`)
|
4
5
|
exts = []
|
5
|
-
prop = `svn propget svn:externals #{parent}`
|
6
6
|
prop.split("\n").each do |prop_line|
|
7
7
|
next if prop_line.strip.empty?
|
8
|
-
exts << External.
|
8
|
+
exts << External.from_property_line(parent, prop_line)
|
9
9
|
end
|
10
10
|
exts
|
11
11
|
end
|
12
12
|
|
13
|
-
attr_reader :name, :path, :url, :revision
|
13
|
+
attr_reader :name, :path, :url, :revision
|
14
14
|
|
15
|
-
def
|
16
|
-
match = /^(\S+)\s+(-r\s*\d*\s+)?(\S+)\s*$/.match(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def say(msg)
|
25
|
-
app.say(msg)
|
15
|
+
def self.from_property_line(parent, property_line)
|
16
|
+
match = /^(\S+)\s+(-r\s*\d*\s+)?(\S+)\s*$/.match(property_line)
|
17
|
+
name = match[1]
|
18
|
+
revision = match[2] ? match[2].gsub(/\D/,'').to_i : nil
|
19
|
+
url = match[3]
|
20
|
+
raise "no url found for '#{property_line}'" if url.nil?
|
21
|
+
new(parent, name, url, revision)
|
26
22
|
end
|
27
|
-
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
app.run(cmd, return_output)
|
23
|
+
|
24
|
+
def initialize(parent, name, url, revision = nil)
|
25
|
+
@name = name
|
26
|
+
@revision = revision
|
27
|
+
@url = url
|
28
|
+
@path = "#{parent}/#{@name}"
|
34
29
|
end
|
35
30
|
|
36
31
|
def ==(other)
|
@@ -38,7 +33,9 @@ class External
|
|
38
33
|
end
|
39
34
|
|
40
35
|
def to_s
|
41
|
-
"
|
36
|
+
"#{name} " +
|
37
|
+
(revision ? "-r #{revision} " : "") +
|
38
|
+
url
|
42
39
|
end
|
43
40
|
|
44
41
|
def revision_actual
|
@@ -52,10 +49,11 @@ class External
|
|
52
49
|
|
53
50
|
#todo: test? (indirectly tested via root.rb)
|
54
51
|
def update
|
52
|
+
raise "path is nil" if path.nil?
|
55
53
|
say "Updating external #{path}"
|
56
54
|
run("svn cleanup #{path}")
|
57
55
|
rev = revision.nil? ? '' : "-r#{revision}"
|
58
|
-
svn("up #{rev} #{path}
|
56
|
+
svn("up #{rev} #{path}", true)
|
59
57
|
end
|
60
58
|
|
61
59
|
def checkout
|
data/lib/sub/root.rb
CHANGED
@@ -1,25 +1,14 @@
|
|
1
1
|
class Root
|
2
|
-
|
2
|
+
include Sys::Client
|
3
|
+
|
4
|
+
attr_reader :root_path
|
3
5
|
|
4
|
-
def initialize(root_path,
|
6
|
+
def initialize(root_path, clean = false)
|
5
7
|
@root_path = root_path
|
6
|
-
@app = app
|
7
8
|
@clean = clean
|
8
9
|
@status = nil
|
9
10
|
end
|
10
11
|
|
11
|
-
def say(msg)
|
12
|
-
app.say(msg)
|
13
|
-
end
|
14
|
-
|
15
|
-
def svn(cmd)
|
16
|
-
app.svn(cmd)
|
17
|
-
end
|
18
|
-
|
19
|
-
def run(cmd, return_output = false)
|
20
|
-
app.run(cmd, return_output)
|
21
|
-
end
|
22
|
-
|
23
12
|
class Processes < Array
|
24
13
|
def initialize
|
25
14
|
end
|
@@ -43,12 +32,14 @@ class Root
|
|
43
32
|
remove_unversioned
|
44
33
|
end
|
45
34
|
|
46
|
-
|
35
|
+
remove_deleted_externals
|
36
|
+
|
37
|
+
externals_before = all_externals
|
47
38
|
|
48
39
|
svn("up --ignore-externals #{root_path}")
|
49
40
|
# for some reason (array - array) doesn't work right here
|
50
41
|
# so i had to write my own subtract method
|
51
|
-
removed_externals = externals_before.subtract(
|
42
|
+
removed_externals = externals_before.subtract(all_externals)
|
52
43
|
removed_externals.each do |ext|
|
53
44
|
say "Removed external #{ext}"
|
54
45
|
FileUtils.rm_rf(ext.path)
|
@@ -57,7 +48,7 @@ class Root
|
|
57
48
|
@processes = Processes.new
|
58
49
|
|
59
50
|
already_up_to_date = []
|
60
|
-
|
51
|
+
all_externals.each do |external|
|
61
52
|
if external.should_update?
|
62
53
|
if File.exists?(external.path)
|
63
54
|
update_external(external)
|
@@ -90,6 +81,37 @@ class Root
|
|
90
81
|
end
|
91
82
|
end
|
92
83
|
|
84
|
+
def remove_deleted_externals
|
85
|
+
directories_containing_externals.each do |dir|
|
86
|
+
deleted_something = false
|
87
|
+
workspace_externals = External.externals_of(dir)
|
88
|
+
Dir.chdir(root_path) do
|
89
|
+
head_property = svn("propget -r HEAD svn:externals #{dir}", true)
|
90
|
+
head_externals = External.externals_of(dir, head_property)
|
91
|
+
deleted_externals = workspace_externals.select do |workspace_external|
|
92
|
+
head_externals.select { |head_external| head_external.name == workspace_external.name }.empty?
|
93
|
+
end
|
94
|
+
unless deleted_externals.empty?
|
95
|
+
deleted_externals.each do |external|
|
96
|
+
# todo: check for local changes
|
97
|
+
say("Deleting external #{external.path}")
|
98
|
+
FileUtils.rm_rf(external.path)
|
99
|
+
end
|
100
|
+
# update the property first
|
101
|
+
svn("up -N #{dir}")
|
102
|
+
# then update all the deleted former externals
|
103
|
+
svn("up " + deleted_externals.map{|external| "#{external.path}"}.join(" "))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
if deleted_something
|
107
|
+
svn("up -N #{dir}")
|
108
|
+
svn("up --ignore-externals #{dir}")
|
109
|
+
puts "pausing"
|
110
|
+
sleep(9999)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
93
115
|
def remove_unversioned
|
94
116
|
status.unversioned.each do |path|
|
95
117
|
if File.directory?(path)
|
@@ -102,13 +124,13 @@ class Root
|
|
102
124
|
end
|
103
125
|
|
104
126
|
def status
|
105
|
-
@status ||= Status.new(
|
127
|
+
@status ||= Status.new(svn("st #{root_path}", true))
|
106
128
|
end
|
107
129
|
|
108
|
-
def
|
130
|
+
def all_externals
|
109
131
|
exts = []
|
110
132
|
directories_containing_externals.collect do |parent|
|
111
|
-
exts += External.
|
133
|
+
exts += External.externals_of(parent)
|
112
134
|
end
|
113
135
|
exts
|
114
136
|
end
|
@@ -116,7 +138,7 @@ class Root
|
|
116
138
|
def directories_containing_externals
|
117
139
|
status.externals.collect do |path|
|
118
140
|
if (path !~ /\//)
|
119
|
-
|
141
|
+
root_path
|
120
142
|
else
|
121
143
|
path.gsub(/\/[^\/]*$/, '')
|
122
144
|
end
|
data/lib/sub/status.rb
CHANGED
data/lib/sub/sys.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
QUIET = 0
|
2
|
+
NORMAL = 1
|
3
|
+
VERBOSE = 2
|
4
|
+
|
5
|
+
class Sys
|
6
|
+
|
7
|
+
module Client
|
8
|
+
def sys
|
9
|
+
Sys.instance
|
10
|
+
end
|
11
|
+
|
12
|
+
#todo: look up ruby dynamic proxy
|
13
|
+
def say(msg)
|
14
|
+
sys.say(msg)
|
15
|
+
end
|
16
|
+
|
17
|
+
def svn(cmd, return_output = false)
|
18
|
+
sys.svn(cmd, return_output)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(cmd, return_output = false)
|
22
|
+
sys.run(cmd, return_output)
|
23
|
+
end
|
24
|
+
|
25
|
+
def verbosity=(level)
|
26
|
+
sys.verbosity = level
|
27
|
+
end
|
28
|
+
|
29
|
+
def verbosity
|
30
|
+
sys.verbosity
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@@instance = Sys.new
|
35
|
+
|
36
|
+
def self.instance
|
37
|
+
@@instance
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :verbosity
|
41
|
+
|
42
|
+
def initialize(verbosity = NORMAL)
|
43
|
+
@verbosity = verbosity
|
44
|
+
end
|
45
|
+
|
46
|
+
def say(msg)
|
47
|
+
puts msg if verbosity > QUIET
|
48
|
+
end
|
49
|
+
|
50
|
+
def svn(cmd, return_output = false)
|
51
|
+
svncmd = "svn"
|
52
|
+
svncmd += " --quiet" if (cmd =~ /^(up|co)/ && verbosity == QUIET)
|
53
|
+
output = run("#{svncmd} #{cmd}", return_output)
|
54
|
+
puts output if output && verbosity == VERBOSE
|
55
|
+
output
|
56
|
+
end
|
57
|
+
|
58
|
+
def run(cmd, return_output = false)
|
59
|
+
val = nil
|
60
|
+
say("\t#{cmd}") if verbosity == VERBOSE
|
61
|
+
if (return_output)
|
62
|
+
val = `#{cmd}`
|
63
|
+
else
|
64
|
+
cmd += ">/dev/null"if verbosity == QUIET
|
65
|
+
system(cmd)
|
66
|
+
end
|
67
|
+
unless $?.success?
|
68
|
+
puts caller.join("\n\t")
|
69
|
+
raise "Failure: '#{cmd}' returned #{$?.exitstatus}"
|
70
|
+
end
|
71
|
+
val
|
72
|
+
end
|
73
|
+
end
|
data/lib/sub/version.rb
CHANGED
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: sub
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.4.0
|
7
|
+
date: 2007-11-18 00:00:00 -08:00
|
8
8
|
summary: sub is a wrapper for svn that adds faster/safer updates, easy trunk checkout, etc.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- lib/sub/external.rb
|
42
42
|
- lib/sub/root.rb
|
43
43
|
- lib/sub/status.rb
|
44
|
+
- lib/sub/sys.rb
|
44
45
|
- lib/sub/version.rb
|
45
46
|
- tasks/environment.rake
|
46
47
|
test_files: []
|