Visitar URL original
Precompose filenames to NFC on macOS when matching archive ignore patterns by kmosher · Pull Request #23566 · pulumi/pulumi · GitHub
Skip to content

Precompose filenames to NFC on macOS when matching archive ignore patterns - #23566

Merged
kmosher merged 3 commits into
masterfrom
kmosher/gitignore-nfc
Jul 13, 2026
Merged

kmosher merged 3 commits into
masterfrom
kmosher/gitignore-nfc

Conversation

@kmosher

@kmosher kmosher commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Description

When archiving a directory for upload (pulumi template publish and policy-pack publish), we honor .gitignore files plus a set of default excludes. The matcher compared filesystem entry names against ignore patterns byte/rune-exact, with no Unicode normalization.

macOS stores filenames in a decomposed (roughly NFD) form, so a name like café read back from readdir is a different rune sequence than the composed (NFC) café a user typically types into a .gitignore. The pattern then fails to match, and the file is included in the archive even though the user intended to exclude it.

This mirrors git's own core.precomposeunicode behavior (default-on for macOS): precompose directory-entry names to NFC before matching. Since we read and honor .gitignore files, matching git here is the correct behavior. The normalization is gated to macOS for the same reason git gates it — on a decomposing filesystem the NFC name still resolves to the same file, whereas on a byte-preserving filesystem (Linux) rewriting the name could point at a file that doesn't exist.

Changes

  • New precompose.go: precomposeUnicode(name) returns the NFC form on filesystems that decompose Unicode (macOS), and the name unchanged elsewhere.
  • archive.go: precompose each directory-entry name at the single point where it enters the path used for matching, the tar header, and file open.
  • New test TestIgnorePrecomposesUnicode: an NFD-on-disk directory with an NFC .gitignore pattern is correctly excluded. Verified it fails without the fix.
  • Promote golang.org/x/text from indirect to direct in sdk/go.mod (effective version unchanged).

Test plan

  • cd sdk && go test ./go/common/util/archive/ -count=1 passes, including the new test.
  • Manually on macOS: create a dir whose name is stored in NFD with an NFC pattern in .gitignore that should match it; confirm the file is now excluded from the archive (was previously included).

🤖 Generated with Claude Code

Internal tracking: pulumi/home#4807

@kmosher
kmosher marked this pull request as ready for review June 23, 2026 18:59
@kmosher
kmosher requested a review from a team as a code owner June 23, 2026 18:59
// to on for macOS; we mirror that gating so our ignore matching agrees with
// git's on the same filesystem.
//
// It is a var rather than a const so tests can exercise the precompose path on

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.

As long as TestIgnorePrecomposesUnicode is running on macOS does it really make sense to mutate this to also test on linux? Feels like we could simplify and just move this into the method, and run the test with the gitignore and file created via the NFC name, and on macOS that implicitly gets decomposed so running the test checks that precompose is working.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call on dropping the package-level var and the Linux machinery — done. But I couldn't take the "create it via NFC and let macOS decompose it" route: that's HFS+ behavior, and modern macOS defaults to APFS, which is normalization-preserving (it stores the exact bytes you write and only compares insensitively). I verified on my box — write NFC, readdir hands back NFC; write NFD, readdir hands back NFD. So an NFC-on-disk name never comes back decomposed, and that test passes even with precomposeUnicode stubbed to a no-op — i.e. it wouldn't actually exercise the fix.

So the test now writes the on-disk name explicitly in NFD (robust: NFD-in gives NFD-back on both APFS and HFS+) with an NFC .gitignore pattern, and skips on non-darwin since the code path is darwin-only anyway. I confirmed it fails without the fix and passes with it. No more mutation, no more Linux path — just the real macOS behavior.

kmosher and others added 3 commits July 13, 2026 16:06
The archive ignore matcher (used by template and policy-pack publish, and
honoring .gitignore files) compared filesystem entry names against ignore
patterns byte/rune-exact, with no Unicode normalization. macOS stores
filenames in a decomposed (roughly NFD) form, so a name like "café" read
back from readdir is a different rune sequence than the composed (NFC)
"café" a user typically types into a .gitignore. The pattern then fails to
match and the file is archived despite being ignored.

Mirror git's core.precomposeunicode: on macOS, precompose directory-entry
names to NFC before matching. Gated to macOS for the same reason git gates
it -- on a decomposing filesystem the NFC name still resolves to the same
file, while on a byte-preserving filesystem rewriting the name could point
at a file that does not exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the package-level precomposeUnicodeFS var and its test-time mutation,
inlining the darwin check into precomposeUnicode. The test now runs only on
macOS against a real NFD-on-disk name, which genuinely exercises the
precompose path (APFS preserves the bytes we write rather than decomposing
them, so the previous NFC-on-disk approach would have passed trivially).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kmosher
kmosher force-pushed the kmosher/gitignore-nfc branch from e70f331 to 03c9891 Compare July 13, 2026 20:07
@kmosher
kmosher enabled auto-merge July 13, 2026 20:38
@kmosher
kmosher added this pull request to the merge queue Jul 13, 2026
Merged via the queue into master with commit 54158ae Jul 13, 2026
247 of 248 checks passed
@kmosher
kmosher deleted the kmosher/gitignore-nfc branch July 13, 2026 21:31
@pulumi-bot

Copy link
Copy Markdown
Contributor

This PR has been shipped in release v3.253.0.

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.

3 participants