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 / svn / reposman.rb @ 1559:21098b932cb8

History | View | Annotate | Download (10.4 KB)

1
#!/usr/bin/env ruby
2

    
3
require 'optparse'
4
require 'find'
5
require 'etc'
6
require 'rubygems'
7

    
8
Version = "1.4"
9
SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
10

    
11
$verbose      = 0
12
$quiet        = false
13
$redmine_host = ''
14
$repos_base   = ''
15
$svn_owner    = 'root'
16
$svn_group    = 'root'
17
$use_groupid  = true
18
$svn_url      = false
19
$test         = false
20
$force        = false
21
$scm          = 'Subversion'
22

    
23
def log(text, options={})
24
  level = options[:level] || 0
25
  puts text unless $quiet or level > $verbose
26
  exit 1 if options[:exit]
27
end
28

    
29
def system_or_raise(command)
30
  raise "\"#{command}\" failed" unless system command
31
end
32

    
33
module SCM
34

    
35
  module Subversion
36
    def self.create(path)
37
      system_or_raise "svnadmin create #{path}"
38
    end
39
  end
40

    
41
  module Git
42
    def self.create(path)
43
      Dir.mkdir path
44
      Dir.chdir(path) do
45
        system_or_raise "git --bare init --shared"
46
        system_or_raise "git update-server-info"
47
      end
48
    end
49
  end
50

    
51
end
52

    
53
def read_key_from_file(filename)
54
  begin
55
    $api_key = File.read(filename).strip
56
  rescue Exception => e
57
    $stderr.puts "Unable to read the key from #{filename}: #{e.message}"
58
    exit 1
59
  end
60
end
61

    
62
def set_scm(scm)
63
  $scm = scm.capitalize
64
  unless SUPPORTED_SCM.include?($scm)
65
    log("Invalid SCM: #{$scm}\nValid SCM are: #{SUPPORTED_SCM.join(', ')}", :exit => true)
66
  end
67
end
68

    
69
optparse = OptionParser.new do |opts|
70
  opts.banner = "Usage: reposman.rb [OPTIONS...] -s [DIR] -r [HOST] -k [KEY]"
71
  opts.separator("")
72
  opts.separator("Manages your repositories with Redmine.")
73
  opts.separator("")
74
  opts.separator("Required arguments:") 
75
  opts.on("-s", "--svn-dir DIR",      "use DIR as base directory for svn repositories") {|v| $repos_base = v}
76
  opts.on("-r", "--redmine-host HOST","assume Redmine is hosted on HOST. Examples:",
77
                                       " -r redmine.example.net",
78
                                       " -r http://redmine.example.net",
79
                                       " -r https://redmine.example.net") {|v| $redmine_host = v}
80
  opts.on("-k", "--key KEY",           "use KEY as the Redmine API key",
81
                                       "(you can use --key-file option as an alternative)") {|v| $api_key = v}
82
  opts.separator("")
83
  opts.separator("Options:")
84
  opts.on("-o", "--owner OWNER",       "owner of the repository. using the rails login", 
85
                                       "allows users to browse the repository within",
86
                                       "Redmine even for private projects. If you want to",
87
                                       "share repositories through Redmine.pm, you need",
88
                                       "to use the apache owner.") {|v| $svn_owner = v; $use_groupid = false}
89
  opts.on("-g", "--group GROUP",       "group of the repository (default: root)") {|v| $svn_group = v; $use_groupid = false}
90
  opts.on("-u", "--url URL",           "the base url Redmine will use to access your",
91
                                       "repositories. This option is used to register",
92
                                       "the repositories in Redmine automatically. The",
93
                                       "project identifier will be appended to this url.",
94
                                       "Examples:",
95
                                       " -u https://example.net/svn",
96
                                       " -u file:///var/svn/",
97
                                       "if this option isn't set, reposman won't register",
98
                                       "the repositories in Redmine") {|v| $svn_url = v}
99
  opts.on(      "--scm SCM",           "the kind of SCM repository you want to create",
100
                                       "(and register) in Redmine (default: Subversion).",
101
                                       "reposman is able to create Git and Subversion",
102
                                       "repositories.",
103
                                       "For all other kind, you must specify a --command",
104
                                       "option") {|v| set_scm(v)}
105
  opts.on("-c", "--command COMMAND",   "use this command instead of `svnadmin create` to",
106
                                       "create a repository. This option can be used to",
107
                                       "create repositories other than subversion and git",
108
                                       "kind.",
109
                                       "This command override the default creation for",
110
                                       "git and subversion.") {|v| $command = v}
111
  opts.on(      "--key-file FILE",     "path to a file that contains the Redmine API key",
112
                                       "(use this option instead of --key if you don't", 
113
                                       "want the key to appear in the command line)") {|v| read_key_from_file(v)}
114
  opts.on("-t", "--test",              "only show what should be done") {$test = true}
115
  opts.on("-f", "--force",             "force repository creation even if the project", "repository is already declared in Redmine") {$force = true}
116
  opts.on("-v", "--verbose",           "verbose") {$verbose += 1}
117
  opts.on("-V", "--version",           "show version and exit") {puts Version; exit}
118
  opts.on("-h", "--help",              "show help and exit") {puts opts; exit 1}
119
  opts.on("-q", "--quiet",             "no log") {$quiet = true}
120
  opts.separator("")
121
  opts.separator("Examples:")
122
  opts.separator("  reposman.rb --svn-dir=/var/svn --redmine-host=redmine.host")
123
  opts.separator("  reposman.rb -s /var/git -r redmine.host -u http://git.host --scm git")
124
  opts.separator("")
125
  opts.separator("You can find more information on the redmine's wiki:\nhttp://www.redmine.org/projects/redmine/wiki/HowTos")
126

    
127
  opts.summary_width = 25
128
end
129
optparse.parse!
130

    
131
if $test
132
  log("running in test mode")
133
end
134

    
135
# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
136
if $command.nil?
137
  begin
138
    scm_module = SCM.const_get($scm)
139
  rescue
140
    log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
141
  end
142
end
143

    
144
$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
145

    
146
if ($redmine_host.empty? or $repos_base.empty?)
147
  puts "Some arguments are missing. Use reposman.rb --help for getting help."
148
  exit 1
149
end
150

    
151
unless File.directory?($repos_base)
152
  log("directory '#{$repos_base}' doesn't exists", :exit => true)
153
end
154

    
155
begin
156
  require 'active_resource'
157
rescue LoadError
158
  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
159
end
160

    
161
class Project < ActiveResource::Base
162
  self.headers["User-agent"] = "Redmine repository manager/#{Version}"
163
  self.format = :xml
164
end
165

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

    
168
$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
169
$redmine_host.gsub!(/\/$/, '')
170

    
171
Project.site = "#{$redmine_host}/sys";
172

    
173
begin
174
  # Get all active projects that have the Repository module enabled
175
  projects = Project.find(:all, :params => {:key => $api_key})
176
rescue ActiveResource::ForbiddenAccess
177
  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.")
178
rescue => e
179
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
180
end
181

    
182
if projects.nil?
183
  log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
184
end
185

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

    
188
def set_owner_and_rights(project, repos_path, &block)
189
  if mswin?
190
    yield if block_given?
191
  else
192
    uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
193
    right = project.is_public ? 0775 : 0770
194
    yield if block_given?
195
    Find.find(repos_path) do |f|
196
      File.chmod right, f
197
      File.chown uid, gid, f
198
    end
199
  end
200
end
201

    
202
def other_read_right?(file)
203
  (File.stat(file).mode & 0007).zero? ? false : true
204
end
205

    
206
def owner_name(file)
207
  mswin? ?
208
    $svn_owner :
209
    Etc.getpwuid( File.stat(file).uid ).name
210
end
211

    
212
def mswin?
213
  (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
214
end
215

    
216
projects.each do |project|
217
  log("treating project #{project.name}", :level => 1)
218

    
219
  if project.identifier.empty?
220
    log("\tno identifier for project #{project.name}")
221
    next
222
  elsif not project.identifier.match(/^[a-z0-9\-_]+$/)
223
    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
224
    next;
225
  end
226

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

    
229
  if File.directory?(repos_path)
230
    # we must verify that repository has the good owner and the good
231
    # rights before leaving
232
    other_read = other_read_right?(repos_path)
233
    owner      = owner_name(repos_path)
234
    next if project.is_public == other_read and owner == $svn_owner
235

    
236
    if $test
237
      log("\tchange mode on #{repos_path}")
238
      next
239
    end
240

    
241
    begin
242
      set_owner_and_rights(project, repos_path)
243
    rescue Errno::EPERM => e
244
      log("\tunable to change mode on #{repos_path} : #{e}\n")
245
      next
246
    end
247

    
248
    log("\tmode change on #{repos_path}");
249

    
250
  else
251
    # if repository is already declared in redmine, we don't create
252
    # unless user use -f with reposman
253
    if $force == false and project.respond_to?(:repository)
254
      log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
255
      next
256
    end
257

    
258
    project.is_public ? File.umask(0002) : File.umask(0007)
259

    
260
    if $test
261
      log("\tcreate repository #{repos_path}")
262
      log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
263
      next
264
    end
265

    
266
    begin
267
      set_owner_and_rights(project, repos_path) do
268
        if scm_module.nil?
269
          system_or_raise "#{$command} #{repos_path}"
270
        else
271
          scm_module.create(repos_path)
272
        end
273
      end
274
    rescue => e
275
      log("\tunable to create #{repos_path} : #{e}\n")
276
      next
277
    end
278

    
279
    if $svn_url
280
      begin
281
        project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key)
282
        log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
283
      rescue => e
284
        log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
285
      end
286
    end
287
    log("\trepository #{repos_path} created");
288
  end
289
end