jj / How jj util gc works

How jj util gc works

jj util gc deletes operations, views and Git objects that the current operation can no longer reach and that are older than a cutoff.

On this page
  1. 1. Command-line surface
  2. 3. End-to-end flow
  3. 4. Core architecture touchpoints

In short: it refuses to run unless it is at the head operation. It then asks the operation store to delete operation and view files not reachable from the current operation, then calls the commit backend's own gc with the commit index's heads. The Git backend rewrites jj's refs/jj/keep/* refs to match those heads and runs git gc; the simple backend does nothing. Anything newer than the cutoff (two weeks ago, or now with --expire=now) is kept, so another process's fresh work isn't deleted. It records no operation.

The command is cmd_util_gc. Its help text: "Run backend-dependent garbage collection." To reclaim old operations, upstream says to run jj op abandon ..<some old operation> first (source). It is the Gc variant of UtilCommand (source, dispatch).

1. Command-line surface

FlagTypeDefaultMeaning
--expirestring2 weeks"Time threshold". Only now is accepted; anything else is the error "--expire only accepts 'now'" (source, check).

Run with --at-op set to anything but @, it fails with "Cannot garbage collect from a non-head operation" (source, test). The global flags are on the CLI page.

3. End-to-end flow

jj util gc: call flow cmd_util_gc checks it is at the head operation, computes keep_newer from --expire, and loads the workspace. It calls the operation store's gc with the current operation id, which walks the reachable operations and deletes unreachable operation and view files older than keep_newer. Then it calls Backend::gc with the index's heads; for the Git backend that recreates the refs/jj/keep refs for those heads, garbage-collects the extra metadata table, and runs git gc with --prune set to keep_newer. head op? keep_newer from --expire then workspace_helper (snapshots) SimpleOpStore::gc([current op]) walk reachable ops; collect their views delete other op and view files only if mtime ≤ keep_newer Backend::gc (Git does the work) index heads = commits to keep recreate_no_gc_refs refs/jj/keep/<id> per head, drop the rest run_git_gc git gc --prune=@<keep_newer> +0000 after the op store

4. Core architecture touchpoints

PieceRole here
OpStore::gcKeeps everything reachable from the given heads plus anything newer than keep_newer, which "mitigates a risk of deleting new heads created concurrently by another process". The simple store does this by file mtime, and notes a remaining race if an existing file is renewed (source).
Index::all_heads_for_gcThe heads of every commit the index knows at this operation, which are the commits to preserve. The Git backend filters out the root commit (source).
Backend::gcThe commit backend's own garbage collection, an async trait method: "All commits reachable from heads won't be removed," and anything newer than keep_newer is kept too. The command calls it after the operation store's gc (source). The simple backend's version does nothing (source); the secret backend passes it to the backend it wraps (source).
refs/jj/keep/The refs that stop git gc from pruning commits only jj knows about. Each kept ref is named after the commit it points to (source); stale ones are deleted in one batched ref edit (source).
The git executableRun as a subprocess in the Git directory. Its output goes straight to the terminal; a TODO says it should go through the UI layer (source).
Operation logPruned but never appended to: the command starts no transaction (source). After jj op abandon ..@- and jj util gc --expire=now, the abandoned operation's file is gone (test).