Skip to content

fsfork: List forks, and prune those done with, with -l and --prune - #11

Merged
penberg merged 8 commits into
mainfrom
fsfork-list
Nov 4, 2026
Merged

penberg merged 8 commits into
mainfrom
fsfork-list

Conversation

@penberg

@penberg penberg commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Forks pile up next to the directory: turso.1, turso.2, … and their
hidden bases, with no way to tell which are done with but running
fsdiff on each. This adds two ways to deal with them, keeping no state:

$ fsfork -l
FORK                UPDATED         STATUS
../turso.1          4 days ago      unchanged
../turso.3          1 day ago       9 files only here
../.turso.2.base    2 hours ago     base only
../turso.swebench   9 minutes ago   merged

$ fsfork --prune
removed ../turso.1
removed ../.turso.2.base
fsfork: kept 1 with work only here, 1 recent

fsfork -l [dir]

Lists the forks next to the directory, found by their bases, so that a
directory merely named like one is not listed. For each:

  • UPDATED: when a file the fork changed last changed, or when the fork
    was made, if none has. The base's birth time is the directory's on macOS,
    as clonefile keeps it, so it cannot say when the fork was made.
  • STATUS:
    • unchanged: as fsdiff would say;
    • merged: the directory has the changes, so removing the fork loses
      nothing. It is merged if the fork's patch undoes in the directory
      (git apply --reverse --check), so it stays merged after the directory
      changes again;
    • N files only here: the changes to N files are only in the fork;
    • base only: the fork was removed without its base.

Those left alone longest come first.

fsfork --prune [dir]

Removes the forks -l lists as unchanged or merged, with their
bases, and bases left over. It keeps:

  • those with work only here;
  • those changed in the last hour, as one just made may have a command
    starting in it (recent);
  • any the working directory is in (in use).

It says each it removed on stdout, and how many it kept, and why, on one
line on stderr. There is no dry run: -l shows what it would remove.

Also

  • fsdiff's git module moves into the fsfork library, so that -l
    compares a fork with its base by the same rules fsdiff does.
  • Debug builds take FSFORK_TEST_LEFT_ALONE=<secs> in place of the hour,
    as a test cannot make a fork's base older; release builds ignore it.

Not done

  • Forks made elsewhere, with a path given, are not listed or pruned.
  • An agent reading in an unchanged fork for over an hour, writing nothing,
    would have it pruned under it. Build output, which is ignored, counts only
    if it makes a new directory at the top of the fork.
  • -l in a fork lists that fork's forks, not its siblings.

Testing

cargo test: conformance tests for listing (each status, a lookalike
directory, from the parent), for staying merged after the directory moves
on, for pruning (nothing within the hour; then what is removed, what is
kept and why, the working directory's fork kept), and for the options
-l and --prune refuse. Tried on real forks of this repository.

So that fsfork can compare a fork with its base too, for listing forks,
by the same rules fsdiff does.
fsfork -l lists the forks next to the directory, each with how long it
has been left alone and whether it is unchanged, applied, or has changes
the directory does not, so that those done with can be told from those
not, and removed. Bases left over from forks removed without them are
listed too.
A fork was applied only while the directory had every file it changed
just as the fork does, so it stopped being applied once the directory
changed one of them again. Now it is applied if the patch fsdiff shows
for it would undo in the directory, as it would once applied there,
whatever changed after.
fsfork --prune removes the forks of the directory that fsfork -l lists
as unchanged or applied, with their bases, and the bases left over, so
that forks need not pile up next to it. It keeps any changed in the last
hour, as one just made may have a command starting in it, and one just
applied may have more to come; any the working directory is in; and any
with changes the directory does not have. It says which it removed and
which it kept, and why.
It said a line for each fork, removed or kept, and why, in the columns
fsfork -l prints, which buried what it did among what it did not. Now
it says each it removed on stdout, as "removed ../turso.1", and then
how many it kept, and why, on one line: "fsfork: kept 2 changed, 3
recent". fsfork -l shows the rest.
As docker ps does: FORK, UPDATED and STATUS, so that each column says
what it is.
"7 files changed, 2 applied" made the reader work out what is left:
that the directory lacks the changes to 5. Now the status says just that,
"5 files not applied", and fsdiff --stat shows them all. A base with no
fork is "base only", one that cannot be compared "unknown", and
fsfork --prune keeps forks as "not applied" rather than "changed".
"5 files not applied" was patch talk, leaving the reader to work out
what it means for the fork: that removing it loses the changes to those
files, which only it has. Now it says so, "5 files only here", and a
fork whose changes the directory has is "merged". fsfork --prune keeps
forks "with work only here".
@penberg
penberg merged commit 635b2f2 into main Nov 4, 2026
2 checks passed
@penberg
penberg deleted the fsfork-list branch November 4, 2026 04:56
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