annotate extra/soundsoftware/reposman-soundsoftware.rb @ 866:2cd212a468b6 bug_152

Close obsolete branch bug_152
author Chris Cannam
date Wed, 11 May 2011 10:08:34 +0100
parents c73f512810a9
children 350acce374a2
rev   line source
Chris@0 1 #!/usr/bin/env ruby
Chris@0 2
Chris@0 3 # == Synopsis
Chris@0 4 #
Chris@0 5 # reposman: manages your repositories with Redmine
Chris@0 6 #
Chris@0 7 # == Usage
Chris@0 8 #
Chris@0 9 # reposman [OPTIONS...] -s [DIR] -r [HOST]
Chris@0 10 #
Chris@0 11 # Examples:
chris@241 12 # reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
Chris@0 13 # reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
Chris@0 14 #
Chris@0 15 # == Arguments (mandatory)
Chris@0 16 #
chris@241 17 # -s, --scm-dir=DIR use DIR as base directory for repositories
Chris@0 18 # -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
Chris@0 19 # -r redmine.example.net
Chris@0 20 # -r http://redmine.example.net
Chris@0 21 # -r https://example.net/redmine
Chris@0 22 # -k, --key=KEY use KEY as the Redmine API key
Chris@0 23 #
Chris@0 24 # == Options
Chris@0 25 #
Chris@0 26 # -o, --owner=OWNER owner of the repository. using the rails login
Chris@0 27 # allow user to browse the repository within
Chris@0 28 # Redmine even for private project. If you want to
Chris@0 29 # share repositories through Redmine.pm, you need
Chris@0 30 # to use the apache owner.
Chris@0 31 # -g, --group=GROUP group of the repository. (default: root)
Chris@0 32 # --scm=SCM the kind of SCM repository you want to create (and
Chris@0 33 # register) in Redmine (default: Subversion).
Chris@0 34 # reposman is able to create Git and Subversion
Chris@0 35 # repositories. For all other kind, you must specify
Chris@0 36 # a --command option
Chris@0 37 # -u, --url=URL the base url Redmine will use to access your
Chris@0 38 # repositories. This option is used to automatically
Chris@0 39 # register the repositories in Redmine. The project
Chris@0 40 # identifier will be appended to this url. Examples:
Chris@0 41 # -u https://example.net/svn
Chris@0 42 # -u file:///var/svn/
chris@28 43 # if this option isn't set, reposman will register
chris@28 44 # the repositories with local file paths in Redmine
Chris@0 45 # -c, --command=COMMAND use this command instead of "svnadmin create" to
Chris@0 46 # create a repository. This option can be used to
Chris@0 47 # create repositories other than subversion and git
Chris@0 48 # kind.
Chris@0 49 # This command override the default creation for git
Chris@0 50 # and subversion.
Chris@13 51 # --http-user=USER User for HTTP Basic authentication with Redmine WS
Chris@13 52 # --http-pass=PASSWORD Password for Basic authentication with Redmine WS
Chris@0 53 # -t, --test only show what should be done
Chris@0 54 # -h, --help show help and exit
Chris@0 55 # -v, --verbose verbose
Chris@0 56 # -V, --version print version and exit
Chris@0 57 # -q, --quiet no log
Chris@0 58 #
Chris@0 59 # == References
Chris@0 60 #
Chris@0 61 # You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
Chris@0 62
Chris@0 63
Chris@0 64 require 'getoptlong'
Chris@0 65 require 'rdoc/usage'
Chris@0 66 require 'find'
Chris@0 67 require 'etc'
Chris@0 68
Chris@0 69 Version = "1.3"
Chris@0 70 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
Chris@0 71
Chris@0 72 opts = GetoptLong.new(
chris@241 73 ['--scm-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 74 ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 75 ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 76 ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 77 ['--group', '-g', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 78 ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 79 ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 80 ['--scm', GetoptLong::REQUIRED_ARGUMENT],
Chris@13 81 ['--http-user', GetoptLong::REQUIRED_ARGUMENT],
Chris@13 82 ['--http-pass', GetoptLong::REQUIRED_ARGUMENT],
Chris@0 83 ['--test', '-t', GetoptLong::NO_ARGUMENT],
Chris@0 84 ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
Chris@0 85 ['--version', '-V', GetoptLong::NO_ARGUMENT],
Chris@0 86 ['--help' , '-h', GetoptLong::NO_ARGUMENT],
Chris@0 87 ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
Chris@0 88 )
Chris@0 89
Chris@0 90 $verbose = 0
Chris@0 91 $quiet = false
Chris@0 92 $redmine_host = ''
Chris@0 93 $repos_base = ''
Chris@13 94 $http_user = ''
Chris@13 95 $http_pass = ''
Chris@0 96 $svn_owner = 'root'
Chris@0 97 $svn_group = 'root'
Chris@0 98 $use_groupid = true
Chris@0 99 $svn_url = false
Chris@0 100 $test = false
Chris@0 101 $scm = 'Subversion'
Chris@0 102
Chris@0 103 def log(text, options={})
Chris@0 104 level = options[:level] || 0
Chris@0 105 puts text unless $quiet or level > $verbose
Chris@0 106 exit 1 if options[:exit]
Chris@0 107 end
Chris@0 108
Chris@0 109 def system_or_raise(command)
Chris@0 110 raise "\"#{command}\" failed" unless system command
Chris@0 111 end
Chris@0 112
Chris@0 113 module SCM
Chris@0 114
Chris@0 115 module Subversion
Chris@0 116 def self.create(path)
Chris@0 117 system_or_raise "svnadmin create #{path}"
Chris@0 118 end
Chris@0 119 end
Chris@0 120
Chris@0 121 module Git
Chris@0 122 def self.create(path)
Chris@0 123 Dir.mkdir path
Chris@0 124 Dir.chdir(path) do
Chris@0 125 system_or_raise "git --bare init --shared"
Chris@0 126 system_or_raise "git update-server-info"
Chris@0 127 end
Chris@0 128 end
Chris@0 129 end
Chris@0 130
Chris@0 131 end
Chris@0 132
Chris@0 133 begin
Chris@0 134 opts.each do |opt, arg|
Chris@0 135 case opt
chris@241 136 when '--scm-dir'; $repos_base = arg.dup
Chris@0 137 when '--redmine-host'; $redmine_host = arg.dup
Chris@0 138 when '--key'; $api_key = arg.dup
Chris@0 139 when '--owner'; $svn_owner = arg.dup; $use_groupid = false;
Chris@0 140 when '--group'; $svn_group = arg.dup; $use_groupid = false;
Chris@0 141 when '--url'; $svn_url = arg.dup
Chris@0 142 when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
Chris@13 143 when '--http-user'; $http_user = arg.dup
Chris@13 144 when '--http-pass'; $http_pass = arg.dup
Chris@0 145 when '--command'; $command = arg.dup
Chris@0 146 when '--verbose'; $verbose += 1
Chris@0 147 when '--test'; $test = true
Chris@0 148 when '--version'; puts Version; exit
Chris@0 149 when '--help'; RDoc::usage
Chris@0 150 when '--quiet'; $quiet = true
Chris@0 151 end
Chris@0 152 end
Chris@0 153 rescue
Chris@0 154 exit 1
Chris@0 155 end
Chris@0 156
Chris@0 157 if $test
Chris@0 158 log("running in test mode")
Chris@0 159 end
Chris@0 160
Chris@0 161 # Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
Chris@0 162 if $command.nil?
Chris@0 163 begin
Chris@0 164 scm_module = SCM.const_get($scm)
Chris@0 165 rescue
Chris@0 166 log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
Chris@0 167 end
Chris@0 168 end
Chris@0 169
Chris@0 170 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
Chris@0 171
Chris@0 172 if ($redmine_host.empty? or $repos_base.empty?)
Chris@0 173 RDoc::usage
Chris@0 174 end
Chris@0 175
Chris@0 176 unless File.directory?($repos_base)
chris@217 177 log("directory '#{$repos_base}' doesn't exist", :exit => true)
Chris@0 178 end
Chris@0 179
Chris@0 180 begin
Chris@0 181 require 'active_resource'
Chris@0 182 rescue LoadError
Chris@0 183 log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
Chris@0 184 end
Chris@0 185
chris@37 186 class Project < ActiveResource::Base
chris@217 187 self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
chris@37 188 end
Chris@0 189
Chris@0 190 log("querying Redmine for projects...", :level => 1);
Chris@0 191
Chris@0 192 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
Chris@0 193 $redmine_host.gsub!(/\/$/, '')
Chris@0 194
Chris@0 195 Project.site = "#{$redmine_host}/sys";
Chris@13 196 Project.user = $http_user;
Chris@13 197 Project.password = $http_pass;
Chris@0 198
Chris@0 199 begin
Chris@0 200 # Get all active projects that have the Repository module enabled
Chris@0 201 projects = Project.find(:all, :params => {:key => $api_key})
Chris@0 202 rescue => e
Chris@0 203 log("Unable to connect to #{Project.site}: #{e}", :exit => true)
Chris@0 204 end
Chris@0 205
Chris@0 206 if projects.nil?
Chris@0 207 log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
Chris@0 208 end
Chris@0 209
Chris@0 210 log("retrieved #{projects.size} projects", :level => 1)
Chris@0 211
Chris@0 212 def set_owner_and_rights(project, repos_path, &block)
Chris@0 213 if RUBY_PLATFORM =~ /mswin/
Chris@0 214 yield if block_given?
Chris@0 215 else
Chris@0 216 uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
Chris@34 217 right = project.is_public ? 02775 : 02770
Chris@0 218 yield if block_given?
Chris@0 219 Find.find(repos_path) do |f|
Chris@0 220 File.chmod right, f
Chris@0 221 File.chown uid, gid, f
Chris@0 222 end
Chris@0 223 end
Chris@0 224 end
Chris@0 225
Chris@0 226 def other_read_right?(file)
Chris@0 227 (File.stat(file).mode & 0007).zero? ? false : true
Chris@0 228 end
Chris@0 229
Chris@0 230 def owner_name(file)
Chris@0 231 mswin? ?
Chris@0 232 $svn_owner :
Chris@0 233 Etc.getpwuid( File.stat(file).uid ).name
Chris@0 234 end
Chris@0 235
Chris@0 236 def mswin?
Chris@0 237 (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
Chris@0 238 end
Chris@0 239
Chris@0 240 projects.each do |project|
Chris@0 241 log("treating project #{project.name}", :level => 1)
Chris@0 242
Chris@0 243 if project.identifier.empty?
Chris@0 244 log("\tno identifier for project #{project.name}")
Chris@0 245 next
Chris@0 246 elsif not project.identifier.match(/^[a-z0-9\-]+$/)
Chris@0 247 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
Chris@0 248 next;
Chris@0 249 end
Chris@0 250
Chris@0 251 repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
Chris@0 252
chris@28 253 create_repos = false
Chris@0 254
chris@28 255 # Logic required for SoundSoftware.ac.uk repositories:
chris@28 256 #
chris@28 257 # * If the project has a repository path declared already,
chris@28 258 # - if it's a local path,
chris@28 259 # - if it does not exist
chris@28 260 # - if it has the right root
chris@28 261 # - create it
chris@28 262 # - else
chris@28 263 # - leave alone (remote repository)
chris@28 264 # * else
chris@28 265 # - create repository with same name as project
chris@28 266 # - set to project
chris@28 267
chris@28 268 if project.respond_to?(:repository)
chris@28 269
chris@28 270 repos_url = project.repository.url;
chris@28 271 log("\texisting url for project #{project.identifier} is #{repos_url}");
chris@28 272
chris@28 273 if repos_url.match(/^file:\//) || repos_url.match(/^\//)
chris@28 274
chris@28 275 repos_url = repos_url.gsub(/^file:\/*/, "/");
chris@28 276 log("\tthis is a local file path, at #{repos_url}");
chris@28 277
chris@28 278 if repos_url.slice(0, $repos_base.length) != $repos_base
chris@28 279 log("\tit is in the wrong place: replacing it");
chris@28 280 # leave repos_path set to our original suggestion
chris@28 281 create_repos = true
chris@28 282 else
chris@28 283 if !File.directory?(repos_url)
chris@28 284 log("\tit doesn't exist; we should create it");
chris@28 285 repos_path = repos_url
chris@28 286 create_repos = true
chris@28 287 else
chris@28 288 log("\tit exists and is in the right place");
chris@28 289 end
chris@28 290 end
chris@28 291 else
chris@28 292 log("\tthis is a remote path, leaving alone");
chris@28 293 end
chris@28 294 else
chris@28 295 log("\tproject #{project.identifier} has no repository registered")
chris@28 296 # if File.directory?(repos_path)
chris@28 297 # log("\trepository path #{repos_path} already exists, not creating")
chris@28 298 # else
chris@28 299 create_repos = true
chris@28 300 # end
chris@28 301 end
chris@28 302
chris@28 303 if create_repos
chris@28 304
chris@28 305 registration_url = repos_path
chris@28 306 if $svn_url
chris@28 307 registration_url = "#{$svn_url}#{project.identifier}"
chris@28 308 end
Chris@0 309
Chris@0 310 if $test
chris@28 311 log("\tproposal: create repository #{repos_path}")
chris@28 312 log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
Chris@0 313 next
Chris@0 314 end
Chris@0 315
Chris@52 316 # No -- we need "other" users to be able to read it. Access control
Chris@52 317 # is not handled through Unix user id anyway
Chris@52 318 # project.is_public ? File.umask(0002) : File.umask(0007)
Chris@52 319 File.umask(0002)
Chris@52 320
chris@28 321 log("\taction: create repository #{repos_path}")
Chris@0 322
Chris@0 323 begin
chris@28 324 if !File.directory?(repos_path)
chris@28 325 set_owner_and_rights(project, repos_path) do
chris@28 326 if scm_module.nil?
chris@28 327 log("\trunning command: #{$command} #{repos_path}")
chris@28 328 system_or_raise "#{$command} #{repos_path}"
chris@28 329 else
chris@28 330 scm_module.create(repos_path)
chris@28 331 end
Chris@0 332 end
Chris@0 333 end
Chris@0 334 rescue => e
Chris@0 335 log("\tunable to create #{repos_path} : #{e}\n")
Chris@0 336 next
Chris@0 337 end
Chris@0 338
chris@28 339 begin
chris@28 340 log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
chris@28 341 project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
chris@28 342 rescue => e
chris@28 343 log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
Chris@0 344 end
Chris@0 345
Chris@0 346 log("\trepository #{repos_path} created");
Chris@0 347 end
Chris@0 348
Chris@0 349 end
Chris@13 350