annotate extra/soundsoftware/convert-external-repos.rb @ 1621:3a510bf6a9bc

Merge from live branch
author Chris Cannam
date Fri, 13 Jul 2018 10:44:33 +0100
parents 0c7b3bb73517
children
rev   line source
chris@217 1 #!/usr/bin/env ruby
chris@217 2
chris@217 3 # == Synopsis
chris@217 4 #
chris@241 5 # convert-external-repos: Update local Mercurial mirrors of external repos,
chris@241 6 # by running an external command for each project requiring an update.
chris@217 7 #
chris@217 8 # == Usage
chris@217 9 #
chris@217 10 # convert-external-repos [OPTIONS...] -s [DIR] -r [HOST]
chris@217 11 #
chris@217 12 # == Arguments (mandatory)
chris@217 13 #
chris@241 14 # -s, --scm-dir=DIR use DIR as base directory for repositories
chris@217 15 # -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
chris@217 16 # -r redmine.example.net
chris@217 17 # -r http://redmine.example.net
chris@217 18 # -r https://example.net/redmine
chris@217 19 # -k, --key=KEY use KEY as the Redmine API key
chris@241 20 # -c, --command=COMMAND use this command to update each external
chris@241 21 # repository: command is called with the name
chris@241 22 # of the project, the path to its repo, and
chris@241 23 # its external repo url as its three args
chris@217 24 #
chris@217 25 # == Options
chris@217 26 #
chris@217 27 # --http-user=USER User for HTTP Basic authentication with Redmine WS
chris@217 28 # --http-pass=PASSWORD Password for Basic authentication with Redmine WS
chris@217 29 # -t, --test only show what should be done
chris@217 30 # -h, --help show help and exit
chris@217 31 # -v, --verbose verbose
chris@217 32 # -V, --version print version and exit
chris@217 33 # -q, --quiet no log
chris@217 34
chris@217 35
chris@217 36 require 'getoptlong'
chris@217 37 require 'find'
chris@217 38 require 'etc'
chris@217 39
chris@217 40 Version = "1.0"
chris@217 41
chris@217 42 opts = GetoptLong.new(
chris@241 43 ['--scm-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
chris@217 44 ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
chris@217 45 ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
chris@217 46 ['--http-user', GetoptLong::REQUIRED_ARGUMENT],
chris@217 47 ['--http-pass', GetoptLong::REQUIRED_ARGUMENT],
chris@241 48 ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
chris@217 49 ['--test', '-t', GetoptLong::NO_ARGUMENT],
chris@217 50 ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
chris@217 51 ['--version', '-V', GetoptLong::NO_ARGUMENT],
chris@217 52 ['--help' , '-h', GetoptLong::NO_ARGUMENT],
chris@217 53 ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
chris@217 54 )
chris@217 55
chris@217 56 $verbose = 0
chris@217 57 $quiet = false
chris@217 58 $redmine_host = ''
chris@217 59 $repos_base = ''
chris@217 60 $http_user = ''
chris@217 61 $http_pass = ''
chris@217 62 $test = false
chris@217 63
chris@437 64 $mirrordir = '/var/mirror'
chris@437 65
chris@217 66 def log(text, options={})
chris@217 67 level = options[:level] || 0
chris@217 68 puts text unless $quiet or level > $verbose
chris@217 69 exit 1 if options[:exit]
chris@217 70 end
chris@217 71
chris@217 72 def system_or_raise(command)
chris@217 73 raise "\"#{command}\" failed" unless system command
chris@217 74 end
chris@217 75
chris@217 76 begin
chris@217 77 opts.each do |opt, arg|
chris@217 78 case opt
chris@241 79 when '--scm-dir'; $repos_base = arg.dup
chris@217 80 when '--redmine-host'; $redmine_host = arg.dup
chris@217 81 when '--key'; $api_key = arg.dup
chris@217 82 when '--http-user'; $http_user = arg.dup
chris@217 83 when '--http-pass'; $http_pass = arg.dup
chris@241 84 when '--command'; $command = arg.dup
chris@217 85 when '--verbose'; $verbose += 1
chris@217 86 when '--test'; $test = true
chris@217 87 when '--version'; puts Version; exit
Chris@1336 88 when '--help'; puts "Read source for documentation"; exit
chris@217 89 when '--quiet'; $quiet = true
chris@217 90 end
chris@217 91 end
chris@217 92 rescue
chris@217 93 exit 1
chris@217 94 end
chris@217 95
chris@217 96 if $test
chris@217 97 log("running in test mode")
chris@217 98 end
chris@217 99
chris@241 100 if ($redmine_host.empty? or $repos_base.empty? or $command.empty?)
Chris@1336 101 puts "Read source for documentation"; exit
chris@217 102 end
chris@217 103
chris@217 104 unless File.directory?($repos_base)
chris@217 105 log("directory '#{$repos_base}' doesn't exist", :exit => true)
chris@217 106 end
chris@217 107
chris@217 108 begin
chris@217 109 require 'active_resource'
chris@217 110 rescue LoadError
chris@217 111 log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
chris@217 112 end
chris@217 113
chris@217 114 class Project < ActiveResource::Base
chris@217 115 self.headers["User-agent"] = "SoundSoftware external repository converter/#{Version}"
Chris@1336 116 self.format = :xml
chris@217 117 end
chris@217 118
chris@217 119 log("querying Redmine for projects...", :level => 1);
chris@217 120
chris@217 121 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
chris@217 122 $redmine_host.gsub!(/\/$/, '')
chris@217 123
chris@217 124 Project.site = "#{$redmine_host}/sys";
chris@217 125 Project.user = $http_user;
chris@217 126 Project.password = $http_pass;
chris@217 127
chris@217 128 begin
chris@217 129 # Get all active projects that have the Repository module enabled
chris@217 130 projects = Project.find(:all, :params => {:key => $api_key})
Chris@1336 131 rescue ActiveResource::ForbiddenAccess
Chris@1336 132 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.")
chris@217 133 rescue => e
chris@217 134 log("Unable to connect to #{Project.site}: #{e}", :exit => true)
chris@217 135 end
chris@217 136
chris@217 137 if projects.nil?
chris@217 138 log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
chris@217 139 end
chris@217 140
chris@217 141 log("retrieved #{projects.size} projects", :level => 1)
chris@217 142
chris@217 143 projects.each do |project|
chris@217 144 log("treating project #{project.name}", :level => 1)
chris@217 145
chris@217 146 if project.identifier.empty?
chris@217 147 log("\tno identifier for project #{project.name}")
chris@217 148 next
Chris@1445 149 elsif not project.identifier.match(/^[a-z0-9_\-]+$/)
chris@217 150 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
chris@217 151 next
chris@217 152 end
chris@217 153
chris@217 154 if !project.respond_to?(:repository) or !project.repository.is_external?
chris@217 155 log("\tproject #{project.identifier} does not use an external repository");
chris@217 156 next
chris@217 157 end
chris@217 158
chris@217 159 external_url = project.repository.external_url;
chris@217 160 log("\tproject #{project.identifier} has external repository url #{external_url}");
chris@217 161
chris@217 162 if !external_url.match(/^[a-z][a-z+]{0,8}[a-z]:\/\//)
chris@217 163 log("\tthis doesn't look like a plausible url to me, skipping")
chris@217 164 next
chris@217 165 end
chris@217 166
chris@217 167 repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
chris@217 168
chris@241 169 unless File.directory?(repos_path)
chris@241 170 log("\tproject repo directory '#{repos_path}' doesn't exist")
chris@217 171 next
chris@217 172 end
chris@217 173
chris@241 174 system($command, project.identifier, repos_path, external_url)
chris@437 175
chris@437 176 $cache_clearance_file = File.join($mirrordir, project.identifier, 'url_changed')
chris@437 177 if File.file?($cache_clearance_file)
chris@437 178 log("\tproject repo url has changed, requesting cache clearance")
chris@437 179 if project.post(:repository_cache, :key => $api_key)
chris@437 180 File.delete($cache_clearance_file)
chris@437 181 end
chris@437 182 end
chris@217 183
chris@217 184 end
chris@217 185