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