feat: add 'gra ls --strays' to find and clean what gra does not manage - #34
Merged
Merged
Conversation
The gra root is flat, so anything can end up sitting in it: a plain 'git clone', a build directory, a file saved in the wrong terminal. No gra command has ever looked at those, which is exactly why they go unnoticed - a root here had 31 directories and 3 files that gra never created, holding 21G between them. 'gra ls --strays' is the one command that sees them. A stray is any entry directly under the root without a '.bare', listed with its size, its age, and a STATUS column: '-' when Git has never heard of it, and otherwise 'dirty' or '↑2' for a checkout that may hold the only copy of something. Names carry the ls -F markers, so a symlink's size reads as its own rather than the tree it points at. '--strays --rm' opens the same table in fzf, where Tab marks as many entries as you want. What was picked is listed back with what it costs, and one question carries it out. There is no flag that deletes every stray at once, because a flat root is exactly where the only copy of something ends up. A file that cannot go is reported and the rest of the selection still goes. fzf_table grew a multi flag and now returns the chosen keys, which is why the branch picker takes the first one. Co-Authored-By: Claude Opus 5 (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.
The gra root is flat, so anything can end up sitting in it: a plain
git clone, a build directory, a file saved in the wrong terminal. No gra command has ever looked at those, which is why they go unnoticed. On the root this was written against: 31 directories and 3 files gra never created, holding 21G.gra ls --straysA stray is any entry directly under the root that is not a repository — gra calls a directory a repository when it holds a
.bare. Names carry thels -Fmarkers:/a directory,@a symlink whoseSIZEis the link's own, not the tree it points at.STATUSis the column that decides whether something is safe to delete.-means Git has never heard of it. Anything else is a checkout,dirtywhen something is uncommitted and↑2when two commits have not been pushed — local refs only, like the rest ofgra ls. A checkout with no remote at all reads as✓ cleanand may still hold the only copy of its commits; the README says so.gra ls --strays --rmOpens the same table in
fzf, whereTabmarks as many entries as you want. What was picked is listed back with what it costs, and one question carries it out:There is no flag that deletes every stray at once. A flat root is exactly where the only copy of something ends up, so the picking stays manual. A file that cannot go — a read-only build artifact — is reported and the rest of the selection still goes, with a non-zero exit naming what stayed.
Notes
--fetchand--straysare mutually exclusive;--rmwithout--straysfails rather than guessing.fzf_tablegainedmulti=and now returnslist[str], sopick_branchtakes[0].Verification
python3 -m pytest tests/ -q→ 149 passed (11 new tests).16 semantic mutations, all killed. Each new test was verified by reversing the behaviour it covers and confirming the expected failure:
root_strayskeeps repositoriesassert 'oans' not in ...stray_statusnever reads Gitaclean/no longer ends✓ cleanremove_straysdeletes all straysbkeep/file.txtgonefzf_tabledrops--multi'--multi' not in [fzf args]remove_straysignores the answerdelete_strayfollows a symlinkls_commanddrops the--rmguardprint_strayssilent on empty root'No strays found.'missinghuman_sizerounds whole'1M' == '1.0M'relative_agedrops plural'2 hour ago' == '2 hours ago'stray_namemarks nothing / marks a link as a dir'link@'missingstray_summarylumps links with files'1 directory, 1 link, 1 file'missingdelete_strayre-raises OSErrorremove_strayssilent about failuresremove_strayscounts failures as deleted'deleted 1 stray'vsdeleted 2stray_total/stray_sizelose the unknown size'over 2.0M'and?missingNot run: the fzf picker with a real fzf (every
--rmtest drives the existing mock, as before); Python 3.10 locally, though CI covers it and the new code uses nothing past 3.10.Known costs:
du -skover the whole root is the price of theSIZEcolumn — under a second warm on 21G of strays, but a cold cache will block on disk.shutil.rmtreecan delete part of a tree before failing; the entry is then reported as failed but is already partly gone.🤖 Generated with Claude Code