aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cmd
diff options
context:
space:
mode:
authorMike McQuaid2016-01-10 20:28:52 +0000
committerMike McQuaid2016-01-17 19:48:02 +0000
commit99234f0256110b23e915f7a8f029e5e25c54362e (patch)
tree8c16a861b1f46d1bb54b30bb98ce41e73b50ef89 /Library/Homebrew/cmd
parent6f91b429ceee780088b659e519e19262bcfd48cf (diff)
downloadbrew-99234f0256110b23e915f7a8f029e5e25c54362e.tar.bz2
Add new update-bash command for testing.
This will become the default updater at a later point in the future.
Diffstat (limited to 'Library/Homebrew/cmd')
-rwxr-xr-xLibrary/Homebrew/cmd/update-bash.sh280
-rw-r--r--Library/Homebrew/cmd/update-report.rb351
2 files changed, 631 insertions, 0 deletions
diff --git a/Library/Homebrew/cmd/update-bash.sh b/Library/Homebrew/cmd/update-bash.sh
new file mode 100755
index 000000000..83355202b
--- /dev/null
+++ b/Library/Homebrew/cmd/update-bash.sh
@@ -0,0 +1,280 @@
+#!/bin/bash
+
+if [ -z "$HOMEBREW_BREW_FILE" ]
+then
+ echo "Error: $(basename "$0") must be called from brew!" >&2
+ exit 1
+fi
+
+brew() {
+ "$HOMEBREW_BREW_FILE" "$@"
+}
+
+which_git() {
+ local which_git
+ which_git="$(which git 2>/dev/null)"
+ if [ -n "$which_git" ] && [ "/usr/bin/git" = "$which_git" ]
+ then
+ local active_developer_dir
+ active_developer_dir="$('/usr/bin/xcode-select' -print-path 2>/dev/null)"
+ if [ -n "$active_developer_dir" ] && [ -x "$active_developer_dir/usr/bin/git" ]
+ then
+ which_git="$active_developer_dir/usr/bin/git"
+ else
+ which_git=""
+ fi
+ fi
+ echo "$which_git"
+}
+
+git_init_if_necessary() {
+ if ! [ -d ".git" ]
+ then
+ git init -q
+ git config --bool core.autocrlf false
+ git config remote.origin.url https://github.com/Homebrew/homebrew.git
+ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
+ fi
+
+ if git remote show origin -n | grep -q "mxcl/homebrew"
+ then
+ git remote set-url origin https://github.com/Homebrew/homebrew.git
+ git remote set-url --delete origin ".*mxcl\/homebrew.*"
+ fi
+}
+
+repo_var() {
+ echo "$1" |
+ sed -e "s|$HOMEBREW_PREFIX||g" \
+ -e 's|Library/Taps/||g' \
+ -e 's|[^a-z0-9]|_|g' |
+ tr "[:lower:]" "[:upper:]"
+}
+
+upstream_branch() {
+ local upstream_branch
+ upstream_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null |
+ sed -e 's|refs/remotes/origin/||' )"
+ [ -z "$upstream_branch" ] && upstream_branch="master"
+ echo "$upstream_branch"
+}
+
+read_current_revision() {
+ git rev-parse -q --verify HEAD
+}
+
+# Don't warn about QUIET_ARGS; they need to be unquoted.
+# shellcheck disable=SC2086
+pop_stash() {
+ [ -z "$STASHED" ] && return
+ git stash pop $QUIET_ARGS
+ if [ -n "$HOMEBREW_VERBOSE" ]
+ then
+ echo "Restoring your stashed changes to $DIR:"
+ git status --short --untracked-files
+ fi
+ unset STASHED
+}
+
+pop_stash_message() {
+ [ -z "$STASHED" ] && return
+ echo "To restore the stashed changes to $DIR run:"
+ echo " 'cd $DIR && git stash pop'"
+ unset STASHED
+}
+
+# Don't warn about QUIET_ARGS; they need to be unquoted.
+# shellcheck disable=SC2086
+reset_on_interrupt() {
+ [ -z "$INITIAL_BRANCH" ] || git checkout "$INITIAL_BRANCH"
+ git reset --hard "$INITIAL_REVISION" $QUIET_ARGS
+ if [ -n "$INITIAL_BRANCH" ]
+ then
+ pop_stash
+ else
+ pop_stash_message
+ fi
+}
+
+# Don't warn about QUIET_ARGS; they need to be unquoted.
+# shellcheck disable=SC2086
+pull() {
+ local DIR="$1"
+ cd "$DIR" || return
+ TAP_VAR=$(repo_var "$DIR")
+ unset STASHED
+
+ # The upstream repository's default branch may not be master;
+ # check refs/remotes/origin/HEAD to see what the default
+ # origin branch name is, and use that. If not set, fall back to "master".
+ INITIAL_BRANCH="$(git symbolic-ref --short HEAD 2>/dev/null)"
+ UPSTREAM_BRANCH="$(upstream_branch)"
+
+ if [ -n "$(git status --untracked-files=all --porcelain 2>/dev/null)" ]
+ then
+ if [ -n "$HOMEBREW_VERBOSE" ]
+ then
+ echo "Stashing uncommitted changes to $DIR."
+ git status --short --untracked-files=all
+ fi
+ git -c "user.email=brew-update@localhost" \
+ -c "user.name=brew update" \
+ stash save --include-untracked $QUIET_ARGS
+ git reset --hard $QUIET_ARGS
+ STASHED="1"
+ fi
+
+ # Used for testing purposes, e.g., for testing formula migration after
+ # renaming it in the currently checked-out branch. To test run
+ # "brew update --simulate-from-current-branch"
+ if [ -n "$HOMEBREW_SIMULATE_FROM_CURRENT_BRANCH" ]
+ then
+ INITIAL_REVISION="$(git rev-parse -q --verify "$(upstream_branch)")"
+ CURRENT_REVISION="$(read_current_revision)"
+ export HOMEBREW_UPDATE_AFTER"$TAP_VAR"="$(git rev-parse "$UPSTREAM_BRANCH")"
+ if ! git merge-base --is-ancestor "$INITIAL_REVISION" "$CURRENT_REVISION"
+ then
+ echo "Your HEAD is not a descendant of $UPSTREAM_BRANCH!" >&2
+ exit 1
+ fi
+ return
+ fi
+
+ if [ "$INITIAL_BRANCH" != "$UPSTREAM_BRANCH" ] && [ -n "$INITIAL_BRANCH" ]
+ then
+ # Recreate and check out `#{upstream_branch}` if unable to fast-forward
+ # it to `origin/#{@upstream_branch}`. Otherwise, just check it out.
+ if git merge-base --is-ancestor "$UPSTREAM_BRANCH" "origin/$UPSTREAM_BRANCH" &>/dev/null
+ then
+ git checkout --force "$UPSTREAM_BRANCH" $QUIET_ARGS
+ else
+ git checkout --force -B "$UPSTREAM_BRANCH" "origin/$UPSTREAM_BRANCH" $QUIET_ARGS
+ fi
+ fi
+
+ INITIAL_REVISION="$(read_current_revision)"
+
+ # ensure we don't munge line endings on checkout
+ git config core.autocrlf false
+
+ trap reset_on_interrupt SIGINT
+
+ if [ -n "$HOMEBREW_REBASE" ]
+ then
+ git rebase $QUIET_ARGS "origin/$UPSTREAM_BRANCH"
+ else
+ git merge --no-edit --ff $QUIET_ARGS "origin/$UPSTREAM_BRANCH"
+ fi
+
+ trap - SIGINT
+
+ CURRENT_REVISION="$(read_current_revision)"
+ export HOMEBREW_UPDATE_AFTER"$TAP_VAR"="$(git rev-parse "$UPSTREAM_BRANCH")"
+
+ if [ "$INITIAL_BRANCH" != "$UPSTREAM_BRANCH" ] && [ -n "$INITIAL_BRANCH" ]
+ then
+ git checkout "$INITIAL_BRANCH" $QUIET_ARGS
+ pop_stash
+ else
+ pop_stash_message
+ fi
+}
+
+update-bash() {
+ if [ -z "$HOMEBREW_DEVELOPER" ]
+ then
+ echo "This command is currently only for Homebrew developers' use." >&2
+ exit 1
+ fi
+
+ for i in "$@"
+ do
+ case "$i" in
+ update|update-bash) shift ;;
+ --help) brew update --help; exit $? ;;
+ --verbose) HOMEBREW_VERBOSE=1 ;;
+ --debug) HOMEBREW_DEBUG=1;;
+ --rebase) HOMEBREW_REBASE=1 ;;
+ --simulate-from-current-branch) HOMEBREW_SIMULATE_FROM_CURRENT_BRANCH=1 ;;
+ --*) ;;
+ -*v*) HOMEBREW_VERBOSE=1 ;;
+ -*v*) HOMEBREW_DEBUG=1 ;;
+ -*) ;;
+ *)
+ echo "This command updates brew itself, and does not take formula names." >&2
+ echo "Use 'brew upgrade <formula>'." >&2
+ exit 1
+ ;;
+ esac
+ done
+
+ if [ -n "$HOMEBREW_DEBUG" ]
+ then
+ set -x
+ fi
+
+ # check permissions
+ if [ "$HOMEBREW_PREFIX" = "/usr/local" ] && ! test -w /usr/local
+ then
+ echo "Error: /usr/local must be writable!" >&2
+ exit 1
+ fi
+
+ if ! test -w "$HOMEBREW_REPOSITORY"
+ then
+ echo "Error: $HOMEBREW_REPOSITORY must be writable!" >&2
+ exit 1
+ fi
+
+ if [ -z "$(which_git)" ]
+ then
+ brew install git
+ if [ -z "$(which_git)" ]
+ then
+ echo "Error: Git must be installed and in your PATH!" >&2
+ exit 1
+ fi
+ fi
+
+ if [ -z "$HOMEBREW_VERBOSE" ]
+ then
+ QUIET_ARGS="-q"
+ fi
+
+ # ensure GIT_CONFIG is unset as we need to operate on .git/config
+ unset GIT_CONFIG
+
+ cd "$HOMEBREW_REPOSITORY" || {
+ echo "Error: failed to cd to $HOMEBREW_REPOSITORY!" >&2
+ exit 1
+ }
+ git_init_if_necessary
+
+ for DIR in "$HOMEBREW_REPOSITORY" "$HOMEBREW_LIBRARY"/Taps/*/*
+ do
+ [ -d "$DIR/.git" ] || continue
+ cd "$DIR" || continue
+ TAP_VAR=$(repo_var "$DIR")
+ export HOMEBREW_UPDATE_BEFORE"$TAP_VAR"="$(git rev-parse "$(upstream_branch)")"
+ UPSTREAM_BRANCH="$(upstream_branch)"
+ # the refspec ensures that the default upstream branch gets updated
+ git fetch $QUIET_ARGS origin \
+ "refs/heads/$UPSTREAM_BRANCH:refs/remotes/origin/$UPSTREAM_BRANCH" &
+ done
+
+ wait
+
+ for DIR in "$HOMEBREW_REPOSITORY" "$HOMEBREW_LIBRARY"/Taps/*/*
+ do
+ [ -d "$DIR/.git" ] || continue
+ pull "$DIR"
+ done
+
+ cd "$HOMEBREW_REPOSITORY" || {
+ echo "Error: failed to cd to $HOMEBREW_REPOSITORY!" >&2
+ exit 1
+ }
+
+ brew update-report "$@"
+ return $?
+}
diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb
new file mode 100644
index 000000000..b40aae422
--- /dev/null
+++ b/Library/Homebrew/cmd/update-report.rb
@@ -0,0 +1,351 @@
+require "cmd/tap"
+require "formula_versions"
+require "migrator"
+require "formulary"
+require "descriptions"
+
+module Homebrew
+ def update_report
+ unless ENV["HOMEBREW_DEVELOPER"]
+ odie "This command is currently only for Homebrew developers' use."
+ end
+
+ # migrate to new directories based tap structure
+ migrate_taps
+
+ report = Report.new
+ master_updater = Reporter.new(HOMEBREW_REPOSITORY)
+ master_updated = master_updater.updated?
+ if master_updated
+ initial_short = shorten_revision(master_updater.initial_revision)
+ current_short = shorten_revision(master_updater.current_revision)
+ puts "Updated Homebrew from #{initial_short} to #{current_short}."
+ end
+ report.update(master_updater.report)
+
+ # rename Taps directories
+ # this procedure will be removed in the future if it seems unnecessasry
+ rename_taps_dir_if_necessary
+
+ updated_taps = []
+ Tap.each do |tap|
+ tap.path.cd do
+ updater = Reporter.new(tap.path)
+ updated_taps << tap.name if updater.updated?
+ report.update(updater.report) do |_key, oldval, newval|
+ oldval.concat(newval)
+ end
+ end
+ end
+ unless updated_taps.empty?
+ puts "Updated #{updated_taps.size} tap#{plural(updated_taps.size)} " \
+ "(#{updated_taps.join(", ")})."
+ end
+ puts "Already up-to-date." unless master_updated || !updated_taps.empty?
+
+ Tap.clear_cache
+ Tap.each(&:link_manpages)
+
+ # automatically tap any migrated formulae's new tap
+ report.select_formula(:D).each do |f|
+ next unless (dir = HOMEBREW_CELLAR/f).exist?
+ migration = TAP_MIGRATIONS[f]
+ next unless migration
+ tap = Tap.fetch(*migration.split("/"))
+ tap.install unless tap.installed?
+
+ # update tap for each Tab
+ tabs = dir.subdirs.map { |d| Tab.for_keg(Keg.new(d)) }
+ next if tabs.first.source["tap"] != "Homebrew/homebrew"
+ tabs.each { |tab| tab.source["tap"] = "#{tap.user}/homebrew-#{tap.repo}" }
+ tabs.each(&:write)
+ end if load_tap_migrations
+
+ load_formula_renames
+ report.update_renamed
+
+ # Migrate installed renamed formulae from core and taps.
+ report.select_formula(:R).each do |oldname, newname|
+ if oldname.include?("/")
+ user, repo, oldname = oldname.split("/", 3)
+ newname = newname.split("/", 3).last
+ else
+ user = "homebrew"
+ repo = "homebrew"
+ end
+
+ next unless (dir = HOMEBREW_CELLAR/oldname).directory? && !dir.subdirs.empty?
+
+ begin
+ f = Formulary.factory("#{user}/#{repo}/#{newname}")
+ # short term fix to prevent situation like https://github.com/Homebrew/homebrew/issues/45616
+ rescue Exception
+ end
+
+ next unless f
+
+ begin
+ migrator = Migrator.new(f)
+ migrator.migrate
+ rescue Migrator::MigratorDifferentTapsError
+ end
+ end
+
+ if report.empty?
+ puts "No changes to formulae." if master_updated || !updated_taps.empty?
+ else
+ report.dump
+ end
+ Descriptions.update_cache(report)
+ end
+
+ private
+
+ def shorten_revision(revision)
+ `git rev-parse --short #{revision}`.chomp
+ end
+
+ def rename_taps_dir_if_necessary
+ Dir.glob("#{HOMEBREW_LIBRARY}/Taps/*/") do |tapd|
+ begin
+ if File.directory?(tapd + "/.git")
+ tapd_basename = File.basename(tapd)
+ if tapd_basename.include?("-")
+ # only replace the *last* dash: yes, tap filenames suck
+ user, repo = tapd_basename.reverse.sub("-", "/").reverse.split("/")
+
+ FileUtils.mkdir_p("#{HOMEBREW_LIBRARY}/Taps/#{user.downcase}")
+ FileUtils.mv(tapd, "#{HOMEBREW_LIBRARY}/Taps/#{user.downcase}/homebrew-#{repo.downcase}")
+
+ if tapd_basename.count("-") >= 2
+ opoo "Homebrew changed the structure of Taps like <someuser>/<sometap>. "\
+ + "So you may need to rename #{HOMEBREW_LIBRARY}/Taps/#{user.downcase}/homebrew-#{repo.downcase} manually."
+ end
+ else
+ opoo "Homebrew changed the structure of Taps like <someuser>/<sometap>. "\
+ "#{tapd} is incorrect name format. You may need to rename it like <someuser>/<sometap> manually."
+ end
+ end
+ rescue => ex
+ onoe ex.message
+ next # next tap directory
+ end
+ end
+ end
+
+ def load_tap_migrations
+ load "tap_migrations.rb"
+ rescue LoadError
+ false
+ end
+
+ def load_formula_renames
+ load "formula_renames.rb"
+ rescue LoadError
+ false
+ end
+end
+
+class Reporter
+ attr_reader :initial_revision, :current_revision, :repository
+
+ def self.repository_variable(repository)
+ repository.to_s.
+ gsub("#{HOMEBREW_PREFIX}", "").
+ gsub("Library/Taps/", "").
+ gsub(/[^a-z0-9]/, "_").
+ upcase
+ end
+
+ def initialize(repository)
+ @repository = repository
+
+ repo_var = Reporter.repository_variable(@repository)
+ initial_revision_var = "HOMEBREW_UPDATE_BEFORE#{repo_var}"
+ @initial_revision = ENV[initial_revision_var].to_s
+ if @initial_revision.empty?
+ raise "#{initial_revision_var} is unset!" if ARGV.homebrew_developer?
+ raise "update-report should not be called directly!"
+ end
+
+ current_revision_var = "HOMEBREW_UPDATE_AFTER#{repo_var}"
+ @current_revision = ENV[current_revision_var].to_s
+ if @current_revision.empty?
+ raise "#{current_revision_var} is unset!" if ARGV.homebrew_developer?
+ raise "update-report should not be called directly!"
+ end
+ end
+
+ def report
+ map = Hash.new { |h, k| h[k] = [] }
+
+ if initial_revision && initial_revision != current_revision
+ wc_revision = read_current_revision
+
+ diff.each_line do |line|
+ status, *paths = line.split
+ src = paths.first
+ dst = paths.last
+
+ next unless File.extname(dst) == ".rb"
+ next unless paths.any? { |p| File.dirname(p) == formula_directory }
+
+ case status
+ when "A", "D"
+ map[status.to_sym] << repository.join(src)
+ when "M"
+ file = repository.join(src)
+ begin
+ formula = Formulary.factory(file)
+ new_version = if wc_revision == current_revision
+ formula.pkg_version
+ else
+ FormulaVersions.new(formula).formula_at_revision(@current_revision, &:pkg_version)
+ end
+ old_version = FormulaVersions.new(formula).formula_at_revision(@initial_revision, &:pkg_version)
+ next if new_version == old_version
+ # short term fix to prevent situation like https://github.com/Homebrew/homebrew/issues/45616
+ rescue Exception => e
+ onoe e if ARGV.homebrew_developer?
+ end
+ map[:M] << file
+ when /^R\d{0,3}/
+ map[:D] << repository.join(src) if File.dirname(src) == formula_directory
+ map[:A] << repository.join(dst) if File.dirname(dst) == formula_directory
+ end
+ end
+ end
+
+ map
+ end
+
+ def updated?
+ initial_revision && initial_revision != current_revision
+ end
+
+ private
+
+ def formula_directory
+ if repository == HOMEBREW_REPOSITORY
+ "Library/Formula"
+ elsif repository.join("Formula").directory?
+ "Formula"
+ elsif repository.join("HomebrewFormula").directory?
+ "HomebrewFormula"
+ else
+ "."
+ end
+ end
+
+ def read_current_revision
+ `git rev-parse -q --verify HEAD`.chomp
+ end
+
+ def diff
+ Utils.popen_read(
+ "git", "diff-tree", "-r", "--name-status", "--diff-filter=AMDR",
+ "-M85%", initial_revision, current_revision
+ )
+ end
+
+ def `(cmd)
+ out = super
+ unless $?.success?
+ $stderr.puts(out) unless out.empty?
+ raise ErrorDuringExecution.new(cmd)
+ end
+ ohai(cmd, out) if ARGV.verbose?
+ out
+ end
+end
+
+class Report
+ def initialize
+ @hash = {}
+ end
+
+ def fetch(*args, &block)
+ @hash.fetch(*args, &block)
+ end
+
+ def update(*args, &block)
+ @hash.update(*args, &block)
+ end
+
+ def empty?
+ @hash.empty?
+ end
+
+ def dump
+ # Key Legend: Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R)
+
+ dump_formula_report :A, "New Formulae"
+ dump_formula_report :M, "Updated Formulae"
+ dump_formula_report :R, "Renamed Formulae"
+ dump_formula_report :D, "Deleted Formulae"
+ end
+
+ def update_renamed
+ renamed_formulae = []
+
+ fetch(:D, []).each do |path|
+ case path.to_s
+ when HOMEBREW_TAP_PATH_REGEX
+ oldname = path.basename(".rb").to_s
+ next unless newname = Tap.fetch($1, $2).formula_renames[oldname]
+ else
+ oldname = path.basename(".rb").to_s
+ next unless newname = CoreFormulaRepository.instance.formula_renames[oldname]
+ end
+
+ if fetch(:A, []).include?(newpath = path.dirname.join("#{newname}.rb"))
+ renamed_formulae << [path, newpath]
+ end
+ end
+
+ unless renamed_formulae.empty?
+ @hash[:A] -= renamed_formulae.map(&:last) if @hash[:A]
+ @hash[:D] -= renamed_formulae.map(&:first) if @hash[:D]
+ @hash[:R] = renamed_formulae
+ end
+ end
+
+ def select_formula(key)
+ fetch(key, []).map do |path, newpath|
+ if path.to_s =~ HOMEBREW_TAP_PATH_REGEX
+ tap = Tap.fetch($1, $2)
+ if newpath
+ ["#{tap}/#{path.basename(".rb")}", "#{tap}/#{newpath.basename(".rb")}"]
+ else
+ "#{tap}/#{path.basename(".rb")}"
+ end
+ elsif newpath
+ ["#{path.basename(".rb")}", "#{newpath.basename(".rb")}"]
+ else
+ path.basename(".rb").to_s
+ end
+ end.sort
+ end
+
+ def dump_formula_report(key, title)
+ formula = select_formula(key).map do |name, new_name|
+ # Format list items of renamed formulae
+ if key == :R
+ new_name = pretty_installed(new_name) if installed?(name)
+ "#{name} -> #{new_name}"
+ else
+ installed?(name) ? pretty_installed(name) : name
+ end
+ end
+
+ unless formula.empty?
+ # Dump formula list.
+ ohai title
+ puts_columns(formula)
+ end
+ end
+
+ def installed?(formula)
+ (HOMEBREW_CELLAR/formula.split("/").last).directory?
+ end
+end