<!-- Generated by crates/swarmops-cli/src/reference.rs from the code: do not edit. Change the code, then run SWARMOPS_REGEN_REFERENCE=1 with the regen_reference test. -->

# swarmops history

List the changesets that changed a path, newest first, along first parents.

```yaml
title: "swarmops history"
description: "List the changesets that changed a path, newest first, along first parents."
group: "read"
since: "0.1.0"
mutates: false
identity_required: false
mcp_tool: "path_history"
envelope_op: "path_history"
exit_codes: [0, 2, 6]
errors: [E_PATH_NOT_FOUND, E_INVALID_ARG]
syntax: "swarmops history [OPTIONS] <REV:PATH>"
```

## Syntax

```text
swarmops history [OPTIONS] <REV:PATH>
```

## Required arguments

### `REV:PATH`

Syntax: `<REV:PATH>`.

The revision and the path (a file, or a directory), split at the first ':'.

## Optional arguments

The command also takes the [global options](../cli.md#global-options).

### `--limit`

Syntax: `-n, --limit <N>`.
Default: `50`.

The most changesets to list.

### `--cursor`

Syntax: `--cursor <CURSOR>`.

Resume a history: the previous page's cursor, exactly as it was returned.

## Examples

### CLI

```sh
swarmops --repo [tmp]/store history main:src/lib.rs
```

```json
{
  "ok": true,
  "op": "path_history",
  "api": 1,
  "result": {
    "path": "src/lib.rs",
    "entries": [
      {
        "changeset": "[c3]",
        "op": "put",
        "kind": "file",
        "id": "[id5]",
        "author_agent": "agent/worker-17",
        "created_at": "[created_at]",
        "title": "c3"
      },
      {
        "changeset": "[c1]",
        "op": "put",
        "kind": "file",
        "id": "[id6]",
        "author_agent": "agent/worker-17",
        "created_at": "[created_at]",
        "title": "c1"
      }
    ],
    "truncated": false
  },
  "meta": {
    "repo": "[tmp]/store",
    "elapsed_ms": "[elapsed_ms]",
    "resolved": {
      "main": "[c4]"
    },
    "auth": "trust"
  }
}
```

Exit code 0. Recorded by the CLI tests. A name in square brackets, such as `[tmp]` or `[c1]`, stands for what differs from run to run: the test's temporary directory, an id, a time.

### MCP

No recorded example yet.

## Details

`history` follows one path back through first parents and lists every changeset that changed
what it holds, newest first, each with what the path held right after it: the blob or tree id
and its kind, or nothing. It answers "who changed this file, and to what", including who
deleted it.

### What counts as a change

A changeset is listed when its own changes, against its first parent, touch the path:

- for a file, a new content, a new kind (a file made executable, a symlink made a file), its
  addition or its deletion;
- for a directory, any of those below it.

Each entry's `op` is `put` when something is at the path after the changeset, with its `kind`
(`file`, `exec`, `symlink` or `dir`) and its `id` (a blob id, or a tree id for a directory), and
`delete` when nothing is. An executable bit changed alone gives a `put` whose `id` did not
change, under a new `kind`. `author_agent`, `created_at` and `title` are what `log` gives for the
same changeset.

The path does not have to exist at REV. A file deleted on `main` keeps its history: the newest
entry is its deletion, the ones below its earlier versions, each `id` readable with `cat` at that
entry's changeset.

### The target

`REV:PATH` is required; the target splits at the first `:`, as `cat`'s does. One trailing `/` is
taken (`main:docs/` is `main:docs`). `REV` alone is `E_INVALID_ARG` ("names no path"), and so is
an empty path or one with a leading `/`. Paths match whole components: `main:src/lib` is not
`src/lib.rs`.

A path that no changeset on the first-parent history of REV touched is `E_PATH_NOT_FOUND`
(exit 6), on the first page only, with the hint of the `ls` of the directory it would be in.
`log --path` with the same path gives an empty page instead.

### Pages

`-n`/`--limit` (50 by default, at least 1) cuts the page; a cut page gives `cursor`, the id of its
last entry, and `--cursor` continues from that changeset's first parent. REV is not read when a
cursor is given, but the path is, so give the same `REV:PATH` on every page. A cursor at the
path's oldest change gives an empty page, not an error. A cursor is a full changeset id the
store holds; anything else is `E_INVALID_ARG`.

### Patterns

- **Blame a file.** `history main:src/lib.rs` lists who changed it and when; for one entry,
  `diff <changeset>~1 <changeset> --path src/lib.rs --format unified` gives the lines it changed.
- **Restore a deleted file.** The entry below the deletion holds the last content's blob id: read
  it with `cat <that changeset>:<path>`, or put it back with a commit whose change names the
  blob, `{"blob": "<id>"}`, which uploads nothing.
- **Watch a directory.** `history main:docs` lists the changesets that changed anything under
  `docs`, with the directory's tree id after each; two equal tree ids mean nothing below changed.
- **Through MCP**, the tool is `path_history`, with `rev` (`main` when omitted), `path`, `limit`
  and `cursor`. Ask for 100 entries or fewer a call: see
  [Result size limits](../../develop/mcp.md#result-size-limits).

### Caveats

- History follows first parents only, as `log` does: a merge's other parents are never walked,
  and what a merge brought in shows as its own change. Native histories have no merges.
- No renames: a file moved elsewhere ends in a `delete` here, and its history at the new path
  starts with a `put`.
- `history` reads a derived value, the newest changeset on the chain that touched the path,
  which the first reader to ask for it derives: a walk back over the changesets that do not touch
  the path, stored with a memo every 64 changesets of a longer walk. The first history of a path
  on a long history pays that walk. `swarmops derive` does not derive these values ahead; a store
  that cannot be written answers all the same, without keeping them. A stored value that fails
  its checks is `E_STORE_CORRUPT`, with the hint `swarmops verify --deep`, and
  [making room](../../operate/making-room.md#3-remove-the-derived-values) explains removing
  the derived values, which readers then derive again.

### Guides

- [Reading and repairing history: Follow one path](../../guides/history-and-undo.md#follow-one-path)
- [Concepts: Derived value](../../concepts.md#derived-value)
- [Known limitations: Commits, refs and history](../../known-limitations.md#commits-refs-and-history)

## Return information

`result` is a `PathHistoryPage` (`swarmops schema PathHistoryPage` prints its JSON Schema).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cursor` | `string or null` | no | When `truncated`: where the next page continues. Opaque: pass it back as given, as the next request's `cursor`. |
| `entries` | `array of PathHistoryEntry` | yes | The changesets that changed what it holds. |
| `path` | `string` | yes | The path, without a trailing `/`. |
| `truncated` | `boolean` | yes | Whether an older changeset that changed it follows this page. |

Envelope `op`: `path_history`.

Exit codes:

- `0`: Success.
- `2`: The request or the command line is malformed.
- `6`: Something the request names does not exist.

## Errors

- [`E_PATH_NOT_FOUND`](../errors.md#e_path_not_found) (exit 6): The path does not exist at that revision.
- [`E_INVALID_ARG`](../errors.md#e_invalid_arg) (exit 2): A malformed argument or request, including a command-line usage error.

Any command that opens a store may also fail with a store error, such as [`E_NOT_A_REPO`](../errors.md#e_not_a_repo), [`E_IO`](../errors.md#e_io) or [`E_STORE_CORRUPT`](../errors.md#e_store_corrupt); a command line the grammar refuses is [`E_INVALID_ARG`](../errors.md#e_invalid_arg) (exit 2).

## See also

- [Operating SwarmOps: Making room](../../operations.md#making-room)
- [`swarmops resolve`](resolve.md)
- [`swarmops cat`](cat.md)
- [`swarmops ls`](ls.md)
- [`swarmops show`](show.md)
- [`swarmops log`](log.md)
- [`swarmops diff`](diff.md)
- [CLI reference](../cli.md)
- [Error codes](../errors.md)
