fix: status lists untracked filter-marked files; warn on plaintext blobs#27
Merged
Conversation
`gitveil status` previously ran plain `git ls-files` and early-returned
when no filter-matched files existed. Two consequences:
1. Untracked files were never shown — even ones that would be
encrypted on staging (filter=git-crypt). git-crypt lists them.
2. In any repo without `filter=git-crypt` entries in .gitattributes
(including freshly-inited or uninitialized repos), status printed
nothing and exited 0 — leaving the user with no feedback.
Rewrite to mirror git-crypt's semantics:
- List tracked + untracked files via two `git ls-files -z` calls
(NUL-delimited; handles filenames with whitespace).
- For every file, print ` encrypted:` (has git-crypt filter) or
`not encrypted:` (no filter) — matches git-crypt's prefix layout.
- Append `*** WARNING: staged/committed version is NOT ENCRYPTED! ***`
when a filter-marked file's index blob is plaintext (file was
staged before .gitattributes took effect). Print a summary at the
end pointing at `gitveil status -f`.
- `-f` only re-stages WARNING files; never auto-adds untracked.
- Recognize named-key filters (`git-crypt-<keyname>`) too.
- Call `find_git_dir()` up front so running outside a git repo
gives the clean `Not a git repository` error instead of a
confusing `git ls-files failed`.
Performance characteristics are preserved: at most one ls-files per
category, one batched check-attr, and one batched cat-file regardless
of repo size. Pipe-deadlock writer threads kept on both batched paths.
12 new integration tests + 1 unit test cover: untracked filter-matched
and non-filter files, tracked non-filter, WARNING + summary, no-warning
when clean, -e/-u/-f flags, fix-stages-only-warning-files, named-key
filter, filenames-with-spaces, not-a-git-repo error, and works-without-
gitveil-init. Existing assertions in test_status_shows_encrypted_files
and test_status_many_files_no_deadlock updated for the new semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…diagnostics Audit-driven follow-ups on the same PR: * `gitveil status -f` previously called `git add -- <file>` for every WARNING file. If the file had been deleted from the working tree (but was still tracked), git would stage the deletion — the opposite of re-encrypting. Skip such files with a clear diagnostic instead. * Add a regression test asserting gitignored files are excluded from status output (relies on `--exclude-standard` in `git ls-files`). * `test_status_fix_restages_only_warning_files` flaked once during local stress runs. Beef up its failure message with full gitveil stdout/stderr + `git diff` + `git status --porcelain` so any future flake gives us the state to diagnose, instead of just `got: ""`. * `Done. re-staged N of M file(s).` summary so the user can tell when some files were skipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…isting
Reverts the verbose-by-default behavior from the previous commits and
restores the original gitveil semantics for -e/-u, on the basis that
they were genuinely more useful and changing them silently broke
existing scripts. The bug-fix gains (untracked files, WARNING, clean
not-a-git-repo error, -f skip-deleted, NUL-delimited parsing) are kept.
New flag layout:
default focused: filter-marked files only (tracked + untracked)
-a / --all verbose: also list files without the git-crypt filter
(git-crypt-style listing — only when explicitly asked for)
-e only files whose blob is encrypted (original meaning)
-u only files marked for encryption with plaintext blob,
i.e. the WARNING set — pair with -f (original meaning)
-f / --fix unchanged
WARNING files are still collected unconditionally so that `-e -f` (which
hides them from display) still re-stages them. The summary is shown
whenever WARNINGs exist, regardless of display flags, so problems can't
be silently hidden.
Tests updated and added accordingly: split the old "non-filter file is
listed" tests into `test_status_default_hides_non_filter_files` (default)
and `test_status_a_flag_lists_non_filter_files` (-a), plus
`test_status_a_long_flag_alias` for --all. Rewrote `-u` and `-e` tests
for the restored semantics; `-u` now asserts the WARNING set, not
non-filter files. Setup of `bad.secret` warning files switched to
explicit `git add <paths>` because `git add .` would re-stage them
through the now-active clean filter and erase the WARNING state we're
testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`encrypted: <file>` on its own is aspirational for a file that isn't even staged yet: the file on disk is still plaintext, it just *will* be encrypted when staged. Append a dim "(untracked)" suffix in that specific case so users can distinguish prospective encryption from the actual committed state without cross-referencing `git status`. Tracked filter+encrypted files are unchanged (no suffix). WARNING files are unchanged (only ever fire for tracked files anyway). Tests: updated `test_status_shows_untracked_filter_matched_file` to assert the marker; added negative pairing `test_status_tracked_filter_file_has_no_untracked_marker` so a regression that prints the suffix on tracked files is caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two bugs in
gitveil status:git ls-fileslists only tracked files, so a freshly-created*.secret(matching afilter=git-cryptrule but not yet staged) was completely hidden..gitattributeshad nofilter=git-cryptentries, the early-returnif git_crypt_files.is_empty() { return Ok(()); }printed nothing and exited 0 — leaving the user with no feedback, even in repos wheregitveil inithadn't been run.This PR fixes both while keeping gitveil's focused default output (rather than adopting git-crypt's verbose-by-default listing). The
-e/-uflag semantics from gitveil ≤ 1.2.0 are preserved, and a new-a/--allflag opts into the verbose git-crypt-style listing.What changed
(untracked)in dim text so users can distinguish prospective encryption (file will be encrypted on staging) from the actual committed state — without having to cross-referencegit status.*** WARNING: staged/committed version is NOT ENCRYPTED! ***when a tracked filter-marked file's index blob is plaintext (typically staged before.gitattributestook effect), with a summary at the end pointing togitveil status -f.-fnow skips files deleted from the working tree (would otherwise stage the deletion), prints a diagnostic, and reportsre-staged N of M file(s). Never auto-adds untracked.git-crypt-<keyname>) too.find_git_dir()up front so running outside a git repo gives the existing cleanNot a git repositoryerror instead ofgit ls-files failed.git ls-filesparsing to NUL-delimited (-z) — filenames with whitespace/newlines no longer split incorrectly.Flag layout
gitveil status(untracked)for untracked filesgitveil status -a/--allgitveil status -egitveil status -ugitveil status -fWARNING files are collected unconditionally so
-e -fstill re-stages them — the display suppression applies to display only. The summary is shown whenever WARNINGs exist, regardless of display flags, so misconfigured files can't be silently hidden.Why focused-by-default (not git-crypt-style)
git-crypt's verbose default lists every tracked + untracked file in the repo. On the kind of large-repo workload gitveil's perf optimizations target (the README's Unity benchmark is ~4,000 files), that's thousands of
not encrypted:lines of noise. The focused default keeps the actionable signal — which files matter for encryption — and-ais one keystroke away when the verbose view is wanted.Cross-compatibility
No wire-format changes. Filter names (
git-crypt/git-crypt-<key>), key file format, and encrypted blob header are untouched.tests/cross_compat.rsstill passes.gitveil status -aoutput matchesgit-crypt statusbyte-for-byte except for the(untracked)annotation, which is a gitveil-only clarity affordance.Security
-z) parsing is safer than the previous line-based parsing: filenames containing whitespace or newlines no longer split incorrectly.-fauto-staging is scoped strictly to tracked filter+plaintext-blob files (warning_files); cannot auto-add untracked files; cannot stage the deletion of a missing tracked file.git addcalls use the--separator (no flag injection).Test plan
cargo fmt --checkcargo clippy --all-targets -- -D warningscargo test— 112 pass (34 unit + 56 integration + 16 GPG + 6 cross-compat); was 95(untracked)marker assertion intest_status_shows_untracked_filter_matched_fileand the deleted-file skip intest_status_fix_skips_file_deleted_from_working_tree.-averbose,-e(encrypted only),-u(WARNING only),-fre-encrypts,-fon deleted file skips, untracked filter file shows with(untracked)and no WARNING, uninitialized repo with filter-marked files surfaces WARNINGs, outside-git-repo gives clean error.New test coverage (all run on Linux/macOS/Windows):
test_status_shows_untracked_filter_matched_file(also asserts the(untracked)marker)test_status_tracked_filter_file_has_no_untracked_markertest_status_default_hides_non_filter_filestest_status_a_flag_lists_non_filter_filestest_status_a_long_flag_aliastest_status_warning_for_filter_with_plain_blobtest_status_no_warning_when_all_correctly_encryptedtest_status_e_flag_only_encrypted_blobstest_status_u_flag_only_warning_filestest_status_fix_restages_only_warning_filestest_status_fix_skips_file_deleted_from_working_treetest_status_excludes_gitignored_filestest_status_not_a_git_repo_clear_errortest_status_works_without_gitveil_inittest_status_handles_filename_with_spacestest_status_named_key_filterhas_git_crypt_filter_recognizes_default_and_named(unit)Documentation updates included
README.md: rewrittengitveil statussection with the new flag table, default behavior, WARNING summary, and(untracked)suffix.CONTRIBUTING.md: bumped test count to 112; expanded the status entry in the coverage list.TESTING.md: updated the manual "Test Status and Fix" walkthrough to reflect the WARNING output; added a short "Untracked file marker" subsection.Known limitations not addressed in this PR
find_git_dir()succeeds in a bare repo, thengit ls-files --othersfails with a less-clean error. Status doesn't make sense in a bare repo anyway, but the error message could be friendlier. Not regressed by this PR.🤖 Generated with Claude Code