Skip to content

feat(engine): -inum / -samefile predicates - #128

Merged
helly25 merged 1 commit into
mainfrom
feat/inum-samefile
Jun 26, 2026
Merged

feat(engine): -inum / -samefile predicates#128
helly25 merged 1 commit into
mainfrom
feat/inum-samefile

Conversation

@helly25

@helly25 helly25 commented Jun 26, 2026

Copy link
Copy Markdown
Owner

First of the roadmap-tail find primaries (the adopt+apply work is done; this starts "go for all missing parts").

Added

  • -inum N - match by inode number (N/+N/-N, like -links), MatchesNumeric on Metadata.ino.
  • -samefile FILE - match the same file as FILE (same inode and device, so hard links match), via a per-entry reference stat following symlinks (mirrors -newer's IsNewerThan; resolving the reference once is a noted later optimization).

Registry descriptors + alphabetical dispatch entries (-inum between -iname/-ipath, -samefile between -regex/-size).

Tests

  • -inum unit (N/+N/-N), -samefile missing-reference unit, and a -samefile conformance case vs system find over a real hard link (GTEST_SKIP where hard links are unsupported).
  • bazel test //... green (default + clang+asan); pre-commit clean.

Add the two inode-identity find primaries from the roadmap backlog:

- -inum N: match by inode number (N / +N / -N, like -links), via
  MatchesNumeric on Metadata.ino.
- -samefile FILE: match the same file as FILE (same inode AND device, so
  hard links match), via a per-entry reference stat following symlinks,
  mirroring -newer's IsNewerThan (resolving the reference once is a later
  optimization).

Registry descriptors (grouped with -links as inode-identity tests) and
alphabetical dispatch entries. Tests: -inum unit (N/+N/-N), -samefile
missing-reference unit, and a -samefile conformance case against a real
hard link vs system find (skips where hard links are unsupported).

bazel test //... green in both default and clang+asan.
@helly25
helly25 enabled auto-merge (squash) June 26, 2026 17:55
@helly25
helly25 merged commit 59e0e3a into main Jun 26, 2026
7 checks passed
@helly25
helly25 deleted the feat/inum-samefile branch June 26, 2026 18:02
helly25 added a commit that referenced this pull request Jul 11, 2026
…333)

GlobToRegex's [...] scanner stopped at the first ']', mistranslating POSIX bracket syntax
that RE2 supports natively:
  [[:ascii:]] -> [[:ascii:]\]   (an ascii char, then a literal ])
  []]         -> []\]           (RE2 error: empty class)
  [!]]        -> similarly broken
Ranges like [a-z] were already fine (passthrough).

Fix: extract the class handling into AppendCharClass, which (1) treats a leading ']' as a
literal member (escaped \] for RE2) and (2) recognizes [:class:] / [.collating.] /
[=equiv=] sub-expressions and passes them through verbatim incl. their inner ']'. Standard
POSIX-glob correctness - no new grammar, no change to '**' / gitignore semantics. Improves
both --regextype=GLOB and the gitignore engine (both use GlobToRegex). The extraction also
drops GlobToRegex's cognitive complexity 68 -> 31.

Also documents in glob.h (resolving #122) that this is the gitignore-'**' dialect, kept
separate from mbo::file::Glob2Re2 (a full FS-globbing lib with different '**' that xff does
not use - it walks its own VFS engine), and that brace expansion is deliberately NOT here
(a future opt-in shell-glob grammar; '{'/'}' stay literal in GLOB).

Verified: glob_test (new POSIX-class + literal-] cases), regex/ignore/gitignore tests green;
e2e --regextype=GLOB -name '[[:alpha:]]' and '[]]' match correctly.
helly25 added a commit that referenced this pull request Jul 11, 2026
…#334)

SHGLOB is a new match grammar: everything GLOB does, plus shell brace alternation, so
`*.{cc,h}` matches either extension. As a matcher (not a shell) a brace group becomes an
RE2 alternation - `*.{cc,h}` -> `[^/]*\.(?:cc|h)` - rather than expanding to several words.

Why a separate grammar and not a GLOB feature: GLOB and the gitignore engine must keep
matching literal `{`/`}` (a .gitignore line `foo{1}` matches literally), so brace
expansion has to be opt-in. Named SHGLOB ("shell glob"), deliberately NOT EXTGLOB - in bash
that names the pattern-lists ?(..)/@(..)/!(..), which we do not ship (the !(..) negation has
no clean RE2 form).

Semantics match bash: each alternative is itself SHGLOB-translated (nesting, `*`/`?`/`[...]`
inside; alts may contain `/` and `**`); a comma-less `{x}` or an unbalanced `{` stays a
literal brace; empty alternatives are allowed; `\{`/`\}`/`\,` escape to literals, as do
`{`/`}`/`,` inside a `[...]` class. Deferred: numeric/char sequences {1..9}/{a..z} and
extglob pattern-lists.

Implementation is DRY: xff::glob gains an internal flavor-aware TranslateInto shared by
GlobToRegex (braces off) and the new ShglobToRegex (braces on, recursive {...} parse); the
`**`/`[...]` logic (incl. the #128 class scanner) is not duplicated. Wired through
Grammar::kShglob (regex Compile), parser GrammarFromGlobals, run.cc ValidateRegextype
(SHGLOB is a core engine, always linked), and the globals/doc-renderer SOT.

Self-doc: --regextype gains a full details body documenting every grammar. It also gives
PCRE2 the note it was missing - PCRE2 is the one build-time extra, present only in a full
build, and `xff --help=extras` reports whether THIS binary has it (RE2/EXACT/FNMATCH/GLOB/
SHGLOB are always built in). Flows into --help=--regextype / --help=full / --man / --markdown.

Also resolves #122: glob.h documents why xff keeps its own gitignore-`**` translator rather
than migrating onto mbo::file::Glob2Re2 (different `**`; xff walks its own VFS engine).

Tests: glob_test (ShglobToRegex + GLOB-braces-literal cases), regex_test (kShglob compile +
match + path semantics), grep_test.sh (--regextype=SHGLOB end-to-end + GLOB literal-brace).
Full suite green.
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.

1 participant