Skip to content
1 change: 1 addition & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ batch document.
| agent | intentionally-different | Libra external-agent capture extension, not a Git command |
| hooks | intentionally-different | Hidden compatibility entry for hook configs installed by `libra agent enable` |
| cat-file | supported | `-e` does not support JSON |
| verify-pack | partial | validates one `.idx` file against a matching `.pack`; Git's multi-index form and `-s` / `--stat-only` are not exposed |
| index-pack | supported | hidden plumbing command |
| checkout | partial | branch compatibility surface (visible in top-level help); prefer `switch` for branches and `restore` for files. JSON/machine success output and render split supported; typed `CheckoutError` pending |
| bisect | partial | `start` / `bad` / `good` / `reset` / `skip` / `log` / `run` / `view` supported; `replay` (see [docs/improvement/compatibility/declined.md#d6-bisect-replay](docs/improvement/compatibility/declined.md#d6-bisect-replay)) / `terms` (see [docs/improvement/compatibility/declined.md#d7-bisect-terms](docs/improvement/compatibility/declined.md#d7-bisect-terms)) deferred |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Commands:
publish Materialise the read-only Cloudflare Worker template; sync/deploy are not yet implemented
agent Manage external-agent capture (Claude Code, Gemini, ...)
cat-file Provide content, type or size info for repository objects
verify-pack Validate pack index files against pack archives
checkout Branch compatibility surface; prefer 'switch' for branches and 'restore' for files
bisect Use binary search to find the commit that introduced a bug
help Print this message or the help of the given subcommand(s)
Expand Down Expand Up @@ -580,7 +581,6 @@ The following Git top-level commands are currently **not implemented** in Libra
- `fsck` – verify repository integrity
- `maintenance` – periodic maintenance tasks
- `hash-object` – compute object hash for raw data
- `verify-pack` – validate pack files
- `pack-objects` / `unpack-objects` – pack and unpack object collections
- `remote-show` – show detailed remote info
- `fetch-pack` / `push-pack` – low-level fetch/push operations
Expand Down
1 change: 1 addition & 0 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Every Libra command accepts the following global flags:
| Command | Alias | Description | Doc |
|---------|-------|-------------|-----|
| `libra cat-file` | | Inspect Git objects and AI objects by type, size, or pretty-printed content | [cat-file.md](cat-file.md) |
| `libra verify-pack` | | Validate pack index files against their pack archives | [verify-pack.md](verify-pack.md) |
| `libra show-ref` | | List local refs (branches, tags, HEAD) and their object IDs | [show-ref.md](show-ref.md) |
| `libra symbolic-ref` | | Read or update the symbolic HEAD ref | [symbolic-ref.md](symbolic-ref.md) |
| `libra index-pack` | | Build a `.idx` pack index file for an existing `.pack` archive (hidden) | [index-pack.md](index-pack.md) |
Expand Down
13 changes: 6 additions & 7 deletions docs/commands/index-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,12 @@ exposes it for three reasons:
`index-pack` to exist. Providing it means Libra can be a drop-in replacement
in CI pipelines that call plumbing commands.

### Why no `--verify`?
### Why a separate `verify-pack` command?

Git's `index-pack --verify` re-reads an existing `.idx` file and checks it
against the pack for consistency. Libra does not yet implement this because the
primary use case (generating indices) is covered, and verification can be done
by regenerating the index and comparing checksums. A dedicated `--verify` flag
is a natural future addition once there is demand from agent or CI workflows.
Git exposes verification through both `verify-pack` and some `index-pack`
workflows. Libra keeps index generation and verification separate:
`index-pack` writes an index, while [`verify-pack`](verify-pack.md) performs a
read-only consistency check between an existing `.idx` and its `.pack`.

### Why limited index versions?

Expand All @@ -144,7 +143,7 @@ algorithms.
| Build index from pack | `libra index-pack <file>` | `git index-pack <file>` | N/A (jj uses its own storage) |
| Custom output path | `-o <path>` | `-o <path>` | N/A |
| Index version | `--index-version 1\|2` (default 1) | `--index-version <N>[,<offset>]` (default 2) | N/A |
| Verify existing index | Not implemented | `--verify` | N/A |
| Verify existing index | `libra verify-pack <idx>` | `verify-pack` / `index-pack --verify` | N/A |
| `--stdin` (read pack from stdin) | Not implemented | Yes | N/A |
| `--fix-thin` (add bases for thin packs) | Not implemented | Yes | N/A |
| `--keep` (create .keep file) | Not implemented | Yes | N/A |
Expand Down
113 changes: 113 additions & 0 deletions docs/commands/verify-pack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# `libra verify-pack`

Validate a Git pack index (`.idx`) against its matching pack archive (`.pack`).

## Synopsis

```bash
libra verify-pack [OPTIONS] <IDX_FILE>
```

## Description

`libra verify-pack` is a read-only plumbing command. It parses the pack index,
decodes the corresponding pack file, and verifies that both files agree on:

- index version and structural layout
- fanout table monotonicity and object-name sorting
- index checksum
- pack checksum stored in the index trailer
- object count, object IDs, and offsets
- CRC32 values for version 2 indexes

By default the pack path is derived by replacing the index file extension with
`.pack`. Use `--pack <PACK_FILE>` when the pack archive lives elsewhere.
The command does not require a Libra repository. When run inside a repository,
it uses that repository's object format. Outside a repository, version 2 index
files infer SHA-1 vs SHA-256 from the index layout; version 1 indexes are SHA-1
only.

Compatibility note: this command currently accepts one `<IDX_FILE>` per
invocation and does not expose Git's `-s` / `--stat-only` form.

## Options

| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `<IDX_FILE>` | | Pack index file to verify | Required |
| `--pack <PATH>` | | Pack archive to verify against | `<IDX_FILE>` with `.pack` extension |
| `--verbose` | `-v` | Print each indexed object using Git-compatible verbose fields | Off |
| `--json` | | Emit a structured JSON envelope | Off |
| `--machine` | | Emit the same envelope as one compact JSON line | Off |

## Examples

```bash
libra verify-pack objects/pack/pack-abc123.idx
libra verify-pack --pack /tmp/pack-abc123.pack /tmp/pack-abc123.idx
libra verify-pack -v pack-abc123.idx
libra verify-pack pack-abc123.idx --json
```

## Human Output

Successful non-verbose verification prints one summary line:

```text
objects/pack/pack-abc123.idx: ok
```

Verbose mode prints indexed objects before the summary line using Git's base
field layout:

```text
3b18e512dba79e4c8300dd08aeb37f8e728b8dad blob 12 21 48
objects/pack/pack-abc123.idx: ok
```

The fields are `<oid> <type> <size> <size-in-pack> <offset>`. CRC32 values for
version 2 indexes are validated and remain available in structured output, but
are not printed in human verbose mode.

## Structured Output

```json
{
"ok": true,
"command": "verify-pack",
"data": {
"idx_file": "objects/pack/pack-abc123.idx",
"pack_file": "objects/pack/pack-abc123.pack",
"index_version": 2,
"object_count": 42,
"pack_hash": "0123456789abcdef0123456789abcdef01234567",
"index_hash": "89abcdef0123456789abcdef0123456789abcdef",
"verified": true
}
}
```

When `--verbose` is combined with `--json`, `data.objects[]` contains `oid`,
`object_type`, `size`, `size_in_pack`, `offset`, and optional `crc32`.

## Compatibility

| Feature | Libra | Git | jj |
|---------|-------|-----|----|
| Verify pack index | `libra verify-pack <idx>` | `git verify-pack <idx>...` | N/A |
| Verbose objects | `-v` / `--verbose` | `-v` | N/A |
| Stat-only mode | Unsupported | `-s` / `--stat-only` | N/A |
| Explicit pack path | `--pack <path>` | N/A | N/A |
| JSON output | `--json` / `--machine` | N/A | N/A |
| Version 1 index | Supported for SHA-1 repositories | Supported | N/A |
| Version 2 index | Supported | Supported | N/A |

## Error Handling

| Scenario | StableErrorCode | Exit |
|----------|-----------------|------|
| Index file cannot be opened | `LBR-IO-001` | 128 |
| Pack file cannot be opened | `LBR-IO-001` | 128 |
| Index is malformed | `LBR-REPO-002` | 128 |
| Pack is malformed | `LBR-REPO-002` | 128 |
| Index and pack disagree | `LBR-REPO-002` | 128 |
31 changes: 30 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Command Groups:
Commit And Branching commit, branch, switch, checkout, tag, merge, rebase, reset, cherry-pick, revert
Remote And Cloud remote, fetch, pull, push, open, cloud, publish
AI And Automation code, code-control, automation, usage, graph, sandbox, agent
Maintenance And Plumbing db, cat-file, rev-parse, rev-list, symbolic-ref, reflog, bisect
Maintenance And Plumbing db, cat-file, verify-pack, rev-parse, rev-list, symbolic-ref, reflog, bisect

Help Topics:
error-codes Print the stable CLI error code table (`libra help error-codes`)
Expand Down Expand Up @@ -253,6 +253,8 @@ enum Commands {
Describe(command::describe::DescribeArgs),
#[command(about = "Provide content, type or size info for repository objects")]
CatFile(command::cat_file::CatFileArgs),
#[command(about = "Validate pack index files against pack archives")]
VerifyPack(command::verify_pack::VerifyPackArgs),
Comment thread
marshawcoco marked this conversation as resolved.

#[command(about = "Record changes to the repository", alias = "ci")]
Commit(command::commit::CommitArgs),
Expand Down Expand Up @@ -719,6 +721,14 @@ impl CommandPreflight {
}
}

fn sha1_without_repo() -> Self {
Self {
storage: None,
check_schema: false,
set_hash_kind: true,
}
}

fn repo(storage: std::path::PathBuf) -> Self {
Self {
storage: Some(storage),
Expand All @@ -734,6 +744,14 @@ impl CommandPreflight {
set_hash_kind: false,
}
}

fn repo_hash_kind_without_schema_guard(storage: std::path::PathBuf) -> Self {
Self {
storage: Some(storage),
check_schema: false,
set_hash_kind: true,
}
}
}

fn command_preflight(command: &Commands) -> CliResult<CommandPreflight> {
Expand All @@ -744,6 +762,12 @@ fn command_preflight(command: &Commands) -> CliResult<CommandPreflight> {
| Commands::CodeControl(_)
| Commands::LsRemote(_)
| Commands::Sandbox(_) => Ok(CommandPreflight::none()),
Commands::VerifyPack(_) => match utils::util::try_get_storage_path(None) {
Ok(storage) => Ok(CommandPreflight::repo_hash_kind_without_schema_guard(
storage,
)),
Err(_) => Ok(CommandPreflight::sha1_without_repo()),
},
Comment thread
marshawcoco marked this conversation as resolved.
#[cfg(unix)]
Commands::Worktree(command::worktree::WorktreeArgs {
command: command::worktree::WorktreeSubcommand::Umount { .. },
Expand Down Expand Up @@ -950,6 +974,8 @@ pub async fn parse_async(args: Option<&[&str]>) -> CliResult<()> {
if preflight.set_hash_kind {
set_local_hash_kind_for_storage(storage).await?;
}
} else if preflight.set_hash_kind {
set_hash_kind(HashKind::Sha1);
}
// Resolve global output flags into a single config before dispatching.
let color = if args.no_color {
Expand Down Expand Up @@ -1033,6 +1059,9 @@ pub async fn parse_async(args: Option<&[&str]>) -> CliResult<()> {
}
Commands::Push(cmd_args) => command::push::execute_safe(cmd_args, &output).await?,
Commands::CatFile(cmd_args) => command::cat_file::execute_safe(cmd_args, &output).await?,
Commands::VerifyPack(cmd_args) => {
command::verify_pack::execute_safe(cmd_args, &output).await?
}
Commands::IndexPack(cmd_args) => command::index_pack::execute_safe(cmd_args, &output)?,
Commands::Fetch(cmd_args) => command::fetch::execute_safe(cmd_args, &output).await?,
Commands::Diff(cmd_args) => command::diff::execute_safe(cmd_args, &output).await?,
Expand Down
1 change: 1 addition & 0 deletions src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod show_ref;
pub mod symbolic_ref;
pub mod tag;
pub mod usage;
pub mod verify_pack;
#[cfg(all(unix, feature = "worktree-fuse"))]
#[path = "worktree-fuse.rs"]
pub mod worktree;
Expand Down
4 changes: 3 additions & 1 deletion src/command/rebase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2032,9 +2032,11 @@ mod tests {
internal::object::tree::{Tree, TreeItem, TreeItemMode},
};

#[cfg(unix)]
use super::path_to_index_key;
use super::{
RebaseError, ReplayErrorKind, classify_relative_to_base, collect_tree_items_and_paths,
path_to_index_key, resolve_three_way, tree_item_name,
resolve_three_way, tree_item_name,
};
use crate::utils::error::{CliError, StableErrorCode};

Expand Down
Loading
Loading