diff --git a/Library/Homebrew/cmd/list.rb b/Library/Homebrew/cmd/list.rb
index e5c7921b8e922fb581f7e51a1df1d9cc1cbcdd9c..790d858aaf205c40fe63d2761618b3f4bbb9f13e 100644
--- a/Library/Homebrew/cmd/list.rb
+++ b/Library/Homebrew/cmd/list.rb
@@ -77,6 +77,7 @@ module Homebrew
     lib/ruby/site_ruby/[12].*
     lib/ruby/vendor_ruby/[12].*
     manpages/brew.1
+    manpages/brew-cask.1
     share/pypy/*
     share/pypy3/*
     share/info/dir
diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb
index 5b4ed247593a8bf11346607240321cd9be8622fb..114c4a8b6fb554d3efd105557c51ec1b5db4459e 100644
--- a/Library/Homebrew/cmd/tap.rb
+++ b/Library/Homebrew/cmd/tap.rb
@@ -38,7 +38,7 @@ module Homebrew
 
   def tap
     if ARGV.include? "--repair"
-      Tap.each(&:link_manpages)
+      Tap.each(&:link_completions_and_manpages)
     elsif ARGV.include? "--list-official"
       require "official_taps"
       puts OFFICIAL_TAPS.map { |t| "homebrew/#{t}" }
diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb
index dcf2891d9051e5c975a0deeb790bf46c2cdc7338..c7c827882eb8010928b7c3ee706873f0af510e3c 100644
--- a/Library/Homebrew/cmd/update-report.rb
+++ b/Library/Homebrew/cmd/update-report.rb
@@ -104,8 +104,8 @@ module Homebrew
       puts if ARGV.include?("--preinstall")
     end
 
-    link_completions_and_docs
-    Tap.each(&:link_manpages)
+    link_completions_manpages_and_docs
+    Tap.each(&:link_completions_and_manpages)
 
     Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
 
@@ -281,7 +281,7 @@ module Homebrew
       EOS
     end
 
-    link_completions_and_docs(new_homebrew_repository)
+    link_completions_manpages_and_docs(new_homebrew_repository)
 
     ohai "Migrated HOMEBREW_REPOSITORY to #{new_homebrew_repository}!"
     puts <<-EOS.undent
@@ -302,16 +302,11 @@ module Homebrew
     $stderr.puts e.backtrace
   end
 
-  def link_completions_and_docs(repository = HOMEBREW_REPOSITORY)
+  def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY)
     command = "brew update"
-    link_src_dst_dirs(repository/"completions/bash",
-                      HOMEBREW_PREFIX/"etc/bash_completion.d", command)
-    link_src_dst_dirs(repository/"docs",
-                      HOMEBREW_PREFIX/"share/doc/homebrew", command, link_dir: true)
-    link_src_dst_dirs(repository/"completions/zsh",
-                      HOMEBREW_PREFIX/"share/zsh/site-functions", command)
-    link_src_dst_dirs(repository/"manpages",
-                      HOMEBREW_PREFIX/"share/man/man1", command)
+    Utils::Link.link_completions(repository, command)
+    Utils::Link.link_manpages(repository, command)
+    Utils::Link.link_docs(repository, command)
   rescue => e
     ofail <<-EOS.undent
       Failed to link all completions, docs and manpages:
diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb
index 4b59f2344621ed8fd2922f810b286872238c1c71..f071904a49441d5be81e1c9d62f2e9d8b15b25a4 100644
--- a/Library/Homebrew/tap.rb
+++ b/Library/Homebrew/tap.rb
@@ -236,7 +236,7 @@ class Tap
       raise
     end
 
-    link_manpages
+    link_completions_and_manpages
 
     formula_count = formula_files.size
     puts "Tapped #{formula_count} formula#{plural(formula_count, "e")} (#{path.abv})" unless quiet
@@ -254,8 +254,10 @@ class Tap
     EOS
   end
 
-  def link_manpages
-    link_path_manpages(path, "brew tap --repair")
+  def link_completions_and_manpages
+    command = "brew tap --repair"
+    Utils::Link.link_manpages(path, command)
+    Utils::Link.link_completions(path, command)
   end
 
   # uninstall this {Tap}.
@@ -267,23 +269,14 @@ class Tap
     unpin if pinned?
     formula_count = formula_files.size
     Descriptions.uncache_formulae(formula_names)
-    unlink_manpages
+    Utils::Link.unlink_manpages(path)
+    Utils::Link.unlink_completions(path)
     path.rmtree
     path.parent.rmdir_if_possible
     puts "Untapped #{formula_count} formula#{plural(formula_count, "e")}"
     clear_cache
   end
 
-  def unlink_manpages
-    return unless (path/"man").exist?
-    (path/"man").find do |src|
-      next if src.directory?
-      dst = HOMEBREW_PREFIX/"share"/src.relative_path_from(path)
-      dst.delete if dst.symlink? && src == dst.resolved_path
-      dst.parent.rmdir_if_possible
-    end
-  end
-
   # True if the {#remote} of {Tap} is customized.
   def custom_remote?
     return true unless remote
diff --git a/Library/Homebrew/test/tap_test.rb b/Library/Homebrew/test/tap_test.rb
index 578326cea8713313b7e455fe3ace6194986dcf4d..44bd2f88be5f3a1dbb22948898e628b1cca2c65d 100644
--- a/Library/Homebrew/test/tap_test.rb
+++ b/Library/Homebrew/test/tap_test.rb
@@ -60,9 +60,18 @@ class TapTest < Homebrew::TestCase
     @cmd_file.parent.mkpath
     touch @cmd_file
     chmod 0755, @cmd_file
-    @manpage_file = @path/"man/man1/brew-tap-cmd.1"
+    @manpage_file = @path/"manpages/brew-tap-cmd.1"
     @manpage_file.parent.mkpath
     touch @manpage_file
+    @bash_completion_file = @path/"completions/bash/brew-tap-cmd"
+    @bash_completion_file.parent.mkpath
+    touch @bash_completion_file
+    @zsh_completion_file = @path/"completions/zsh/_brew-tap-cmd"
+    @zsh_completion_file.parent.mkpath
+    touch @zsh_completion_file
+    @fish_completion_file = @path/"completions/fish/brew-tap-cmd.fish"
+    @fish_completion_file.parent.mkpath
+    touch @fish_completion_file
   end
 
   def setup_git_repo
@@ -238,10 +247,39 @@ class TapTest < Homebrew::TestCase
     shutup { tap.install clone_target: @tap.path/".git" }
     assert_predicate tap, :installed?
     assert_predicate HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1", :file?
+    assert_predicate HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd", :file?
+    assert_predicate HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd", :file?
+    assert_predicate HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish", :file?
     shutup { tap.uninstall }
     refute_predicate tap, :installed?
     refute_predicate HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1", :exist?
     refute_predicate HOMEBREW_PREFIX/"share/man/man1", :exist?
+    refute_predicate HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd", :exist?
+    refute_predicate HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd", :exist?
+    refute_predicate HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish", :exist?
+  ensure
+    (HOMEBREW_PREFIX/"etc").rmtree if (HOMEBREW_PREFIX/"etc").exist?
+    (HOMEBREW_PREFIX/"share").rmtree if (HOMEBREW_PREFIX/"share").exist?
+  end
+
+  def test_link_completions_and_manpages
+    setup_tap_files
+    setup_git_repo
+    tap = Tap.new("Homebrew", "baz")
+    shutup { tap.install clone_target: @tap.path/".git" }
+    (HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").delete
+    (HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd").delete
+    (HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd").delete
+    (HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish").delete
+    shutup { tap.link_completions_and_manpages }
+    assert_predicate HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1", :file?
+    assert_predicate HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd", :file?
+    assert_predicate HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd", :file?
+    assert_predicate HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish", :file?
+    shutup { tap.uninstall }
+  ensure
+    (HOMEBREW_PREFIX/"etc").rmtree if (HOMEBREW_PREFIX/"etc").exist?
+    (HOMEBREW_PREFIX/"share").rmtree if (HOMEBREW_PREFIX/"share").exist?
   end
 
   def test_pin_and_unpin
diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb
index 70d2787d976250ca959aa167157e29964649dede..ae1bffa875199b0f00ce188d1ae106d8047f079a 100644
--- a/Library/Homebrew/utils.rb
+++ b/Library/Homebrew/utils.rb
@@ -9,6 +9,7 @@ require "utils/git"
 require "utils/github"
 require "utils/hash"
 require "utils/inreplace"
+require "utils/link"
 require "utils/popen"
 require "utils/tty"
 require "time"
@@ -481,38 +482,6 @@ def truncate_text_to_approximate_size(s, max_bytes, options = {})
   out
 end
 
-def link_src_dst_dirs(src_dir, dst_dir, command, link_dir: false)
-  return unless src_dir.exist?
-  conflicts = []
-  src_paths = link_dir ? [src_dir] : src_dir.find
-  src_paths.each do |src|
-    next if src.directory? && !link_dir
-    dst = dst_dir/src.relative_path_from(src_dir)
-    if dst.symlink?
-      next if src == dst.resolved_path
-      dst.unlink
-    end
-    if dst.exist?
-      conflicts << dst
-      next
-    end
-    dst_dir.parent.mkpath
-    dst.make_relative_symlink(src)
-  end
-
-  return if conflicts.empty?
-  onoe <<-EOS.undent
-    Could not link:
-    #{conflicts.join("\n")}
-
-    Please delete these paths and run `#{command}`.
-  EOS
-end
-
-def link_path_manpages(path, command)
-  link_src_dst_dirs(path/"man", HOMEBREW_PREFIX/"share/man", command)
-end
-
 def migrate_legacy_keg_symlinks_if_necessary
   legacy_linked_kegs = HOMEBREW_LIBRARY/"LinkedKegs"
   return unless legacy_linked_kegs.directory?
diff --git a/Library/Homebrew/utils/link.rb b/Library/Homebrew/utils/link.rb
new file mode 100644
index 0000000000000000000000000000000000000000..095dba20915f964c394c798480b01e89fc77178d
--- /dev/null
+++ b/Library/Homebrew/utils/link.rb
@@ -0,0 +1,70 @@
+module Utils
+  module Link
+    module_function
+
+    def link_src_dst_dirs(src_dir, dst_dir, command, link_dir: false)
+      return unless src_dir.exist?
+      conflicts = []
+      src_paths = link_dir ? [src_dir] : src_dir.find
+      src_paths.each do |src|
+        next if src.directory? && !link_dir
+        dst = dst_dir/src.relative_path_from(src_dir)
+        if dst.symlink?
+          next if src == dst.resolved_path
+          dst.unlink
+        end
+        if dst.exist?
+          conflicts << dst
+          next
+        end
+        dst_dir.parent.mkpath
+        dst.make_relative_symlink(src)
+      end
+
+      return if conflicts.empty?
+      onoe <<-EOS.undent
+        Could not link:
+        #{conflicts.join("\n")}
+
+        Please delete these paths and run `#{command}`.
+      EOS
+    end
+    private_class_method :link_src_dst_dirs
+
+    def unlink_src_dst_dirs(src_dir, dst_dir, unlink_dir: false)
+      return unless src_dir.exist?
+      src_paths = unlink_dir ? [src_dir] : src_dir.find
+      src_paths.each do |src|
+        next if src.directory? && !unlink_dir
+        dst = dst_dir/src.relative_path_from(src_dir)
+        dst.delete if dst.symlink? && src == dst.resolved_path
+        dst.parent.rmdir_if_possible
+      end
+    end
+    private_class_method :unlink_src_dst_dirs
+
+    def link_manpages(path, command)
+      link_src_dst_dirs(path/"manpages", HOMEBREW_PREFIX/"share/man/man1", command)
+    end
+
+    def unlink_manpages(path)
+      unlink_src_dst_dirs(path/"manpages", HOMEBREW_PREFIX/"share/man/man1")
+    end
+
+    def link_completions(path, command)
+      link_src_dst_dirs(path/"completions/bash", HOMEBREW_PREFIX/"etc/bash_completion.d", command)
+      link_src_dst_dirs(path/"completions/zsh", HOMEBREW_PREFIX/"share/zsh/site-functions", command)
+      link_src_dst_dirs(path/"completions/fish", HOMEBREW_PREFIX/"share/fish/vendor_completions.d", command)
+    end
+
+    def unlink_completions(path)
+      unlink_src_dst_dirs(path/"completions/bash", HOMEBREW_PREFIX/"etc/bash_completion.d")
+      unlink_src_dst_dirs(path/"completions/zsh", HOMEBREW_PREFIX/"share/zsh/site-functions")
+      unlink_src_dst_dirs(path/"completions/fish", HOMEBREW_PREFIX/"share/fish/vendor_completions.d")
+    end
+
+    def link_docs(path, command)
+      link_src_dst_dirs(path/"docs", HOMEBREW_PREFIX/"share/doc/homebrew", command, link_dir: true)
+    end
+  end
+end