From 63f2f6cca7da787229d72cb8b38402a5df8a348a Mon Sep 17 00:00:00 2001 From: XuehaiPan Date: Mon, 11 Oct 2021 17:00:43 +0800 Subject: [PATCH] tap: allow to change tap remote with `brew tap --custom-remote` --- Library/Homebrew/cmd/tap.rb | 5 +++- Library/Homebrew/exceptions.rb | 30 +++++++++++++++++++- Library/Homebrew/tap.rb | 46 ++++++++++++++++++++++++------- Library/Homebrew/test/tap_spec.rb | 27 ++++++++++++++++++ 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb index f044c18df3..c81b369aa2 100644 --- a/Library/Homebrew/cmd/tap.rb +++ b/Library/Homebrew/cmd/tap.rb @@ -35,6 +35,8 @@ module Homebrew switch "--force-auto-update", description: "Auto-update tap even if it is not hosted on GitHub. By default, only taps "\ "hosted on GitHub are auto-updated (for performance reasons)." + switch "--custom-remote", + description: "Install or change a tap with a custom remote. Useful for mirrors." switch "--repair", description: "Migrate tapped formulae from symlink-based to directory-based structure." switch "--list-pinned", @@ -64,8 +66,9 @@ module Homebrew begin tap.install clone_target: args.named.second, force_auto_update: force_auto_update?(args: args), + custom_remote: args.custom_remote?, quiet: args.quiet? - rescue TapRemoteMismatchError => e + rescue TapRemoteMismatchError, TapNoCustomRemoteError => e odie e rescue TapAlreadyTappedError nil diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 02928cf3fd..58aa822dc0 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -324,13 +324,28 @@ class TapRemoteMismatchError < RuntimeError @expected_remote = expected_remote @actual_remote = actual_remote - super <<~EOS + super message + end + + def message + <<~EOS Tap #{name} remote mismatch. #{expected_remote} != #{actual_remote} EOS end end +# Raised when the remote of Homebrew/core does not match HOMEBREW_CORE_GIT_REMOTE. +class TapCoreRemoteMismatchError < TapRemoteMismatchError + def message + <<~EOS + Tap #{name} remote does mot match HOMEBREW_CORE_GIT_REMOTE. + #{expected_remote} != #{actual_remote} + Please set HOMEBREW_CORE_GIT_REMOTE="#{actual_remote}" and run `brew update` instead. + EOS + end +end + # Raised when a tap is already installed. class TapAlreadyTappedError < RuntimeError attr_reader :name @@ -344,6 +359,19 @@ class TapAlreadyTappedError < RuntimeError end end +# Raised when run `brew tap --custom-remote` without a remote URL. +class TapNoCustomRemoteError < RuntimeError + attr_reader :name + + def initialize(name) + @name = name + + super <<~EOS + Tap #{name} with option `--custom-remote` but without a remote URL. + EOS + end +end + # Raised when another Homebrew operation is already in progress. class OperationInProgressError < RuntimeError def initialize(name) diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 23b61b2c91..a190778508 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -246,7 +246,8 @@ class Tap # @param force_auto_update [Boolean, nil] If present, whether to override the # logic that skips non-GitHub repositories during auto-updates. # @param quiet [Boolean] If set, suppress all output. - def install(quiet: false, clone_target: nil, force_auto_update: nil) + # @param custom_remote [Boolean] If set, change the tap's remote if already installed. + def install(quiet: false, clone_target: nil, force_auto_update: nil, custom_remote: false) require "descriptions" require "readall" @@ -257,9 +258,11 @@ class Tap odie "#{name} was moved. Tap homebrew/#{new_repo} instead." end + raise TapNoCustomRemoteError, name if custom_remote && clone_target.nil? + requested_remote = clone_target || default_remote - if installed? + if installed? && !custom_remote raise TapRemoteMismatchError.new(name, @remote, requested_remote) if clone_target && requested_remote != remote raise TapAlreadyTappedError, name if force_auto_update.nil? && !shallow? end @@ -268,6 +271,10 @@ class Tap Utils::Git.ensure_installed! if installed? + if requested_remote != remote # we are sure that clone_target is not nil and custom_remote is true here + fix_remote_configuration(requested_remote: requested_remote, quiet: quiet) + end + unless force_auto_update.nil? config["forceautoupdate"] = force_auto_update return @@ -358,20 +365,33 @@ class Tap end end - def fix_remote_configuration - return unless remote.include? "github.com" + def fix_remote_configuration(requested_remote: nil, quiet: false) + unless requested_remote.nil? + path.cd do + safe_system "git", "remote", "set-url", "origin", requested_remote + safe_system "git", "config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*" + end + $stderr.ohai "#{name}: changed remote from #{remote} to #{requested_remote}" unless quiet + end current_upstream_head = path.git_origin_branch - return if path.git_origin_has_branch? current_upstream_head + return if requested_remote.nil? && path.git_origin_has_branch?(current_upstream_head) - safe_system "git", "-C", path, "fetch", "origin" + args = %w[fetch] + args << "-q" if quiet + args << "origin" + safe_system "git", "-C", path, *args path.git_origin_set_head_auto new_upstream_head = path.git_origin_branch + return if new_upstream_head == old_upstream_head + path.git_rename_branch old: current_upstream_head, new: new_upstream_head path.git_branch_set_upstream local: new_upstream_head, origin: new_upstream_head - ohai "#{name}: changed default branch name from #{current_upstream_head} to #{new_upstream_head}!" + unless quiet # rubocop:disable Style/GuardClause + $stderr.ohai "#{name}: changed default branch name from #{current_upstream_head} to #{new_upstream_head}!" + end end # Uninstall this {Tap}. @@ -741,12 +761,18 @@ class CoreTap < Tap end # CoreTap never allows shallow clones (on request from GitHub). - def install(quiet: false, clone_target: nil, force_auto_update: nil) + def install(quiet: false, clone_target: nil, force_auto_update: nil, custom_remote: false) remote = Homebrew::EnvConfig.core_git_remote + requested_remote = clone_target || default_remote + + # The remote will changed again on `brew update` since remotes for Homebrew/core are mismatch + raise TapCoreRemoteMismatchError.new(name, remote, requested_remote) if requested_remote != remote + if remote != default_remote - $stderr.puts "HOMEBREW_CORE_GIT_REMOTE set: using #{remote} for Homebrew/core Git remote URL." + $stderr.puts "HOMEBREW_CORE_GIT_REMOTE set: using #{remote} for Homebrew/core Git remote." end - super(quiet: quiet, clone_target: remote, force_auto_update: force_auto_update) + + super(quiet: quiet, clone_target: remote, force_auto_update: force_auto_update, custom_remote: custom_remote) end # @private diff --git a/Library/Homebrew/test/tap_spec.rb b/Library/Homebrew/test/tap_spec.rb index f574550272..ae7ef7a253 100644 --- a/Library/Homebrew/test/tap_spec.rb +++ b/Library/Homebrew/test/tap_spec.rb @@ -289,6 +289,33 @@ describe Tap do }.to raise_error(TapRemoteMismatchError) end + it "raises an error when the remote for Homebrew/core doesn't match HOMEBREW_CORE_GIT_REMOTE" do + core_tap = described_class.fetch("Homebrew", "core") + wrong_remote = "#{Homebrew::EnvConfig.core_git_remote}-oops" + expect { + core_tap.install clone_target: wrong_remote + }.to raise_error(TapCoreRemoteMismatchError) + end + + it "raises an error when run `brew tap --custom-remote` without a custom remote (already installed)" do + setup_git_repo + already_tapped_tap = described_class.new("Homebrew", "foo") + expect(already_tapped_tap).to be_installed + + expect { + already_tapped_tap.install clone_target: nil, custom_remote: true + }.to raise_error(TapNoCustomRemoteError) + end + + it "raises an error when run `brew tap --custom-remote` without a custom remote (not installed)" do + not_tapped_tap = described_class.new("Homebrew", "bar") + expect(not_tapped_tap).not_to be_installed + + expect { + not_tapped_tap.install clone_target: nil, custom_remote: true + }.to raise_error(TapNoCustomRemoteError) + end + describe "force_auto_update" do before do setup_git_repo