Project

General

Profile

Patch #5669 » reposman.rb

Reposman.rb which includes support for creating and initializing a svn mirror repository. - Bryce Nordgren, 2010-06-10 23:46

 
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 --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
14
#
15
# == Arguments (mandatory)
16
#
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:
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 won't register
44
#                             the repositories 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
#   -f, --force               force repository creation even if the project
52
#                             repository is already declared in Redmine
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
require 'uri'
69

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

    
73
opts = GetoptLong.new(
74
                      ['--svn-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
75
                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
76
                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
77
                      ['--owner',        '-o', GetoptLong::REQUIRED_ARGUMENT],
78
                      ['--group',        '-g', GetoptLong::REQUIRED_ARGUMENT],
79
                      ['--url',          '-u', GetoptLong::REQUIRED_ARGUMENT],
80
                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
81
                      ['--scm',                GetoptLong::REQUIRED_ARGUMENT],
82
                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
83
                      ['--force',        '-f', 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
$svn_owner    = 'root'
95
$svn_group    = 'root'
96
$use_groupid  = true
97
$svn_url      = false
98
$test         = false
99
$force        = false
100
$scm          = 'Subversion'
101
$identifier   = false
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
    
120
    def self.disregard_registration?
121
      false
122
    end
123
  end
124
  
125
  module Subversion_mirror
126
    def self.create(path)
127
        
128
      # Create the repository
129
      system_or_raise "svnadmin create #{path}"
130
      
131
      # enable pre-revprop-change
132
      if mswin? 
133
        revprop_file = File.join(path,"hooks","pre-revprop-change.bat")
134
      else
135
        revprop_file = File.join(path,"hooks","pre-revprop-change")        
136
      end
137
      File.new(revprop_file, "w+")
138
      File.chmod(0755, revprop_file)
139
      
140
      # Switch the slashes if on Windows.        
141
      if mswin?
142
        normalpath=path.gsub("\\","/")
143
      else
144
        normalpath=path
145
      end
146
      
147
      # initialize the sync
148
      file_url = "file://#{normalpath}"
149
      system_or_raise "svnsync init #{file_url} #{$svn_url}#{$identifier}"       
150
    end
151
    
152
    def self.disregard_registration?
153
      true
154
    end
155
    
156
    def self.validate_opts
157
      log("Subversion_mirror requires --url option", :exit => true) if not $svn_url
158
    end
159
  end
160

    
161
  module Git
162
    def self.create(path)
163
      Dir.mkdir path
164
      Dir.chdir(path) do
165
        system_or_raise "git --bare init --shared"
166
        system_or_raise "git update-server-info"
167
      end
168
    end
169
    
170
    def self.disregard_registration?
171
      false
172
    end
173
  end
174

    
175
end
176

    
177
begin
178
  opts.each do |opt, arg|
179
    case opt
180
    when '--svn-dir';        $repos_base   = arg.dup
181
    when '--redmine-host';   $redmine_host = arg.dup
182
    when '--key';            $api_key      = arg.dup
183
    when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
184
    when '--group';          $svn_group    = arg.dup; $use_groupid = false;
185
    when '--url';            $svn_url      = arg.dup
186
    when '--scm';            $scm          = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
187
    when '--command';        $command =      arg.dup
188
    when '--verbose';        $verbose += 1
189
    when '--test';           $test = true
190
    when '--force';          $force = true
191
    when '--version';        puts Version; exit
192
    when '--help';           RDoc::usage
193
    when '--quiet';          $quiet = true
194
    end
195
  end
196
rescue
197
  exit 1
198
end
199

    
200
if $test
201
  log("running in test mode")
202
end
203

    
204
# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
205
if $command.nil?
206
  begin
207
    scm_module = SCM.const_get($scm)
208
  rescue
209
    log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
210
  end
211
end
212

    
213
$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
214

    
215
if ($redmine_host.empty? or $repos_base.empty?)
216
  RDoc::usage
217
end
218

    
219
unless File.directory?($repos_base)
220
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
221
end
222

    
223
# Perform SCM-specific option validation
224
if (not scm_module.nil? and scm_module.respond_to?("validate_opts"))
225
  scm_module.validate_opts
226
end
227

    
228
begin
229
  require 'active_resource'
230
rescue LoadError
231
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
232
end
233

    
234
class Project < ActiveResource::Base; end
235

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

    
238
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
239
$redmine_host.gsub!(/\/$/, '')
240

    
241
Project.site = "#{$redmine_host}/sys";
242

    
243
begin
244
  # Get all active projects that have the Repository module enabled
245
  projects = Project.find(:all, :params => {:key => $api_key})
246
rescue => e
247
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
248
end
249

    
250
if projects.nil?
251
  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
252
end
253

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

    
256
def set_owner_and_rights(project, repos_path, &block)
257
  if mswin?
258
    yield if block_given?
259
  else
260
    uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
261
    right = project.is_public ? 0775 : 0770
262
    yield if block_given?
263
    Find.find(repos_path) do |f|
264
      File.chmod right, f
265
      File.chown uid, gid, f
266
    end
267
  end
268
end
269

    
270
def other_read_right?(file)
271
  (File.stat(file).mode & 0007).zero? ? false : true
272
end
273

    
274
def owner_name(file)
275
  mswin? ?
276
    $svn_owner :
277
    Etc.getpwuid( File.stat(file).uid ).name  
278
end
279
  
280
def mswin?
281
  (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
282
end
283

    
284
projects.each do |project|
285
  log("treating project #{project.name}", :level => 1)
286

    
287
  if project.identifier.empty?
288
    log("\tno identifier for project #{project.name}")
289
    next
290
  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
291
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
292
    next;
293
  end
294
  
295
  if project.respond_to?(:repository) and not project.repository.nil?
296
    url_pieces = URI.split(project.repository.url)
297
    path_pieces = File.split(url_pieces[5])
298
    $identifier = path_pieces[1]
299
  else
300
    $identifier = project.identifier
301
  end
302
  repos_path = File.join($repos_base, $identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
303

    
304
  if File.directory?(repos_path)
305

    
306
    # we must verify that repository has the good owner and the good
307
    # rights before leaving
308
    other_read = other_read_right?(repos_path)
309
    owner      = owner_name(repos_path)
310
    next if project.is_public == other_read and owner == $svn_owner
311

    
312
    if $test
313
      log("\tchange mode on #{repos_path}")
314
      next
315
    end
316

    
317
    begin
318
      set_owner_and_rights(project, repos_path)
319
    rescue Errno::EPERM => e
320
      log("\tunable to change mode on #{repos_path} : #{e}\n")
321
      next
322
    end
323

    
324
    log("\tmode change on #{repos_path}");
325

    
326
  else
327
    # if there's an SCM module defined, check to see if it disregards 
328
    # registration of a project in Redmine. (if there's no scm module defined,
329
    # value should be false)
330
    disregard_registration = ((not scm_module.nil?) and scm_module.disregard_registration?)
331

    
332
    # if repository is already declared in redmine, we don't create
333
    # unless user use -f with reposman, or the SCM module specifies that 
334
    # the Redmine registration is disregarded.
335
    if (($force == false) and ((not disregard_registration) and project.respond_to?(:repository)))
336
      log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
337
      next
338
    end
339

    
340
    project.is_public ? File.umask(0002) : File.umask(0007)
341

    
342
    if $test
343
      log("\tcreate repository #{repos_path}")
344
      log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url and not disregard_registration;
345
      next
346
    end
347

    
348
    begin
349
      set_owner_and_rights(project, repos_path) do
350
        if scm_module.nil?
351
          system_or_raise "#{$command} #{repos_path}"
352
        else
353
          scm_module.create(repos_path)
354
        end
355
      end
356
    rescue => e
357
      log("\tunable to create #{repos_path} : #{e}\n")
358
      next
359
    end
360

    
361
    if $svn_url and not disregard_registration
362
      begin
363
        project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key)
364
        log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
365
      rescue => e
366
        log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
367
      end
368
    end
369

    
370
    log("\trepository #{repos_path} created");
371
  end
372
end
373
  
(2-2/2)