pub enum Rendering {
Global,
PerPattern {
fold_case: bool,
},
}Expand description
Spells a pattern so it matches either case of every ASCII letter.
The other half of open decision 13, settled on 2026-08-05, and it is not
optional: [crate::rules::declaration::MATCHING] folds ASCII case unconditionally, so a
rendered line that did not would be narrower than the filter — and the
narrow direction is the one measured eating 34 CR bytes out of a 2 MB
ciphertext and losing the file at checkout. Emitting the fold rather than
leaning on core.ignorecase is what makes the two agree on every machine:
measured on git 2.55, **/secrets/** answers unspecified for
SEcrets/db.txt where the setting is false, while
**/[sS][eE][cC][rR][eE][tT][sS]/** answers unset whatever it is set to.
Four things in a pattern are not plain letters, and each is left meaning what it meant:
- a glob escape.
\sis the literals, so it becomes[sS]— the backslash was doing nothing a character class does not.\*and every other escaped metacharacter is passed through with its backslash. - a character class.
[a-z]cannot become[[aA]-[zZ]]; the counterpart is added inside the brackets instead, giving[a-zA-Z]. A negated class gets it too, which is right: folding[!a]must stop it matchingA. - a POSIX class,
[[:alpha:]], whose members are named rather than spelled, so there is nothing to rewrite and it is copied whole — except the two classes that are a case:[:upper:]and[:lower:]each gain their counterpart, because the selection side folds them too. Measured:gix-globunderCase::Foldlowercases the candidate first, so[[:upper:]]dir/selectsxdir/a.env— and a verbatim copy answersunspecifiedfor it atcore.ignorecase=false, the narrower-than-the- filter direction that costs the file. - anything outside ASCII, which is copied byte for byte. That is the
documented boundary — see [
crate::rules::declaration::MATCHING].
Quoting is not this function’s business. It runs before [spell], which puts
the quotes back around a pattern that needs them, and [, ] and - are
ordinary characters to git’s C-unquoting. It runs after [guard] for the
opposite reason: guard recognises a literal [attr] opening, and folding
first would turn it into [attrATTR] and hide it.
How the managed section spells what it protects.
Two shapes, and the choice is a trade this project measured rather than guessed.
PerPattern writes a line per declared pattern, each naming the filter, the
-text that protects the ciphertext and the diff driver — the shape
git-crypt users will recognise. It is what sync writes when nothing asks
otherwise, and it confines the diff driver to declared paths.
Global is one line covering the whole repository. init writes it, so a
repository works correctly before sync has ever run and nothing can go
stale; sync --global puts it back. Its cost is the diff driver on every
file.
The cost that decides between them is the diff driver, and it is a process
per blob — git has no long-running protocol for textconv the way it has one
for filters. Measured on git 2.55, 2026-08-06, against the same repository
with the driver unregistered:
| files in the diff | global | per pattern |
|---|---|---|
| 5 | 72 ms | 21 ms |
| 20 | 201 ms | 22 ms |
| 100 | 899 ms | 25 ms |
| 1000 | 8461 ms | 23 ms |
So an everyday diff pays nothing anyone notices, and a thousand-file review
pays eight seconds. init writes Global so a fresh repository is correct
with no second command; sync writes PerPattern, which is what a
repository settles into once anyone runs it.
The other half of Global is -text on every path, which stops git
normalising line endings anywhere in the repository. That is the price of
needing no sync: the same attribute is what keeps git’s CRLF conversion
off the ciphertext, and one line cannot say it for some paths only.
Variants§
Global
One line, covering everything. Correct with no sync in the flow.
PerPattern
One line per declared pattern, with ASCII case folded when asked.