aboutsummaryrefslogtreecommitdiffstats
path: root/Library
diff options
context:
space:
mode:
authorVlad Shablinsky2015-08-09 14:49:19 +0300
committerMike McQuaid2015-08-10 13:57:59 +0100
commit832c5875b01bf86652f53be1543742907da1febb (patch)
treed6e6387a3b17548568a313bd2fa23d3ef2836721 /Library
parent556ab3bcd6125e22506bb8845576c73a4330f14f (diff)
downloadbrew-832c5875b01bf86652f53be1543742907da1febb.tar.bz2
add migrator class for migrating renamed formulae
Diffstat (limited to 'Library')
-rw-r--r--Library/Homebrew/migrator.rb303
1 files changed, 303 insertions, 0 deletions
diff --git a/Library/Homebrew/migrator.rb b/Library/Homebrew/migrator.rb
new file mode 100644
index 000000000..5bd3c6c57
--- /dev/null
+++ b/Library/Homebrew/migrator.rb
@@ -0,0 +1,303 @@
+require "formula"
+require "keg"
+require "tab"
+require "tap_migrations"
+
+class Migrator
+ class MigratorNoOldnameError < RuntimeError
+ def initialize(formula)
+ super "#{formula.name} doesn't replace any formula."
+ end
+ end
+
+ class MigratorNoOldpathError < RuntimeError
+ def initialize(formula)
+ super "#{HOMEBREW_CELLAR/formula.oldname} doesn't exist."
+ end
+ end
+
+ class MigratorDifferentTapsError < RuntimeError
+ def initialize(formula, tap)
+ if tap.nil?
+ super <<-EOS.undent
+ #{formula.name} from #{formula.tap} is given, but old name #{formula.oldname} wasn't installed from taps or core formulae
+
+ You can try `brew migrate --force #{formula.oldname}`.
+ EOS
+ else
+ user, repo = tap.split("/")
+ repo.sub!("homebrew-", "")
+ name = "fully-qualified #{user}/#{repo}/#{formula.oldname}"
+ name = formula.oldname if tap == "Homebrew/homebrew"
+ super <<-EOS.undent
+ #{formula.name} from #{formula.tap} is given, but old name #{formula.oldname} was installed from #{tap}
+
+ Please try to use #{name} to refer the formula
+ EOS
+ end
+ end
+ end
+
+ attr_reader :formula
+ attr_reader :oldname, :oldpath, :old_pin_record, :old_opt_record
+ attr_reader :old_linked_keg_record, :oldkeg, :old_tabs, :old_tap
+ attr_reader :newname, :newpath, :new_pin_record
+ attr_reader :old_pin_link_record
+
+ def initialize(formula)
+ @oldname = formula.oldname
+ @newname = formula.name
+ raise MigratorNoOldnameError.new(formula) unless oldname
+
+ @formula = formula
+ @oldpath = HOMEBREW_CELLAR/formula.oldname
+ raise MigratorNoOldpathError.new(formula) unless oldpath.exist?
+
+ @old_tabs = oldpath.subdirs.each.map { |d| Tab.for_keg(Keg.new(d)) }
+ @old_tap = old_tabs.first.tap
+ raise MigratorDifferentTapsError.new(formula, old_tap) unless from_same_taps?
+
+ @newpath = HOMEBREW_CELLAR/formula.name
+
+ if @oldkeg = get_linked_oldkeg
+ @old_linked_keg_record = oldkeg.linked_keg_record if oldkeg.linked?
+ @old_opt_record = oldkeg.opt_record if oldkeg.optlinked?
+ end
+
+ @old_pin_record = HOMEBREW_LIBRARY/"PinnedKegs"/oldname
+ @new_pin_record = HOMEBREW_LIBRARY/"PinnedKegs"/newname
+ @pinned = old_pin_record.symlink?
+ @old_pin_link_record = old_pin_record.readlink if @pinned
+ end
+
+ # Fix INSTALL_RECEIPTS for tap-migrated formula.
+ def fix_tabs
+ old_tabs.each do |tab|
+ tab.source["tap"] = formula.tap
+ tab.write
+ end
+ end
+
+ def from_same_taps?
+ if old_tap == nil && formula.core_formula? && ARGV.force?
+ true
+ elsif formula.tap == old_tap
+ true
+ # Homebrew didn't use to update tabs while performing tap-migrations,
+ # so there can be INSTALL_RECEIPT's containing wrong information about
+ # tap (tap is Homebrew/homebrew if installed formula migrates to a tap), so
+ # we check if there is an entry about oldname migrated to tap and if
+ # newname's tap is the same as tap to which oldname migrated, then we
+ # can perform migrations and the taps for oldname and newname are the same.
+ elsif TAP_MIGRATIONS && (rec = TAP_MIGRATIONS[formula.oldname]) \
+ && rec == formula.tap.sub("homebrew-", "")
+ fix_tabs
+ true
+ elsif formula.tap
+ false
+ end
+ end
+
+ def get_linked_oldkeg
+ kegs = oldpath.subdirs.map { |d| Keg.new(d) }
+ kegs.detect(&:linked?) || kegs.detect(&:optlinked?)
+ end
+
+ def pinned?
+ @pinned
+ end
+
+ def oldkeg_linked?
+ !!oldkeg
+ end
+
+ def migrate
+ if newpath.exist?
+ onoe "#{newpath} already exists; remove it manually and run brew migrate #{oldname}."
+ return
+ end
+
+ begin
+ oh1 "Migrating #{Tty.green}#{oldname}#{Tty.white} to #{Tty.green}#{newname}#{Tty.reset}"
+ unlink_oldname
+ move_to_new_directory
+ repin
+ link_newname
+ link_oldname_opt
+ link_oldname_cellar
+ update_tabs
+ rescue Interrupt
+ ignore_interrupts { backup_oldname }
+ rescue Exception => e
+ onoe "error occured while migrating."
+ puts e if ARGV.debug?
+ puts "Backuping..."
+ ignore_interrupts { backup_oldname }
+ end
+ end
+
+ # move everything from Cellar/oldname to Cellar/newname
+ def move_to_new_directory
+ puts "Moving to: #{newpath}"
+ FileUtils.mv(oldpath, newpath)
+ end
+
+ def repin
+ if pinned?
+ # old_pin_record is a relative symlink and when we try to to read it
+ # from <dir> we actually try to find file
+ # <dir>/../<...>/../Cellar/name/version.
+ # To repin formula we need to update the link thus that it points to
+ # the right directory.
+ # NOTE: old_pin_record.realpath.sub(oldname, newname) is unacceptable
+ # here, because it resolves every symlink for old_pin_record and then
+ # substitutes oldname with newname. It breaks things like
+ # Pathname#make_relative_symlink, where Pathname#relative_path_from
+ # is used to find relative path from source to destination parent and
+ # it assumes no symlinks.
+ src_oldname = old_pin_record.dirname.join(old_pin_link_record).expand_path
+ new_pin_record.make_relative_symlink(src_oldname.sub(oldname, newname))
+ old_pin_record.delete
+ end
+ end
+
+ def unlink_oldname
+ oh1 "Unlinking #{Tty.green}#{oldname}#{Tty.reset}"
+ oldpath.subdirs.each do |d|
+ keg = Keg.new(d)
+ keg.unlink
+ end
+ end
+
+ def link_newname
+ oh1 "Linking #{Tty.green}#{newname}#{Tty.reset}"
+ keg = Keg.new(formula.installed_prefix)
+
+ if formula.keg_only?
+ begin
+ keg.optlink
+ rescue Keg::LinkError => e
+ onoe "Failed to create #{formula.opt_prefix}"
+ puts e
+ raise
+ end
+ return
+ end
+
+ keg.remove_linked_keg_record if keg.linked?
+
+ begin
+ keg.link
+ rescue Keg::ConflictError => e
+ onoe "Error while executing `brew link` step on #{newname}"
+ puts e
+ puts
+ puts "Possible conflicting files are:"
+ mode = OpenStruct.new(:dry_run => true, :overwrite => true)
+ keg.link(mode)
+ raise
+ rescue Keg::LinkError => e
+ onoe "Error while linking"
+ puts e
+ puts
+ puts "You can try again using:"
+ puts " brew link #{formula.name}"
+ rescue Exception => e
+ onoe "An unexpected error occurred during linking"
+ puts e
+ puts e.backtrace
+ ignore_interrupts { keg.unlink }
+ raise e
+ end
+ end
+
+ # Link keg to opt if it was linked before migrating.
+ def link_oldname_opt
+ if old_opt_record
+ old_opt_record.delete if old_opt_record.symlink? || old_opt_record.exist?
+ old_opt_record.make_relative_symlink(formula.installed_prefix)
+ end
+ end
+
+ # After migtaion every INSTALL_RECEIPT.json has wrong path to the formula
+ # so we must update INSTALL_RECEIPTs
+ def update_tabs
+ new_tabs = newpath.subdirs.map { |d| Tab.for_keg(Keg.new(d)) }
+ new_tabs.each do |tab|
+ tab.source["path"] = formula.path.to_s if tab.source["path"]
+ tab.write
+ end
+ end
+
+ # Remove opt/oldname link if it belongs to newname.
+ def unlink_oldname_opt
+ return unless old_opt_record
+ if old_opt_record.symlink? && formula.installed_prefix.exist? \
+ && formula.installed_prefix.realpath == old_opt_record.realpath
+ old_opt_record.unlink
+ old_opt_record.parent.rmdir_if_possible
+ end
+ end
+
+ # Remove oldpath if it exists
+ def link_oldname_cellar
+ oldpath.delete if oldpath.symlink? || oldpath.exist?
+ oldpath.make_relative_symlink(formula.rack)
+ end
+
+ # Remove Cellar/oldname link if it belongs to newname.
+ def unlink_oldname_cellar
+ if (oldpath.symlink? && !oldpath.exist?) || (oldpath.symlink? \
+ && formula.rack.exist? && formula.rack.realpath == oldpath.realpath)
+ oldpath.unlink
+ end
+ end
+
+ # Backup everything if errors occured while migrating.
+ def backup_oldname
+ unlink_oldname_opt
+ unlink_oldname_cellar
+ backup_oldname_cellar
+ backup_old_tabs
+
+ if pinned? && !old_pin_record.symlink?
+ src_oldname = old_pin_record.dirname.join(old_pin_link_record).expand_path
+ old_pin_record.make_relative_symlink(src_oldname)
+ new_pin_record.delete
+ end
+
+ if newpath.exist?
+ newpath.subdirs.each do |d|
+ newname_keg = Keg.new(d)
+ newname_keg.unlink
+ newname_keg.uninstall
+ end
+ end
+
+ if oldkeg_linked?
+ begin
+ # The keg used to be linked and when we backup everything we restore
+ # Cellar/oldname, the target also gets restored, so we are able to
+ # create a keg using its old path
+ keg = Keg.new(Pathname.new(oldkeg.to_s))
+ keg.link
+ rescue Keg::LinkError
+ keg.unlink
+ raise
+ rescue Keg::AlreadyLinkedError
+ keg.unlink
+ retry
+ end
+ end
+ end
+
+ def backup_oldname_cellar
+ unless oldpath.exist?
+ FileUtils.mv(newpath, oldpath)
+ end
+ end
+
+ def backup_old_tabs
+ old_tabs.each(&:write)
+ end
+end