You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

reposman.rb 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/usr/bin/env ruby
  2. # frozen_string_literal: false
  3. require 'optparse'
  4. require 'find'
  5. require 'etc'
  6. require 'rubygems'
  7. Version = "1.5"
  8. SUPPORTED_SCM = %w( Subversion Mercurial Bazaar Git Filesystem )
  9. $verbose = 0
  10. $quiet = false
  11. $redmine_host = ''
  12. $repos_base = ''
  13. $svn_owner = 'root'
  14. $svn_group = 'root'
  15. $use_groupid = true
  16. $svn_url = false
  17. $test = false
  18. $force = false
  19. $scm = 'Subversion'
  20. def log(text, options={})
  21. level = options[:level] || 0
  22. puts text unless $quiet or level > $verbose
  23. exit 1 if options[:exit]
  24. end
  25. def system_or_raise(command)
  26. raise "\"#{command}\" failed" unless system command
  27. end
  28. module SCM
  29. module Subversion
  30. def self.create(path)
  31. system_or_raise "svnadmin create #{path}"
  32. end
  33. end
  34. module Git
  35. def self.create(path)
  36. Dir.mkdir path
  37. Dir.chdir(path) do
  38. system_or_raise "git --bare init --shared"
  39. system_or_raise "git update-server-info"
  40. end
  41. end
  42. end
  43. end
  44. def read_key_from_file(filename)
  45. begin
  46. $api_key = File.read(filename).strip
  47. rescue => e
  48. $stderr.puts "Unable to read the key from #{filename}: #{e.message}"
  49. exit 1
  50. end
  51. end
  52. def set_scm(scm)
  53. $scm = scm.capitalize
  54. unless SUPPORTED_SCM.include?($scm)
  55. log("Invalid SCM: #{$scm}\nValid SCM are: #{SUPPORTED_SCM.join(', ')}", :exit => true)
  56. end
  57. end
  58. optparse = OptionParser.new do |opts|
  59. opts.banner = "Usage: reposman.rb [OPTIONS...] -s [DIR] -r [HOST] -k [KEY]"
  60. opts.separator("")
  61. opts.separator("Manages your repositories with Redmine.")
  62. opts.separator("")
  63. opts.separator("Required arguments:")
  64. opts.on("-s", "--svn-dir DIR", "use DIR as base directory for svn repositories") {|v| $repos_base = v}
  65. opts.on("-r", "--redmine-host HOST","assume Redmine is hosted on HOST. Examples:",
  66. " -r redmine.example.net",
  67. " -r http://redmine.example.net",
  68. " -r https://redmine.example.net") {|v| $redmine_host = v}
  69. opts.on("-k", "--key KEY", "use KEY as the Redmine API key",
  70. "(you can use --key-file option as an alternative)") {|v| $api_key = v}
  71. opts.separator("")
  72. opts.separator("Options:")
  73. opts.on("-o", "--owner OWNER", "owner of the repository. using the rails login",
  74. "allows users to browse the repository within",
  75. "Redmine even for private projects. If you want to",
  76. "share repositories through Redmine.pm, you need",
  77. "to use the apache owner.") {|v| $svn_owner = v; $use_groupid = false}
  78. opts.on("-g", "--group GROUP", "group of the repository (default: root)") {|v| $svn_group = v; $use_groupid = false}
  79. opts.on("-u", "--url URL", "the base url Redmine will use to access your",
  80. "repositories. This option is used to register",
  81. "the repositories in Redmine automatically. The",
  82. "project identifier will be appended to this url.",
  83. "Examples:",
  84. " -u https://example.net/svn",
  85. " -u file:///var/svn/",
  86. "if this option isn't set, reposman won't register",
  87. "the repositories in Redmine") {|v| $svn_url = v}
  88. opts.on( "--scm SCM", "the kind of SCM repository you want to create",
  89. "(and register) in Redmine (default: Subversion).",
  90. "reposman is able to create Git and Subversion",
  91. "repositories.",
  92. "For all other kind, you must specify a --command",
  93. "option") {|v| set_scm(v)}
  94. opts.on("-c", "--command COMMAND", "use this command instead of `svnadmin create` to",
  95. "create a repository. This option can be used to",
  96. "create repositories other than subversion and git",
  97. "kind.",
  98. "This command override the default creation for",
  99. "git and subversion.") {|v| $command = v}
  100. opts.on( "--key-file FILE", "path to a file that contains the Redmine API key",
  101. "(use this option instead of --key if you don't",
  102. "want the key to appear in the command line)") {|v| read_key_from_file(v)}
  103. opts.on("-t", "--test", "only show what should be done") {$test = true}
  104. opts.on("-f", "--force", "force repository creation even if the project", "repository is already declared in Redmine") {$force = true}
  105. opts.on("-v", "--verbose", "verbose") {$verbose += 1}
  106. opts.on("-V", "--version", "show version and exit") {puts Version; exit}
  107. opts.on("-h", "--help", "show help and exit") {puts opts; exit 1}
  108. opts.on("-q", "--quiet", "no log") {$quiet = true}
  109. opts.separator("")
  110. opts.separator("Examples:")
  111. opts.separator(" reposman.rb --svn-dir=/var/svn --redmine-host=redmine.host")
  112. opts.separator(" reposman.rb -s /var/git -r redmine.host -u http://git.host --scm git")
  113. opts.separator("")
  114. opts.separator("You can find more information on the redmine's wiki:\nhttp://www.redmine.org/projects/redmine/wiki/HowTos")
  115. opts.summary_width = 25
  116. end
  117. optparse.parse!
  118. if $test
  119. log("running in test mode")
  120. end
  121. # Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
  122. if $command.nil?
  123. begin
  124. scm_module = SCM.const_get($scm)
  125. rescue
  126. log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
  127. end
  128. end
  129. $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
  130. if ($redmine_host.empty? or $repos_base.empty?)
  131. puts "Some arguments are missing. Use reposman.rb --help for getting help."
  132. exit 1
  133. end
  134. unless File.directory?($repos_base)
  135. log("directory '#{$repos_base}' doesn't exists", :exit => true)
  136. end
  137. begin
  138. require 'active_resource'
  139. rescue LoadError
  140. log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
  141. end
  142. class Project < ActiveResource::Base
  143. self.headers["User-agent"] = "Redmine repository manager/#{Version}"
  144. self.format = :json
  145. end
  146. log("querying Redmine for active projects with repository module enabled...", :level => 1);
  147. $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
  148. $redmine_host.gsub!(/\/$/, '')
  149. Project.site = "#{$redmine_host}/sys";
  150. begin
  151. # Get all active projects that have the Repository module enabled
  152. projects = Project.find(:all, :params => {:key => $api_key})
  153. rescue ActiveResource::ForbiddenAccess
  154. 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.")
  155. rescue => e
  156. log("Unable to connect to #{Project.site}: #{e}", :exit => true)
  157. end
  158. if projects.nil?
  159. log('No project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
  160. end
  161. log("retrieved #{projects.size} projects", :level => 1)
  162. def set_owner_and_rights(project, repos_path, &block)
  163. if mswin?
  164. yield if block_given?
  165. else
  166. uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
  167. right = project.is_public ? 0775 : 0770
  168. yield if block_given?
  169. Find.find(repos_path) do |f|
  170. File.chmod right, f
  171. File.chown uid, gid, f
  172. end
  173. end
  174. end
  175. def other_read_right?(file)
  176. (File.stat(file).mode & 0007).zero? ? false : true
  177. end
  178. def owner_name(file)
  179. mswin? ?
  180. $svn_owner :
  181. Etc.getpwuid( File.stat(file).uid ).name
  182. end
  183. def mswin?
  184. (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
  185. end
  186. projects.each do |project|
  187. if project.identifier.empty?
  188. log("\tno identifier for project #{project.name}")
  189. next
  190. elsif not project.identifier.match(/^[a-z0-9\-_]+$/)
  191. log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
  192. next;
  193. end
  194. log("processing project #{project.identifier} (#{project.name})", :level => 1)
  195. repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
  196. if File.directory?(repos_path)
  197. # we must verify that repository has the good owner and the good
  198. # rights before leaving
  199. other_read = other_read_right?(repos_path)
  200. owner = owner_name(repos_path)
  201. next if project.is_public == other_read and owner == $svn_owner
  202. if $test
  203. log("\tchange mode on #{repos_path}")
  204. next
  205. end
  206. begin
  207. set_owner_and_rights(project, repos_path)
  208. rescue Errno::EPERM => e
  209. log("\tunable to change mode on #{repos_path} : #{e}\n")
  210. next
  211. end
  212. log("\tmode change on #{repos_path}");
  213. else
  214. # if repository is already declared in redmine, we don't create
  215. # unless user use -f with reposman
  216. if $force == false and project.respond_to?(:repository)
  217. log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
  218. next
  219. end
  220. project.is_public ? File.umask(0002) : File.umask(0007)
  221. if $test
  222. log("\trepository #{repos_path} created")
  223. log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
  224. next
  225. end
  226. begin
  227. set_owner_and_rights(project, repos_path) do
  228. if scm_module.nil?
  229. system_or_raise "#{$command} #{repos_path}"
  230. else
  231. scm_module.create(repos_path)
  232. end
  233. end
  234. rescue => e
  235. log("\tunable to create #{repos_path} : #{e}\n")
  236. next
  237. end
  238. log("\trepository #{repos_path} created");
  239. if $svn_url
  240. begin
  241. project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"}, :key => $api_key)
  242. log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
  243. rescue => e
  244. log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
  245. end
  246. end
  247. end
  248. end