Summary
readdir order is the one place filesystem nondeterminism enters putup. Today every consumer is responsible for canonicalizing it, which is a conventional invariant — and two of the five consumers got it wrong (#171, #175).
Consumers of pup::platform::read_directory / walk_directory:
| site |
status |
src/parser/glob.cpp:230,243 |
was readdir-ordered → fixed in #174 |
src/core/layout.cpp:208 |
was readdir-ordered → fixed in #176 |
src/cli/context.cpp:237 |
benign: handle sort is a set structure, iteration order re-derived by content in sort_dirs_by_depth |
src/cli/target.cpp:162 |
unsorted, currently unobservable: variants are content-sorted at multi_variant.cpp:76 before use |
Proposal
Sort entries by name inside read_directory, and per-directory inside walk_directory before visiting and recursing. Then enumeration order is a function of directory content at the one site that produces it (count=1), instead of at each consumer that happens to remember.
This is what the equivalent boundary in tup already provides: listings come out of node_dir_index (dir, name).
What it does not replace
Boundary sorting gives determinism, not canonical path order, for recursive walks: DFS pre-order over per-directory sorted entries is not lexicographic — sub.txt sorts before sub/a.txt as a string, but the walk emits sub/a.txt first because it descends sub when it reaches it. So glob_expand's own sort stays necessary and is not made redundant by this change.
Cost
One sort per directory read. walk_directory is on the project-discovery path; the existing guarantee that it interns no per-entry strings (test_platform_file_io.cpp) is unaffected, since sorting compares the name views already in the listing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
Summary
readdirorder is the one place filesystem nondeterminism enters putup. Today every consumer is responsible for canonicalizing it, which is a conventional invariant — and two of the five consumers got it wrong (#171, #175).Consumers of
pup::platform::read_directory/walk_directory:src/parser/glob.cpp:230,243src/core/layout.cpp:208src/cli/context.cpp:237sort_dirs_by_depthsrc/cli/target.cpp:162multi_variant.cpp:76before useProposal
Sort entries by name inside
read_directory, and per-directory insidewalk_directorybefore visiting and recursing. Then enumeration order is a function of directory content at the one site that produces it (count=1), instead of at each consumer that happens to remember.This is what the equivalent boundary in tup already provides: listings come out of
node_dir_index (dir, name).What it does not replace
Boundary sorting gives determinism, not canonical path order, for recursive walks: DFS pre-order over per-directory sorted entries is not lexicographic —
sub.txtsorts beforesub/a.txtas a string, but the walk emitssub/a.txtfirst because it descendssubwhen it reaches it. Soglob_expand's own sort stays necessary and is not made redundant by this change.Cost
One sort per directory read.
walk_directoryis on the project-discovery path; the existing guarantee that it interns no per-entry strings (test_platform_file_io.cpp) is unaffected, since sorting compares the name views already in the listing.🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA