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