# Change ids

> Stable change-id commit headers, derivation, and grit.changeIds config.

Every commit Grit creates can carry a **`change-id`** header: an opaque token that stays with the same logical change across rewrites (amend, rebase, history edits). Commits without the header still have a **derived** id (Jujutsu-compatible) computed from the commit object id.

For CLI usage, interop, config, and revision syntax, see the [Change ids](https://grit-scm.com/docs/library/change-ids/index.md) guide.

## Header format

- Primary header: `change-id <value>`
- Legacy name: `gitbutler-change-id <value>` (read for compatibility; new writes use `change-id` only)
- Random ids use **reverse-hex**: 16 bytes encoded as 32 characters where nibble `n` maps to `'z' - n` (alphabet `k`–`z`).

[`ChangeId::from_bytes`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeId.html#method.from_bytes) and [`ChangeId::parse_reverse_hex`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeId.html#method.parse_reverse_hex) handle that encoding. Legacy values may be arbitrary strings (for example UUIDs).

## Reading and writing

On [`CommitData`](https://docs.rs/grit-lib/latest/grit_lib/objects/struct.CommitData.html):

- [`change_id`](https://docs.rs/grit-lib/latest/grit_lib/change_id/fn.change_id.html) — stored header if present
- [`set_change_id`](https://docs.rs/grit-lib/latest/grit_lib/change_id/fn.set_change_id.html) — set or replace the header before any `gpgsig` / `gpgsig-sha256` block
- [`resolved_change_id`](https://docs.rs/grit-lib/latest/grit_lib/change_id/fn.resolved_change_id.html) — stored id or [`ChangeId::derived_from_commit_id`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeId.html#method.derived_from_commit_id)

Derivation takes object-id bytes `4..20`, reverses their order, bit-reverses each byte, then reverse-hex encodes (same scheme as Jujutsu/GitButler).

## Policy and config

[`ChangeIdPolicy::from_config`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeIdPolicy.html#method.from_config) reads:

| Key | Default | Effect |
| --- | --- | --- |
| `grit.changeIds` | `true` | When false, [`id_for_new_commit`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeIdPolicy.html#method.id_for_new_commit) returns `None` |
| `grit.testing.changeId` | unset | `content-hash` derives ids from the hash of the serialized commit without change-id headers; any other non-empty value is used verbatim as the id |

Embedders inject randomness with [`ChangeIdGenerator`](https://docs.rs/grit-lib/latest/grit_lib/change_id/trait.ChangeIdGenerator.html) on [`RepositoryOptions::with_change_id_generator`](https://docs.rs/grit-lib/latest/grit_lib/config/environment/struct.RepositoryOptions.html#method.with_change_id_generator); [`Repository::change_id_generator`](https://docs.rs/grit-lib/latest/grit_lib/repo/struct.Repository.html#method.change_id_generator) exposes the active generator.

## Where ids are assigned

[`create_commit`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/commit/fn.create_commit.html) (and the CLI `grit commit` built on it) applies [`ChangeIdPolicy`](https://docs.rs/grit-lib/latest/grit_lib/change_id/struct.ChangeIdPolicy.html) before serializing: new commits, the initial commit, merge conclusions, and amends that need a stored id. Amends keep an existing `change-id` / `gitbutler-change-id` header; when the replaced tip had none, the derived id from that tip is stored.

[`prepare_commit_bytes`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/commit/fn.prepare_commit_bytes.html) and [`write_commit_object`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/commit/fn.write_commit_object.html) do **not** add change ids — stash, notes, and other auxiliary commits stay header-free unless the caller sets one.

## Revisions

After refs and abbreviated object ids, grit resolves **change ids** as revision tokens:

- **Alphabet:** reverse-hex ids use `k`–`z` (4–32 characters). A token in that range is treated as a change-id prefix, not a Git object id.
- **Lookup:** [`find_commits_by_change_id_prefix`](https://docs.rs/grit-lib/latest/grit_lib/change_id/fn.find_commits_by_change_id_prefix.html) walks commits reachable from all refs and `HEAD` (not reflogs), compares [`resolved_change_id`](https://docs.rs/grit-lib/latest/grit_lib/change_id/fn.resolved_change_id.html) on each commit, and returns a unique match or [`RevParseError::AmbiguousChangeId`](https://docs.rs/grit-lib/latest/grit_lib/revwalk/rev_parse_error/enum.RevParseError.html#variant.AmbiguousChangeId).
- **Composition:** change ids compose with the usual suffixes — `\<cid\>~1`, `\<cid\>^`, `\<cid\>..main`, peel suffixes, and path suffixes — because they plug into the same base-name resolution as branches and short oids.
- **Precedence:** a branch or tag named like a change-id token wins (for example `refs/heads/zzzz` before the change-id `zzzz…`).

Use `grit resolve`, `grit show`, `grit log`, and other commands that take revisions; no separate flag is required.

## Git compatibility

The header is an ordinary commit header line. System `git` preserves it on read/write; `git fsck --strict` accepts commits that carry `change-id`.
