svnauto 1.0.2 → 1.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/bin/sc +2 -10
- data/{lib/sc/constants.rb → bin/sva} +8 -13
- data/lib/{sc.rb → svnauto.rb} +13 -9
- data/lib/{sc → svnauto}/command.rb +8 -3
- data/lib/{sc → svnauto}/commands/bug.rb +5 -2
- data/lib/{sc → svnauto}/commands/checkout.rb +3 -1
- data/lib/{sc → svnauto}/commands/config.rb +18 -2
- data/lib/{sc → svnauto}/commands/create.rb +2 -1
- data/lib/{sc → svnauto}/commands/experimental.rb +19 -5
- data/lib/svnauto/commands/externals.rb +128 -0
- data/lib/{sc → svnauto}/commands/info.rb +1 -1
- data/lib/{sc → svnauto}/commands/list.rb +3 -1
- data/lib/{sc → svnauto}/commands/release.rb +39 -7
- data/lib/{sc → svnauto}/config_file.rb +3 -3
- data/lib/{sc/path.rb → svnauto/constants.rb} +24 -18
- data/lib/{sc → svnauto}/dispatcher.rb +11 -4
- data/lib/svnauto/path.rb +138 -0
- data/lib/{sc → svnauto}/project.rb +16 -11
- data/lib/{sc → svnauto}/repository.rb +2 -2
- data/lib/{sc → svnauto}/svn.rb +51 -10
- data/lib/svnauto/svn_externals.rb +187 -0
- data/lib/svnauto/svn_info.rb +96 -0
- data/lib/{sc → svnauto}/version.rb +2 -2
- data/test/setup.rb +24 -28
- data/test/test_bug.rb +10 -10
- data/test/test_checkout.rb +3 -3
- data/test/test_create.rb +3 -3
- data/test/test_experimental.rb +33 -18
- data/test/test_externals.rb +55 -0
- data/test/test_path.rb +148 -0
- data/test/test_release.rb +11 -11
- data/test/test_svninfo.rb +17 -0
- data/test/test_version.rb +16 -16
- metadata +35 -42
- data/INSTALL +0 -48
- data/LICENSE +0 -22
- data/README +0 -81
- data/THANKS +0 -8
- data/TODO +0 -8
- data/doc/manual.txt +0 -241
@@ -24,12 +24,12 @@
|
|
24
24
|
################################################################################
|
25
25
|
require 'yaml'
|
26
26
|
################################################################################
|
27
|
-
module
|
27
|
+
module SvnAuto
|
28
28
|
################################################################################
|
29
29
|
class ConfigFile
|
30
30
|
################################################################################
|
31
|
-
ENV_OVERRIDE = '
|
32
|
-
FILENAME = File.join(ENV['HOME'], '.
|
31
|
+
ENV_OVERRIDE = 'SvnAuto_CONFIG_FILE'
|
32
|
+
FILENAME = File.join(ENV['HOME'], '.sva')
|
33
33
|
|
34
34
|
################################################################################
|
35
35
|
# load in the user's configuration file
|
@@ -22,30 +22,36 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
#
|
24
24
|
################################################################################
|
25
|
-
module
|
26
|
-
|
27
|
-
module Path
|
25
|
+
module SvnAuto
|
26
|
+
module Constants
|
28
27
|
################################################################################
|
29
|
-
#
|
30
|
-
|
31
|
-
home = File.expand_path('~')
|
32
|
-
path.strip!
|
28
|
+
# The version number for this copy of SvnAuto
|
29
|
+
VERSION = '1.1.0'
|
33
30
|
|
34
|
-
|
35
|
-
|
31
|
+
################################################################################
|
32
|
+
# What to call myself
|
33
|
+
ME = 'sva'
|
36
34
|
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
################################################################################
|
36
|
+
# This regex is used to replace special characters in project/branch
|
37
|
+
# names so that they don't have problems with the file system or shell
|
38
|
+
NAME_RE = /[^\w.,_+:-]/
|
40
39
|
|
41
40
|
################################################################################
|
42
|
-
|
43
|
-
def self.relative_to_home (path)
|
44
|
-
home = File.expand_path('~')
|
45
|
-
path[0, home.length] == home ? path.sub(home, '~') : path
|
46
|
-
end
|
41
|
+
TERMINAL = HighLine.new
|
47
42
|
|
43
|
+
################################################################################
|
44
|
+
# We use the HOME environment variable all the time, make sure it's set up
|
45
|
+
if ENV['HOME'].nil?
|
46
|
+
if !ENV['HOMEDRIVE'].nil? and !ENV['HOMEPATH'].nil?
|
47
|
+
ENV['HOME'] = ENV['HOMEDRIVE'] + ENV['HOMEPATH']
|
48
|
+
elsif !ENV['USERPROFILE'].nil?
|
49
|
+
ENV['HOME'] = ENV['USERPROFILE']
|
50
|
+
else
|
51
|
+
raise "please set your HOME environment variable"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
48
55
|
end
|
49
|
-
################################################################################
|
50
56
|
end
|
51
57
|
################################################################################
|
@@ -25,7 +25,7 @@
|
|
25
25
|
require 'abbrev'
|
26
26
|
require 'optparse'
|
27
27
|
################################################################################
|
28
|
-
module
|
28
|
+
module SvnAuto
|
29
29
|
################################################################################
|
30
30
|
class Dispatcher
|
31
31
|
################################################################################
|
@@ -124,7 +124,7 @@ module SC
|
|
124
124
|
# make sure the selected repository is in the config file
|
125
125
|
if !klass.without_repository and !config.find_repository(@project.repository.name)
|
126
126
|
error = "the selected repository (#{@project.repository.url}) is not in the configuration file, "
|
127
|
-
error << "please use the '
|
127
|
+
error << "please use the 'sva config --add' command to add it first."
|
128
128
|
raise error
|
129
129
|
end
|
130
130
|
|
@@ -163,10 +163,10 @@ module SC
|
|
163
163
|
|
164
164
|
if klass.args_min and command_extras.length < klass.args_min
|
165
165
|
puts usage_for(klass)
|
166
|
-
|
166
|
+
raise "you did not give enough arguments"
|
167
167
|
elsif klass.args_max and command_extras.length > klass.args_max
|
168
168
|
puts usage_for(klass)
|
169
|
-
|
169
|
+
raise "you gave too many arguments"
|
170
170
|
end
|
171
171
|
|
172
172
|
klass.new.run(project, command_extras)
|
@@ -184,6 +184,13 @@ module SC
|
|
184
184
|
lines.first << "\nShortcuts: #{klass.name[1..-1].join(', ')}"
|
185
185
|
end
|
186
186
|
|
187
|
+
if klass.example
|
188
|
+
lines.first << "\n\n\nExamples:\n"
|
189
|
+
klass.example.each do |example|
|
190
|
+
lines.first << " #{Constants::ME} #{name} #{example}\n"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
187
194
|
lines.first << "\n\nOptions for the #{name} command:"
|
188
195
|
lines.join("\n")
|
189
196
|
end
|
data/lib/svnauto/path.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
module SvnAuto
|
26
|
+
################################################################################
|
27
|
+
module Path
|
28
|
+
################################################################################
|
29
|
+
# make a relative path to home absolute
|
30
|
+
def self.absolute_from_home (path)
|
31
|
+
home = File.expand_path('~')
|
32
|
+
path.strip!
|
33
|
+
|
34
|
+
# path may start with ~
|
35
|
+
path = File.expand_path(path) if path[0, 1] == '~'
|
36
|
+
|
37
|
+
# allow path to be relative or absolute
|
38
|
+
absolute?(path) ? path : File.join(home, path)
|
39
|
+
end
|
40
|
+
|
41
|
+
################################################################################
|
42
|
+
# make an absolute path relative to home
|
43
|
+
def self.relative_to_home (path)
|
44
|
+
home = File.expand_path('~')
|
45
|
+
absolute_path = path.dup
|
46
|
+
|
47
|
+
if case_insensitive_filesystem?
|
48
|
+
home.downcase!
|
49
|
+
absolute_path.downcase!
|
50
|
+
end
|
51
|
+
|
52
|
+
absolute_path[0, home.length] == home ? absolute_path.sub(home, '~') : absolute_path
|
53
|
+
end
|
54
|
+
|
55
|
+
################################################################################
|
56
|
+
# make file:// url from local path.
|
57
|
+
def self.to_url (path)
|
58
|
+
if windows?
|
59
|
+
if unc?(path)
|
60
|
+
return URI.escape("file:#{path}")
|
61
|
+
elsif has_drive?(path)
|
62
|
+
return URI.escape("file:///#{path}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
url?(path) ? path : URI.escape("file://#{path}")
|
67
|
+
end
|
68
|
+
|
69
|
+
################################################################################
|
70
|
+
# extract local path element from url.
|
71
|
+
def self.to_path (url)
|
72
|
+
unless windows?
|
73
|
+
return url.is_a?(URI) ? url.path : url
|
74
|
+
end
|
75
|
+
|
76
|
+
path = url.to_s
|
77
|
+
return path unless url?(path)
|
78
|
+
return path unless /^file:/ =~ path
|
79
|
+
|
80
|
+
path = path.sub(/^file:/, "")
|
81
|
+
return path unless path =~ /^\//
|
82
|
+
|
83
|
+
if has_drive?(path.sub(/^\/+/, ""))
|
84
|
+
path.sub(/^\/+/, "")
|
85
|
+
elsif unc?(path)
|
86
|
+
path
|
87
|
+
else
|
88
|
+
path.sub(/^\/+/, "/")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
################################################################################
|
93
|
+
# path is URL?
|
94
|
+
def self.url? (path)
|
95
|
+
begin
|
96
|
+
u = URI.parse(path)
|
97
|
+
return (u.scheme and u.scheme.length > 0)
|
98
|
+
rescue URI::InvalidURIError
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
################################################################################
|
104
|
+
# path is absolute?
|
105
|
+
def self.absolute? (path)
|
106
|
+
Pathname.new(path).absolute?
|
107
|
+
end
|
108
|
+
|
109
|
+
################################################################################
|
110
|
+
# Is this platform running the Windows OS?
|
111
|
+
def self.windows?
|
112
|
+
RUBY_PLATFORM.match(/djgpp|(cyg|ms|bcc)win|mingw/)
|
113
|
+
end
|
114
|
+
|
115
|
+
################################################################################
|
116
|
+
# path has Windows drive letter?
|
117
|
+
def self.has_drive? (path)
|
118
|
+
path.match(/^[a-z]:/i)
|
119
|
+
end
|
120
|
+
|
121
|
+
################################################################################
|
122
|
+
# path is UNC?
|
123
|
+
def self.unc? (path)
|
124
|
+
path.match(%r{^//[^/]})
|
125
|
+
end
|
126
|
+
|
127
|
+
################################################################################
|
128
|
+
# are we running under a case insensitive filesystem? (such as Windows)
|
129
|
+
def self.case_insensitive_filesystem?
|
130
|
+
ruby_version = Version.new(RUBY_VERSION) rescue nil
|
131
|
+
return !File::FNM_SYSCASE.zero? if ruby_version >= Version.new('1.8.5')
|
132
|
+
return windows?
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
################################################################################
|
137
|
+
end
|
138
|
+
################################################################################
|
@@ -22,7 +22,7 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
#
|
24
24
|
################################################################################
|
25
|
-
module
|
25
|
+
module SvnAuto
|
26
26
|
################################################################################
|
27
27
|
class Project
|
28
28
|
################################################################################
|
@@ -40,12 +40,12 @@ module SC
|
|
40
40
|
project = nil
|
41
41
|
|
42
42
|
# see if we're in an svn directory
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
info = SvnInfo.for('.', false)
|
44
|
+
|
45
|
+
if info.status
|
46
|
+
url = info.repository_root
|
47
|
+
project = info.url
|
47
48
|
|
48
|
-
if url and project
|
49
49
|
# try to find a matching repository
|
50
50
|
repository = config[:repositories].find {|r| r.url == url}
|
51
51
|
repository ||= Repository.new(:url => url)
|
@@ -62,7 +62,7 @@ module SC
|
|
62
62
|
################################################################################
|
63
63
|
# remove special characters from a project name
|
64
64
|
def self.clean_name (non_clean_name)
|
65
|
-
non_clean_name.gsub(
|
65
|
+
non_clean_name.gsub(Constants::NAME_RE, '_')
|
66
66
|
end
|
67
67
|
|
68
68
|
################################################################################
|
@@ -120,7 +120,7 @@ module SC
|
|
120
120
|
end
|
121
121
|
|
122
122
|
################################################################################
|
123
|
-
# get a list of the directories that
|
123
|
+
# get a list of the directories that SvnAuto uses
|
124
124
|
def directories
|
125
125
|
[
|
126
126
|
url,
|
@@ -141,7 +141,7 @@ module SC
|
|
141
141
|
def create
|
142
142
|
directories.each do |dir|
|
143
143
|
unless Svn.has_path(dir)
|
144
|
-
Svn.mkdir('-m', "
|
144
|
+
Svn.mkdir('-m', "#{Constants::ME}: creating #{dir} for project #{name}.", dir)
|
145
145
|
end
|
146
146
|
end
|
147
147
|
end
|
@@ -232,11 +232,16 @@ module SC
|
|
232
232
|
raise message
|
233
233
|
end
|
234
234
|
|
235
|
-
Svn.commit('-m', "
|
235
|
+
Svn.commit('-m', "#{Constants::ME}: merging branch using tags #{start_tag} and #{end_tag}")
|
236
236
|
end
|
237
237
|
|
238
238
|
# clean up after merge
|
239
|
-
|
239
|
+
begin
|
240
|
+
FileUtils.rm_r(dir)
|
241
|
+
rescue
|
242
|
+
# turn the error message into something the user will understand
|
243
|
+
raise "failed to remove tmp merge directory: #{dir}"
|
244
|
+
end
|
240
245
|
end
|
241
246
|
|
242
247
|
|
@@ -22,7 +22,7 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
#
|
24
24
|
################################################################################
|
25
|
-
module
|
25
|
+
module SvnAuto
|
26
26
|
################################################################################
|
27
27
|
class Repository
|
28
28
|
################################################################################
|
@@ -46,7 +46,7 @@ module SC
|
|
46
46
|
def self.ask (config=nil)
|
47
47
|
options = {}
|
48
48
|
|
49
|
-
options[:name] = Constants::TERMINAL.ask("Repository Name (used with
|
49
|
+
options[:name] = Constants::TERMINAL.ask("Repository Name (used with sva -r): ")
|
50
50
|
|
51
51
|
options[:url] = Constants::TERMINAL.ask("Repository URL: ") do |question|
|
52
52
|
if config
|
data/lib/{sc → svnauto}/svn.rb
RENAMED
@@ -22,26 +22,24 @@
|
|
22
22
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
23
|
#
|
24
24
|
################################################################################
|
25
|
-
module
|
25
|
+
module SvnAuto
|
26
26
|
class Svn
|
27
27
|
################################################################################
|
28
28
|
# Since we're using regexes, we can only support one language, so we pick
|
29
29
|
# English. This setting forces svn to output English.
|
30
30
|
ENV['LC_MESSAGES'] = 'C'
|
31
|
+
ENV['LC_ALL'] = 'en_US'
|
31
32
|
|
32
33
|
################################################################################
|
33
34
|
# Run a svn subcommand
|
34
|
-
def self._svn (*args)
|
35
|
+
def self._svn (*args, &block)
|
35
36
|
subcommand = caller(0)[0].sub(/^.*:in\s`([^']+).*$/, '\1')
|
36
|
-
shell_line = "#{subcommand} #{args.join(' ')}"
|
37
37
|
|
38
|
+
shell_line = "#{subcommand} #{args.join(' ')}"
|
38
39
|
Constants::TERMINAL.say(Constants::TERMINAL.color("svn #{shell_line}", :yellow)) if $DEBUG
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
system("svn #{shell_line}")
|
44
|
-
end
|
41
|
+
block ||= lambda {|line| print(line)}
|
42
|
+
subprocess('svn', subcommand, *args, &block)
|
45
43
|
|
46
44
|
if $? != 0 and !['info', 'list', 'merge'].include?(subcommand)
|
47
45
|
raise "svn command failed: #{shell_line}"
|
@@ -60,11 +58,28 @@ module SC
|
|
60
58
|
true
|
61
59
|
end
|
62
60
|
|
61
|
+
################################################################################
|
62
|
+
# get the current revision number from the given path
|
63
|
+
def self.current_revision (path)
|
64
|
+
rev = nil
|
65
|
+
|
66
|
+
Svn.info(path) do |line|
|
67
|
+
m = line.match(/^Revision:\s+(\d+)$/) and rev = m[1].to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
rev
|
71
|
+
end
|
72
|
+
|
63
73
|
################################################################################
|
64
74
|
# create a branch using svn copy
|
65
|
-
def self.branch (project, source, dest)
|
75
|
+
def self.branch (project, source, dest, options={})
|
66
76
|
return if self.has_path(dest)
|
67
77
|
|
78
|
+
configuration = {
|
79
|
+
:revision => 'HEAD',
|
80
|
+
|
81
|
+
}.update(options)
|
82
|
+
|
68
83
|
relative_dest = dest.sub("#{project.url}/", '')
|
69
84
|
dest_dirs = relative_dest.split(/\//)
|
70
85
|
dest_dirs.pop # don't need the last one
|
@@ -77,7 +92,8 @@ module SC
|
|
77
92
|
self.mkdir('-m', "'#{Constants::ME}: creating #{branch_or_tag} path #{dest_path}'", dest_path) unless self.has_path(dest_path)
|
78
93
|
end
|
79
94
|
|
80
|
-
self.copy('-m', "'#{Constants::ME}: creating #{branch_or_tag} #{relative_dest}'",
|
95
|
+
self.copy('-m', "'#{Constants::ME}: creating #{branch_or_tag} #{relative_dest}'",
|
96
|
+
'-r', configuration[:revision], source, dest)
|
81
97
|
end
|
82
98
|
|
83
99
|
################################################################################
|
@@ -96,6 +112,31 @@ module SC
|
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
115
|
+
################################################################################
|
116
|
+
private
|
117
|
+
|
118
|
+
################################################################################
|
119
|
+
def self.subprocess (*args, &block)
|
120
|
+
tmp = Tempfile.new(Constants::ME)
|
121
|
+
stdout = $stdout.dup
|
122
|
+
stderr = $stderr.dup
|
123
|
+
|
124
|
+
begin
|
125
|
+
$stdout.reopen(tmp)
|
126
|
+
$stderr.reopen(tmp)
|
127
|
+
system(*args)
|
128
|
+
ensure
|
129
|
+
$stdout.reopen(stdout)
|
130
|
+
$stderr.reopen(stderr)
|
131
|
+
end
|
132
|
+
|
133
|
+
tmp.close
|
134
|
+
IO.foreach(tmp.path, &block)
|
135
|
+
|
136
|
+
ensure
|
137
|
+
File.unlink(tmp.path) if tmp
|
138
|
+
end
|
139
|
+
|
99
140
|
end
|
100
141
|
end
|
101
142
|
################################################################################
|
@@ -0,0 +1,187 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
require 'fileutils'
|
26
|
+
################################################################################
|
27
|
+
module SvnAuto
|
28
|
+
################################################################################
|
29
|
+
class SvnExternals
|
30
|
+
################################################################################
|
31
|
+
SVN_EXT = 'svn:externals'
|
32
|
+
SVNAUTO_LOCKED = 'sc:locked'
|
33
|
+
|
34
|
+
################################################################################
|
35
|
+
attr_reader :unlocked
|
36
|
+
attr_reader :locked
|
37
|
+
|
38
|
+
################################################################################
|
39
|
+
def initialize (directories=[], recursive=true)
|
40
|
+
if directories.empty?
|
41
|
+
Dir.foreach('.') {|f| directories << f if f[0,1] != '.' and File.directory?(f)}
|
42
|
+
end
|
43
|
+
|
44
|
+
fetch_unlocked(directories, recursive)
|
45
|
+
fetch_locked(directories, recursive)
|
46
|
+
end
|
47
|
+
|
48
|
+
################################################################################
|
49
|
+
def lock
|
50
|
+
affected_parents = Set.new
|
51
|
+
|
52
|
+
@unlocked.each do |external|
|
53
|
+
affected_parents << File.dirname(external.path)
|
54
|
+
@locked << external
|
55
|
+
|
56
|
+
FileUtils.rm_r(external.path)
|
57
|
+
Svn.export('-r', external.revision, external.url, external.path)
|
58
|
+
Svn.add(external.path)
|
59
|
+
end
|
60
|
+
|
61
|
+
@unlocked.clear
|
62
|
+
update_properties_for(affected_parents)
|
63
|
+
|
64
|
+
short_parents = affected_parents.to_a
|
65
|
+
Svn.commit('-m', "#{Constants::ME}: updated lock properties for #{short_parents.join(', ')}", *short_parents)
|
66
|
+
end
|
67
|
+
|
68
|
+
################################################################################
|
69
|
+
def unlock
|
70
|
+
affected_parents = Set.new
|
71
|
+
|
72
|
+
@locked.each do |external|
|
73
|
+
affected_parents << File.dirname(external.path)
|
74
|
+
@unlocked << external
|
75
|
+
Svn.delete(external.path)
|
76
|
+
Svn.commit('-m', "#{Constants::ME}: switch #{external.path} from locked to unlocked", external.path)
|
77
|
+
FileUtils.rm_rf(external.path)
|
78
|
+
end
|
79
|
+
|
80
|
+
@locked.clear
|
81
|
+
update_properties_for(affected_parents)
|
82
|
+
short_parents = affected_parents.to_a
|
83
|
+
|
84
|
+
Svn.update(*short_parents)
|
85
|
+
Svn.commit('-m', "#{Constants::ME}: updated lock properties for #{short_parents.join(', ')}", *short_parents)
|
86
|
+
end
|
87
|
+
|
88
|
+
################################################################################
|
89
|
+
private
|
90
|
+
|
91
|
+
################################################################################
|
92
|
+
def fetch_unlocked (directories, recursive)
|
93
|
+
@unlocked = []
|
94
|
+
directories.each {|d| property_lookup(d, recursive, SVN_EXT) {|e| @unlocked << e}}
|
95
|
+
end
|
96
|
+
|
97
|
+
################################################################################
|
98
|
+
def fetch_locked (directories, recursive)
|
99
|
+
@locked = []
|
100
|
+
directories.each {|d| property_lookup(d, recursive, SVNAUTO_LOCKED) {|e| @locked << e}}
|
101
|
+
end
|
102
|
+
|
103
|
+
################################################################################
|
104
|
+
def property_lookup (dir, recursive, property, &block)
|
105
|
+
# properties will be set on the parent directory
|
106
|
+
parent = File.dirname(dir)
|
107
|
+
base = File.basename(dir)
|
108
|
+
|
109
|
+
@property_cache ||= {}
|
110
|
+
@property_cache[property] ||= {}
|
111
|
+
|
112
|
+
unless @property_cache[property].has_key?(parent)
|
113
|
+
@property_cache[property][parent] = {}
|
114
|
+
|
115
|
+
Svn.propget(property, parent) do |line|
|
116
|
+
next if line.match(/^\s*$/)
|
117
|
+
|
118
|
+
if m = line.match(/^\s*(\S+)\s+-r\s*(\d+)\s+(.+)\s*$/)
|
119
|
+
path = File.join(parent, m[1])
|
120
|
+
@property_cache[property][parent][m[1]] = SvnInfo.new(:path => path, :revision => m[2], :url => m[3])
|
121
|
+
elsif m = line.match(/^\s*(\S+)\s+(.+)\s*$/)
|
122
|
+
path = File.join(parent, m[1])
|
123
|
+
@property_cache[property][parent][m[1]] = SvnInfo.for(path, true)
|
124
|
+
else
|
125
|
+
raise "can't parse #{property} entry from #{parent}: #{line.chomp}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if @property_cache[property][parent].has_key?(base)
|
131
|
+
yield(@property_cache[property][parent][base])
|
132
|
+
elsif recursive
|
133
|
+
# we don't recurse into externals because when we lock the parent
|
134
|
+
# external, it will lock any sub directories that are also externals
|
135
|
+
directories = []
|
136
|
+
|
137
|
+
Dir.foreach(dir) do |file|
|
138
|
+
next if file[0,1] == '.'
|
139
|
+
name = File.join(dir, file)
|
140
|
+
directories << name if File.directory?(name)
|
141
|
+
end
|
142
|
+
|
143
|
+
directories.each {|d| property_lookup(d, recursive, property, &block)}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
################################################################################
|
148
|
+
def update_properties_for (parents)
|
149
|
+
parents.each do |dir|
|
150
|
+
set_properties_for(dir, SVN_EXT, generate_properties_for(dir, SVN_EXT, false, @unlocked, @locked))
|
151
|
+
set_properties_for(dir, SVNAUTO_LOCKED, generate_properties_for(dir, SVNAUTO_LOCKED, true, @locked, @unlocked))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
################################################################################
|
156
|
+
def generate_properties_for (dir, property, with_revision, includes, excludes)
|
157
|
+
output_set = includes.select {|i| File.dirname(i.path) == dir}
|
158
|
+
result = ""
|
159
|
+
|
160
|
+
@property_cache[property][dir].values.each do |external|
|
161
|
+
output_set << external unless excludes.include?(external)
|
162
|
+
end
|
163
|
+
|
164
|
+
output_set.uniq.each do |external|
|
165
|
+
result << File.basename(external.path) << " "
|
166
|
+
result << "-r#{external.revision} " if with_revision
|
167
|
+
result << external.url << "\n"
|
168
|
+
end
|
169
|
+
|
170
|
+
result
|
171
|
+
end
|
172
|
+
|
173
|
+
################################################################################
|
174
|
+
def set_properties_for (dir, property, data)
|
175
|
+
begin
|
176
|
+
file = Tempfile.new('sc-property-setting')
|
177
|
+
file << data
|
178
|
+
file.close
|
179
|
+
Svn.propset('--file', file.path, property, dir)
|
180
|
+
ensure
|
181
|
+
file.unlink
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
################################################################################
|