pub enum SetupGap {
MissingKey(String),
NotTrue {
key: String,
value: String,
},
CatchAllMissing,
FilterUnresolved {
paths: Vec<String>,
total: usize,
resolved: String,
},
CiphertextConverted {
paths: Vec<String>,
total: usize,
culprit: String,
},
DeclarationMissing,
SectionStale,
Untracked(String),
}Expand description
One reason git would not be filtering this repository.
Variants§
MissingKey(String)
A filter.git-xcrypt.* key is not set anywhere git reads.
NotTrue
The key is set, but not to anything git reads as true.
CatchAllMissing
.gitattributes carries no * filter=git-xcrypt line.
FilterUnresolved
Git resolves filter to something other than this tool for declared paths.
The catch-all is one line among many and git takes the last match, so
an attribute line below the managed section, a .gitattributes in a
subdirectory, or $GIT_DIR/info/attributes — which is not versioned and
outranks everything — turns this tool off for paths it believes it
protects. Measured on git 2.55: git check-attr filter then answers
unset, the next git add stores the plaintext, and every other check in
this command passes.
Until 2026-08-04 this was a note: the report named the files and the
lines and left the reader to run git check-attr. That was the last route
to a green report on a repository that does not encrypt, because a note
does not fail a CI gate.
Fields
CiphertextConverted
Git converts the line endings of declared paths itself.
The twin of SetupGap::FilterUnresolved, on the second attribute the
managed section sets, and it costs more rather than less. The section
writes -text on every encrypted path precisely so that git’s own CRLF
conversion never touches the ciphertext; an attribute line that outranks
it puts the conversion back.
Measured on git 2.55, with sync freshly run so nothing else in this
command had anything to say: a 2 MB file under secrets/** text lost 34
CR bytes out of its ciphertext, git add exited 0, git commit
exited 0, and the checkout failed the authentication tag and left no file
at all. Nobody can decrypt what was committed — not the author, not with
the key, not ever. status printed VERDICT: no findings. and exited 0.
A gap rather than a note for the reason the unresolved filter is one: both mean the declaration is not enforced, and a note does not fail a CI gate.
Fields
DeclarationMissing
.git-xcrypt is not there, so nothing declares what to encrypt.
Filed as a gap rather than only as a question since 2026-08-05. It is not
an exposure — the check-in path refuses on this state, so no git add
stores anything in the clear over it — but it is precisely a
configuration that enforces nothing, and the remedy is a file, not a
rotated secret. It still puts the rest of the run in undetermined,
because without the declaration neither the index nor history can be
judged at all.
SectionStale
The managed .gitattributes section no longer matches .git-xcrypt.
A gap since 2026-08-06, and a note before that (open decision 11).
What forced the change was not the severity but a contradiction: on one
and the same stale section sync --check exited 1 and status exited
0 printing VERDICT: no findings. — two commands of one tool
disagreeing about one state, so the answer a CI job got depended on which
one it happened to run.
The argument for keeping it a note is recorded rather than erased,
because it is true: it takes a foreign text attribute to make this
cost anything, since our own magic starts with NUL and text=auto or
core.autocrlf alone see binary and leave the ciphertext be. So this is
the one gap in this list that is conditional. It was resolved the other
way because the condition is invisible from here — the foreign attribute
may arrive in a .gitattributes a subdirectory away, or in
$GIT_DIR/info/attributes, which is not versioned — and because a
declaration whose -text does not reach every declared path is, in the
plain sense of what this command answers, not being enforced.
What it costs when the condition is met is measured, on git 2.55: a 2 MB
file lost 34 CR bytes out of its ciphertext, git add exited 0,
the commit succeeded, and the checkout failed the authentication tag and
left no file at all. Nobody can decrypt that blob, ever.
Every rendering this build writes counts as current, so a repository that
ran sync --global or sync --ignorecase is not sent to run the very
command that produced its section.
Untracked(String)
A file the whole mechanism bootstraps from is not tracked.
.gitattributes is what makes git call the filter and .git-xcrypt is
what the filter reads. Neither is any use to a clone unless it is
committed, and init creates them without committing them — so a
repository can look perfectly configured locally and publish nothing that
enforces anything. The clone finds out; the machine that pushed does not.