Skip to content

Commit 7efc2be

Browse files
authored
Merge pull request #22455 from Homebrew/tap-worktrees
Use worktrees for local core taps
2 parents e5d931c + a63f971 commit 7efc2be

2 files changed

Lines changed: 171 additions & 3 deletions

File tree

Library/Homebrew/tap.rb

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,36 @@ def clear_cache
270270
@config = nil
271271
end
272272

273+
sig { params(path: Pathname).returns(T.nilable(Pathname)) }
274+
def worktree_source_tap_path_for(path:)
275+
return unless (git_file = path/".git").file?
276+
return unless (git_dir = git_file.read[/\Agitdir: (.+)\n?\z/, 1])
277+
278+
git_dir_path = Pathname(git_dir)
279+
git_dir_path = path/git_dir_path unless git_dir_path.absolute?
280+
281+
# A linked worktree points at `<source>/.git/worktrees/<name>`, so use
282+
# the matching source tap when it is already checked out there.
283+
if git_dir_path.dirname.dirname.basename.to_s == ".git" && git_dir_path.dirname.basename.to_s == "worktrees"
284+
source_path = git_dir_path.dirname.dirname.dirname
285+
return source_path if path != HOMEBREW_REPOSITORY
286+
287+
candidate_source_tap_path = source_path/"Library/Taps/#{full_name.downcase}"
288+
return candidate_source_tap_path if (candidate_source_tap_path/".git").exist?
289+
290+
end
291+
292+
Utils.popen_read("git", "-C", path, "worktree", "list", "--porcelain")
293+
.each_line do |line|
294+
next unless line.start_with?("worktree ")
295+
296+
candidate_source_tap_path = Pathname(line.delete_prefix("worktree ").chomp)/"Library/Taps/#{full_name.downcase}"
297+
return candidate_source_tap_path if (candidate_source_tap_path/".git").exist?
298+
end
299+
300+
nil
301+
end
302+
273303
sig { overridable.void }
274304
def ensure_installed!
275305
return if installed?
@@ -486,6 +516,9 @@ def install(quiet: false, clone_target: nil,
486516
# ensure git is installed
487517
Utils::Git.ensure_installed!
488518

519+
use_worktree_source_tap = core_tap? || (core_cask_tap? && clone_target.nil? && !custom_remote)
520+
worktree_source_tap_path = use_worktree_source_tap ? worktree_source_tap_path_for(path: HOMEBREW_REPOSITORY) : nil
521+
489522
if installed?
490523
if requested_remote != remote # we are sure that clone_target is not nil and custom_remote is true here
491524
fix_remote_configuration(requested_remote:, quiet:)
@@ -500,7 +533,8 @@ def install(quiet: false, clone_target: nil,
500533
args << "-q" if quiet
501534
path.cd { safe_system "git", *args }
502535
return
503-
elsif (core_tap? || core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api? && !force
536+
elsif (core_tap? || core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api? && !force &&
537+
worktree_source_tap_path.blank?
504538
odie "Tapping #{name} is no longer typically necessary.\n" \
505539
"Add #{Formatter.option("--force")} if you are sure you need it for contributing to Homebrew."
506540
end
@@ -522,7 +556,15 @@ def install(quiet: false, clone_target: nil,
522556
args << "--config" << "core.fsmonitor=false"
523557

524558
begin
525-
safe_system "git", *args
559+
if worktree_source_tap_path
560+
# Keep core and cask taps connected to the same local source checkout as brew.
561+
worktree_args = ["-C", worktree_source_tap_path, "worktree", "add"]
562+
worktree_args << "--quiet" if quiet
563+
worktree_args += ["--detach", path, "HEAD"]
564+
safe_system "git", *worktree_args
565+
else
566+
safe_system "git", *args
567+
end
526568

527569
if verify && !Homebrew::EnvConfig.developer? && !Readall.valid_tap?(self, aliases: true)
528570
raise "Cannot tap #{name}: invalid syntax in tap!"
@@ -659,7 +701,10 @@ def uninstall(manual: false)
659701
require "utils/link"
660702
Utils::Link.unlink_manpages(path)
661703
Utils::Link.unlink_completions(path)
662-
FileUtils.rm_r(path)
704+
if (worktree_source_tap_path = worktree_source_tap_path_for(path:))
705+
safe_system "git", "-C", worktree_source_tap_path, "worktree", "remove", "--force", path
706+
end
707+
FileUtils.rm_r(path) if path.exist?
663708
path.parent.rmdir_if_possible
664709
$stderr.puts "Untapped#{formatted_contents} (#{abv})."
665710

Library/Homebrew/test/tap_spec.rb

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,109 @@ def setup_completion(link:)
355355
end.to raise_error(TapCoreRemoteMismatchError)
356356
end
357357

358+
it "creates core and cask taps as worktrees when the brew source repository has them" do
359+
source_repository = HOMEBREW_PREFIX.parent/"source-repository"
360+
worktree_git_dir = HOMEBREW_REPOSITORY/".git"
361+
362+
[CoreTap.instance, CoreCaskTap.instance].each do |tap|
363+
source_tap = source_repository/"Library/Taps/#{tap.full_name.downcase}"
364+
365+
FileUtils.rm_rf tap.path
366+
source_tap.mkpath
367+
source_tap.cd do
368+
system "git", "init"
369+
FileUtils.touch "README.md"
370+
system "git", "add", "--all"
371+
system "git", "commit", "-m", "init"
372+
end
373+
FileUtils.mkdir_p worktree_git_dir.dirname
374+
worktree_git_dir.write "gitdir: #{source_repository}/.git/worktrees/#{HOMEBREW_REPOSITORY.basename}\n"
375+
376+
allow(tap).to receive_messages(command_files: [], formula_files: [], cask_files: [],
377+
formula_names: [], cask_tokens: [])
378+
expect(tap).to receive(:safe_system)
379+
.with("git", "-C", source_tap, "worktree", "add", "--detach", tap.path, "HEAD")
380+
.and_wrap_original do
381+
tap.path.mkpath
382+
(tap.path/".git").write "gitdir: #{source_tap}/.git/worktrees/#{tap.full_repository.downcase}\n"
383+
end
384+
385+
tap.install
386+
end
387+
ensure
388+
FileUtils.rm_rf source_repository
389+
FileUtils.rm_rf CoreTap.instance.path
390+
FileUtils.rm_rf CoreCaskTap.instance.path
391+
(CoreTap.instance.path/"Formula").mkpath
392+
end
393+
394+
it "creates a tap from another brew worktree when that has the source repository" do
395+
tap = CoreCaskTap.instance
396+
source_repository = HOMEBREW_PREFIX.parent/"source-repository"
397+
source_worktree = HOMEBREW_PREFIX.parent/"source-worktree"
398+
source_tap = source_worktree/"Library/Taps/#{tap.full_name.downcase}"
399+
400+
FileUtils.rm_rf tap.path
401+
source_tap.mkpath
402+
source_tap.cd do
403+
system "git", "init"
404+
FileUtils.touch "README.md"
405+
system "git", "add", "--all"
406+
system "git", "commit", "-m", "init"
407+
end
408+
FileUtils.mkdir_p (HOMEBREW_REPOSITORY/".git").dirname
409+
(HOMEBREW_REPOSITORY/".git")
410+
.write "gitdir: #{source_repository}/.git/worktrees/#{HOMEBREW_REPOSITORY.basename}\n"
411+
412+
allow(Utils).to receive(:popen_read).and_call_original
413+
allow(Utils).to receive(:popen_read)
414+
.with("git", "-C", HOMEBREW_REPOSITORY, "worktree", "list", "--porcelain")
415+
.and_return("worktree #{source_worktree}\n")
416+
allow(tap).to receive_messages(command_files: [], formula_files: [], cask_files: [],
417+
formula_names: [], cask_tokens: [])
418+
expect(tap).to receive(:safe_system)
419+
.with("git", "-C", source_tap, "worktree", "add", "--detach", tap.path, "HEAD")
420+
.and_wrap_original do
421+
tap.path.mkpath
422+
(tap.path/".git").write "gitdir: #{source_tap}/.git/worktrees/#{tap.full_repository.downcase}\n"
423+
end
424+
425+
tap.install
426+
ensure
427+
FileUtils.rm_rf source_repository
428+
FileUtils.rm_rf source_worktree
429+
FileUtils.rm_rf CoreCaskTap.instance.path
430+
end
431+
432+
it "uses the requested remote for cask taps with an explicit clone target" do
433+
tap = CoreCaskTap.instance
434+
requested_remote = "https://example.com/Homebrew/homebrew-cask"
435+
source_repository = HOMEBREW_PREFIX.parent/"source-repository"
436+
source_tap = source_repository/"Library/Taps/#{tap.full_name.downcase}"
437+
438+
FileUtils.rm_rf tap.path
439+
source_tap.mkpath
440+
(source_tap/".git").mkpath
441+
FileUtils.mkdir_p (HOMEBREW_REPOSITORY/".git").dirname
442+
(HOMEBREW_REPOSITORY/".git")
443+
.write "gitdir: #{source_repository}/.git/worktrees/#{HOMEBREW_REPOSITORY.basename}\n"
444+
445+
allow(tap).to receive_messages(command_files: [], formula_files: [], cask_files: [],
446+
formula_names: [], cask_tokens: [])
447+
expect(tap).to receive(:safe_system)
448+
.with("git", "clone", requested_remote, tap.path.to_s, "--origin=origin", "--template=",
449+
"--config", "core.fsmonitor=false")
450+
.and_wrap_original do
451+
tap.path.mkpath
452+
(tap.path/".git").mkpath
453+
end
454+
455+
tap.install clone_target: requested_remote, force: true
456+
ensure
457+
FileUtils.rm_rf source_repository
458+
FileUtils.rm_rf CoreCaskTap.instance.path
459+
end
460+
358461
it "raises an error when run `brew tap --custom-remote` without a custom remote (already installed)" do
359462
setup_git_repo
360463
already_tapped_tap = klass.fetch("Homebrew", "foo")
@@ -391,6 +494,26 @@ def setup_completion(link:)
391494
tap = klass.fetch("Homebrew", "bar")
392495
expect { tap.uninstall }.to raise_error(TapUnavailableError)
393496
end
497+
498+
it "removes Git worktree metadata for worktree-installed taps" do
499+
tap = CoreCaskTap.instance
500+
source_tap = HOMEBREW_PREFIX.parent/"source-tap"
501+
502+
FileUtils.rm_rf tap.path
503+
source_tap.mkpath
504+
(source_tap/".git").mkpath
505+
tap.path.mkpath
506+
(tap.path/".git").write "gitdir: #{source_tap}/.git/worktrees/#{tap.full_repository.downcase}\n"
507+
508+
allow(tap).to receive_messages(contents: [], formula_names: [], cask_tokens: [])
509+
expect(tap).to receive(:safe_system)
510+
.with("git", "-C", source_tap, "worktree", "remove", "--force", tap.path)
511+
512+
tap.uninstall
513+
ensure
514+
FileUtils.rm_rf source_tap
515+
FileUtils.rm_rf CoreCaskTap.instance.path
516+
end
394517
end
395518

396519
specify "#install and #uninstall" do

0 commit comments

Comments
 (0)