In short: jj resolves -r (nothing if it's omitted), checks the commits are mutable, and keeps
only those that are signed. In one transaction it rewrites them with the sign behavior set to drop and
reparents their descendants, which keep or lose signatures according to signing.behavior. It
prints Unsigned N commits:, warns about commits authored by someone else, and records
unsign commit <id>.
The command is cmd_unsign. It mirrors
jj sign step for step; this page focuses on the
differences. How commits are stored is on the commits page.
1. Command-line surface
| Flag | Type | Default | Meaning |
|---|---|---|---|
-r, --revision (alias --revisions) | revsets, repeatable | none | What to unsign (source). |
Without -r the list is empty, and a union of no revsets is none()
(union_all), so the command changes nothing.
2. Configuration
No setting chooses what to unsign. signing.behavior (default keep) still applies to
the rebased descendants, and user.email decides the "not authored by you" warning. Because the
targets are written with drop, no backend is consulted for them, so jj unsign
works even with signing.backend = "none"
(write_to_store).
3. End-to-end flow
Filtering to signed commits means unsigned targets aren't rewritten at all and don't appear in the output, and their descendants aren't rebased because of them (source).
4. Core architecture touchpoints
- Commit writing:
Dropmakesshould_signfalse, and the old signature is cleared before writing, so the new Git commit has no signature header (source). - Rewrites: each unsigned commit is a rewrite of the signed one; bookmarks follow and
the working copy moves if
@was a target. See transactions. - Commit model: a commit is signed when its
secure_sigis set (is_signed).
5. Transaction and operation
- Transaction: one.
- Operation description:
unsign commit <id>orunsign commit <first id> and <N> more, with the new IDs (source). - Nothing to do:
-r 'none()', or only unsigned targets, printsNothing changed.(test). jj undobrings the signed commits back.
6. Algorithm and invariants
So under the default keep, unsigning one commit in the middle of a signed stack leaves its
descendants signed: they are rewritten, but signed again (test). Unsigning
doesn't need your key, but re-signing a descendant does, so the backend must still be usable when
descendants are signed.
Invariants: afterwards no target is signed. Unsigned targets are untouched. Contents, descriptions and authors don't change.
7. Errors and edge cases
| Message | When | Source |
|---|---|---|
Warning: N of these commits are not authored by you. | Some unsigned commits have another author email. | unsign.rs, test |
| Immutable-commit error | A target is immutable, even if it isn't signed. | unsign.rs |
Nothing changed. | No signed targets. | test |
8. Worked example
From upstream's test_unsign, with behavior = "keep". All of
one, two, three and @ are signed. jj unsign -r @-
unsigns three and rebases @, which stays signed:
Upstream's output (source):
Unsigned 1 commits:
kkmpptxz 9efcace6 (empty) three
Rebased 1 descendant commits.
Working copy (@) now at: zsuskuln 654e12b9 (empty) (no description set)
Parent commit (@-) : kkmpptxz 9efcace6 (empty) three
Next, jj unsign -r ..@-- unsigns one and two and rebases three
(still unsigned) and @ (still signed) (source).