To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / extra / soundsoftware / reposman-soundsoftware.rb @ 1590:c18460da6620

History | View | Annotate | Download (12.6 KB)

1 0:513646585e45 Chris
#!/usr/bin/env ruby
2
3
# == Synopsis
4
#
5
# reposman: manages your repositories with Redmine
6
#
7
# == Usage
8
#
9
#    reposman [OPTIONS...] -s [DIR] -r [HOST]
10 441:cbce1fd3b1b7 Chris
#
11 0:513646585e45 Chris
#  Examples:
12 241:7658d21a1493 chris
#    reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
13 0:513646585e45 Chris
#    reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
14
#
15
# == Arguments (mandatory)
16
#
17 241:7658d21a1493 chris
#   -s, --scm-dir=DIR         use DIR as base directory for repositories
18 0:513646585e45 Chris
#   -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 909:cbb26bc654de Chris
#   -k, --key=KEY             use KEY as the Redmine API key (you can use the
23
#                             --key-file option as an alternative)
24 0:513646585e45 Chris
#
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 28:12420e46bed9 chris
#                             if this option isn't set, reposman will register
45
#                             the repositories with local file paths in Redmine
46 0:513646585e45 Chris
#   -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 13:80433603a2cd Chris
#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
53
#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
54 909:cbb26bc654de Chris
#       --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 0:513646585e45 Chris
#   -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 441:cbce1fd3b1b7 Chris
#
65 0:513646585e45 Chris
# You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
66
67
68
require 'getoptlong'
69 1332:1d1cb01c0417 Chris
#require 'rdoc/usage'
70 0:513646585e45 Chris
require 'find'
71
require 'etc'
72
73
Version = "1.3"
74
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
75
76
opts = GetoptLong.new(
77 241:7658d21a1493 chris
                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
78 0:513646585e45 Chris
                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
79
                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
80 909:cbb26bc654de Chris
                      ['--key-file',           GetoptLong::REQUIRED_ARGUMENT],
81 0:513646585e45 Chris
                      ['--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 13:80433603a2cd Chris
                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
87
                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
88 0:513646585e45 Chris
                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
89
                      ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
90
                      ['--version',      '-V', GetoptLong::NO_ARGUMENT],
91
                      ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
92
                      ['--quiet'  ,      '-q', GetoptLong::NO_ARGUMENT]
93
                      )
94
95
$verbose      = 0
96
$quiet        = false
97
$redmine_host = ''
98
$repos_base   = ''
99 13:80433603a2cd Chris
$http_user    = ''
100
$http_pass    = ''
101 0:513646585e45 Chris
$svn_owner    = 'root'
102
$svn_group    = 'root'
103
$use_groupid  = true
104
$svn_url      = false
105
$test         = false
106
$scm          = 'Subversion'
107
108
def log(text, options={})
109
  level = options[:level] || 0
110
  puts text unless $quiet or level > $verbose
111
  exit 1 if options[:exit]
112
end
113
114
def system_or_raise(command)
115
  raise "\"#{command}\" failed" unless system command
116
end
117
118 1332:1d1cb01c0417 Chris
def usage
119
  puts "See source code for supported options"
120
  exit
121
end
122
123 0:513646585e45 Chris
module SCM
124
125
  module Subversion
126
    def self.create(path)
127
      system_or_raise "svnadmin create #{path}"
128
    end
129
  end
130
131
  module Git
132
    def self.create(path)
133
      Dir.mkdir path
134
      Dir.chdir(path) do
135
        system_or_raise "git --bare init --shared"
136
        system_or_raise "git update-server-info"
137
      end
138
    end
139
  end
140
141
end
142
143
begin
144
  opts.each do |opt, arg|
145
    case opt
146 241:7658d21a1493 chris
    when '--scm-dir';        $repos_base   = arg.dup
147 0:513646585e45 Chris
    when '--redmine-host';   $redmine_host = arg.dup
148
    when '--key';            $api_key      = arg.dup
149 909:cbb26bc654de Chris
    when '--key-file'
150
      begin
151
        $api_key = File.read(arg).strip
152
      rescue Exception => e
153
        $stderr.puts "Unable to read the key from #{arg}: #{e.message}"
154
        exit 1
155
      end
156 0:513646585e45 Chris
    when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
157
    when '--group';          $svn_group    = arg.dup; $use_groupid = false;
158
    when '--url';            $svn_url      = arg.dup
159
    when '--scm';            $scm          = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
160 13:80433603a2cd Chris
    when '--http-user';      $http_user    = arg.dup
161
    when '--http-pass';      $http_pass    = arg.dup
162 0:513646585e45 Chris
    when '--command';        $command =      arg.dup
163
    when '--verbose';        $verbose += 1
164
    when '--test';           $test = true
165
    when '--version';        puts Version; exit
166 1332:1d1cb01c0417 Chris
    when '--help';           usage
167 0:513646585e45 Chris
    when '--quiet';          $quiet = true
168
    end
169
  end
170
rescue
171
  exit 1
172
end
173
174
if $test
175
  log("running in test mode")
176
end
177
178
# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
179
if $command.nil?
180
  begin
181
    scm_module = SCM.const_get($scm)
182
  rescue
183
    log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
184
  end
185
end
186
187
$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
188
189
if ($redmine_host.empty? or $repos_base.empty?)
190 1332:1d1cb01c0417 Chris
  usage
191 0:513646585e45 Chris
end
192
193
unless File.directory?($repos_base)
194 217:ed8222a04634 chris
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
195 0:513646585e45 Chris
end
196
197
begin
198
  require 'active_resource'
199
rescue LoadError
200
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
201
end
202
203 37:94944d00e43c chris
class Project < ActiveResource::Base
204 217:ed8222a04634 chris
  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
205 909:cbb26bc654de Chris
  self.format = :xml
206 37:94944d00e43c chris
end
207 0:513646585e45 Chris
208
log("querying Redmine for projects...", :level => 1);
209
210
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
211
$redmine_host.gsub!(/\/$/, '')
212
213
Project.site = "#{$redmine_host}/sys";
214 13:80433603a2cd Chris
Project.user = $http_user;
215
Project.password = $http_pass;
216 0:513646585e45 Chris
217
begin
218
  # Get all active projects that have the Repository module enabled
219
  projects = Project.find(:all, :params => {:key => $api_key})
220 909:cbb26bc654de Chris
rescue ActiveResource::ForbiddenAccess
221
  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.")
222 0:513646585e45 Chris
rescue => e
223
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
224
end
225
226
if projects.nil?
227 909:cbb26bc654de Chris
  log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
228 0:513646585e45 Chris
end
229
230 1107:4f45ab104990 Chris
log("found #{projects.size} projects at " + Time.now.inspect);
231 0:513646585e45 Chris
232
def set_owner_and_rights(project, repos_path, &block)
233 441:cbce1fd3b1b7 Chris
  if mswin?
234 0:513646585e45 Chris
    yield if block_given?
235
  else
236
    uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
237 34:09b1d4349da3 Chris
    right = project.is_public ? 02775 : 02770
238 0:513646585e45 Chris
    yield if block_given?
239
    Find.find(repos_path) do |f|
240
      File.chmod right, f
241
      File.chown uid, gid, f
242
    end
243
  end
244
end
245
246
def other_read_right?(file)
247
  (File.stat(file).mode & 0007).zero? ? false : true
248
end
249
250
def owner_name(file)
251
  mswin? ?
252
    $svn_owner :
253 441:cbce1fd3b1b7 Chris
    Etc.getpwuid( File.stat(file).uid ).name
254 0:513646585e45 Chris
end
255 441:cbce1fd3b1b7 Chris
256 0:513646585e45 Chris
def mswin?
257
  (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
258
end
259
260
projects.each do |project|
261 1107:4f45ab104990 Chris
  log("inspecting project #{project.name}", :level => 1)
262 0:513646585e45 Chris
263
  if project.identifier.empty?
264 1107:4f45ab104990 Chris
    log("\tno identifier for project #{project.name}!")
265 0:513646585e45 Chris
    next
266 1445:0c7b3bb73517 Chris
  elsif not project.identifier.match(/^[a-z0-9_\-]+$/)
267 1107:4f45ab104990 Chris
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}!");
268 0:513646585e45 Chris
    next;
269
  end
270
271
  repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
272
273 28:12420e46bed9 chris
  create_repos = false
274
  # Logic required for SoundSoftware.ac.uk repositories:
275
  #
276
  # * If the project has a repository path declared already,
277
  #   - if it's a local path,
278
  #     - if it does not exist
279
  #       - if it has the right root
280
  #         - create it
281
  #   - else
282
  #     - leave alone (remote repository)
283
  # * else
284
  #   - create repository with same name as project
285
  #   - set to project
286
287
  if project.respond_to?(:repository)
288
289
    repos_url = project.repository.url;
290 1107:4f45ab104990 Chris
    log("\texisting url for project #{project.identifier} is #{repos_url}", :level => 2);
291 28:12420e46bed9 chris
292
    if repos_url.match(/^file:\//) || repos_url.match(/^\//)
293
294
      repos_url = repos_url.gsub(/^file:\/*/, "/");
295 1107:4f45ab104990 Chris
      log("\tthis is a local file path, at #{repos_url}", :level => 2);
296 28:12420e46bed9 chris
297
      if repos_url.slice(0, $repos_base.length) != $repos_base
298
        # leave repos_path set to our original suggestion
299 1107:4f45ab104990 Chris
        log("\tpreparing to replace incorrect repo location #{repos_url} for #{project.name} with #{repos_path}");
300 28:12420e46bed9 chris
        create_repos = true
301
      else
302
        if !File.directory?(repos_url)
303 1107:4f45ab104990 Chris
          log("\tpreparing to create repo for #{project.name} at #{repos_url}");
304 28:12420e46bed9 chris
          repos_path = repos_url
305
          create_repos = true
306
        else
307 1107:4f45ab104990 Chris
          log("\tit exists and is in the right place", :level => 2);
308 28:12420e46bed9 chris
        end
309
      end
310
    else
311 1107:4f45ab104990 Chris
      log("\tthis is a remote path, leaving alone", :level => 2);
312 28:12420e46bed9 chris
    end
313
  else
314 1107:4f45ab104990 Chris
    log("\tpreparing to set repo location and create for #{project.name} at #{repos_url}")
315 28:12420e46bed9 chris
#    if File.directory?(repos_path)
316
#      log("\trepository path #{repos_path} already exists, not creating")
317
#    else
318
      create_repos = true
319
#    end
320
  end
321
322
  if create_repos
323
324
    registration_url = repos_path
325
    if $svn_url
326
      registration_url = "#{$svn_url}#{project.identifier}"
327
    end
328 0:513646585e45 Chris
329
    if $test
330 28:12420e46bed9 chris
      log("\tproposal: create repository #{repos_path}")
331
      log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
332 0:513646585e45 Chris
      next
333
    end
334
335 52:8c3409528d3a Chris
# No -- we need "other" users to be able to read it.  Access control
336
# is not handled through Unix user id anyway
337
#    project.is_public ? File.umask(0002) : File.umask(0007)
338
    File.umask(0002)
339
340 28:12420e46bed9 chris
    log("\taction: create repository #{repos_path}")
341 0:513646585e45 Chris
342
    begin
343 28:12420e46bed9 chris
      if !File.directory?(repos_path)
344
        set_owner_and_rights(project, repos_path) do
345
          if scm_module.nil?
346
            log("\trunning command: #{$command} #{repos_path}")
347
            system_or_raise "#{$command} #{repos_path}"
348
          else
349
            scm_module.create(repos_path)
350
          end
351 0:513646585e45 Chris
        end
352
      end
353
    rescue => e
354
      log("\tunable to create #{repos_path} : #{e}\n")
355
      next
356
    end
357
358 28:12420e46bed9 chris
    begin
359
      log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
360
      project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
361
    rescue => e
362
      log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
363 0:513646585e45 Chris
    end
364
    log("\trepository #{repos_path} created");
365
  end
366 37:94944d00e43c chris
end
367 0:513646585e45 Chris
368 1107:4f45ab104990 Chris
log("project review completed at " + Time.now.inspect);