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 @ 434:dcf38f417f1f

History | View | Annotate | Download (11.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
23
#
24
# == Options
25
#
26
#   -o, --owner=OWNER         owner of the repository. using the rails login
27
#                             allow user to browse the repository within
28
#                             Redmine even for private project. If you want to
29
#                             share repositories through Redmine.pm, you need
30
#                             to use the apache owner.
31
#   -g, --group=GROUP         group of the repository. (default: root)
32
#   --scm=SCM                 the kind of SCM repository you want to create (and
33
#                             register) in Redmine (default: Subversion).
34
#                             reposman is able to create Git and Subversion
35
#                             repositories. For all other kind, you must specify
36
#                             a --command option
37
#   -u, --url=URL             the base url Redmine will use to access your
38
#                             repositories. This option is used to automatically
39
#                             register the repositories in Redmine. The project
40
#                             identifier will be appended to this url. Examples:
41
#                             -u https://example.net/svn
42
#                             -u file:///var/svn/
43
#                             if this option isn't set, reposman will register
44
#                             the repositories with local file paths in Redmine
45
#   -c, --command=COMMAND     use this command instead of "svnadmin create" to
46
#                             create a repository. This option can be used to
47
#                             create repositories other than subversion and git
48
#                             kind.
49
#                             This command override the default creation for git
50
#                             and subversion.
51
#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
52
#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
53
#   -t, --test                only show what should be done
54
#   -h, --help                show help and exit
55
#   -v, --verbose             verbose
56
#   -V, --version             print version and exit
57
#   -q, --quiet               no log
58
#
59
# == References
60
# 
61
# You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
62

    
63

    
64
require 'getoptlong'
65
require 'rdoc/usage'
66
require 'find'
67
require 'etc'
68

    
69
Version = "1.3"
70
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
71

    
72
opts = GetoptLong.new(
73
                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
74
                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
75
                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
76
                      ['--owner',        '-o', GetoptLong::REQUIRED_ARGUMENT],
77
                      ['--group',        '-g', GetoptLong::REQUIRED_ARGUMENT],
78
                      ['--url',          '-u', GetoptLong::REQUIRED_ARGUMENT],
79
                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
80
                      ['--scm',                GetoptLong::REQUIRED_ARGUMENT],
81
                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
82
                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
83
                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
84
                      ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
85
                      ['--version',      '-V', GetoptLong::NO_ARGUMENT],
86
                      ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
87
                      ['--quiet'  ,      '-q', GetoptLong::NO_ARGUMENT]
88
                      )
89

    
90
$verbose      = 0
91
$quiet        = false
92
$redmine_host = ''
93
$repos_base   = ''
94
$http_user    = ''
95
$http_pass    = ''
96
$svn_owner    = 'root'
97
$svn_group    = 'root'
98
$use_groupid  = true
99
$svn_url      = false
100
$test         = false
101
$scm          = 'Subversion'
102

    
103
def log(text, options={})
104
  level = options[:level] || 0
105
  puts text unless $quiet or level > $verbose
106
  exit 1 if options[:exit]
107
end
108

    
109
def system_or_raise(command)
110
  raise "\"#{command}\" failed" unless system command
111
end
112

    
113
module SCM
114

    
115
  module Subversion
116
    def self.create(path)
117
      system_or_raise "svnadmin create #{path}"
118
    end
119
  end
120

    
121
  module Git
122
    def self.create(path)
123
      Dir.mkdir path
124
      Dir.chdir(path) do
125
        system_or_raise "git --bare init --shared"
126
        system_or_raise "git update-server-info"
127
      end
128
    end
129
  end
130

    
131
end
132

    
133
begin
134
  opts.each do |opt, arg|
135
    case opt
136
    when '--scm-dir';        $repos_base   = arg.dup
137
    when '--redmine-host';   $redmine_host = arg.dup
138
    when '--key';            $api_key      = arg.dup
139
    when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
140
    when '--group';          $svn_group    = arg.dup; $use_groupid = false;
141
    when '--url';            $svn_url      = arg.dup
142
    when '--scm';            $scm          = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
143
    when '--http-user';      $http_user    = arg.dup
144
    when '--http-pass';      $http_pass    = arg.dup
145
    when '--command';        $command =      arg.dup
146
    when '--verbose';        $verbose += 1
147
    when '--test';           $test = true
148
    when '--version';        puts Version; exit
149
    when '--help';           RDoc::usage
150
    when '--quiet';          $quiet = true
151
    end
152
  end
153
rescue
154
  exit 1
155
end
156

    
157
if $test
158
  log("running in test mode")
159
end
160

    
161
# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
162
if $command.nil?
163
  begin
164
    scm_module = SCM.const_get($scm)
165
  rescue
166
    log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
167
  end
168
end
169

    
170
$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
171

    
172
if ($redmine_host.empty? or $repos_base.empty?)
173
  RDoc::usage
174
end
175

    
176
unless File.directory?($repos_base)
177
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
178
end
179

    
180
begin
181
  require 'active_resource'
182
rescue LoadError
183
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
184
end
185

    
186
class Project < ActiveResource::Base
187
  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
188
end
189

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

    
192
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
193
$redmine_host.gsub!(/\/$/, '')
194

    
195
Project.site = "#{$redmine_host}/sys";
196
Project.user = $http_user;
197
Project.password = $http_pass;
198

    
199
begin
200
  # Get all active projects that have the Repository module enabled
201
  projects = Project.find(:all, :params => {:key => $api_key})
202
rescue => e
203
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
204
end
205

    
206
if projects.nil?
207
  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
208
end
209

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

    
212
def set_owner_and_rights(project, repos_path, &block)
213
  if RUBY_PLATFORM =~ /mswin/
214
    yield if block_given?
215
  else
216
    uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
217
    right = project.is_public ? 02775 : 02770
218
    yield if block_given?
219
    Find.find(repos_path) do |f|
220
      File.chmod right, f
221
      File.chown uid, gid, f
222
    end
223
  end
224
end
225

    
226
def other_read_right?(file)
227
  (File.stat(file).mode & 0007).zero? ? false : true
228
end
229

    
230
def owner_name(file)
231
  mswin? ?
232
    $svn_owner :
233
    Etc.getpwuid( File.stat(file).uid ).name  
234
end
235
  
236
def mswin?
237
  (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
238
end
239

    
240
projects.each do |project|
241
  log("treating project #{project.name}", :level => 1)
242

    
243
  if project.identifier.empty?
244
    log("\tno identifier for project #{project.name}")
245
    next
246
  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
247
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
248
    next;
249
  end
250

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

    
253
  create_repos = false
254

    
255
  # Logic required for SoundSoftware.ac.uk repositories:
256
  #
257
  # * If the project has a repository path declared already,
258
  #   - if it's a local path,
259
  #     - if it does not exist
260
  #       - if it has the right root
261
  #         - create it
262
  #   - else
263
  #     - leave alone (remote repository)
264
  # * else
265
  #   - create repository with same name as project
266
  #   - set to project
267

    
268
  if project.respond_to?(:repository)
269

    
270
    repos_url = project.repository.url;
271
    log("\texisting url for project #{project.identifier} is #{repos_url}");
272

    
273
    if repos_url.match(/^file:\//) || repos_url.match(/^\//)
274

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

    
278
      if repos_url.slice(0, $repos_base.length) != $repos_base
279
        log("\tit is in the wrong place: replacing it");
280
        # leave repos_path set to our original suggestion
281
        create_repos = true
282
      else
283
        if !File.directory?(repos_url)
284
          log("\tit doesn't exist; we should create it");
285
          repos_path = repos_url
286
          create_repos = true
287
        else
288
          log("\tit exists and is in the right place");
289
        end
290
      end
291
    else
292
      log("\tthis is a remote path, leaving alone");
293
    end
294
  else
295
    log("\tproject #{project.identifier} has no repository registered")
296
#    if File.directory?(repos_path)
297
#      log("\trepository path #{repos_path} already exists, not creating")
298
#    else 
299
      create_repos = true
300
#    end
301
  end
302

    
303
  if create_repos
304

    
305
    registration_url = repos_path
306
    if $svn_url
307
      registration_url = "#{$svn_url}#{project.identifier}"
308
    end
309

    
310
    if $test
311
      log("\tproposal: create repository #{repos_path}")
312
      log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
313
      next
314
    end
315

    
316
# No -- we need "other" users to be able to read it.  Access control
317
# is not handled through Unix user id anyway
318
#    project.is_public ? File.umask(0002) : File.umask(0007)
319
    File.umask(0002)
320

    
321
    log("\taction: create repository #{repos_path}")
322

    
323
    begin
324
      if !File.directory?(repos_path)
325
        set_owner_and_rights(project, repos_path) do
326
          if scm_module.nil?
327
            log("\trunning command: #{$command} #{repos_path}")
328
            system_or_raise "#{$command} #{repos_path}"
329
          else
330
            scm_module.create(repos_path)
331
          end
332
        end
333
      end
334
    rescue => e
335
      log("\tunable to create #{repos_path} : #{e}\n")
336
      next
337
    end
338

    
339
    begin
340
      log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
341
      project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
342
    rescue => e
343
      log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
344
    end
345

    
346
    log("\trepository #{repos_path} created");
347
  end
348

    
349
end
350