Merge pull request #12221 from XuehaiPan/brew-retap

tap: allow to change tap remote with `brew tap --custom-remote`
This commit is contained in:
Mike McQuaid 2021-10-12 14:08:21 +01:00 committed by GitHub
commit cc2c19be98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 12 deletions

View File

@ -293,6 +293,9 @@ module Homebrew
sig { returns(T::Boolean) }
def write_only?; end
sig { returns(T::Boolean) }
def custom_remote?; end
end
end
end

View File

@ -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

View File

@ -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)

View File

@ -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)
if requested_remote.present?
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.blank? && path.git_origin_has_branch?(current_upstream_head)
safe_system "git", "-C", path, "fetch", "origin"
args = %w[fetch]
args << "--quiet" 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}!"
return if quiet
$stderr.ohai "#{name}: changed default branch name from #{current_upstream_head} to #{new_upstream_head}!"
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 mismatched
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

View File

@ -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

View File

@ -1902,6 +1902,7 @@ _brew_tap() {
case "${cur}" in
-*)
__brewcomp "
--custom-remote
--debug
--force-auto-update
--full

View File

@ -1288,6 +1288,7 @@ __fish_brew_complete_arg 'style; and not __fish_seen_argument -l formula -l form
__fish_brew_complete_cmd 'tap' 'Tap a formula repository'
__fish_brew_complete_arg 'tap' -l custom-remote -d 'Install or change a tap with a custom remote. Useful for mirrors'
__fish_brew_complete_arg 'tap' -l debug -d 'Display any debugging information'
__fish_brew_complete_arg 'tap' -l force-auto-update -d 'Auto-update tap even if it is not hosted on GitHub. By default, only taps hosted on GitHub are auto-updated (for performance reasons)'
__fish_brew_complete_arg 'tap' -l full -d 'Convert a shallow clone to a full clone without untapping. Taps are only cloned as shallow clones if `--shallow` was originally passed'

View File

@ -1579,6 +1579,7 @@ _brew_style() {
# brew tap
_brew_tap() {
_arguments \
'--custom-remote[Install or change a tap with a custom remote. Useful for mirrors]' \
'--debug[Display any debugging information]' \
'--force-auto-update[Auto-update tap even if it is not hosted on GitHub. By default, only taps hosted on GitHub are auto-updated (for performance reasons)]' \
'--full[Convert a shallow clone to a full clone without untapping. Taps are only cloned as shallow clones if `--shallow` was originally passed]' \

View File

@ -600,6 +600,8 @@ using protocols other than HTTPS, e.g. SSH, git, HTTP, FTP(S), rsync.
Fetch tap as a shallow clone rather than a full clone. Useful for continuous integration.
* `--force-auto-update`:
Auto-update tap even if it is not hosted on GitHub. By default, only taps hosted on GitHub are auto-updated (for performance reasons).
* `--custom-remote`:
Install or change a tap with a custom remote. Useful for mirrors.
* `--repair`:
Migrate tapped formulae from symlink-based to directory-based structure.
* `--list-pinned`:

View File

@ -831,6 +831,10 @@ Fetch tap as a shallow clone rather than a full clone\. Useful for continuous in
Auto\-update tap even if it is not hosted on GitHub\. By default, only taps hosted on GitHub are auto\-updated (for performance reasons)\.
.
.TP
\fB\-\-custom\-remote\fR
Install or change a tap with a custom remote\. Useful for mirrors\.
.
.TP
\fB\-\-repair\fR
Migrate tapped formulae from symlink\-based to directory\-based structure\.
.