# typed: strict # frozen_string_literal: true require "abstract_command" require "fileutils" module Homebrew module DevCmd class UpdateTest < AbstractCommand include FileUtils cmd_args do description <<~EOS Run a test of `brew update` with a new repository clone. If no options are passed, use `origin/main` as the start commit. EOS switch "--to-tag", description: "Set `$HOMEBREW_UPDATE_TO_TAG` to test updating between tags." switch "--keep-tmp", description: "Retain the temporary directory containing the new repository clone." flag "--commit=", description: "Use the specified as the start commit." flag "--before=", description: "Use the commit at the specified as the start commit." named_args :none end sig { override.void } def run # Avoid `update-report.rb` tapping Homebrew/homebrew-core ENV["HOMEBREW_UPDATE_TEST"] = "1" # Avoid accidentally updating when we don't expect it. ENV["HOMEBREW_NO_AUTO_UPDATE"] = "1" # Use default behaviours ENV["HOMEBREW_AUTO_UPDATE_SECS"] = nil ENV["HOMEBREW_DEVELOPER"] = nil ENV["HOMEBREW_DEV_CMD_RUN"] = nil ENV["HOMEBREW_MERGE"] = nil ENV["HOMEBREW_NO_UPDATE_CLEANUP"] = nil ENV["HOMEBREW_UPDATE_TO_TAG"] = nil branch = if args.to_tag? ENV["HOMEBREW_UPDATE_TO_TAG"] = "1" "stable" else ENV["HOMEBREW_DEV_CMD_RUN"] = "1" "main" end # Utils.popen_read returns a String without a block argument, but that isn't easily typed. We thus label this # as untyped for now. start_commit = T.let("", T.untyped) end_commit = "HEAD" cd HOMEBREW_REPOSITORY do start_commit = if (commit = args.commit) commit elsif (date = args.before) Utils.popen_read("git", "rev-list", "-n1", "--before=#{date}", "origin/main").chomp elsif args.to_tag? tags = git_tags current_tag, previous_tag, = tags.lines current_tag = current_tag.to_s.chomp odie "Could not find current tag in:\n#{tags}" if current_tag.empty? # ^0 ensures this points to the commit rather than the tag object. end_commit = "#{current_tag}^0" previous_tag = previous_tag.to_s.chomp odie "Could not find previous tag in:\n#{tags}" if previous_tag.empty? # ^0 ensures this points to the commit rather than the tag object. "#{previous_tag}^0" else Utils.popen_read("git", "merge-base", "origin/main", end_commit).chomp end odie "Could not find start commit!" if start_commit.empty? start_commit = Utils.popen_read("git", "rev-parse", start_commit).chomp odie "Could not find start commit!" if start_commit.empty? end_commit = T.cast(Utils.popen_read("git", "rev-parse", end_commit).chomp, String) odie "Could not find end commit!" if end_commit.empty? if Utils.popen_read("git", "branch", "--list", "main").blank? safe_system "git", "branch", "main", "origin/HEAD" end end puts <<~EOS Start commit: #{start_commit} End commit: #{end_commit} EOS mkdir "update-test" chdir "update-test" do curdir = Pathname.new(Dir.pwd) oh1 "Preparing test environment..." # copy Homebrew installation safe_system "git", "clone", "#{HOMEBREW_REPOSITORY}/.git", ".", "--branch", "main", "--single-branch" # set git origin to another copy safe_system "git", "clone", "#{HOMEBREW_REPOSITORY}/.git", "remote.git", "--bare", "--branch", "main", "--single-branch" safe_system "git", "config", "remote.origin.url", "#{curdir}/remote.git" ENV["HOMEBREW_BREW_GIT_REMOTE"] = "#{curdir}/remote.git" # force push origin to end_commit safe_system "git", "checkout", "-B", "main", end_commit safe_system "git", "push", "--force", "origin", "main" # set test copy to start_commit safe_system "git", "reset", "--hard", start_commit # update ENV["PATH"] ENV["PATH"] = PATH.new(ENV.fetch("PATH")).prepend(curdir/"bin").to_s # Run `brew help` to install `portable-ruby` (if needed). quiet_system "brew", "help" # run brew update oh1 "Running `brew update`..." safe_system "brew", "update", "--verbose", "--debug" actual_end_commit = Utils.popen_read("git", "rev-parse", branch).chomp if actual_end_commit != end_commit start_log = Utils.popen_read("git", "log", "-1", "--decorate", "--oneline", start_commit).chomp end_log = Utils.popen_read("git", "log", "-1", "--decorate", "--oneline", end_commit).chomp actual_log = Utils.popen_read("git", "log", "-1", "--decorate", "--oneline", actual_end_commit).chomp odie <<~EOS `brew update` didn't update #{branch}! Start commit: #{start_log} Expected end commit: #{end_log} Actual end commit: #{actual_log} EOS end end ensure FileUtils.rm_rf "update-test" unless args.keep_tmp? end private sig { returns(String) } def git_tags tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname") if tags.blank? tags = if (HOMEBREW_REPOSITORY/".git/shallow").exist? safe_system "git", "fetch", "--tags", "--depth=1" Utils.popen_read("git", "tag", "--list", "--sort=-version:refname") end end tags end end end end require "extend/os/dev-cmd/update-test"