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 @ 912:5e80956cc792

History | View | Annotate | Download (12.4 KB)

1 0:513646585e45 Chris
#!/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 441:cbce1fd3b1b7 Chris
#
11 0:513646585e45 Chris
#  Examples:
12 241:7658d21a1493 chris
#    reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
13 0:513646585e45 Chris
#    reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
14
#
15
# == Arguments (mandatory)
16
#
17 241:7658d21a1493 chris
#   -s, --scm-dir=DIR         use DIR as base directory for repositories
18 0:513646585e45 Chris
#   -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 909:cbb26bc654de Chris
#   -k, --key=KEY             use KEY as the Redmine API key (you can use the
23
#                             --key-file option as an alternative)
24 0:513646585e45 Chris
#
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 28:12420e46bed9 chris
#                             if this option isn't set, reposman will register
45
#                             the repositories with local file paths in Redmine
46 0:513646585e45 Chris
#   -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 13:80433603a2cd Chris
#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
53
#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
54 909:cbb26bc654de Chris
#       --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 0:513646585e45 Chris
#   -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 441:cbce1fd3b1b7 Chris
#
65 0:513646585e45 Chris
# 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 241:7658d21a1493 chris
                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
78 0:513646585e45 Chris
                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
79
                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
80 909:cbb26bc654de Chris
                      ['--key-file',           GetoptLong::REQUIRED_ARGUMENT],
81 0:513646585e45 Chris
                      ['--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 13:80433603a2cd Chris
                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
87
                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
88 0:513646585e45 Chris
                      ['--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 13:80433603a2cd Chris
$http_user    = ''
100
$http_pass    = ''
101 0:513646585e45 Chris
$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 241:7658d21a1493 chris
    when '--scm-dir';        $repos_base   = arg.dup
142 0:513646585e45 Chris
    when '--redmine-host';   $redmine_host = arg.dup
143
    when '--key';            $api_key      = arg.dup
144 909:cbb26bc654de Chris
    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 0:513646585e45 Chris
    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 13:80433603a2cd Chris
    when '--http-user';      $http_user    = arg.dup
156
    when '--http-pass';      $http_pass    = arg.dup
157 0:513646585e45 Chris
    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 217:ed8222a04634 chris
  log("directory '#{$repos_base}' doesn't exist", :exit => true)
190 0:513646585e45 Chris
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 37:94944d00e43c chris
class Project < ActiveResource::Base
199 217:ed8222a04634 chris
  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
200 909:cbb26bc654de Chris
  self.format = :xml
201 37:94944d00e43c chris
end
202 0:513646585e45 Chris
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 13:80433603a2cd Chris
Project.user = $http_user;
210
Project.password = $http_pass;
211 0:513646585e45 Chris
212
begin
213
  # Get all active projects that have the Repository module enabled
214
  projects = Project.find(:all, :params => {:key => $api_key})
215 909:cbb26bc654de Chris
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 0:513646585e45 Chris
rescue => e
218
  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
219
end
220
221
if projects.nil?
222 909:cbb26bc654de Chris
  log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
223 0:513646585e45 Chris
end
224
225
log("retrieved #{projects.size} projects", :level => 1)
226
227
def set_owner_and_rights(project, repos_path, &block)
228 441:cbce1fd3b1b7 Chris
  if mswin?
229 0:513646585e45 Chris
    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 34:09b1d4349da3 Chris
    right = project.is_public ? 02775 : 02770
233 0:513646585e45 Chris
    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 441:cbce1fd3b1b7 Chris
    Etc.getpwuid( File.stat(file).uid ).name
249 0:513646585e45 Chris
end
250 441:cbce1fd3b1b7 Chris
251 0:513646585e45 Chris
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 28:12420e46bed9 chris
  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 0:513646585e45 Chris
324
    if $test
325 28:12420e46bed9 chris
      log("\tproposal: create repository #{repos_path}")
326
      log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
327 0:513646585e45 Chris
      next
328
    end
329
330 52:8c3409528d3a Chris
# 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 28:12420e46bed9 chris
    log("\taction: create repository #{repos_path}")
336 0:513646585e45 Chris
337
    begin
338 28:12420e46bed9 chris
      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 0:513646585e45 Chris
        end
347
      end
348
    rescue => e
349
      log("\tunable to create #{repos_path} : #{e}\n")
350
      next
351
    end
352
353 28:12420e46bed9 chris
    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 0:513646585e45 Chris
    end
359
    log("\trepository #{repos_path} created");
360
  end
361 37:94944d00e43c chris
end