##// END OF EJS Templates
Merged r9742 from trunk....
Jean-Philippe Lang -
r9568:93d8f998842f
parent child
Show More
@@ -1,288 +1,289
1 #!/usr/bin/env ruby
1 #!/usr/bin/env ruby
2
2
3 require 'optparse'
3 require 'optparse'
4 require 'find'
4 require 'find'
5 require 'etc'
5 require 'etc'
6 require 'rubygems'
6
7
7 Version = "1.4"
8 Version = "1.4"
8 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
9 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
9
10
10 $verbose = 0
11 $verbose = 0
11 $quiet = false
12 $quiet = false
12 $redmine_host = ''
13 $redmine_host = ''
13 $repos_base = ''
14 $repos_base = ''
14 $svn_owner = 'root'
15 $svn_owner = 'root'
15 $svn_group = 'root'
16 $svn_group = 'root'
16 $use_groupid = true
17 $use_groupid = true
17 $svn_url = false
18 $svn_url = false
18 $test = false
19 $test = false
19 $force = false
20 $force = false
20 $scm = 'Subversion'
21 $scm = 'Subversion'
21
22
22 def log(text, options={})
23 def log(text, options={})
23 level = options[:level] || 0
24 level = options[:level] || 0
24 puts text unless $quiet or level > $verbose
25 puts text unless $quiet or level > $verbose
25 exit 1 if options[:exit]
26 exit 1 if options[:exit]
26 end
27 end
27
28
28 def system_or_raise(command)
29 def system_or_raise(command)
29 raise "\"#{command}\" failed" unless system command
30 raise "\"#{command}\" failed" unless system command
30 end
31 end
31
32
32 module SCM
33 module SCM
33
34
34 module Subversion
35 module Subversion
35 def self.create(path)
36 def self.create(path)
36 system_or_raise "svnadmin create #{path}"
37 system_or_raise "svnadmin create #{path}"
37 end
38 end
38 end
39 end
39
40
40 module Git
41 module Git
41 def self.create(path)
42 def self.create(path)
42 Dir.mkdir path
43 Dir.mkdir path
43 Dir.chdir(path) do
44 Dir.chdir(path) do
44 system_or_raise "git --bare init --shared"
45 system_or_raise "git --bare init --shared"
45 system_or_raise "git update-server-info"
46 system_or_raise "git update-server-info"
46 end
47 end
47 end
48 end
48 end
49 end
49
50
50 end
51 end
51
52
52 def read_key_from_file(filename)
53 def read_key_from_file(filename)
53 begin
54 begin
54 $api_key = File.read(filename).strip
55 $api_key = File.read(filename).strip
55 rescue Exception => e
56 rescue Exception => e
56 $stderr.puts "Unable to read the key from #{filename}: #{e.message}"
57 $stderr.puts "Unable to read the key from #{filename}: #{e.message}"
57 exit 1
58 exit 1
58 end
59 end
59 end
60 end
60
61
61 def set_scm(scm)
62 def set_scm(scm)
62 $scm = scm.capitalize
63 $scm = scm.capitalize
63 unless SUPPORTED_SCM.include?($scm)
64 unless SUPPORTED_SCM.include?($scm)
64 log("Invalid SCM: #{$scm}\nValid SCM are: #{SUPPORTED_SCM.join(', ')}", :exit => true)
65 log("Invalid SCM: #{$scm}\nValid SCM are: #{SUPPORTED_SCM.join(', ')}", :exit => true)
65 end
66 end
66 end
67 end
67
68
68 optparse = OptionParser.new do |opts|
69 optparse = OptionParser.new do |opts|
69 opts.banner = "Usage: reposman.rb [OPTIONS...] -s [DIR] -r [HOST] -k [KEY]"
70 opts.banner = "Usage: reposman.rb [OPTIONS...] -s [DIR] -r [HOST] -k [KEY]"
70 opts.separator("")
71 opts.separator("")
71 opts.separator("Manages your repositories with Redmine.")
72 opts.separator("Manages your repositories with Redmine.")
72 opts.separator("")
73 opts.separator("")
73 opts.separator("Required arguments:")
74 opts.separator("Required arguments:")
74 opts.on("-s", "--svn-dir DIR", "use DIR as base directory for svn repositories") {|v| $repos_base = v}
75 opts.on("-s", "--svn-dir DIR", "use DIR as base directory for svn repositories") {|v| $repos_base = v}
75 opts.on("-r", "--redmine-host HOST","assume Redmine is hosted on HOST. Examples:",
76 opts.on("-r", "--redmine-host HOST","assume Redmine is hosted on HOST. Examples:",
76 " -r redmine.example.net",
77 " -r redmine.example.net",
77 " -r http://redmine.example.net",
78 " -r http://redmine.example.net",
78 " -r https://redmine.example.net") {|v| $redmine_host = v}
79 " -r https://redmine.example.net") {|v| $redmine_host = v}
79 opts.on("-k", "--key KEY", "use KEY as the Redmine API key",
80 opts.on("-k", "--key KEY", "use KEY as the Redmine API key",
80 "(you can use --key-file option as an alternative)") {|v| $api_key = v}
81 "(you can use --key-file option as an alternative)") {|v| $api_key = v}
81 opts.separator("")
82 opts.separator("")
82 opts.separator("Options:")
83 opts.separator("Options:")
83 opts.on("-o", "--owner OWNER", "owner of the repository. using the rails login",
84 opts.on("-o", "--owner OWNER", "owner of the repository. using the rails login",
84 "allows users to browse the repository within",
85 "allows users to browse the repository within",
85 "Redmine even for private projects. If you want to",
86 "Redmine even for private projects. If you want to",
86 "share repositories through Redmine.pm, you need",
87 "share repositories through Redmine.pm, you need",
87 "to use the apache owner.") {|v| $svn_owner = v; $use_groupid = false}
88 "to use the apache owner.") {|v| $svn_owner = v; $use_groupid = false}
88 opts.on("-g", "--group GROUP", "group of the repository (default: root)") {|v| $svn_group = v; $use_groupid = false}
89 opts.on("-g", "--group GROUP", "group of the repository (default: root)") {|v| $svn_group = v; $use_groupid = false}
89 opts.on("-u", "--url URL", "the base url Redmine will use to access your",
90 opts.on("-u", "--url URL", "the base url Redmine will use to access your",
90 "repositories. This option is used to register",
91 "repositories. This option is used to register",
91 "the repositories in Redmine automatically. The",
92 "the repositories in Redmine automatically. The",
92 "project identifier will be appended to this url.",
93 "project identifier will be appended to this url.",
93 "Examples:",
94 "Examples:",
94 " -u https://example.net/svn",
95 " -u https://example.net/svn",
95 " -u file:///var/svn/",
96 " -u file:///var/svn/",
96 "if this option isn't set, reposman won't register",
97 "if this option isn't set, reposman won't register",
97 "the repositories in Redmine") {|v| $svn_url = v}
98 "the repositories in Redmine") {|v| $svn_url = v}
98 opts.on( "--scm SCM", "the kind of SCM repository you want to create",
99 opts.on( "--scm SCM", "the kind of SCM repository you want to create",
99 "(and register) in Redmine (default: Subversion).",
100 "(and register) in Redmine (default: Subversion).",
100 "reposman is able to create Git and Subversion",
101 "reposman is able to create Git and Subversion",
101 "repositories.",
102 "repositories.",
102 "For all other kind, you must specify a --command",
103 "For all other kind, you must specify a --command",
103 "option") {|v| set_scm(v)}
104 "option") {|v| set_scm(v)}
104 opts.on("-c", "--command COMMAND", "use this command instead of `svnadmin create` to",
105 opts.on("-c", "--command COMMAND", "use this command instead of `svnadmin create` to",
105 "create a repository. This option can be used to",
106 "create a repository. This option can be used to",
106 "create repositories other than subversion and git",
107 "create repositories other than subversion and git",
107 "kind.",
108 "kind.",
108 "This command override the default creation for",
109 "This command override the default creation for",
109 "git and subversion.") {|v| $command = v}
110 "git and subversion.") {|v| $command = v}
110 opts.on( "--key-file FILE", "path to a file that contains the Redmine API key",
111 opts.on( "--key-file FILE", "path to a file that contains the Redmine API key",
111 "(use this option instead of --key if you don't",
112 "(use this option instead of --key if you don't",
112 "want the key to appear in the command line)") {|v| read_key_from_file(v)}
113 "want the key to appear in the command line)") {|v| read_key_from_file(v)}
113 opts.on("-t", "--test", "only show what should be done") {$test = true}
114 opts.on("-t", "--test", "only show what should be done") {$test = true}
114 opts.on("-f", "--force", "force repository creation even if the project", "repository is already declared in Redmine") {$force = true}
115 opts.on("-f", "--force", "force repository creation even if the project", "repository is already declared in Redmine") {$force = true}
115 opts.on("-v", "--verbose", "verbose") {$verbose += 1}
116 opts.on("-v", "--verbose", "verbose") {$verbose += 1}
116 opts.on("-V", "--version", "show version and exit") {puts Version; exit}
117 opts.on("-V", "--version", "show version and exit") {puts Version; exit}
117 opts.on("-h", "--help", "show help and exit") {puts opts; exit 1}
118 opts.on("-h", "--help", "show help and exit") {puts opts; exit 1}
118 opts.on("-q", "--quiet", "no log") {$quiet = true}
119 opts.on("-q", "--quiet", "no log") {$quiet = true}
119 opts.separator("")
120 opts.separator("")
120 opts.separator("Examples:")
121 opts.separator("Examples:")
121 opts.separator(" reposman.rb --svn-dir=/var/svn --redmine-host=redmine.host")
122 opts.separator(" reposman.rb --svn-dir=/var/svn --redmine-host=redmine.host")
122 opts.separator(" reposman.rb -s /var/git -r redmine.host -u http://git.host --scm git")
123 opts.separator(" reposman.rb -s /var/git -r redmine.host -u http://git.host --scm git")
123 opts.separator("")
124 opts.separator("")
124 opts.separator("You can find more information on the redmine's wiki:\nhttp://www.redmine.org/projects/redmine/wiki/HowTos")
125 opts.separator("You can find more information on the redmine's wiki:\nhttp://www.redmine.org/projects/redmine/wiki/HowTos")
125
126
126 opts.summary_width = 25
127 opts.summary_width = 25
127 end
128 end
128 optparse.parse!
129 optparse.parse!
129
130
130 if $test
131 if $test
131 log("running in test mode")
132 log("running in test mode")
132 end
133 end
133
134
134 # Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
135 # Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
135 if $command.nil?
136 if $command.nil?
136 begin
137 begin
137 scm_module = SCM.const_get($scm)
138 scm_module = SCM.const_get($scm)
138 rescue
139 rescue
139 log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
140 log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
140 end
141 end
141 end
142 end
142
143
143 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
144 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
144
145
145 if ($redmine_host.empty? or $repos_base.empty?)
146 if ($redmine_host.empty? or $repos_base.empty?)
146 puts "Some arguments are missing. Use reposman.rb --help for getting help."
147 puts "Some arguments are missing. Use reposman.rb --help for getting help."
147 exit 1
148 exit 1
148 end
149 end
149
150
150 unless File.directory?($repos_base)
151 unless File.directory?($repos_base)
151 log("directory '#{$repos_base}' doesn't exists", :exit => true)
152 log("directory '#{$repos_base}' doesn't exists", :exit => true)
152 end
153 end
153
154
154 begin
155 begin
155 require 'active_resource'
156 require 'active_resource'
156 rescue LoadError
157 rescue LoadError
157 log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
158 log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
158 end
159 end
159
160
160 class Project < ActiveResource::Base
161 class Project < ActiveResource::Base
161 self.headers["User-agent"] = "Redmine repository manager/#{Version}"
162 self.headers["User-agent"] = "Redmine repository manager/#{Version}"
162 self.format = :xml
163 self.format = :xml
163 end
164 end
164
165
165 log("querying Redmine for projects...", :level => 1);
166 log("querying Redmine for projects...", :level => 1);
166
167
167 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
168 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
168 $redmine_host.gsub!(/\/$/, '')
169 $redmine_host.gsub!(/\/$/, '')
169
170
170 Project.site = "#{$redmine_host}/sys";
171 Project.site = "#{$redmine_host}/sys";
171
172
172 begin
173 begin
173 # Get all active projects that have the Repository module enabled
174 # Get all active projects that have the Repository module enabled
174 projects = Project.find(:all, :params => {:key => $api_key})
175 projects = Project.find(:all, :params => {:key => $api_key})
175 rescue ActiveResource::ForbiddenAccess
176 rescue ActiveResource::ForbiddenAccess
176 log("Request was denied by your Redmine server. Make sure that 'WS for repository management' is enabled in application settings and that you provided the correct API key.")
177 log("Request was denied by your Redmine server. Make sure that 'WS for repository management' is enabled in application settings and that you provided the correct API key.")
177 rescue => e
178 rescue => e
178 log("Unable to connect to #{Project.site}: #{e}", :exit => true)
179 log("Unable to connect to #{Project.site}: #{e}", :exit => true)
179 end
180 end
180
181
181 if projects.nil?
182 if projects.nil?
182 log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
183 log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
183 end
184 end
184
185
185 log("retrieved #{projects.size} projects", :level => 1)
186 log("retrieved #{projects.size} projects", :level => 1)
186
187
187 def set_owner_and_rights(project, repos_path, &block)
188 def set_owner_and_rights(project, repos_path, &block)
188 if mswin?
189 if mswin?
189 yield if block_given?
190 yield if block_given?
190 else
191 else
191 uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
192 uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
192 right = project.is_public ? 0775 : 0770
193 right = project.is_public ? 0775 : 0770
193 yield if block_given?
194 yield if block_given?
194 Find.find(repos_path) do |f|
195 Find.find(repos_path) do |f|
195 File.chmod right, f
196 File.chmod right, f
196 File.chown uid, gid, f
197 File.chown uid, gid, f
197 end
198 end
198 end
199 end
199 end
200 end
200
201
201 def other_read_right?(file)
202 def other_read_right?(file)
202 (File.stat(file).mode & 0007).zero? ? false : true
203 (File.stat(file).mode & 0007).zero? ? false : true
203 end
204 end
204
205
205 def owner_name(file)
206 def owner_name(file)
206 mswin? ?
207 mswin? ?
207 $svn_owner :
208 $svn_owner :
208 Etc.getpwuid( File.stat(file).uid ).name
209 Etc.getpwuid( File.stat(file).uid ).name
209 end
210 end
210
211
211 def mswin?
212 def mswin?
212 (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
213 (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
213 end
214 end
214
215
215 projects.each do |project|
216 projects.each do |project|
216 log("treating project #{project.name}", :level => 1)
217 log("treating project #{project.name}", :level => 1)
217
218
218 if project.identifier.empty?
219 if project.identifier.empty?
219 log("\tno identifier for project #{project.name}")
220 log("\tno identifier for project #{project.name}")
220 next
221 next
221 elsif not project.identifier.match(/^[a-z0-9\-_]+$/)
222 elsif not project.identifier.match(/^[a-z0-9\-_]+$/)
222 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
223 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
223 next;
224 next;
224 end
225 end
225
226
226 repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
227 repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
227
228
228 if File.directory?(repos_path)
229 if File.directory?(repos_path)
229 # we must verify that repository has the good owner and the good
230 # we must verify that repository has the good owner and the good
230 # rights before leaving
231 # rights before leaving
231 other_read = other_read_right?(repos_path)
232 other_read = other_read_right?(repos_path)
232 owner = owner_name(repos_path)
233 owner = owner_name(repos_path)
233 next if project.is_public == other_read and owner == $svn_owner
234 next if project.is_public == other_read and owner == $svn_owner
234
235
235 if $test
236 if $test
236 log("\tchange mode on #{repos_path}")
237 log("\tchange mode on #{repos_path}")
237 next
238 next
238 end
239 end
239
240
240 begin
241 begin
241 set_owner_and_rights(project, repos_path)
242 set_owner_and_rights(project, repos_path)
242 rescue Errno::EPERM => e
243 rescue Errno::EPERM => e
243 log("\tunable to change mode on #{repos_path} : #{e}\n")
244 log("\tunable to change mode on #{repos_path} : #{e}\n")
244 next
245 next
245 end
246 end
246
247
247 log("\tmode change on #{repos_path}");
248 log("\tmode change on #{repos_path}");
248
249
249 else
250 else
250 # if repository is already declared in redmine, we don't create
251 # if repository is already declared in redmine, we don't create
251 # unless user use -f with reposman
252 # unless user use -f with reposman
252 if $force == false and project.respond_to?(:repository)
253 if $force == false and project.respond_to?(:repository)
253 log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
254 log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
254 next
255 next
255 end
256 end
256
257
257 project.is_public ? File.umask(0002) : File.umask(0007)
258 project.is_public ? File.umask(0002) : File.umask(0007)
258
259
259 if $test
260 if $test
260 log("\tcreate repository #{repos_path}")
261 log("\tcreate repository #{repos_path}")
261 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
262 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
262 next
263 next
263 end
264 end
264
265
265 begin
266 begin
266 set_owner_and_rights(project, repos_path) do
267 set_owner_and_rights(project, repos_path) do
267 if scm_module.nil?
268 if scm_module.nil?
268 system_or_raise "#{$command} #{repos_path}"
269 system_or_raise "#{$command} #{repos_path}"
269 else
270 else
270 scm_module.create(repos_path)
271 scm_module.create(repos_path)
271 end
272 end
272 end
273 end
273 rescue => e
274 rescue => e
274 log("\tunable to create #{repos_path} : #{e}\n")
275 log("\tunable to create #{repos_path} : #{e}\n")
275 next
276 next
276 end
277 end
277
278
278 if $svn_url
279 if $svn_url
279 begin
280 begin
280 project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key)
281 project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key)
281 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
282 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
282 rescue => e
283 rescue => e
283 log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
284 log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
284 end
285 end
285 end
286 end
286 log("\trepository #{repos_path} created");
287 log("\trepository #{repos_path} created");
287 end
288 end
288 end
289 end
General Comments 0
You need to be logged in to leave comments. Login now