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 @ 1078:b9e34a051f82

History | View | Annotate | Download (12.4 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
module SCM
119

    
120
  module Subversion
121
    def self.create(path)
122
      system_or_raise "svnadmin create #{path}"
123
    end
124
  end
125

    
126
  module Git
127
    def self.create(path)
128
      Dir.mkdir path
129
      Dir.chdir(path) do
130
        system_or_raise "git --bare init --shared"
131
        system_or_raise "git update-server-info"
132
      end
133
    end
134
  end
135

    
136
end
137

    
138
begin
139
  opts.each do |opt, arg|
140
    case opt
141
    when '--scm-dir';        $repos_base   = arg.dup
142
    when '--redmine-host';   $redmine_host = arg.dup
143
    when '--key';            $api_key      = arg.dup
144
    when '--key-file'
145
      begin
146
        $api_key = File.read(arg).strip
147
      rescue Exception => e
148
        $stderr.puts "Unable to read the key from #{arg}: #{e.message}"
149
        exit 1
150
      end
151
    when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
152
    when '--group';          $svn_group    = arg.dup; $use_groupid = false;
153
    when '--url';            $svn_url      = arg.dup
154
    when '--scm';            $scm          = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
155
    when '--http-user';      $http_user    = arg.dup
156
    when '--http-pass';      $http_pass    = arg.dup
157
    when '--command';        $command =      arg.dup
158
    when '--verbose';        $verbose += 1
159
    when '--test';           $test = true
160
    when '--version';        puts Version; exit
161
    when '--help';           RDoc::usage
162
    when '--quiet';          $quiet = true
163
    end
164
  end
165
rescue
166
  exit 1
167
end
168

    
169
if $test
170
  log("running in test mode")
171
end
172

    
173
# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
174
if $command.nil?
175
  begin
176
    scm_module = SCM.const_get($scm)
177
  rescue
178
    log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
179
  end
180
end
181

    
182
$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
183

    
184
if ($redmine_host.empty? or $repos_base.empty?)
185
  RDoc::usage
186
end
187

    
188
unless File.directory?($repos_base)
189
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
190
end
191

    
192
begin
193
  require 'active_resource'
194
rescue LoadError
195
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
196
end
197

    
198
class Project < ActiveResource::Base
199
  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
200
  self.format = :xml
201
end
202

    
203
log("querying Redmine for projects...", :level => 1);
204

    
205
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
206
$redmine_host.gsub!(/\/$/, '')
207

    
208
Project.site = "#{$redmine_host}/sys";
209
Project.user = $http_user;
210
Project.password = $http_pass;
211

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

    
221
if projects.nil?
222
  log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
223
end
224

    
225
log("retrieved #{projects.size} projects", :level => 1)
226

    
227
def set_owner_and_rights(project, repos_path, &block)
228
  if mswin?
229
    yield if block_given?
230
  else
231
    uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
232
    right = project.is_public ? 02775 : 02770
233
    yield if block_given?
234
    Find.find(repos_path) do |f|
235
      File.chmod right, f
236
      File.chown uid, gid, f
237
    end
238
  end
239
end
240

    
241
def other_read_right?(file)
242
  (File.stat(file).mode & 0007).zero? ? false : true
243
end
244

    
245
def owner_name(file)
246
  mswin? ?
247
    $svn_owner :
248
    Etc.getpwuid( File.stat(file).uid ).name
249
end
250

    
251
def mswin?
252
  (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
253
end
254

    
255
projects.each do |project|
256
  log("treating project #{project.name}", :level => 1)
257

    
258
  if project.identifier.empty?
259
    log("\tno identifier for project #{project.name}")
260
    next
261
  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
262
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
263
    next;
264
  end
265

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

    
268
  create_repos = false
269
  # Logic required for SoundSoftware.ac.uk repositories:
270
  #
271
  # * If the project has a repository path declared already,
272
  #   - if it's a local path,
273
  #     - if it does not exist
274
  #       - if it has the right root
275
  #         - create it
276
  #   - else
277
  #     - leave alone (remote repository)
278
  # * else
279
  #   - create repository with same name as project
280
  #   - set to project
281

    
282
  if project.respond_to?(:repository)
283

    
284
    repos_url = project.repository.url;
285
    log("\texisting url for project #{project.identifier} is #{repos_url}");
286

    
287
    if repos_url.match(/^file:\//) || repos_url.match(/^\//)
288

    
289
      repos_url = repos_url.gsub(/^file:\/*/, "/");
290
      log("\tthis is a local file path, at #{repos_url}");
291

    
292
      if repos_url.slice(0, $repos_base.length) != $repos_base
293
        log("\tit is in the wrong place: replacing it");
294
        # leave repos_path set to our original suggestion
295
        create_repos = true
296
      else
297
        if !File.directory?(repos_url)
298
          log("\tit doesn't exist; we should create it");
299
          repos_path = repos_url
300
          create_repos = true
301
        else
302
          log("\tit exists and is in the right place");
303
        end
304
      end
305
    else
306
      log("\tthis is a remote path, leaving alone");
307
    end
308
  else
309
    log("\tproject #{project.identifier} has no repository registered")
310
#    if File.directory?(repos_path)
311
#      log("\trepository path #{repos_path} already exists, not creating")
312
#    else 
313
      create_repos = true
314
#    end
315
  end
316

    
317
  if create_repos
318

    
319
    registration_url = repos_path
320
    if $svn_url
321
      registration_url = "#{$svn_url}#{project.identifier}"
322
    end
323

    
324
    if $test
325
      log("\tproposal: create repository #{repos_path}")
326
      log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
327
      next
328
    end
329

    
330
# No -- we need "other" users to be able to read it.  Access control
331
# is not handled through Unix user id anyway
332
#    project.is_public ? File.umask(0002) : File.umask(0007)
333
    File.umask(0002)
334

    
335
    log("\taction: create repository #{repos_path}")
336

    
337
    begin
338
      if !File.directory?(repos_path)
339
        set_owner_and_rights(project, repos_path) do
340
          if scm_module.nil?
341
            log("\trunning command: #{$command} #{repos_path}")
342
            system_or_raise "#{$command} #{repos_path}"
343
          else
344
            scm_module.create(repos_path)
345
          end
346
        end
347
      end
348
    rescue => e
349
      log("\tunable to create #{repos_path} : #{e}\n")
350
      next
351
    end
352

    
353
    begin
354
      log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
355
      project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
356
    rescue => e
357
      log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
358
    end
359
    log("\trepository #{repos_path} created");
360
  end
361
end
362