Skip to content

bundle: preserve formula conflict handling - #24356

Merged
MikeMcQuaid merged 5 commits into
Homebrew:mainfrom
thiagogenez:preserve-bundle-conflict-handling
Nov 1, 2026
Merged

MikeMcQuaid merged 5 commits into
Homebrew:mainfrom
thiagogenez:preserve-bundle-conflict-handling

Conversation

@thiagogenez

@thiagogenez thiagogenez commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

brew bundle install can fail to restore packages from a Brewfile generated by brew bundle dump. Batch installation skips unlinking conflicting formulae, so installation fails with a conflict error. This patch keeps a formula out of the batch when a conflicting formula is already installed, requested in the Brewfile or included in a requested package's dependency tree. Bundle then uses its existing individual installer to unlink the conflicting formula before installation.

Conflict checks reuse Homebrew's formula and cask dependency traversal. Optional dependencies are included even when disabled, so some formulae may use individual installation unnecessarily. The expanded dependency list is reused across conflict checks.

Example 1: Install moreutils when GNU parallel is linked

./bin/brew bundle dump produced the following Brewfile with moreutils linked and GNU parallel installed but unlinked:

# Collection of tools that nobody wrote when UNIX was young
brew "moreutils"
# Shell command parallelization utility
brew "parallel", link: false

The moreutils package and GNU parallel package provide different executables named parallel. The Brewfile requests that GNU parallel remain installed but unlinked (link: false), with bin/parallel pointing to the executable from moreutils.

The batch installer added in Homebrew/brew#23659 skips Brew#resolve_conflicts!. With GNU parallel already linked and moreutils absent, Bundle tries to install moreutils before unlinking GNU parallel. The installation fails. Bundle then unlinks GNU parallel, leaving moreutils uninstalled and bin/parallel absent.

With the fix, Bundle runs the commands in the required order:

./bin/brew unlink parallel
./bin/brew install --formula moreutils

Reproduce with the same Brewfile entries in a disposable Homebrew installation, starting with moreutils and GNU parallel uninstalled:

./bin/brew install parallel
brewfile_dir="$(mktemp -d)"
printf 'brew "moreutils"\nbrew "parallel", link: false\n' > "$brewfile_dir/Brewfile"
./bin/brew bundle install --no-upgrade --verbose --file="$brewfile_dir/Brewfile"

The recorded tests used the unedited dumped Brewfile. The initial state was restored before each run:

Stage moreutils GNU parallel bin/parallel points to
Initial state for each run Not installed Installed, linked GNU parallel executable
Before fix (exit 1) Not installed Installed, unlinked Missing
After fix (exit 0) Installed, linked Installed, unlinked moreutils executable

Before (exit 1):

Error: moreutils: Cannot install moreutils because conflicting formulae are installed.
Installing moreutils has failed!
`brew bundle` failed! 1 Brewfile dependency failed to install

After (exit 0):

Unlinking parallel formula.
Installing moreutils formula. It is not currently installed.
`brew bundle` complete! 2 Brewfile dependencies now installed.

Example 2: Install ctags and b2sum in one batch

Batch installation is preserved when no conflicting formula is installed or present in the Brewfile's declared dependency tree. For example:

brew "ctags"
brew "b2sum"

ctags declares a conflict with universal-ctags, and b2sum declares a conflict with coreutils. With all four formulae initially absent, ./bin/brew bundle install --no-upgrade --file=./Brewfile installed ctags and b2sum in one call:

./bin/brew install homebrew/core/ctags homebrew/core/b2sum

Validation

./bin/brew lgtm --online passed. The installer and cask specs passed with 90 examples. Tests cover installed conflicts, conflicts requested through aliases or formula and cask dependencies, including transitive optional dependencies. Tests also check that an unrelated entry's install arguments preserve batching, cask dependencies reuse loaded formulae and non-recursive cask queries retain their existing behaviour.

For the moreutils reproduction, ./bin/brew bundle check --no-upgrade --file="$brewfile_dir/Brewfile" and a second install passed. For the ctags and b2sum example, ./bin/brew bundle check --no-upgrade --file=./Brewfile passed.


  • Have you followed our Contributing guidelines?
  • Have you checked for other open Pull Requests for the same change?
  • Have you explained what your changes do? Performance claims (e.g. "this is faster") must include brew benchmark results.
  • Have you explained why you'd like these changes included, not just what they do?
  • For bug fixes, have you given step-by-step brew commands to reproduce the bug?
  • Have you written new tests (excluding integration tests)? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) locally?

  • I did not use AI/LLM to create this PR, or I disclosed the tool/model below and reviewed its output; I did not attribute commits to AI and will answer maintainer questions and review comments myself without AI/LLM.

Codex (GPT-6) helped investigate the bug, write the patch and regression tests, run the reproductions and benchmark and draft this description.


- Keep formulae with declared conflicts out of batched installs so
  the existing installer can unlink conflicting formulae first.
- Cover existing conflicts and conflicts introduced by a batch.
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9e970bfc-a55f-4947-98c4-fdb481ee3fcf
Commits

Reviewing files that changed from the base of the PR and between 4b2e32e and bbe9021.

Files selected for processing: 3
  • Library/Homebrew/bundle/installer.rb
  • Library/Homebrew/test/bundle/cask_spec.rb
  • Library/Homebrew/test/bundle/installer_spec.rb
Files with no reviewable changes: 1
  • Library/Homebrew/bundle/installer.rb

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Summary

Summary by CodeRabbit

  • Bug Fixes
    • Batch installation now avoids grouping formulae when conflicting formulae are already installed or are among the requested formulae and their dependencies.
    • Conflict checks include recursive optional dependencies and formula dependencies from casks, including build and test dependencies.
    • Cask dependency checks use the current cask definition and report an error when a requested cask is unavailable.

The installer collects requested formula names and dependencies when it checks whether Brew entries can share an install batch. It rejects batching when a formula conflict is installed or requested. Recursive Cask dependency lookup contributes formula dependency names.

Changes: Formula batching

Layer / File(s) Summary
Collect requested formula names
Library/Homebrew/bundle/brew.rb, Library/Homebrew/bundle/cask.rb, Library/Homebrew/bundle/installer.rb, Library/Homebrew/test/bundle/cask_spec.rb
Formula-name collection includes requested formulae and recursive dependencies, including formula dependencies of requested Casks. Cask specs cover nested Casks, dependency types, installed definitions, and unavailable Casks.
Exclude conflicting formulas from batching
Library/Homebrew/bundle/installer.rb, Library/Homebrew/test/bundle/installer_spec.rb
The installer checks whether formula conflicts are installed or included in the requested formula names before it batches Brew entries. Specs cover conflict handling for direct, transitive optional, and Cask dependencies, and verify dependency expansion occurs once.
Merge Risk: Minimal · up to bbe90

This change keeps conflicting formulae out of the install batch so that bundle can unlink the conflict first. No unresolved behavioral risk was found in the reviewed change.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: preserving formula conflict handling in Bundle installation.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The pull request description clearly explains the formula conflict handling changes, the affected batch installation behaviour, reproduction steps, tests, and validation results.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good idea, one question.

Comment thread Library/Homebrew/bundle/installer.rb Resolved
- Keep batch installation when conflicts are absent.
- Check requested formula and cask dependency trees for conflicts.
- Include optional dependencies to cover saved install options.
Comment thread Library/Homebrew/bundle/installer.rb Resolved
- Reuse `Cask.formula_dependencies` for batch conflict checks.
- Share formula expansion and reuse loaded cask dependencies.
- Preserve cleanup's installed-cask behaviour.

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, getting there! Would be nice to slim the diff as much as possible here 🙇🏻

Comment thread Library/Homebrew/bundle/installer.rb Resolved
- Remove the blanket `args` check for unrelated entries.
- Keep tests focused on batching and conflict resolution.
- Exercise installed-state refresh through the test Cellar.

@MikeMcQuaid MikeMcQuaid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@MikeMcQuaid
MikeMcQuaid enabled auto-merge November 1, 2026 15:08
@MikeMcQuaid
MikeMcQuaid disabled auto-merge November 1, 2026 15:08
@MikeMcQuaid
MikeMcQuaid added this pull request to the merge queue Nov 1, 2026
Merged via the queue into Homebrew:main with commit 2581840 Nov 1, 2026
54 checks passed
@thiagogenez
thiagogenez deleted the preserve-bundle-conflict-handling branch November 1, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants