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