Skip to content

build: stop git apply from silently skipping dep patches with a diff --git header - #35099

Open
robobun wants to merge 2 commits into
mainfrom
farm/451eeecb/fix-dep-patch-silent-skip
Open

build: stop git apply from silently skipping dep patches with a diff --git header#35099
robobun wants to merge 2 commits into
mainfrom
farm/451eeecb/fix-dep-patch-silent-skip

Conversation

@robobun

@robobun robobun commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What

applyPatch() in scripts/build/fetch-cli.ts runs git apply --no-index with cwd=vendor/<dep>/. That directory is a subdirectory of the bun repo's worktree, and --no-index doesn't suppress repo discovery: git apply finds the enclosing repo, and for a patch with a diff --git a/... b/... header treats its paths as toplevel-relative. From git-apply(1):

When running from a subdirectory in a repository, patched paths outside the directory are ignored.

The dep's src/foo.c is outside the vendor/<dep>/ prefix, so git prints Skipped patch 'src/foo.c'. (only under -v; normal verbosity prints nothing) and exits 0 having changed nothing. applyPatch only checked the exit status, so the .ref stamp certified an unpatched source tree.

Plain unified diffs (--- a/X / +++ b/X with no diff --git line) are resolved cwd-relative instead, which is why most of patches/ was unaffected.

Repro

mkdir -p vendor/libuv/src/win
curl -s https://raw.githubusercontent.com/oven-sh/libuv/96873308d4505884ba4f82ac89905fe0fd97eb9e/src/win/poll.c -o vendor/libuv/src/win/poll.c
cd vendor/libuv
git apply --ignore-whitespace --ignore-space-change --no-index -v - \
  < ../../patches/libuv/win-poll-abort-with-disconnect.patch
# -> Skipped patch 'src/win/poll.c'.
# -> exit 0, file unchanged

Fix

  • Set GIT_CEILING_DIRECTORIES=dirname(dest) so repo discovery stops above the dep dir.
  • Drop inherited GIT_DIR / GIT_WORK_TREE (git hooks, some CI wrappers) which would bypass the ceiling.
  • Add -v + LC_ALL=C and throw when stderr contains Skipped patch, so any remaining exit-0-but-skipped case is a build error instead of a silent no-op.
  • Strip the diff --git / index header from patches/libuv/win-poll-abort-with-disconnect.patch. The patch-content hash feeds computeSourceIdentity(), so existing vendor/libuv/.ref stamps written against the silently-skipped patch now mismatch and the tree is re-fetched instead of reporting "up to date". Also brings this patch in line with the other header-less entries under patches/.

Consequence on main

patches/libuv/win-poll-abort-with-disconnect.patch (added in #32488) was being silently skipped and has never been applied to the Windows build since it landed. This PR activates it: uv__fast_poll_submit_poll_req / uv__fast_poll_process_poll_req in src/win/poll.c now subscribe and report AFD_POLL_ABORT as UV_DISCONNECT, which is a runtime behavior change for every bun-usockets socket on Windows (bun-usockets arms UV_DISCONNECT unconditionally at packages/bun-usockets/src/eventing/libuv.c). Verified:

#32761, #33094 and #33018 each carry an ad-hoc copy of the applyPatch fix as part of unrelated work; landing it standalone lets those drop their copies.

Tests

test/internal/dep-patch-apply.test.ts drives applyPatch directly against a temp git repo with an extracted dep tree under it. Fail-before with the original function body (export retained for the import):

(fail) applies a diff --git patch from a repo subdirectory      # file untouched
(pass) applies a plain unified diff from a repo subdirectory    # always worked
(fail) a patch that does not apply is reported as an error      # bad diff --git patch also silently skipped
(fail) ignores inherited GIT_DIR / GIT_WORK_TREE                # file untouched

All four pass after. Verified on Linux and Windows.

The fix is in scripts/ and patches/, not src/ or packages/, so the gate's git stash push -- src/ packages/ leaves it in place; the fail-before above is the manual equivalent.


[stamp-90s] gate passed · iteration 0 · 3 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/internal/dep-patch-apply.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/internal/dep-patch-apply.test.ts
bun test v1.4.0 (a6cd153f6)

test/internal/dep-patch-apply.test.ts:
(pass) applyPatch (scripts/build/fetch-cli.ts) > applies a diff --git patch from a repo subdirectory [123.42ms]
(pass) applyPatch (scripts/build/fetch-cli.ts) > applies a plain unified diff from a repo subdirectory [32.54ms]
(pass) applyPatch (scripts/build/fetch-cli.ts) > a patch that does not apply is reported as an error [41.82ms]
(pass) applyPatch (scripts/build/fetch-cli.ts) > ignores inherited GIT_DIR / GIT_WORK_TREE [30.87ms]

 4 pass
 0 fail
 4 expect() calls
Ran 4 tests across 1 file. [2.41s]
Exit: 0
diff hotspot
patches/libuv/win-poll-abort-with-disconnect.patch |  2 -
 scripts/build/fetch-cli.ts                         | 35 +++++++-
 test/internal/dep-patch-apply.test.ts              | 98 ++++++++++++++++++++++
 3 files changed, 130 insertions(+), 5 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                reads  edits  tests
patches/libuv/win-poll-abort-with-disconnect.patch      1      1      0
scripts/build/fetch-cli.ts                              2      5      0
test/internal/dep-patch-apply.test.ts                   2      6      0

…--git header

fetch-cli's applyPatch runs git apply --no-index with cwd=vendor/<dep>/,
which is a subdirectory of the bun repo. git still discovers the enclosing
repo, and for a patch with a 'diff --git a/... b/...' header treats the
paths as toplevel-relative (git-apply(1): 'When running from a subdirectory
in a repository, patched paths outside the directory are ignored'). The
dep's src/foo.c is outside the vendor/<dep>/ prefix, so git prints
'Skipped patch ...' only under -v and exits 0 having changed nothing.
applyPatch checked only the exit status, so the .ref stamp certified an
unpatched tree.

Set GIT_CEILING_DIRECTORIES=dirname(dest) so repo discovery stops above
the dep dir (and drop inherited GIT_DIR/GIT_WORK_TREE which would bypass
the ceiling). Add -v + LC_ALL=C and throw on 'Skipped patch' in stderr as
a safety net.

patches/libuv/win-poll-abort-with-disconnect.patch (added in #32488) has a
diff --git header and was silently not being applied; with this change it
applies.
@robobun

robobun commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:41 AM PT - Jul 22nd, 2026

@robobun, your commit a6cd153 has 3 failures in Build #77662 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 35099

That installs a local version of the PR into your bun-35099 executable, so you can run:

bun-35099 --bun

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

applyPatch now constrains Git repository discovery, reports silently skipped patches, and is exported for direct testing. Regression tests cover repository-relative and plain diffs, failed application, and inherited Git environment variables.

Patch application safeguards

Layer / File(s) Summary
Harden applyPatch execution
scripts/build/fetch-cli.ts
Exports applyPatch, sets Git ceiling and locale environment variables, and throws a BuildError when stderr reports a skipped patch.
Validate dependency patch scenarios
test/internal/dep-patch-apply.test.ts
Tests Git-header and unified diff application, failed patches, repository layouts, and inherited Git environment restoration.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly and concisely summarizes the main change: preventing silent skipping of dep patches in git apply.
Description check ✅ Passed The description covers both what changed and how it was verified, even though it does not use the template's exact headings.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build/fetch-cli.ts`:
- Around line 245-260: Condense the comments to three lines or fewer while
preserving the durable behavioral contract. In scripts/build/fetch-cli.ts lines
245-260, retain only the concise git apply invariant; in
test/internal/dep-patch-apply.test.ts lines 1-16, 50-54, 72-75, and 98-101,
shorten the header, fixture-helper documentation, pre-fix behavior explanation,
and inherited-environment explanation respectively, moving historical detail
elsewhere if needed.

In `@test/internal/dep-patch-apply.test.ts`:
- Line 78: Strengthen the assertions in test/internal/dep-patch-apply.test.ts at
lines 78, 87, and 110 by replacing each PATCHED_LINE containment check with an
exact equality assertion against ORIGINAL.replace("line two", PATCHED_LINE),
ensuring the complete patched file contents match the intended replacement at
all three sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6765408d-bcdc-47c2-934b-b1aeef5bc2a3

📥 Commits

Reviewing files that changed from the base of the PR and between 47597ab and 9ff8998.

📒 Files selected for processing (2)
  • scripts/build/fetch-cli.ts
  • test/internal/dep-patch-apply.test.ts

Comment thread scripts/build/fetch-cli.ts
Comment thread test/internal/dep-patch-apply.test.ts Outdated
Comment thread scripts/build/fetch-cli.ts
- Strip the diff --git / index header from win-poll-abort-with-disconnect.patch.
  The header change alters the patch-content hash, so computeSourceIdentity()
  produces a new .ref identity for libuv and existing vendor/libuv/ trees that
  were stamped against the silently-skipped patch are re-fetched. Also brings
  the patch in line with the other header-less entries under patches/.
- Tighten the applyPatch tests to assert exact file contents with toBe()
  instead of toContain(), and trim the comment headers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/internal/dep-patch-apply.test.ts`:
- Around line 2-5: Condense the regression-test comment to three lines or fewer
while preserving the non-obvious behavior that `git apply` from a repository
subdirectory treats `diff --git` paths as top-level-relative and may silently
skip them with exit 0; retain the reference to `applyPatch` and its doc comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c74dd610-59b7-470d-b4ca-c0747020ecf7

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff8998 and a6cd153.

📒 Files selected for processing (2)
  • patches/libuv/win-poll-abort-with-disconnect.patch
  • test/internal/dep-patch-apply.test.ts
💤 Files with no reviewable changes (1)
  • patches/libuv/win-poll-abort-with-disconnect.patch

Comment thread test/internal/dep-patch-apply.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — a6cd153 addresses the stale-.ref invalidation concern by stripping the diff --git header from the libuv patch (changes its content hash, so existing vendor/libuv/ trees re-fetch), and tightens the test assertions to exact toBe().

What was reviewed:

  • Verified the patch-content change flows through computeSourceIdentity() → new .ref identity → fetchDep() no longer short-circuits on previously-stamped trees.
  • Checked patches/*/ for other diff --git-header patches — none remain; patches/ncrypto.patch has one but is not routed through applyPatch().
  • GIT_CEILING_DIRECTORIES + GIT_DIR/GIT_WORK_TREE scrubbing + -v/LC_ALL=C skip-detection are correctly layered; env destructuring drops the inherited vars rather than passing empty strings.
Extended reasoning...

Overview

Build-tooling fix in scripts/build/fetch-cli.ts::applyPatch(): git apply --no-index from a repo subdirectory silently skips diff --git-header patches (paths resolved toplevel-relative, exit 0). The fix sets GIT_CEILING_DIRECTORIES=dirname(dest), scrubs inherited GIT_DIR/GIT_WORK_TREE, and adds a belt-and-suspenders Skipped patch stderr check under -v+LC_ALL=C. The follow-up commit strips the diff --git/index header from patches/libuv/win-poll-abort-with-disconnect.patch, which both aligns it with the other header-less dep patches and — critically — changes its content hash so computeSourceIdentity() produces a new .ref identity and stale vendor/libuv/ trees re-fetch. New tests in test/internal/dep-patch-apply.test.ts cover git-header diffs, plain diffs, apply-failure, and inherited git env.

Security risks

None. This is local build tooling that shells out to git apply with a fixed argv and controlled env; no user-controlled input, no network beyond the existing tarball fetch (unchanged here).

Level of scrutiny

Moderate — build infrastructure, not shipped runtime. A regression here fails the build loudly (patch-apply error or compile error), not silently at runtime. The one silent-failure risk (stale .ref certifying an unpatched tree) was my prior review's finding and is now resolved by the patch-file content change.

Other factors

  • My previous 🔴 finding (fix doesn't invalidate existing stamped trees) is addressed exactly as suggested — targeted content change to the affected patch file, so only libuv re-fetches.
  • CodeRabbit's toContaintoBe assertion tightening is addressed; the comment-length nit is a style preference and the remaining doc comment carries genuinely non-obvious git semantics worth keeping.
  • Grepped patches/*/*.patch for remaining diff --git headers — none; the whole affected class is covered. patches/ncrypto.patch (top-level) has a header but is unreferenced by scripts/ so does not flow through applyPatch().
  • Tests use tempDir + using, restore mutated process.env in finally, and assert exact file contents.

@robobun

robobun commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

Self-review complete; both concerns it raised are addressed:

Remaining reds on 77662, none related to this diff:

  • test-net-connect-memleak.js (ubuntu x64, debian x64) and test-gc-http-client-connaborted.js (ubuntu x64): GC-timing tests also red on main builds 77601 and 77580. On Linux this diff does not touch the built binary (no src/ change; libuv is enabled: cfg => cfg.windows).
  • step-failed-outside-runner on darwin 26 aarch64: runner-level infra failure, not a test.

Ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant