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 @ 1544:e9e55585ebf2

History | View | Annotate | Download (12.6 KB)

1
#!/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
#
11
#  Examples:
12
#    reposman --scm-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, --scm-dir=DIR         use DIR as base directory for 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 will register
45
#                             the repositories with local file paths 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
#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
53
#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
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'
71
require 'etc'
72

    
73
Version = "1.3"
74
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
75

    
76
opts = GetoptLong.new(
77
                      ['--scm-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
                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
87
                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
88
                      ['--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
$http_user    = ''
100
$http_pass    = ''
101
$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
def usage
119
  puts "See source code for supported options"
120
  exit
121
end
122

    
123
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
    when '--scm-dir';        $repos_base   = arg.dup
147
    when '--redmine-host';   $redmine_host = arg.dup
148
    when '--key';            $api_key      = arg.dup
149
    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
    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
    when '--http-user';      $http_user    = arg.dup
161
    when '--http-pass';      $http_pass    = arg.dup
162
    when '--command';        $command =      arg.dup
163
    when '--verbose';        $verbose += 1
164
    when '--test';           $test = true
165
    when '--version';        puts Version; exit
166
    when '--help';           usage
167
    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
  usage
191
end
192

    
193
unless File.directory?($repos_base)
194
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
195
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
class Project < ActiveResource::Base
204
  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
205
  self.format = :xml
206
end
207

    
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
Project.user = $http_user;
215
Project.password = $http_pass;
216

    
217
begin
218
  # Get all active projects that have the Repository module enabled
219
  projects = Project.find(:all, :params => {:key => $api_key})
220
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
rescue => e
223
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
224
end
225

    
226
if projects.nil?
227
  log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
228
end
229

    
230
log("found #{projects.size} projects at " + Time.now.inspect);
231

    
232
def set_owner_and_rights(project, repos_path, &block)
233
  if mswin?
234
    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
    right = project.is_public ? 02775 : 02770
238
    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
    Etc.getpwuid( File.stat(file).uid ).name
254
end
255

    
256
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
  log("inspecting project #{project.name}", :level => 1)
262

    
263
  if project.identifier.empty?
264
    log("\tno identifier for project #{project.name}!")
265
    next
266
  elsif not project.identifier.match(/^[a-z0-9_\-]+$/)
267
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}!");
268
    next;
269
  end
270

    
271
  repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
272

    
273
  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
    log("\texisting url for project #{project.identifier} is #{repos_url}", :level => 2);
291

    
292
    if repos_url.match(/^file:\//) || repos_url.match(/^\//)
293

    
294
      repos_url = repos_url.gsub(/^file:\/*/, "/");
295
      log("\tthis is a local file path, at #{repos_url}", :level => 2);
296

    
297
      if repos_url.slice(0, $repos_base.length) != $repos_base
298
        # leave repos_path set to our original suggestion
299
        log("\tpreparing to replace incorrect repo location #{repos_url} for #{project.name} with #{repos_path}");
300
        create_repos = true
301
      else
302
        if !File.directory?(repos_url)
303
          log("\tpreparing to create repo for #{project.name} at #{repos_url}");
304
          repos_path = repos_url
305
          create_repos = true
306
        else
307
          log("\tit exists and is in the right place", :level => 2);
308
        end
309
      end
310
    else
311
      log("\tthis is a remote path, leaving alone", :level => 2);
312
    end
313
  else
314
    log("\tpreparing to set repo location and create for #{project.name} at #{repos_url}")
315
#    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

    
329
    if $test
330
      log("\tproposal: create repository #{repos_path}")
331
      log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
332
      next
333
    end
334

    
335
# 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
    log("\taction: create repository #{repos_path}")
341

    
342
    begin
343
      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
        end
352
      end
353
    rescue => e
354
      log("\tunable to create #{repos_path} : #{e}\n")
355
      next
356
    end
357

    
358
    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
    end
364
    log("\trepository #{repos_path} created");
365
  end
366
end
367

    
368
log("project review completed at " + Time.now.inspect);
369