Docs/CLI reference

CLI reference

tama format fold

Folds the formatters' form into the unpublished commits that wrote each file: rustfmt's for Rust files, and for any other file the form of the formatter the commit's own tree declares for its extension in .tama/formatters.json (see tama standing). The push gate (check-rust-format) refuses a push whose commits carry Rust files rustfmt would write differently, a repository's own format gate refuses its other files, the commit review charges the session that commits a file out of its formatter's form, and it refuses a commit that only reformats, so the formatting belongs inside the commits that wrote those files. tama format fold puts it there.

Folds the formatters' form into the unpublished commits that wrote each file: rustfmt's for Rust files, and for any other file the form of the formatter the commit's own tree declares for its extension in .tama/formatters.json (see tama standing). The push gate (check-rust-format) refuses a push whose commits carry Rust files rustfmt would write differently, a repository's own format gate refuses its other files, the commit review charges the session that commits a file out of its formatter's form, and it refuses a commit that only reformats, so the formatting belongs inside the commits that wrote those files. tama format fold puts it there.

#Invocation

tama format fold [--repository <PATH>] [--json]

Without --repository it folds the checkout the command runs in.

#What it does

It reads the branch the checkout has checked out, its head, and the commits between the branch's upstream and that head, oldest first. For each commit it writes out the commit's tree in the checkout's ignored build directory (.build/tama-rust-format), runs rustfmt on every Rust file the commit adds or changes, under that file's edition and the tree's own configuration, exactly as the push gate checks it, and keeps each file rustfmt wrote differently. The commit is then written again on its (possibly folded) parent with the same message, author, committer and dates, its own changes laid on that parent through a private index, and its formatted files in rustfmt's form. A commit after the first folded one is carried onto the folded parent even when it needed no formatting of its own. Commits before the first one that needed formatting keep their identities. A commit's note under refs/notes/attestation is copied to the commit that replaces it.

Every other file the commit adds or changes whose extension the commit's tree declares a formatter for is run through that formatter in the written-out tree, with the file's path appended to the declared argv; its standard output is the file's formatted text, and a file it writes differently is folded like a Rust file. A tree that declares no formatter leaves its other files as they are; a declaration that cannot be read or parsed, an entry with an empty argv, and a formatter that fails refuse the fold with the cause.

A commit whose own changes were only the form rustfmt writes, such as a commit that formatted what an earlier unpublished commit wrote, has nothing left once that formatting is folded into the earlier commit: it is dropped, with its note, and the commits after it are carried onto its parent. A commit that was empty before the fold is kept.

A merge in the unpublished range is written again when it records only published history: every parent but its first is a commit the upstream already holds, and its tree is its first parent's, as when the remote took a change on an older base and the checkout merged it back with nothing of its own. It is written on its folded first parent with the same published parents, so the published commits it merged stay merged. Any other merge refuses the fold.

Every commit the fold writes, and the commit a dropped one is folded into, carries a rewrite record: a note under refs/notes/rewritten listing, one per line, the ids of the commits it stands for and of those they stood for. Formatting changes a commit's patch, so nothing matching patches can follow it; the record lets the branch-move review (review-ref-move) and oko defects rebased follow a commit the defect register names to the commit that replaced it.

The branch moves to the folded head only from the head the fold read, through git update-ref, so Git's reference hooks see the move. A commit made meanwhile, or a hook that refuses the move, leaves the branch where it is and the folded commits unreferenced. After the move, every path the fold rewrote follows the new head in the index and in the working tree only where it still held the old content; a change someone staged or made in the working tree is kept as it is. The upstream and every published commit are never rewritten.

#Output

One line per rewritten commit, <old> -> <new>: <files>, naming the files that were formatted, or carried onto the folded parent for a commit that needed none, and <old> dropped: … for a dropped commit. When nothing needed formatting it says so and rewrites nothing. --json prints {"repository": …, "folded": [{"old", "new", "files"}]}, new being null for a dropped commit.

#Refusals and failures

Exit 0 is success, exit 1 a refusal or failure, exit 2 a usage error. Each refusal names its cause:

  • the checkout's head names no branch to fold — the head is detached.
  • <branch> has no upstream to mark what is already published — nothing says which commits are unpublished.
  • the unpublished range … holds merge <commit>, which a fold does not rewrite: it merges <parent>, which <upstream> does not hold, or …: its tree differs from its first parent's, so it carries a change of its own — the merge brings history or a change no published commit holds.
  • no formatter was run: … — Oko's ruling does not admit running build tools on this product; the ruling is quoted.
  • <branch> stays at <commit> and the folded head <commit> is left unreferenced because it moved while the fold ran, or because Git refused to move it, followed by Git's own output, such as a reference hook's refusal.
  • A manifest or edition that cannot be read, a scratch directory Git does not ignore, and a Git or rustfmt command that fails are named with their command and output, as the push gate names them.

The real journey tests/format/fold.rs runs the built tama against a published remote and a checkout with unpublished commits:

cargo test --manifest-path rust/Cargo.toml -p tama-cli --test format_fold

Return to the complete CLI command tree.