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.
@@ -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
@@ -10,5 +10,6 @@ lib/sub/array.rb
10
10
  lib/sub/external.rb
11
11
  lib/sub/root.rb
12
12
  lib/sub/status.rb
13
+ lib/sub/sys.rb
13
14
  lib/sub/version.rb
14
15
  tasks/environment.rake
data/lib/sub.rb CHANGED
@@ -1,5 +1,6 @@
1
- require 'sub/app'
2
1
  require 'sub/array'
2
+ require 'sub/sys'
3
+ require 'sub/app'
3
4
  require 'sub/external'
4
5
  require 'sub/status'
5
6
  require 'sub/version'
@@ -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 = :up
13
+ command = defaults[:command]
16
14
  options = {}
17
- while still_parsing_options
15
+ real_args = []
16
+ while !args.empty?
18
17
  case args[0]
19
- when 'up', 'co', 'help'
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 '--version'
34
- puts "Sub version #{Sub::VERSION::STRING}"
35
- exit 0
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
- still_parsing_options = false
49
+ real_args << args[0]
38
50
  end
39
- args.shift if still_parsing_options
51
+ args.shift
40
52
  end
41
53
 
42
- Sub.new(options, args)
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
- @verbosity = options[:verbosity]
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] || ENV['SUB_BASE_URL'] || ENV['SVN'] || DEFAULT_BASE_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 #{url}/#{project}/trunk #{dir_name}")
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
- sub co project_name [dir_name]
94
- checks out [base_url]/project_name/trunk into ./project_name (or dir_name if specified)
95
- sub up [dir]*
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, self, clean).update
186
+ Root.new(root, clean).update
129
187
  end
130
188
 
131
- def externals(root)
132
- Root.new(root, self, clean).externals
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 parse_externals(st)
136
- exts = []
137
- st.split("\n").select do |line|
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
- exts
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 say(msg)
152
- puts msg if verbosity > QUIET
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 svn(cmd)
156
- svncmd = "svn"
157
- svncmd += " --quiet" if (cmd =~ /^(up|co)/ && verbosity == QUIET)
158
- run("#{svncmd} #{cmd}")
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
-
@@ -1,36 +1,31 @@
1
1
  class External
2
+ include Sys::Client
2
3
 
3
- def self.externals_in_directory(parent, app)
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.new(parent, prop_line, app)
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, :app
13
+ attr_reader :name, :path, :url, :revision
14
14
 
15
- def initialize(parent, property, app = nil)
16
- match = /^(\S+)\s+(-r\s*\d*\s+)?(\S+)\s*$/.match(property)
17
- @name = match[1]
18
- @revision = match[2] ? match[2].gsub(/\D/,'').to_i : nil
19
- @url = match[3]
20
- @path = "#{parent}/#{@name}"
21
- @app = app
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 svn(cmd)
29
- app.svn(cmd)
30
- end
31
-
32
- def run(cmd, return_output = false)
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
- "External[name=#{name}, path=#{path}, url=#{url}, revision=#{revision}]"
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} | grep -v 'At revision'")
56
+ svn("up #{rev} #{path}", true)
59
57
  end
60
58
 
61
59
  def checkout
@@ -1,25 +1,14 @@
1
1
  class Root
2
- attr_reader :app, :root_path
2
+ include Sys::Client
3
+
4
+ attr_reader :root_path
3
5
 
4
- def initialize(root_path, app, clean = false)
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
- externals_before = externals
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(externals)
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
- externals.each do |external|
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(run("svn st #{root_path}", true))
127
+ @status ||= Status.new(svn("st #{root_path}", true))
106
128
  end
107
129
 
108
- def externals
130
+ def all_externals
109
131
  exts = []
110
132
  directories_containing_externals.collect do |parent|
111
- exts += External.externals_in_directory(parent, app)
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
@@ -25,7 +25,6 @@ class Status
25
25
  end
26
26
  end
27
27
 
28
-
29
28
  def initialize(text)
30
29
  @lines = text.split("\n").collect do |line|
31
30
  Status::Line.new(line)
@@ -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
@@ -1,7 +1,7 @@
1
1
  class Sub #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 3
4
+ MINOR = 4
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.2
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.3.0
7
- date: 2007-10-02 00:00:00 -07:00
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: []