385 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			385 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require "cask/cask_loader"
 | |
| require "cask/download"
 | |
| 
 | |
| module UpdateMigrator
 | |
|   class << self
 | |
|     def formula_resources(formula)
 | |
|       specs = [formula.stable, formula.devel, formula.head].compact
 | |
| 
 | |
|       [*formula.bottle&.resource] + specs.flat_map do |spec|
 | |
|         [
 | |
|           spec,
 | |
|           *spec.resources.values,
 | |
|           *spec.patches.select(&:external?).map(&:resource),
 | |
|         ]
 | |
|       end
 | |
|     end
 | |
|     private :formula_resources
 | |
| 
 | |
|     def parse_extname(url)
 | |
|       uri_path = if URI::DEFAULT_PARSER.make_regexp =~ url
 | |
|         uri = URI(url)
 | |
|         uri.query ? "#{uri.path}?#{uri.query}" : uri.path
 | |
|       else
 | |
|         url
 | |
|       end
 | |
| 
 | |
|       # Given a URL like https://example.com/download.php?file=foo-1.0.tar.gz
 | |
|       # the extension we want is ".tar.gz", not ".php".
 | |
|       Pathname.new(uri_path).ascend do |path|
 | |
|         ext = path.extname[/[^?&]+/]
 | |
|         return ext if ext
 | |
|       end
 | |
| 
 | |
|       nil
 | |
|     end
 | |
|     private :parse_extname
 | |
| 
 | |
|     def migrate_cache_entries_to_double_dashes(initial_version)
 | |
|       return if initial_version && initial_version > "1.7.1"
 | |
| 
 | |
|       return if ENV.key?("HOMEBREW_DISABLE_LOAD_FORMULA")
 | |
| 
 | |
|       return unless HOMEBREW_CACHE.directory?
 | |
|       return if HOMEBREW_CACHE.children.empty?
 | |
| 
 | |
|       ohai "Migrating cache entries..."
 | |
| 
 | |
|       Formula.each do |formula|
 | |
|         formula_resources(formula).each do |resource|
 | |
|           downloader = resource.downloader
 | |
| 
 | |
|           url = downloader.url
 | |
|           name = resource.download_name
 | |
|           version = resource.version
 | |
| 
 | |
|           extname = parse_extname(url)
 | |
|           old_location = downloader.cache/"#{name}-#{version}#{extname}"
 | |
|           new_location = downloader.cache/"#{name}--#{version}#{extname}"
 | |
| 
 | |
|           next unless old_location.file?
 | |
| 
 | |
|           if new_location.exist?
 | |
|             begin
 | |
|               FileUtils.rm_rf old_location
 | |
|             rescue Errno::EACCES
 | |
|               opoo "Could not remove #{old_location}, please do so manually."
 | |
|             end
 | |
|           else
 | |
|             begin
 | |
|               FileUtils.mv old_location, new_location
 | |
|             rescue Errno::EACCES
 | |
|               opoo "Could not move #{old_location} to #{new_location}, please do so manually."
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def migrate_cache_entries_to_symlinks(initial_version)
 | |
|       return if initial_version && initial_version > "1.7.2"
 | |
| 
 | |
|       return if ENV.key?("HOMEBREW_DISABLE_LOAD_FORMULA")
 | |
| 
 | |
|       return unless HOMEBREW_CACHE.directory?
 | |
|       return if HOMEBREW_CACHE.children.empty?
 | |
| 
 | |
|       ohai "Migrating cache entries..."
 | |
| 
 | |
|       cache_entries = lambda do |path|
 | |
|         if path.directory?
 | |
|           path.children
 | |
|               .reject(&:symlink?)
 | |
|               .select(&:file?)
 | |
|               .map { |child| child.basename.to_s }
 | |
|               .select { |basename| basename.include?("--") }
 | |
|               .map { |basename| basename.sub(/\-\-.*/, "") }
 | |
|               .uniq
 | |
|         else
 | |
|           []
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       load_formula = lambda do |formula|
 | |
|         begin
 | |
|           Formula[formula]
 | |
|         rescue FormulaUnavailableError
 | |
|           nil
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       load_cask = lambda do |cask|
 | |
|         begin
 | |
|           Cask::CaskLoader.load(cask)
 | |
|         rescue Cask::CaskUnavailableError
 | |
|           nil
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       formula_downloaders =
 | |
|         cache_entries.call(HOMEBREW_CACHE)
 | |
|                      .map(&load_formula)
 | |
|                      .compact
 | |
|                      .flat_map { |formula| formula_resources(formula) }
 | |
|                      .map { |resource| [resource.downloader, resource.download_name, resource.version] }
 | |
| 
 | |
|       cask_downloaders =
 | |
|         cache_entries.call(HOMEBREW_CACHE/"Cask")
 | |
|                      .map(&load_cask)
 | |
|                      .compact
 | |
|                      .map { |cask| [Cask::Download.new(cask).downloader, cask.token, cask.version] }
 | |
| 
 | |
|       downloaders = formula_downloaders + cask_downloaders
 | |
| 
 | |
|       downloaders.each do |downloader, name, version|
 | |
|         next unless downloader.respond_to?(:symlink_location)
 | |
| 
 | |
|         url = downloader.url
 | |
|         extname = parse_extname(url)
 | |
|         old_location = downloader.cache/"#{name}--#{version}#{extname}"
 | |
|         next unless old_location.file?
 | |
| 
 | |
|         new_symlink_location = downloader.symlink_location
 | |
|         new_location = downloader.cached_location
 | |
| 
 | |
|         if new_location.exist? && new_symlink_location.symlink?
 | |
|           begin
 | |
|             FileUtils.rm_rf old_location unless old_location == new_symlink_location
 | |
|           rescue Errno::EACCES
 | |
|             opoo "Could not remove #{old_location}, please do so manually."
 | |
|           end
 | |
|         else
 | |
|           begin
 | |
|             new_location.dirname.mkpath
 | |
|             if new_location.exist?
 | |
|               FileUtils.rm_rf old_location
 | |
|             else
 | |
|               FileUtils.mv old_location, new_location
 | |
|             end
 | |
|             symlink_target = new_location.relative_path_from(new_symlink_location.dirname)
 | |
|             new_symlink_location.dirname.mkpath
 | |
|             FileUtils.ln_s symlink_target, new_symlink_location, force: true
 | |
|           rescue Errno::EACCES
 | |
|             opoo "Could not move #{old_location} to #{new_location}, please do so manually."
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def migrate_legacy_cache_if_necessary
 | |
|       legacy_cache = Pathname.new "/Library/Caches/Homebrew"
 | |
|       return if HOMEBREW_CACHE.to_s == legacy_cache.to_s
 | |
|       return unless legacy_cache.directory?
 | |
|       return unless legacy_cache.readable_real?
 | |
| 
 | |
|       migration_attempted_file = legacy_cache/".migration_attempted"
 | |
|       return if migration_attempted_file.exist?
 | |
| 
 | |
|       return unless legacy_cache.writable_real?
 | |
| 
 | |
|       FileUtils.touch migration_attempted_file
 | |
| 
 | |
|       # This directory could have been compromised if it's world-writable/
 | |
|       # a symlink/owned by another user so don't copy files in those cases.
 | |
|       world_writable = legacy_cache.stat.mode & 0777 == 0777
 | |
|       return if world_writable
 | |
|       return if legacy_cache.symlink?
 | |
|       return if !legacy_cache.owned? && legacy_cache.lstat.uid.nonzero?
 | |
| 
 | |
|       ohai "Migrating #{legacy_cache} to #{HOMEBREW_CACHE}..."
 | |
|       HOMEBREW_CACHE.mkpath
 | |
|       legacy_cache.cd do
 | |
|         legacy_cache.entries.each do |f|
 | |
|           next if [".", "..", ".migration_attempted"].include? f.to_s
 | |
| 
 | |
|           begin
 | |
|             FileUtils.cp_r f, HOMEBREW_CACHE
 | |
|           rescue
 | |
|             @migration_failed ||= true
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       if @migration_failed
 | |
|         opoo <<~EOS
 | |
|           Failed to migrate #{legacy_cache} to
 | |
|           #{HOMEBREW_CACHE}. Please do so manually.
 | |
|         EOS
 | |
|       else
 | |
|         ohai "Deleting #{legacy_cache}..."
 | |
|         FileUtils.rm_rf legacy_cache
 | |
|         if legacy_cache.exist?
 | |
|           FileUtils.touch migration_attempted_file
 | |
|           opoo <<~EOS
 | |
|             Failed to delete #{legacy_cache}.
 | |
|             Please do so manually.
 | |
|           EOS
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def migrate_legacy_keg_symlinks_if_necessary
 | |
|       legacy_linked_kegs = HOMEBREW_LIBRARY/"LinkedKegs"
 | |
|       return unless legacy_linked_kegs.directory?
 | |
| 
 | |
|       HOMEBREW_LINKED_KEGS.mkpath unless legacy_linked_kegs.children.empty?
 | |
|       legacy_linked_kegs.children.each do |link|
 | |
|         name = link.basename.to_s
 | |
|         src = begin
 | |
|           link.realpath
 | |
|         rescue Errno::ENOENT
 | |
|           begin
 | |
|             (HOMEBREW_PREFIX/"opt/#{name}").realpath
 | |
|           rescue Errno::ENOENT
 | |
|             begin
 | |
|               Formulary.factory(name).installed_prefix
 | |
|             rescue
 | |
|               next
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|         dst = HOMEBREW_LINKED_KEGS/name
 | |
|         dst.unlink if dst.exist?
 | |
|         FileUtils.ln_sf(src.relative_path_from(dst.parent), dst)
 | |
|       end
 | |
|       FileUtils.rm_rf legacy_linked_kegs
 | |
| 
 | |
|       legacy_pinned_kegs = HOMEBREW_LIBRARY/"PinnedKegs"
 | |
|       return unless legacy_pinned_kegs.directory?
 | |
| 
 | |
|       HOMEBREW_PINNED_KEGS.mkpath unless legacy_pinned_kegs.children.empty?
 | |
|       legacy_pinned_kegs.children.each do |link|
 | |
|         name = link.basename.to_s
 | |
|         src = link.realpath
 | |
|         dst = HOMEBREW_PINNED_KEGS/name
 | |
|         FileUtils.ln_sf(src.relative_path_from(dst.parent), dst)
 | |
|       end
 | |
|       FileUtils.rm_rf legacy_pinned_kegs
 | |
|     end
 | |
| 
 | |
|     def migrate_legacy_repository_if_necessary
 | |
|       return unless Homebrew.default_prefix?
 | |
|       return unless Homebrew.default_prefix?(HOMEBREW_REPOSITORY)
 | |
| 
 | |
|       ohai "Migrating HOMEBREW_REPOSITORY (please wait)..."
 | |
| 
 | |
|       unless HOMEBREW_PREFIX.writable_real?
 | |
|         ofail <<~EOS
 | |
|           #{HOMEBREW_PREFIX} is not writable.
 | |
| 
 | |
|           You should change the ownership and permissions of #{HOMEBREW_PREFIX}
 | |
|           temporarily back to your user account so we can complete the Homebrew
 | |
|           repository migration:
 | |
|             sudo chown -R $(whoami) #{HOMEBREW_PREFIX}
 | |
|         EOS
 | |
|         return
 | |
|       end
 | |
| 
 | |
|       new_homebrew_repository = Pathname.new "#{HOMEBREW_PREFIX}/Homebrew"
 | |
|       new_homebrew_repository.rmdir_if_possible
 | |
|       if new_homebrew_repository.exist?
 | |
|         ofail <<~EOS
 | |
|           #{new_homebrew_repository} already exists.
 | |
|           Please remove it manually or uninstall and reinstall Homebrew into a new
 | |
|           location as the migration cannot be done automatically.
 | |
|         EOS
 | |
|         return
 | |
|       end
 | |
|       new_homebrew_repository.mkpath
 | |
| 
 | |
|       repo_files = HOMEBREW_REPOSITORY.cd do
 | |
|         Utils.popen_read("git ls-files").lines.map(&:chomp)
 | |
|       end
 | |
| 
 | |
|       unless Utils.popen_read("git status --untracked-files=all --porcelain").empty?
 | |
|         HOMEBREW_REPOSITORY.cd do
 | |
|           quiet_system "git", "merge", "--abort"
 | |
|           quiet_system "git", "rebase", "--abort"
 | |
|           quiet_system "git", "reset", "--mixed"
 | |
|           safe_system "git", "-c", "user.email=brew-update@localhost",
 | |
|                              "-c", "user.name=brew update",
 | |
|                              "stash", "save", "--include-untracked"
 | |
|         end
 | |
|         stashed = true
 | |
|       end
 | |
| 
 | |
|       FileUtils.cp_r "#{HOMEBREW_REPOSITORY}/.git", "#{new_homebrew_repository}/.git"
 | |
|       new_homebrew_repository.cd do
 | |
|         safe_system "git", "checkout", "--force", "."
 | |
|         safe_system "git", "stash", "pop" if stashed
 | |
|       end
 | |
| 
 | |
|       if (HOMEBREW_REPOSITORY/"Library/Locks").exist?
 | |
|         FileUtils.cp_r "#{HOMEBREW_REPOSITORY}/Library/Locks", "#{new_homebrew_repository}/Library/Locks"
 | |
|       end
 | |
| 
 | |
|       if (HOMEBREW_REPOSITORY/"Library/Taps").exist?
 | |
|         FileUtils.cp_r "#{HOMEBREW_REPOSITORY}/Library/Taps", "#{new_homebrew_repository}/Library/Taps"
 | |
|       end
 | |
| 
 | |
|       unremovable_paths = []
 | |
|       extra_remove_paths = [
 | |
|         ".git",
 | |
|         "Library/Locks",
 | |
|         "Library/Taps",
 | |
|         "Library/Homebrew/cask",
 | |
|         "Library/Homebrew/test",
 | |
|       ]
 | |
|       (repo_files + extra_remove_paths).each do |file|
 | |
|         path = Pathname.new "#{HOMEBREW_REPOSITORY}/#{file}"
 | |
|         begin
 | |
|           FileUtils.rm_rf path
 | |
|         rescue Errno::EACCES
 | |
|           unremovable_paths << path
 | |
|         end
 | |
|         quiet_system "rmdir", "-p", path.parent if path.parent.exist?
 | |
|       end
 | |
| 
 | |
|       unless unremovable_paths.empty?
 | |
|         ofail <<~EOS
 | |
|           Could not remove old HOMEBREW_REPOSITORY paths!
 | |
|           Please do this manually with:
 | |
|             sudo rm -rf #{unremovable_paths.join " "}
 | |
|         EOS
 | |
|       end
 | |
| 
 | |
|       Keg::MUST_EXIST_DIRECTORIES.each { |dir| FileUtils.mkdir_p dir }
 | |
| 
 | |
|       src = Pathname.new("#{new_homebrew_repository}/bin/brew")
 | |
|       dst = Pathname.new("#{HOMEBREW_PREFIX}/bin/brew")
 | |
|       begin
 | |
|         FileUtils.ln_s(src.relative_path_from(dst.parent), dst)
 | |
|       rescue Errno::EACCES, Errno::ENOENT
 | |
|         ofail <<~EOS
 | |
|           Could not create symlink at #{dst}!
 | |
|           Please do this manually with:
 | |
|             sudo ln -sf #{src} #{dst}
 | |
|             sudo chown $(whoami) #{dst}
 | |
|         EOS
 | |
|       end
 | |
| 
 | |
|       link_completions_manpages_and_docs(new_homebrew_repository)
 | |
| 
 | |
|       ohai "Migrated HOMEBREW_REPOSITORY to #{new_homebrew_repository}!"
 | |
|       if HOMEBREW_PREFIX == "/usr/local"
 | |
|         puts <<~EOS
 | |
|           Homebrew no longer needs to have ownership of #{HOMEBREW_PREFIX}. If you wish you can
 | |
|           return #{HOMEBREW_PREFIX} to its default ownership with:
 | |
|             sudo chown root:wheel #{HOMEBREW_PREFIX}
 | |
|         EOS
 | |
|       end
 | |
|     rescue => e
 | |
|       ofail <<~EOS
 | |
|         #{Tty.bold}Failed to migrate HOMEBREW_REPOSITORY to #{new_homebrew_repository}!#{Tty.reset}
 | |
|         The error was:
 | |
|           #{e}
 | |
|         Please try to resolve this error yourself and then run `brew update` again to
 | |
|         complete the migration. If you need help please +1 an existing error or comment
 | |
|         with your new error in issue:
 | |
|           #{Formatter.url("https://github.com/Homebrew/brew/issues/987")}
 | |
|       EOS
 | |
|       $stderr.puts e.backtrace
 | |
|     end
 | |
|   end
 | |
| end
 | 
