From 516a39f5a471822b0565ae24189cc5389caaf923 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Sat, 25 Aug 2018 02:44:46 +0200 Subject: [PATCH] Move migrations into `UpdateMigrator` module. --- Library/Homebrew/brew.rb | 4 +- Library/Homebrew/cmd/update-report.rb | 329 +----------------- ...report_spec.rb => update_migrator_spec.rb} | 16 +- Library/Homebrew/update_migrator.rb | 39 +++ .../cache_entries_to_double_dashes.rb | 41 +++ .../cache_entries_to_symlinks.rb | 90 +++++ .../Homebrew/update_migrator/legacy_cache.rb | 53 +++ .../update_migrator/legacy_keg_symlinks.rb | 42 +++ .../update_migrator/legacy_repository.rb | 122 +++++++ Library/Homebrew/utils.rb | 39 --- 10 files changed, 404 insertions(+), 371 deletions(-) rename Library/Homebrew/test/{cmd/update-report_spec.rb => update_migrator_spec.rb} (81%) create mode 100644 Library/Homebrew/update_migrator.rb create mode 100644 Library/Homebrew/update_migrator/cache_entries_to_double_dashes.rb create mode 100644 Library/Homebrew/update_migrator/cache_entries_to_symlinks.rb create mode 100644 Library/Homebrew/update_migrator/legacy_cache.rb create mode 100644 Library/Homebrew/update_migrator/legacy_keg_symlinks.rb create mode 100644 Library/Homebrew/update_migrator/legacy_repository.rb diff --git a/Library/Homebrew/brew.rb b/Library/Homebrew/brew.rb index 527dec79e7..162fae41e1 100644 --- a/Library/Homebrew/brew.rb +++ b/Library/Homebrew/brew.rb @@ -14,6 +14,8 @@ end require_relative "global" +require "update_migrator" + begin trap("INT", std_trap) # restore default CTRL-C handler @@ -76,7 +78,7 @@ begin end # Migrate LinkedKegs/PinnedKegs if update didn't already do so - migrate_legacy_keg_symlinks_if_necessary + UpdateMigrator.migrate_legacy_keg_symlinks_if_necessary # Uninstall old brew-cask if it's still around; we just use the tap now. if cmd == "cask" && (HOMEBREW_CELLAR/"brew-cask").exist? diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 7c8a334959..009180407b 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -7,7 +7,7 @@ require "migrator" require "formulary" require "descriptions" require "cleanup" -require "hbc/download" +require "update_migrator" module Homebrew module_function @@ -111,10 +111,10 @@ module Homebrew updated = true end - migrate_legacy_cache_if_necessary - migrate_cache_entries_to_double_dashes(initial_version) - migrate_cache_entries_to_symlinks(initial_version) - migrate_legacy_keg_symlinks_if_necessary + UpdateMigrator.migrate_legacy_cache_if_necessary + UpdateMigrator.migrate_cache_entries_to_double_dashes(initial_version) + UpdateMigrator.migrate_cache_entries_to_symlinks(initial_version) + UpdateMigrator.migrate_legacy_keg_symlinks_if_necessary if !updated if !ARGV.include?("--preinstall") && !ENV["HOMEBREW_UPDATE_FAILED"] @@ -140,7 +140,7 @@ module Homebrew # This should always be the last thing to run (but skip on auto-update). if !ARGV.include?("--preinstall") || ENV["HOMEBREW_ENABLE_AUTO_UPDATE_MIGRATION"] - migrate_legacy_repository_if_necessary + UpdateMigrator.migrate_legacy_repository_if_necessary end end @@ -158,323 +158,6 @@ module Homebrew ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision 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 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 - - 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 - - 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") - - 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") - - ohai "Migrating cache entries..." - - load_formula = lambda do |formula| - begin - Formula[formula] - rescue FormulaUnavailableError - nil - end - end - - load_cask = lambda do |cask| - begin - Hbc::CaskLoader.load(cask) - rescue Hbc::CaskUnavailableError - nil - end - end - - formula_downloaders = if HOMEBREW_CACHE.directory? - HOMEBREW_CACHE.children - .select(&:file?) - .map { |child| child.basename.to_s.sub(/\-\-.*/, "") } - .uniq - .map(&load_formula) - .compact - .flat_map { |formula| formula_resources(formula) } - .map { |resource| [resource.downloader, resource.download_name, resource.version] } - else - [] - end - - cask_downloaders = if (HOMEBREW_CACHE/"Cask").directory? - (HOMEBREW_CACHE/"Cask").children - .map { |child| child.basename.to_s.sub(/\-\-.*/, "") } - .uniq - .map(&load_cask) - .compact - .map { |cask| [Hbc::Download.new(cask).downloader, cask.token, cask.version] } - else - [] - end - - 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 - FileUtils.mv old_location, new_location unless new_location.exist? - 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_repository_if_necessary - return unless HOMEBREW_PREFIX.to_s == "/usr/local" - return unless HOMEBREW_REPOSITORY.to_s == "/usr/local" - - 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 "/usr/local/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::ALL_TOP_LEVEL_DIRECTORIES + ["Cellar"]).each do |dir| - FileUtils.mkdir_p "#{HOMEBREW_PREFIX}/#{dir}" - end - - 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}!" - puts <<~EOS - Homebrew no longer needs to have ownership of /usr/local. If you wish you can - return /usr/local to its default ownership with: - sudo chown root:wheel #{HOMEBREW_PREFIX} - EOS - 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 - def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY) command = "brew update" Utils::Link.link_completions(repository, command) diff --git a/Library/Homebrew/test/cmd/update-report_spec.rb b/Library/Homebrew/test/update_migrator_spec.rb similarity index 81% rename from Library/Homebrew/test/cmd/update-report_spec.rb rename to Library/Homebrew/test/update_migrator_spec.rb index 71ebe2cb23..811d035e1e 100644 --- a/Library/Homebrew/test/cmd/update-report_spec.rb +++ b/Library/Homebrew/test/update_migrator_spec.rb @@ -1,6 +1,6 @@ -require "cmd/update-report" +require "update_migrator" -describe "brew update-report" do +describe UpdateMigrator do describe "::migrate_cache_entries_to_double_dashes" do let(:formula_name) { "foo" } let(:f) { @@ -18,7 +18,7 @@ describe "brew update-report" do end it "moves old files to use double dashes when upgrading from <= 1.7.1" do - Homebrew.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) + described_class.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) expect(old_cache_file).not_to exist expect(new_cache_file).to exist @@ -28,8 +28,8 @@ describe "brew update-report" do let(:formula_name) { "foo-bar" } it "does not introduce extra double dashes when called multiple times" do - Homebrew.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) - Homebrew.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) + described_class.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) + described_class.migrate_cache_entries_to_double_dashes(Version.new("1.7.1")) expect(old_cache_file).not_to exist expect(new_cache_file).to exist @@ -37,7 +37,7 @@ describe "brew update-report" do end it "does not move files if upgrading from > 1.7.1" do - Homebrew.migrate_cache_entries_to_double_dashes(Version.new("1.7.2")) + described_class.migrate_cache_entries_to_double_dashes(Version.new("1.7.2")) expect(old_cache_file).to exist expect(new_cache_file).not_to exist @@ -63,7 +63,7 @@ describe "brew update-report" do end it "moves old files to use symlinks when upgrading from <= 1.7.2" do - Homebrew.migrate_cache_entries_to_symlinks(Version.new("1.7.2")) + described_class.migrate_cache_entries_to_symlinks(Version.new("1.7.2")) expect(old_cache_file).to eq(new_cache_symlink) expect(new_cache_symlink).to be_a_symlink @@ -74,7 +74,7 @@ describe "brew update-report" do end it "does not move files if upgrading from > 1.7.2" do - Homebrew.migrate_cache_entries_to_symlinks(Version.new("1.7.3")) + described_class.migrate_cache_entries_to_symlinks(Version.new("1.7.3")) expect(old_cache_file).to exist expect(new_cache_file).not_to exist diff --git a/Library/Homebrew/update_migrator.rb b/Library/Homebrew/update_migrator.rb new file mode 100644 index 0000000000..bffea1ec11 --- /dev/null +++ b/Library/Homebrew/update_migrator.rb @@ -0,0 +1,39 @@ +require "update_migrator/cache_entries_to_double_dashes" +require "update_migrator/cache_entries_to_symlinks" +require "update_migrator/legacy_cache" +require "update_migrator/legacy_keg_symlinks" +require "update_migrator/legacy_repository" + +module UpdateMigrator + module_function + + 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 + + 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 +end diff --git a/Library/Homebrew/update_migrator/cache_entries_to_double_dashes.rb b/Library/Homebrew/update_migrator/cache_entries_to_double_dashes.rb new file mode 100644 index 0000000000..90ef6e58aa --- /dev/null +++ b/Library/Homebrew/update_migrator/cache_entries_to_double_dashes.rb @@ -0,0 +1,41 @@ +module UpdateMigrator + module_function + + 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") + + 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 +end diff --git a/Library/Homebrew/update_migrator/cache_entries_to_symlinks.rb b/Library/Homebrew/update_migrator/cache_entries_to_symlinks.rb new file mode 100644 index 0000000000..371a2ce542 --- /dev/null +++ b/Library/Homebrew/update_migrator/cache_entries_to_symlinks.rb @@ -0,0 +1,90 @@ +require "hbc/cask_loader" +require "hbc/download" + +module UpdateMigrator + module_function + + 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") + + ohai "Migrating cache entries..." + + load_formula = lambda do |formula| + begin + Formula[formula] + rescue FormulaUnavailableError + nil + end + end + + load_cask = lambda do |cask| + begin + Hbc::CaskLoader.load(cask) + rescue Hbc::CaskUnavailableError + nil + end + end + + formula_downloaders = if HOMEBREW_CACHE.directory? + HOMEBREW_CACHE.children + .select(&:file?) + .map { |child| child.basename.to_s.sub(/\-\-.*/, "") } + .uniq + .map(&load_formula) + .compact + .flat_map { |formula| formula_resources(formula) } + .map { |resource| [resource.downloader, resource.download_name, resource.version] } + else + [] + end + + cask_downloaders = if (HOMEBREW_CACHE/"Cask").directory? + (HOMEBREW_CACHE/"Cask").children + .map { |child| child.basename.to_s.sub(/\-\-.*/, "") } + .uniq + .map(&load_cask) + .compact + .map { |cask| [Hbc::Download.new(cask).downloader, cask.token, cask.version] } + else + [] + end + + 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 +end diff --git a/Library/Homebrew/update_migrator/legacy_cache.rb b/Library/Homebrew/update_migrator/legacy_cache.rb new file mode 100644 index 0000000000..675dc29686 --- /dev/null +++ b/Library/Homebrew/update_migrator/legacy_cache.rb @@ -0,0 +1,53 @@ +module UpdateMigrator + module_function + + 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 +end diff --git a/Library/Homebrew/update_migrator/legacy_keg_symlinks.rb b/Library/Homebrew/update_migrator/legacy_keg_symlinks.rb new file mode 100644 index 0000000000..76c346da9b --- /dev/null +++ b/Library/Homebrew/update_migrator/legacy_keg_symlinks.rb @@ -0,0 +1,42 @@ +module UpdateMigrator + module_function + + 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 +end diff --git a/Library/Homebrew/update_migrator/legacy_repository.rb b/Library/Homebrew/update_migrator/legacy_repository.rb new file mode 100644 index 0000000000..698e479e64 --- /dev/null +++ b/Library/Homebrew/update_migrator/legacy_repository.rb @@ -0,0 +1,122 @@ +module UpdateMigrator + module_function + + def migrate_legacy_repository_if_necessary + return unless HOMEBREW_PREFIX.to_s == "/usr/local" + return unless HOMEBREW_REPOSITORY.to_s == "/usr/local" + + 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 "/usr/local/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::ALL_TOP_LEVEL_DIRECTORIES + ["Cellar"]).each do |dir| + FileUtils.mkdir_p "#{HOMEBREW_PREFIX}/#{dir}" + end + + 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}!" + puts <<~EOS + Homebrew no longer needs to have ownership of /usr/local. If you wish you can + return /usr/local to its default ownership with: + sudo chown root:wheel #{HOMEBREW_PREFIX} + EOS + 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 diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 20319c4c35..b3ecb0d091 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -498,45 +498,6 @@ def truncate_text_to_approximate_size(s, max_bytes, options = {}) out 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 - # Calls the given block with the passed environment variables # added to ENV, then restores ENV afterwards. # Example: