4 tiers:
supported/partial/unsupported/intentionally-differentSource of truth: top-levelCommandsvariants insrc/cli.rs.
This file declares which Git command surfaces Libra promises to support, and at what level. The four tiers have the following user-facing semantics:
| Tier | Meaning | What users should expect |
|---|---|---|
supported |
Command/flag behavior matches stock Git or is functionally equivalent | Use as you would in Git |
partial |
Command is exposed but the subcommand surface or flag set is incomplete | Common paths work; advanced paths may be missing |
unsupported |
Not implemented, no public plumbing | Use stock Git or look for an equivalent Libra command |
intentionally-different |
Behavior deliberately diverges from Git; documented | Read the migration notes before relying on it |
The tier here describes Git surface compatibility only. It does not
describe whether a command has been modernized for CLIG (--json / --machine
/ stable error codes / run_<cmd>() split); that work is tracked in
docs/development/commands/_general.md and in each command
batch document.
fetch <remote> <src>:<dst> writes the exact validated destination and an
explicit refspec overrides configuration. Without one, Libra consumes every
remote.<name>.fetch value (exact or one matching * per side), falling back
to the default refs/remotes/<name>/* branch mapping only when the key is
absent. Config variable names are case-insensitive; destinations are currently
limited to refs/heads/* and refs/remotes/<remote>/*. Multi-ref destination
updates, their reflogs, and remote HEAD are one SQLite transaction;
non-fast-forward mappings need + or --force, and a local destination checked
out by any worktree is refused. Prune follows the effective mapped destinations,
and a full fetch removes a cached remote HEAD whose default source is no longer mapped.
FETCH_HEAD records selected source
refs even when already up to date; plain fetch does not touch ORIG_HEAD.
remote update uses non-empty remotes.default when invoked without names,
then falls back to all remotes. remote rename transactionally migrates remote
config (including refspec destinations), branch upstream values, SSH keys,
tracking refs, remote HEAD, and matching reflogs. ls-remote --symref retains
its capability-derived Git output; local Libra remotes still intentionally do
not synthesize a symref.
Stdout-producing commands treat a downstream closed pipe as normal Unix
pipeline termination: they exit quietly without printing panic/backtrace or
Broken pipe diagnostics. This is enforced for the global output layer and the
large-output commands covered by compat_broken_pipe_output (log, diff,
grep, ls-files, show, for-each-ref, cat-file, and JSON emit paths).
Verbose commit --dry-run preflights every changed HEAD/staged/auto-staged
blob before loading it (32 MiB per object, 64 MiB charged aggregate, 4,096
unique objects). The full object count is rejected before batch sizing. Auto-stage reserves provisionally before reading worktree
payloads. Loose objects are streamed to an exact declared/zlib boundary;
actual reads use an explicit local-only bounded API, and loose/non-delta pack
objects reject over-limit declarations before payload decoding. Oversized delta
instructions are rejected before base traversal; accepted packed deltas validate
and charge the complete bounded base/instruction/result chain. Pack
batches receive the remaining 64 MiB aggregate budget, apply the same 4 KiB
minimum per-object charge as the preview cache, and stop probing later payloads
as soon as it is exceeded; they enumerate once and open each existing index once, never
rebuilding a missing index. Bounded packed reads use a dedicated uncached delta
decoder and move the final payload to the caller, so they neither populate the
200 MiB process-wide pack cache nor add full-payload return clones outside the
charged peak. The 256 MiB scratch quota is rooted in common
repository storage, so linked worktrees cannot obtain independent quotas.
Auto-stage treats tracked symlinks (including dangling and LFS-pattern paths) as
mode-120000 link-target blobs and never follows them in real or preview flows.
Status-template collection also fails closed with LBR-IO-001 when an enabled
stash count encounters an unreadable/non-file ref (including a symlink) or
cannot read/parse its log.
restore can remove an empty materialized gitlink directory or replace it with
a regular file, symlink, deletion result, or rebuilt conflict-marker file.
Source/index, side-selection, and --merge/--conflict=diff3 flows preflight all
selected directory transitions before writing; a non-empty directory is refused,
so nested repositories and user files are never recursively deleted or left
behind after a partial multi-path restore.
Remote/cloud commands that may rely on global tiered-storage configuration
(clone, fetch, pull, push, and cloud) fail closed with
LBR-CONFIG-001 when ~/.libra/config.db (or LIBRA_CONFIG_GLOBAL_DB) has a
schema version newer than this Libra binary supports. This prevents an older
binary from silently ignoring global storage config and falling back to local
objects. --offline and LIBRA_READ_POLICY=offline|local are the explicit
local-only escape hatch and emit a warning instead. If process environment or
repo-local vault.env.LIBRA_STORAGE_* entries fully supply the values that
storage initialization would query, the global config DB is unnecessary and the
command continues with a warning rather than LBR-CONFIG-001. The
compatibility guard is pinned by compat_global_config_schema_future.
| Command | Tier | Notes |
|---|---|---|
| init | partial | common initialization plus Git-style safe re-initialization/top-up of an existing repo (prints Reinitialized existing ..., re-creates missing layout, re-applies --shared, persists core.sharedRepository, preserves config/HEAD/refs/objects/vault/repo-id otherwise) are supported; when -b/--initial-branch is omitted for a new repo, init.defaultBranch is read local→global→system with case-insensitive variable matching, local/global encrypted values decrypted, and main fallback; empty/invalid values fail before layout with LBR-CLI-002, local/global config DB read failures use LBR-IO-001 (except a future-schema global store — newer than this binary — which is skipped with a one-time warning, see LBR-CONFIG-001), and unreadable/unsupported system scope is skipped; --from-git-repository reports the source HEAD branch instead of applying the configured default; numeric --shared=<mode> is prevalidated so non-traversable modes such as 0660 fail before a partial .libra is created; recursive submodule init is not |
| clone | partial | --depth, --single-branch/--no-single-branch (toggle; --no-single-branch countermands --single-branch, last wins, and clone fetches all branches by default so --no-single-branch alone is a no-op), --tags/--no-tags (clone fetches all tags by default like Git; --no-tags skips them and records remote.<name>.tagOpt=--no-tags, where <name> is the remote name — origin by default or the -o/--origin value), and --no-progress (suppresses the fetch "Receiving objects" progress meter during the clone, like git clone --no-progress), --no-checkout (set up objects/refs/HEAD but do not check out the working tree), and -o/--origin <name> (name the remote and its tracking refs instead of origin; standard clones only — libra+cloud clones use origin), -l/--local and --no-local (accepted no-ops — Libra never hardlinks objects (it always copies), and how it reads a local-path source is determined by the source type — a local Libra repo is read directly, a local Git repo is read in-process (no git-upload-pack dependency) — not by these flags), and --reject-shallow (fail when the clone is shallow without --depth, i.e. a shallow source — exit 128; --depth is allowed and, as a documented narrowing vs Git, suppresses the check since Libra cannot distinguish a shallow source from --depth-induced shallowness), and the object-alternates flags: --shared/-s (lore.md 2.11 — for a LOCAL Libra source, registers the source's object store as an alternate of the clone via lore.md 2.3's guarded path: borrowed reads + base gc/evict/obliterate protection; NON-FATAL on any guard/io failure — the clone still succeeds; v1 STILL COPIES every object, so this only adds the borrow link + base protection, NOT disk savings — copy-avoidance is deferred; a no-op warning for a remote/local-Git source), --no-shared (countermands --shared / a clone.shared=true default), the clone.shared config (global default; default OFF), --dissociate (forces no-share — a self-contained clone), and --reference <repo>/--reference-if-able <repo> (still accepted no-ops — Libra has no fetch-side alternate negotiation yet, so --reference copies + warns and --reference-if-able is silently ignored; use libra alternates add to borrow), and --mirror (implies --bare; maps every fetched branch verbatim into refs/heads/* and keeps tags, drops the refs/remotes/* tracking refs, and records the remote.<name>.mirror=true marker; rejected for libra+cloud://. Documented narrowings: Libra mirrors only what it fetches — refs/notes/* and other un-fetched namespaces are not mirrored, and because fetch collapses refs/mr/* into the branch tracking namespace those refs are mirrored as refs/heads/mr/*; the marker is informational since libra fetch is not yet mirror-aware, so no +refs/*:refs/* refspec is recorded), and the fetch-optimization flags --filter <spec>/--shallow-since <date>/--shallow-exclude <rev> (accepted no-ops for Git remotes — Libra has no partial-clone/promisor support and its fetch only does --depth shallow, so each is ignored with a warning and the optimization is simply not applied — the clone fetches everything those flags would trim, subject only to --depth if also given (without --depth, a complete clone, a correct superset), matching Git's own full-clone fallback when a server cannot filter; rejected for libra+cloud:// like --depth), and the dependency-filtered clone flags --deps-of <path> (repeatable) / --deps-depth-limit <N> (lore.md 3.2, intentionally-different — Git has no file-dependency concept; NOT partial-clone/--filter and NOT --sparse/D10): after a normal FULL, commit-safe checkout, scope the read-only sparse VIEW (2.2) to the forward dependency closure (3.1) of the given roots and record remote.<name>.fetchNotesDeps=true; implies --notes (the graph is fetched to compute the closure). Objects are NEVER wire-filtered — the whole pack is downloaded and the whole tree stays on disk (working-tree disk narrowing is deferred, D18); only a local Libra source can travel the graph (D17), a network/foreign-Git remote performs a full clone without scoping and warns; conflicts with --no-checkout/--bare/--mirror and is rejected for libra+cloud:// supported; --sparse unsupported (see docs/development/commands/_compatibility.md#d10-clone---sparse-与顶层-sparse-checkout-命令); --recurse-submodules unsupported (see docs/development/commands/_compatibility.md#d4-clone---recurse-submodules) |
| code | intentionally-different | Libra AI extension, not a Git command |
| code-control | intentionally-different | Libra AI automation extension, not a Git command |
| automation | intentionally-different | Libra AI automation rules/history extension, not a Git command |
| usage | intentionally-different | Libra AI provider/model usage reporting extension, not a Git command |
| graph | intentionally-different | Libra AI graph inspection extension, not a Git command; the interactive thread-graph TUI plus a global --json/--machine structured output (thread metadata + a nodes array) for agents |
| sandbox | intentionally-different | Libra AI sandbox diagnostics extension, not a Git command |
| cache | intentionally-different | Diagnostic for the tiered-storage / LRU cache (cache info): reports the resolved LIBRA_STORAGE_TYPE, whether a durable tier is active, and the LIBRA_STORAGE_THRESHOLD (small/large threshold) + LIBRA_STORAGE_CACHE_SIZE (LRU disk budget) tunables. Git has no equivalent; needs no repository. --json/--machine → { storage_type, tiered, threshold_bytes, cache_size_bytes }. Exit 0 (non-zero if a storage config value cannot be resolved, e.g. an unreadable global config DB); cache evict [--dry-run] [--max-size <bytes>] [--min-age <secs>] (lore.md 2.9): evicts verified-durable LARGE loose objects oldest-first until under the configured budget — every unlink is gated on an error-aware durability probe run immediately before it (confirmed-absent objects are skipped with a push hint; probe ERRORS are never treated as absence; 3 leading probe failures abort with nothing deleted); presence≠integrity residual documented; tiered get now self-heals a vanished local file from the durable tier; local-only repos report nothing-evictable; offline read policy refuses; also runnable as maintenance run --task cache-evict (excluded from the default task set) — and maintenance loose-objects no longer packs >=threshold cache residents under a tiered config |
| layer | intentionally-different | Lore's local-overlay primitive (lore.md 2.4) — a Libra-only extension, no Git equivalent. `layer add |
| file | intentionally-different | Object-level operations (Libra-only extension). file obliterate <oid> [--reason] [--dry-run] [--yes] / --recover (lore.md 2.5): the "保留 ADDRESS 删 PAYLOAD" compliance model — physically removes an object's PAYLOAD bytes while PRESERVING its address (referencing history stays traversable). Destructive/irreversible: dry-run default + --yes required (LBR-OBLITERATE-003); packed-only refused (LBR-OBLITERATE-002, no pack surgery); absent/unknown → LBR-OBLITERATE-001. Crash-safe state machine (tombstone fsynced BEFORE payload delete; (no row)=Live → obliterating → obliterated; --recover finishes an interrupted run). Mandatory durable append-only 0600 audit (.libra/obliteration-audit.jsonl, §7.8 — address+actor+approval+outcome, never cleartext). fsck reports obliterated objects as INTENTIONALLY-ABSENT — a diagnostic distinct from missing that never flips the exit code — across the object/tree/commit/parent/tag/index seams; fsck --heal and cloud restore never resurrect them (拒绝重建). Tombstone state in the object_obliteration SQLite side-table (owner internal::obliteration), never serialized into an object. Deferred: byte-level in-object erasure (declined), pack surgery/history rewrite (declined), §6.8 media/LFS chunk obliterate |
| sparse-view | intentionally-different | Read-only sparse VIEW filter (lore.md 2.2, Libra-only extension) — the NON-declined complement of git sparse-checkout (the materializing sparse-checkout command and clone --sparse stay declined, D10). `sparse-view set |
| alternates | intentionally-different | Object alternates (lore.md 2.3, Libra extension) — borrow objects from a shared/parent object store instead of copying. `alternates add |
| deps | intentionally-different | File dependency graph (lore.md 3.1, Libra extension — Git has no file-dependency concept). `deps add |
| media | intentionally-different | FastCDC LFS media chunking CLIENT (lore.md §6, feature-gated fastcdc, default OFF — Libra media extension, Git has no equivalent; absent from the default binary). The honest v1 client substrate with NO live media server: media chunk content-defines chunks of a file (in-tree deterministic gear-hash FastCDC, frozen fastcdc-v1 params) and builds a versioned manifest; --store persists chunks + manifest to a private .libra/media/ store that is a physical SIBLING of objects/ (a chunk is NEVER a Git object ID, §6.2). media inspect validates a manifest; media verify reassembles from the store and verifies the full media_oid before publishing (never a corrupt file). media probe capability-probes a remote (libra/media/v1/capabilities, §0.2 backoff) and reports the §6.4 transfer decision — chunked vs a SAFE standard-LFS fallback (no endpoint / disabled / incompatible / unknown-higher-version / 5xx-after-backoff → standard LFS; server-refuses-fallback + no-local-fallback → BLOCK, never a chunk-only half-write). media_oid is ALWAYS SHA-256 of the full content (independent of core.objectformat), byte-identical to a standard LFS pointer OID. --json on all. DEFERRED / FROZEN (the Libra-aware media SERVER, lore.md §6.5–6.8): real cross-machine chunked upload/download, capability + chunk + manifest-finalize endpoints, the Pending→Finalized lifecycle + GC/fsck/heal, every §6.7 anti-side-channel guarantee, and chunk-only repo policy — so against every reachable remote today the probe falls back to standard Git LFS |
| hydrate | intentionally-different | On-demand whole-object hydration (lore.md 3.3, Libra extension — the honest v1 of Lore's "hydrating VFS": an EXPLICIT libra hydrate <path>... command, NOT a transparent FUSE-on-access filesystem, which stays a worktree-fuse follow-up). Materializes a path's content (and, by default, its transitive forward deps via 3.1's closure — --no-deps/--depth-limit bound it) into the working tree, resolving each blob local → alternate (2.3) → remote durable tier; the read policy is honored (--offline/--local refuse a remote fetch). FAILURE-RECOVERY contract: each blob is OID-verified on a borrowed/remote hit (--verify also re-hashes the local path, healing on mismatch) and published via an atomic temp-write + rename, so a hydration that fails for ANY reason (missing object, unreachable remote, verify mismatch, interruption) leaves the pre-existing file UNTOUCHED — never a truncated/half-written file. An active sparse view (2.2) gates the FULL set (roots AND deps); out-of-view paths are refused unless --ignore-sparse. Already-present (byte-identical) content is a no-op skip; --dry-run writes nothing; --fail-fast/--json. Whole-object only (NO FastCDC byte-range). Cross-machine dep expansion now works once the graph is fetched via 3.2's fetch/pull --notes (local Libra source; network deferred, D17). Deferred: LFS-pointer blobs (their download is not yet atomic — skipped cleanly), symlink/gitlink entries, and transparent FUSE on-access hydration |
| logfile | intentionally-different | Diagnostic for the tracing log-file sink (logfile info): reports the env-resolved path, rolling strategy (LIBRA_LOG_ROTATION), filter, and size; also enables tracing-appender time-rolled logs. Git has no equivalent (mirrors Lore's logfile); needs no repository. --json/--machine → { enabled, file, rotation, filter, size_bytes, file_count }. Rotation splits logs by time (each file bounded); it does not prune old files, so total disk use needs external retention. Exit 0 |
| completions | intentionally-different | Shell completion generator (bash/zsh/fish/powershell/elvish) built from the live clap command tree; Git ships completions via contrib/completion rather than a git completions subcommand. Prints the script to stdout (--json/--machine wraps it as { shell, script }); needs no repository. Exit 0 / 129 (unknown or missing shell, Git-style usage error) |
| add | partial | shared pathspec engine for pathspecs (plain prefix, wildcard with literal path/directory-prefix fallback for bracket-like names, :(top)/:/, :(glob), :(literal), :(icase), :(exclude)/:!/:^), -A/-u/--refresh, -f, -n/--dry-run, --ignore-errors, --pathspec-from-file/--pathspec-file-nul, `--chmod=(+ |
| apply | partial | --check only (MVP): validates a unified-diff patch (single or multi-file, new/modify/delete, git-style or plain) against the working tree without writing, via the same diffy engine as merge; -p<n> path strip (default 1), patch from files or stdin, 64 MiB cap, --json/--machine. Target paths that are absolute, contain empty/./.. components or NUL, escape the worktree, point inside .libra/, or traverse an existing symlink component are rejected. Exit 0 (applies) / 1 (does not apply) / 128 (no repo, missing --check, malformed/oversized patch, unsafe path). Actually writing the patch (no --check), --index/--cached, --3way, --reverse, --unidiff-zero, and binary/rename/mode hunks are not yet supported |
| am | partial | Minimal plain-text mail sequencer: am <patch>..., --continue, --skip, --abort, JSON/machine; preserves subject/body, author, and RFC 2822 date while creating one commit per mail. UTF-8 single-part 7bit/8bit/binary/quoted-printable/base64 and UTF-8/US-ASCII RFC 2047 B/Q headers are accepted through the shared mailinfo parser; [PATCH ...] and in-body From: are handled. New/modify/delete text diffs use the shared safe apply engine (64 MiB aggregate/10,000 mails; absolute/empty/./../NUL/.libra/symlink targets rejected; existing permissions retained). New series require a clean tracked state and reject existing non-index target collisions, including ignored paths. Unified sequencer state is written before worktree mutation; each commit atomically updates branch+reflog+next state, --continue retries a pristine mail after a clean-window interruption, resume/skip pin the expected branch tip, and abort restores the original tip/index/tracked worktree plus pre-stage new-file remnants. Deferred: stdin/multi-message mbox, multipart/attachments, binary/rename/mode-only, --3way, applypatch hooks, and wider Git flags (P2-03) |
| mailinfo | partial | Minimal repo-independent mail plumbing: mailinfo <msg> <patch> < mail reads one bounded UTF-8 single-part text mail, decodes 7bit/8bit/binary/quoted-printable/base64 plus UTF-8/US-ASCII RFC 2047 B/Q headers, applies [PATCH ...] and in-body From: cleanup shared with am, prints Git-shaped Author/Email/Subject/Date metadata, and atomically replaces body-only MSG plus separator-through-signature PATCH outputs after staging both payloads. JSON/machine and quiet are supported; parent aliases cannot make the outputs collide. Deferred: Git's wider flags, MIME/attachments, non-UTF-8 charsets, binary/non-diff --git patches, and multi-message mbox |
| revision | intentionally-different | Revision ordinal index (lore.md 1.16, porting Lore's revision find number): per-branch FIRST-PARENT chains numbered 1(root)..N(tip) in a rebuildable SQLite side table; deterministic (a pure function of the tip; rebuilds reproduce identical rows — test-pinned); freshness validated on EVERY read in the same txn as the lookup (fingerprint = tip OID + refs/replace digest; fast-forward appends without renumbering, rewrites/replace changes rebuild — a stale index never answers); commits only reachable through non-first parents have no ordinal (explicit miss, never invented). find --number / number <commitish> / index [--rebuild] (+prune of deleted refs). Nearest Git analogues: git rev-list --first-parent --count, <tip>~<k>. Exit 0 / 1 miss / 128 / 129 |
| rm | partial | shared pathspec engine for remove selection (plain prefix, wildcard with literal path/directory-prefix fallback for bracket-like names, :(top)/:/, :(glob), :(literal), :(icase), :(exclude)/:!/:^), --force / --dry-run / --cached / --recursive / --ignore-unmatch / --pathspec-from-file / --pathspec-file-nul supported; --sparse accepted as a no-op (no sparse-checkout cone); per-command --quiet not exposed (use global --quiet) |
| metadata | intentionally-different | Branch/repo metadata KV extension (lore.md 1.5 — the foundation for branch protect/archive/lineage; nearest Git analogue git config branch.<name>.*): get/set/unset(alias clear)/list with required exclusive scope --branch <name> (unified metadata_kv table; follows the branch through rename/copy/delete) or --repo (the config store's metadata.* namespace — intended dual surface with libra config; encrypted values render <REDACTED>; multi-valued keys refused with an unset-all hint). protect/archive are recorded but NOT yet enforced (stderr notice; enforcement lands in the branch-policy layer, fail-closed reads). Local-only — never pushed/pulled/published. Key ≤256B, value ≤1MiB, empty value legal. Revision scope --revision <rev> (lore.md 1.10): reads merge the commit's immutable trailer block (the 1.9 Git-faithful parser) with a mutable notes layer under refs/notes/metadata (one bounded JSON doc per commit; notes win; `source: note |
| mv | partial | -k / --skip-errors supported; --sparse accepted as a no-op because Libra does not maintain sparse-checkout state; case-only renames (mv Foo foo, lore.md 1.14) are first-class in every mode — same-inode + fold-equal classification (device+inode on Unix, never canonicalize — macOS returns the queried casing), no --force needed, the force-remove branch is bypassed (it deleted the source's own inode on case-insensitive FS: the data-loss class the row exists to prevent), direct rename first (atomic case change on APFS/NTFS) with a two-step temp fallback; case-only DIRECTORY renames route correctly instead of nesting dir/Dir |
| restore | partial | --source, --staged, --worktree, path restore with shared pathspec magic (plain prefix, wildcard with literal path/directory-prefix fallback for bracket-like names, :(top)/:/, :(glob), :(literal), :(icase), :(exclude)/:!/:^) including symlink tree/index entries restored as real symlinks on supported platforms (link target bytes are not followed), --pathspec-from-file/--pathspec-file-nul, --overlay/--no-overlay (real toggle, last wins — overlay only creates/updates source paths and never removes tracked paths absent from the source; default no-overlay removes them), and --no-progress (accepted no-op — Libra's restore renders no progress meter) are supported; unsupported platforms fail closed with an explicit symlink diagnostic rather than writing a regular file; conflict-stage restore --ours/-2 and --theirs/-3 write the chosen merge stage to the working tree only (index left unmerged), a plain restore over an unmerged path is refused (LBR-CONFLICT-001, exit 128), and --ignore-unmerged skips unmerged paths; a modify/delete conflict (the requested side deleted the file — its stage absent) removes the working-tree file and exits 0 in the default no-overlay mode (restoring a deletion means deleting; matches git restore), while --overlay instead errors does not have our/their version (exit 128); mid-rebase --ours/--theirs follow Git's swap (stages read verbatim, so --ours = the branch rebased onto / new base, --theirs = the commit being replayed), whereas merge/cherry-pick keep ours = HEAD / theirs = incoming; --ours/--theirs operate only on unmerged paths — a non-conflicted pathspec is skipped (or PathspecNotMatched when every pathspec is non-conflicted), and Libra intentionally does not fall through to Git's stage-0 (index) restore for such paths so a dirty file is never silently reverted; --merge (and `--conflict=merge |
| status | supported | common Git status surface plus <pathspec>... filtering (shared pathspec engine: plain prefixes, default wildcards, :(top), :(exclude), :(icase), :(literal), :(glob)), --porcelain v1/v2, -u/--untracked-files (no/normal/all; bare = all, short attached values -uno/-uall/-unormal), --short, --long (explicit default; conflicts with --short/--porcelain), --branch/-b (+--no-branch), --show-stash/--no-show-stash, the status.* display config defaults (`status.showUntrackedFiles=no |
| clean | partial | -n, -f, -d, -x, -X, -e/--exclude, and <pathspec>... positional filtering are supported; -i is intentionally different/not exposed |
| stash | partial | push / pop / list / apply / drop / show / branch / clear supported; stash show supports the file-level summary (--name-only / --name-status) and -p/--patch (unified diff of the stashed changes, via the shared diff engine). stash push supports -m, -u / --include-untracked (with --no-include-untracked to countermand it; last one wins, and untracked files are excluded by default so --no-include-untracked alone is a no-op), -a / --all, -k/--keep-index, and -- <pathspec> (stash only the changes to the named paths/dirs — file or directory, . selects the whole tree — leaving the rest of the working tree intact; a pathspec matching nothing tracked is LBR-CLI-003; -u/-a/-k cannot be combined with a pathspec — LBR-CLI-002); included untracked/ignored files are stored in a third stash parent and restored by apply / pop. apply / pop perform the three-way restore against the CURRENT working tree (not HEAD), so unrelated uncommitted changes — including the paths a pathspec push left behind — are preserved; by default they leave the current index intact, so restored tracked changes appear as unstaged working-tree changes. Deferred: pop/apply --index, create, and store (see docs/development/commands/_compatibility.md#d8-stash-create and #d9-stash-store) |
| lfs | partial | built-in Libra LFS command; reads Git/Libra attributes sources for filter=lfs (core.attributesFile, per-directory .gitattributes, .libra_attributes, and .git/info/attributes) while track/untrack write the root .libra_attributes; it still does not implement Git LFS smudge/clean filters/hooks (see docs/development/commands/_compatibility.md#d5-git-lfs-gitattributes-filter--hooks-bridge); lfs.lockEnforce (lore.md 2.8, Libra extension — git-lfs has no equivalent config): opt-in off(default) |
| ls-files | partial | default cached listing plus --cached/-c, --deleted/-d, --modified/-m (tracked symlinks are inspected as symlinks, so dangling symlinks are not deleted and target-byte changes are modified), --stage / -s, --abbrev[=<n>] (fixed-length object-name abbreviation in -s/--stage output; bare = 7), --others/-o, --exclude-standard (standard Git/Libra ignore sources: .gitignore, .git/info/exclude, core.excludesFile, and .libraignore), -i/--ignored (list only the ignored set — -i -o ignored untracked files, -i -c tracked files matching an exclude pattern; requires -o/-c and an exclude source — --exclude-standard or an explicit -x/-X pattern — else exit 128, matching Git), <pathspec>... (shared pathspec engine: plain prefixes, default wildcards, :(top), :(exclude), :(icase), :(literal), :(glob)), --error-unmatch (unmatched positive pathspec exits 1 with LBR-CLI-003), -z text output, status tags -t (H/R/C/?/M), unmerged-only -u/--unmerged, --full-name (accepted no-op; Libra always prints repo-root-relative paths), explicit exclude sources -x/--exclude <pattern> and -X/--exclude-from <file> (gitignore syntax; filter the --others listing and count toward the -i ignored set), --eol (prefix each cached entry with i/<eol> w/<eol> attr/<attr> line-ending info — lf/crlf/mixed/none/-text for the index blob and the worktree file, byte-compatible with git ls-files --eol; attr/ is always empty because line-ending attribute reporting is not implemented), and JSON/machine output supported; pathspecs resolve from the caller's CWD (:(top) anchors at repo root) and pathspecs outside the repo are rejected; resolve-undo and sparse-checkout integration are not exposed |
| log | partial | common Git log surface plus the named --pretty/--format presets oneline/short/full/fuller/reference/raw (medium is the default with a full commit id) and custom placeholders including %H/%h, %P/%p, %s/%f, %b/%B, %n, ASCII/control %xNN, %%, %an/%ae/%ad/%aI/%at, %cn/%ce/%cd/%cI/%ct, %d/%D, %m, and common %C... color placeholders (unknown placeholders remain literal; %C(always,...)/%Creset follow Git color-policy behavior), plus -z/--null NUL separators for log records and --name-only/--name-status path fields, --range revision expressions, --all, --reverse, --author-date-order (sort by author date instead of committer date; timestamp-only, no topological constraint), --date-order (accepted no-op; selects the default committer-date order, conflicts with --author-date-order), --no-expand-tabs (accepted no-op; Libra never expands tabs in commit messages), --no-notes (accepted no-op; Libra's log never displays notes inline), --no-mailmap (accepted no-op; Libra's log never applies a mailmap), --no-show-signature (accepted no-op; Libra's log never displays commit signatures inline), --follow, -L, --parents/--children (append parent / in-range child ids after each commit hash), -i/--regexp-ignore-case + --invert-grep (case-insensitive / inverted --grep), --patch-with-stat (diffstat block then patch, Git's synonym for -p --stat; an explicit -p --stat likewise shows both), and positional revision-range syntax (log A..B / A...B / ^A / a bare <rev>, followed by optional pathspecs; a bare name that is both a revision and a path is rejected as ambiguous — use --range) supported; format.pretty/log.date/log.follow use strict local→global→system defaults with matching CLI overrides (--no-follow included), fail-closed invalid/read errors, single-existing-file human+JSON follow while directory filters remain unchanged, and repo-root path normalization; high-bit raw %x80..%xff byte emission, --expand-tabs[=<n>] tab expansion, --show-signature signature display, and exact function-range tracking remain partial; Libra extensions (lore.md 1.9): --trailer <KEY[=VALUE]> (repeatable AND filter over the commit's Git-qualifying trailer block — key ASCII case-insensitive, =VALUE exact on the unfolded value; Git has no such flag, nearest is a fragile --grep='^Key: ') and --only-trailers (show only each commit's trailer block, key-filtered when combined with --trailer; nearest Git equivalent --pretty='%(trailers)', whose placeholder is not yet implemented); --json log gains an additive trailers: [{key,value}] field (empty array when none; body unchanged) |
| service | intentionally-different | Headless local service (lore.md 1.11): run (foreground; --host restricted to literal loopback IPs at parse AND bind time — no outward TCP port ever; --port 0 default with the real address published in .libra/service/service.json; single instance via lock file; Ctrl-C/SIGTERM graceful shutdown), status (pid/URL/health; exit 1 when not running), events (SSE tail, NDJSON under --json). Notification v1: {seq,type,at,data} envelopes, at-most-once (resync event on lag, seq restarts with the service; durable facts live in SQLite). Endpoints: /api/health (loopback), /api/service/events + /api/service/dirty/mark + /api/service/notify (loopback + 0600 token X-Libra-Service-Token; 256KiB body cap; marks go through the validated DirtyCache owner API — repo-escaping batches refused). Git has no equivalent (git daemon is the network wire protocol — the opposite). Exit 0 / 1 / 128 / 129 |
| shortlog | partial | basic author summary, email, count sorting, time filters, single revision, -c/--committer grouping, --group=author/--group=committer/--group=trailer:<key> (group by a commit-message trailer value), --merges/--no-merges (mutually overriding), --top/--min-count/--reverse, --author filtering, and -w[<width>[,<indent1>[,<indent2>]]] subject wrapping (defaults 76/6/9; width 0 indents without wrapping), and --format <format> (render each commit line with a custom template — the same %-placeholder subset as log --format — instead of the subject), and stdin pipe input (`git log |
| show | partial | object/commit display, --name-only, --name-status, --stat, --patch-with-stat (the diffstat block followed by the full patch — Git's legacy synonym for -p --stat; reuses the --stat diffstat), --summary (condensed create/delete file mode summary, like diff --summary; created/deleted files only), --oneline, --pretty / --format, --abbrev-commit/--no-abbrev-commit (toggle the header hash abbreviation; --no-abbrev-commit countermands --abbrev-commit, last wins, and the full hash is the default so --no-abbrev-commit alone is a no-op), --no-expand-tabs/--no-notes/--no-mailmap/--no-show-signature (accepted no-ops — Libra's show expands no tabs, displays no notes inline, applies no mailmap, and never displays commit signatures inline), the named pretty presets short/full/fuller/reference/raw (rendered distinctly, matching Git's preset structure; medium is the default), --raw (the raw :<old-mode> <new-mode> <old-sha> <new-sha> <status>\t<path> diff format, ids abbreviated to 7), and path filters supported; format.pretty and log.date provide strict local→global→system human-output defaults, with --oneline/--pretty/--format and the new --date winning; JSON is config-immune; the positive --expand-tabs/--notes/--mailmap/--show-signature are not separately rendered/exposed |
| show-ref | supported | branch/tag/HEAD listing, --heads / --branches, --hash[=<n>] / --no-hash, --abbrev[=<n>] / --no-abbrev, --dereference / --no-dereference (annotated tags add refs/tags/<name>^{} recursively peeled to their target), --verify / --no-verify, --exists / --no-exists, --head / --no-head, and --exclude-existing[=<pattern>] supported |
| for-each-ref | partial | --heads / --tags / --remotes / --all / --format / --sort (refname/objectname/version:refname/committerdate/authordate/creatordate — date keys peel annotated tags to the commit; creatordate uses an annotated tag's tagger date; each reversible) / objectsize (sort by the ref object's byte size, with the %(objectsize) atom) / *objectname / *objecttype / *objectsize (an annotated tag's dereferenced object id / type / byte size, with the matching %(*…) atoms; empty deref sorts first) / --count / --points-at / --contains / --no-contains / --merged / --no-merged / --exclude / <pattern> and --shell/--perl/--python/--tcl output quoting modes, and the %(*objecttype) / %(*objectsize) deref atoms (the dereferenced object's type/byte size for an annotated tag; empty for non-tag refs) and the %(align:<width>[,<position>])…%(end) alignment block (pads to a column width — left/right/middle; no truncation; nestable) and the `%(if[:equals |
| ls-remote | partial | heads/tags/refs filtering, patterns, --get-url, --sort=refname/version:refname, --exit-code, and --symref supported; --symref prefers advertised capabilities and falls back to resolving HEAD from advertised branch tips for transports such as local Libra |
| ls-tree | partial | Commit/tree listing, recursive listing, current-directory-relative path prefix filters, --full-name, --full-tree, REV:path tree-ish syntax, JSON, common output flags, and partial --format atom support are supported; full Git pathspec magic remains incomplete |
| symbolic-ref | partial | Supports local HEAD only; other symbolic refs are rejected because Libra stores refs in SQLite |
| branch | partial | create/list/delete/rename/upstream set+unset/current/contains filters, --points-at (annotated tag/full tag refs recursively peel to the target commit), --merged/--no-merged, --sort (refname/version:refname/committerdate/creatordate/authordate — date keys sort by the tip commit's committer date, or its author date for authordate — and objectsize (the tip object's byte size) and objectname (the tip commit's object id); each reversible with a leading -), --ignore-case, -c/-C/--copy (copy a branch and its upstream config, keeping the source; -C overwrites), `--column[=<always |
| bundle | partial | create <file> [<rev>...] accepts explicit revisions plus --all/--branches/--tags, writes a bounded full Git v2 bundle through a private synced temp file, preserves annotated tag-object heads, and packs their reachable closure with the repository hash kind. verify checks header, prerequisites, PACK v2, and the complete trailer checksum; list-heads reads advertised heads; unbundle validates and installs a SHA-1/SHA-256 pack+index pair, verifies an already-installed pair on repeat, prints heads, and intentionally does not update refs (matching Git). System Git can clone the result. Exit 0 / 1 (verify/list-heads invalid/unreadable/missing prerequisite) / 128 (create/unbundle/repository/IO); input/output cap 1 GiB. Prerequisite/thin/incremental create, Libra clone-from-bundle, and exhaustive verify entry decoding are deferred |
| tag | partial | lightweight tags, message-based annotated tags (via -m/-F), -F/--file (annotated message from a file or stdin), force, delete, list, -n, --points-at <object>, --contains/--no-contains, --merged/--no-merged, --sort, --column[=<options>] (comma/space-separated always/auto/never + column/row/plain (fill order; plain = one column) + dense/nodense (column widths); column-major + nodense by default, laid out by terminal display width, byte-compatible with git tag --column; --no-column countermands it — equivalent to --column=never, last one wins, and tags list one-per-line by default so --no-column alone is a no-op), vault-PGP -s/--sign (with --no-sign to countermand it; last one wins, and tags are unsigned by default so --no-sign alone is a no-op), -v/--verify, and -e/--edit (compose or edit the annotated-tag message in an editor; comments stripped, an empty result aborts) supported; -u and Git GPG interoperability are not exposed; tag.sort config default honored (strict cascade, --sort wins; a configured sort never turns creation into a listing; unset lists refname-ascending matching Git — previously insertion order; invalid value fails closed LBR-CLI-002 / unreadable store LBR-IO-001 before output; repeated values apply only the last of the winning scope — Git stacks them) |
| commit | partial | common Git commit surface plus --date (author date; overrides GIT_AUTHOR_DATE), GIT_AUTHOR_* / GIT_COMMITTER_* identity and date env overrides (Git env wins over config unless user.useConfigOnly=true; existing LIBRA_COMMITTER_* remains a lower-priority fallback), --cleanup, --dry-run (no message required; skips hook/editor, rerere, and post-commit automation side effects), --fixup, --squash, -C/-c (reuse message AND author metadata), --trailer, --reset-author, -e/--edit (open the editor even with -m/-F/-C; bare non-dry-run commit opens it too), -v/--verbose (staged diff in the editor template, stripped at the scissors line so it never enters the message; dry-run prints it directly), --porcelain (machine-readable status v1 preview of the would-be-committed state; like Git it implies --dry-run and does not create the commit; inert under --json), and commit.status / --status / --no-status (status is included by default; only an applicable editor/stripping-cleanup path reads the strict local→global→system Git boolean, with the last explicit CLI toggle overriding/bypassing it and invalid/unreadable config failing before auto-stage; -m, dry-run/porcelain, JSON, and non-stripping cleanup bypass the key; status is seeded only when an editor opens and cleanup strips comments, so verbatim/whitespace/scissors never leak it; --dry-run -a uses an isolated task-local index and persists no temporary auto-stage blob/LFS/tree object, so the live index is never replaced; verbose preview budgets changed HEAD/already-staged/auto-staged diff blobs before loading (32 MiB per blob, 64 MiB charged total, 4,096 objects) and repository scratch is capped at 256 MiB across runs; changed blobs without a bounded constant-memory local preflight (including remote-only or pack-without-index) are refused before loading and previews never rebuild pack indexes; real LFS auto-stage atomically replaces stale backups and honors --sync-data staging/destination durability, including write-through replacement on Windows), and the commit.cleanup/commit.verbose config keys (the default cleanup mode / verbose flag when the CLI flag is unset; an explicit --cleanup/-v overrides the config; config cascade local→global; an invalid value is fatal) supported; commit.verbose is on/off only (a bool-or-int value enables verbose when non-zero, but Libra's -v has no verbosity level — commit.verbose=2 behaves like true, with no -vv/unstaged-diff rendering — and there is no --no-verbose to force verbose off for a single commit); -t/--template (use FILE as the initial message — seeds the editor, or used directly with --no-edit; falls back to the commit.template config; ignored when a message source is given; an unedited template aborts the commit), and --no-gpg-sign (force an unsigned commit — skips Libra's vault GPG signing for this commit; vault signing runs when vault.signing=true (the init default) and a vault unseal key is available, so this is a no-op only when signing would not have happened anyway) supported; clean --amend --no-edit rewrites HEAD and refreshes the committer date even when tree/parents/author/message are unchanged, so it never prints a successful amend summary for an unchanged ref; -S/--gpg-sign (force-sign; Libra's commit signing is instead driven by the vault.signing config) and --allow-empty-message not yet exposed (D-empty-message); trailer-writer fixes (lore.md 1.9): -s combined with --trailer now forms ONE Git-parseable trailer block (previously two paragraphs), --trailer always separates from the body with a blank line, and --cleanup=strip/default now collapses consecutive blank lines instead of deleting every interior blank (Git-faithful — multi-paragraph messages and user-typed trailer blocks survive) |
| switch | partial | - previous-checkout target (worktree-scoped HEAD reflog; local branch/current tip and detached/full-OID toggling; missing/deleted/corrupt target fails closed), -C/--force-create, --orphan (unborn branch: preserves index/worktree, first user commit has no parents, existing branch names fail closed), --detach, --track, -f/--force (alias --discard-changes; proceed despite local changes, discarding them when switching to a different commit), --guess/--no-guess (DWIM remote-tracking guess; default-on via checkout.guess, checkout.defaultRemote tie-break), and --no-progress (accepted no-op — Libra's switch renders no progress meter) supported; merge/conflict/submodule flags not exposed; case-collision preflight (lore.md 1.14): materializing a tree with fold-colliding paths on a case-insensitive view refuses atomically BEFORE the HEAD update and any worktree write under core.casehandling=error (LBR-CASE-001, groups listed), warns-and-proceeds under warn (git parity — git warns and clobbers), silent under allow |
| rebase | partial | --onto <newbase> [<upstream>] [<branch>], --autosquash, explicit --reapply-cherry-picks, and non-interactive P1-07 controls are supported. --autostash / --no-autostash preserve tracked index/worktree changes in an fsynced held object across success, conflict, continue, skip, and abort, restoring the staged index and unstaged worktree as separate three-way layers; a restore conflict promotes the object to the normal stash list. Repeatable --exec <cmd> runs after each replayed commit under a required workspace-write, network-denied sandbox; non-zero/timeout stops with LBR-CONFLICT-002, --continue retries, and --skip keeps the applied commit while skipping its remaining exec commands. --update-refs / --no-update-refs atomically retarget other local branches in the rewritten range using captured-tip checks, including autosquash/empty/skip mappings, while excluding branches checked out in any worktree. --fork-point / --no-fork-point select the most specific upstream-reflog tip still ancestral to HEAD, with merge-base fallback. Toggle pairs are last-wins. --no-rerere-autoupdate remains an accepted no-op (rerere is auto-integrated when enabled; staging follows rerere.autoUpdate); --keep-empty (default no-op), --no-keep-empty, and `--empty=<drop |
| merge | partial | fast-forward and single-head three-way merge supported; P1-07b adds -s ours (two parents, complete current tree), repeatable last-wins -X ours/theirs (favor only conflicting regions while retaining clean changes), --allow-unrelated-histories (virtual empty base persisted safely across conflict restart/continue), and --log[=<n>]/--no-log (CLI override of merge.log, explicit -m --log supported, resolved message persisted across continue); -m <msg>, --ff-only, --no-ff, --squash, --no-commit, --no-edit (accepted no-op; Libra never opens an editor for merge), --stat/-n/--no-stat (last-wins toggle; --stat prints a post-merge diffstat of the merge's changes, the default is no diffstat), --no-progress (accepted no-op; Libra's merge renders no progress meter), --verify-signatures (verify the merged tip's PGP signature against the local vault key — like tag -v, only signatures made by this repository's vault key can be validated; no external keyring) / --no-verify-signatures (the default; toggle pair, last wins), --no-rerere-autoupdate (accepted no-op — never auto-stages replayed resolutions; rerere IS auto-integrated when rerere.enabled is set, with staging following rerere.autoUpdate), and --no-gpg-sign (accepted no-op; Libra's merge never signs the merge commit) supported; merge.conflictStyle supports merge/diff3 and fails closed on unknown values; Libra extensions: --dry-run (zero-write preview; exit 0 clean, exit 1 would-conflict) and --restart (abort then re-run the recorded target, replaying recovery-critical unrelated-history permission but not presentation/policy options); octopus, strategies other than ours, strategy options other than ours/theirs, --rerere-autoupdate, and -S/--gpg-sign deferred; --autostash/--no-autostash + merge.autostash hold tracked dirty state outside stash list across conflicts/restart and separately restore staged index + unstaged worktree, promoting a conflicting re-apply into the stash list; JSON adds `autostash: applied |
| merge-base | partial | Prints the best common ancestor of two commits — a true LCA (a common ancestor that is not a strict ancestor of another), shared with diff A...B via internal/merge_base.rs; --all prints every lowest common ancestor, --is-ancestor tests ancestry (exit 0/1), --json/--machine supported. Exit 0 (base found / ancestry holds) / 1 (no common ancestor / not an ancestor; no output, matching Git) / 128 (unresolvable commit or wrong arg count). More than two commits, --octopus/--independent/--fork-point not exposed. rebase now computes its merge base through this shared LCA (no longer a first-found walk), and log A...B excludes the reachable-set intersection (correct for multiple merge bases) — the consolidation is complete |
| merge-file | partial | File-level three-way merge of <current> <base> <other> reusing the same diffy merge as merge (markers labelled ours/theirs, ` |
| reset | partial | --soft/--mixed/--hard, guarded --merge/--keep, and pathspec un-staging supported. --merge updates paths changed between the target and index while preserving safe unstaged worktree changes and carrying unmerged index stages; it refuses a target/index path whose worktree also differs from the index. --keep updates paths changed between HEAD and the target while preserving local changes on unaffected paths; it refuses any affected path with staged or unstaged changes. Both guarded modes preload target objects, reject tracked/untracked overwrite and file/directory collisions, validate index/tree paths against escape or .libra metadata access, never follow worktree symlink ancestors (including ignored links), snapshot the exact index bytes plus affected worktree entries through bounded object references, and restore that snapshot if a worktree or final ref/reflog update fails. Pathspecs are incompatible with --merge/--keep. Pathspec reset preserves the target tree mode in the index, including symlink mode 120000; reset --hard preserves tree item type, including restoring mode 120000 entries as real symlinks on supported platforms without following their targets; unsupported platforms return an explicit diagnostic. Bare pathspecs follow Git disambiguation: libra reset <path> unstages from HEAD, libra reset -- <path> forces path interpretation for revision-like filenames, and a token that is both revision and filename is rejected as ambiguous (LBR-CLI-002). --pathspec-from-file/--pathspec-file-nul supported for bulk/stdin pathspec input, but paths are taken literally — Git's default-mode C-style quoted-path decoding is intentionally not performed (use --pathspec-file-nul for special characters). --no-refresh is accepted as a no-op (Libra's reset never refreshes the index, so there is no refresh to skip; no --refresh). |
| rev-parse | partial | strict revision/object parsing supports branch/tag/remote/full refs, @, ^N/~N, typed peel `^{commit |
| rev-list | partial | multi-revision reachability, ^ exclusions, A..B/A...B ranges, --count, -n/--max-count, --skip, --since/--after, --until/--before, parent-count filters and reset aliases, --first-parent, --author, --committer, --grep, path limitation after --, symmetric side filters (--left-right, --left-only, --right-only), cherry filters (--cherry, --cherry-pick, --cherry-mark), --parents, --children, --timestamp, and --reverse (limit-then-reverse output ordering), --all (seed the walk from every branch/remote/tag ref and HEAD), --date-order (no-op for Libra's default committer-date ordering; unlike Git, no topo constraint under date skew), and --boundary (append the frontier commits — parents of listed commits that are not themselves listed, whether excluded by a range or beyond a --max-count/--skip cut — each prefixed with - and carrying --parents/--children/--timestamp metadata, with Git-faithful merge handling under --first-parent --parents and --children, leading under --reverse, and counted by --count), and object-enumeration output (--objects, plus --objects-edge/--objects-edge-aggressive which also emit --prefixed edge commits — the latter an alias, a documented narrowing) supported — objects are the deduplicated reachable trees/blobs printed after the commits as <oid> <path> (root tree empty path), byte-for-byte matching git rev-list --objects, with excluded-side objects treated as uninteresting (range/^ closure), -- <pathspec> walk pruning, gitlinks skipped, and a hard error on a corrupt included tree |
| describe | partial | basic describe, --tags, --always, --abbrev, --exact-match, --long, --dirty[=<mark>], --first-parent, --match/--exclude (wax globs, ≤256 chars; exclude wins over match), --candidates <n> (n=0 ⇒ exact-match), --all (use any ref — branches/remotes/tags — with heads//remotes//tags/ prefixes), and --contains (git name-rev: name a commit relative to the nearest descendant tag — <tag>, <tag>~<n>, or <tag>~<n>^<m>~<k>) supported |
| notes | partial | add / append / copy / edit / show / list / remove / merge supported; --ref supported; notes merge is a 2-way merge of the flat note rows (Libra notes are SQLite-backed, not commit-backed trees) with --strategy=manual (default; aborts on a conflicting note — no NOTES_MERGE worktree)/ours/theirs/union/cat_sort_uniq; prune (remove notes whose annotated object no longer exists in the object store — -n/--dry-run and -v) and get-ref (print the active notes ref) supported; the interactive editor fallback for add/edit/append when no -m/-F is given is supported (edit pre-fills the existing note; notes preserve # lines — not stripped as comments) |
| cherry-pick | partial | one-or-more commit replay, preserving the picked commit's author metadata while using current committer metadata (including GIT_COMMITTER_* date/identity overrides), de-signing source messages before deriving the replayed body/subject, -n/--no-commit (now also for multi-commit), -x, -s/--signoff, -e/--edit, -m/--mainline, --ff, -S/--gpg-sign, --allow-empty, --allow-empty-message, --keep-redundant-commits, --empty=<mode> (stop default / drop skips a redundant pick / keep records the empty commit — == --keep-redundant-commits), --cleanup=<mode> (strip/whitespace/verbatim/scissors/default message cleanup — cleans the body/edited buffer first, then appends -x/Signed-off-by trailers; default/scissors fall back to whitespace without an editor), repeatable last-wins `-X/--strategy-option ours |
| push | partial | branch/tag update, multi-refspec, delete (-d/--delete or a :<ref> refspec), --tags, and --mirror supported; --force-with-lease[=<ref>[:<expect>]] (validates the remote still matches the tracking-ref/expected OID before sending; conflicts with --force) and --porcelain (machine-readable per-ref lines; conflicts with --json/--machine) supported; --atomic supported (advertises the atomic capability so the remote applies all ref updates together; refused up-front if the remote does not advertise atomic); --push-option/-o <opt> supported (sends a push-options section gated on the remote's push-options capability); --follow-tags supported (also pushes annotated tags reachable from a pushed ref and missing on the remote); --signed supported (builds a GPG-signed push certificate via the vault signer, gated on the remote's push-cert capability/nonce); --no-progress supported (suppresses the "Compressing objects" / "Writing objects" progress meter on stderr, like git push --no-progress); --no-verify accepted as a no-op (Libra runs no client-side pre-push hook; declined-decision D3); --no-thin spells the self-contained default explicitly. local file remote rejected — intentional (see docs/development/commands/_compatibility.md#d2-本地-file-remote-的-push); lore.md 2.10 refinements: --force-if-includes is REAL (with the All/Ref lease forms it additionally requires the remote-tracking tip to be integrated locally — pushing the tip, a descendant, or a tip REACHABLE from the pushed branch's reflog; conservative rejections, whole-push error rather than a per-ref porcelain row — documented divergence; silent no-op with the exact lease form or no lease, Git parity); --thin is REAL (REF_DELTA entries against server-known bases — the advertised old tips; self-contained Libra delta encoder with git-convention 64KiB copy ops since git-internal's delta module is private; net-win + 8MiB caps, full fallback on any miss; verified round-trip against real git receive-pack on BOTH unpack paths); self-contained remains the DEFAULT (push.thin config unsupported — intentionally different from git's thin-by-default, revisit recorded); lease tracking lookup now consults both tracking-ref naming conventions (clone=short, fetch=full — a pre-existing fetch-then-lease miss); real-git-server interop matrix in L1 (capability degrades refuse cleanly with nothing sent; push-options round-trip via pre-receive hook) |
| fetch | partial | repository/refspec supports short sources, exact <src>:<dst>, one wildcard per side, case-insensitive remote.<name>.fetch, and exact destinations limited to refs/heads/* / refs/remotes/<remote>/* (reserved HEAD is refused). Destination/reflog/remote-HEAD writes are transactional; a checked-out destination in any worktree and non-fast-forward mappings without +/--force fail closed. --all, --depth, --dry-run (no download/writes; unknown pre-download ancestry is not reported forced), -v/--verbose, --porcelain (rejects --json), tags, -f/--force, --no-auto-gc, --no-progress, --notes, and FETCH_HEAD/--append are supported. --prune/-p deletes tracking refs not live under the effective configured destination mapping; one-off explicit refspecs preserve configured mappings plus ordinary advertised tracking destinations; empty advertisements skip prune. Deletes/reflogs are transactional and --dry-run only reports. --no-prune overrides strict remote.<name>.prune then fetch.prune defaults. --refmap, --atomic, and shallow-expansion flags remain deferred. Network fetches have bounded connect, idle/read, and first-byte timeouts; local remotes are exempt. |
| format-patch | partial | A..B/single-revision ranges, exact -1 [rev], --root [rev], and --ignore-if-in-upstream stable patch-id suppression are supported; merge commits are skipped. Output controls include -o/--output-directory, --stdout, -n/--numbered, --start-number, --numbered-files, --suffix, --subject-prefix, --cover-letter, --thread/--no-thread, --in-reply-to, -v/--reroll-count, -s/--signoff and --no-signoff, --full-index, --minimal (equivalent to Libra's already-shortest default Myers), --histogram, --src-prefix/--dst-prefix, --no-stat, --keep-subject, --zero-commit, signature controls, and RFC 2047 header encoding. format.subjectPrefix, format.signOff, format.outputDirectory, and format.suffix use the strict local→global→system cascade with explicit CLI precedence (--stdout bypasses outputDirectory). Mail controls include repeatable --to/--cc, --no-to/--no-cc, --from, --notes[=<ref>], and mutually exclusive --attach/--inline; generated plain and MIME output is consumed by real Git am, and Git plain format-patch output is consumed by Libra am. --base <commit> emits a base-commit: plus oldest-first stable prerequisite-patch-id: lines on the last patch/cover letter; the base must be an ancestor, --base=auto is rejected, and binary prerequisite ids are not guaranteed to match Git. --interdiff and --range-diff remain unexposed (--force is not a Git format-patch flag). |
| pull | partial | fetch + fast-forward/three-way merge supported; --ff-only, --rebase, --no-rebase (countermands --rebase, last wins), --ff, --no-ff (forces a merge commit), and pull.rebase / branch.<name>.rebase / `pull.ff=true |
| dirty | intentionally-different | Advisory dirty-set marks (lore.md 1.1): libra dirty <paths> upserts manual marks into the working_dirty SQLite cache (no file reads, no index writes; over-report-only, repo-escaping paths refused atomically), --list shows the cache + freshness. The cache is rebuilt only by status --scan, consumed by status --cached/--check-dirty, and NEVER read or written by default status. Git has no equivalent surface. Exit 0 / 128 / 129 |
| diff | partial | staged/old-new/pathspec/name/stat/numstat/shortstat/summary/output/algorithm, default working-tree diff excludes untracked paths (including untracked .libraignore) from machine output and --quiet/--exit-code decisions, working-tree symlink target-byte diffs (dangling symlinks are treated as existing symlinks, not deleted paths), and --exit-code/-s/--no-patch/-z/-U<n> (--unified=<n>; lines of context in the patch, default 3)/-w (--ignore-all-space; re-diff ignoring whitespace — whitespace-only changes drop out and counts/name/JSON reflect the re-diff)/-b (--ignore-space-change; ignore changes in whitespace amount)/--ignore-space-at-eol (ignore trailing-whitespace changes only)/--ignore-cr-at-eol (ignore a carriage return at end of line — a CRLF↔LF-only change drops out; the weakest whitespace flag, subsumed by -w/-b/--ignore-space-at-eol; approximation vs Git: compares with ALL trailing CRs stripped rather than Git's non-transitive allow-one-remaining-CR rule, so only pathological multi-CR endings differ)/--ignore-blank-lines (ignore changes whose lines are all empty — a blank-only change drops out; a blank within <ctxlen of a real change rides along; faithful port of Git's xdl_get_hunk blank-aware hunk selection)/--check (warn on added-line trailing-whitespace / space-before-tab / leftover conflict marker / new blank line at EOF, exit 2)/-R (--reverse; swap the two sides)/-a (--text; force the content diff of files detected as binary — a NUL byte in either side, or non-UTF-8 content — suppressing the "Binary files … differ" line; Libra's diff is text-based, so a non-UTF-8 change identical after lossy-UTF-8 conversion still shows the marker)/--binary (emit a GIT binary patch — full-index header + base85 literal chunks for both directions — for binary files; valid and appliable, but the compressed bytes are not byte-identical to Git's, since Libra deflates with flate2 and always emits literal rather than Git's smaller-of-literal/delta; binary files otherwise show Binary files … differ, --stat shows Bin <old> -> <new> bytes, and --numstat shows -/-)/--no-ext-diff (disable the external diff driver for this run, forcing the built-in engine)/--color-moved[=<mode>] (color moved lines — deleted in one place, added in another — distinctly in colored output: removed → bold magenta, added → bold cyan; bare --color-moved and the block modes default/zebra/blocks/dimmed-zebra are accepted but approximated by plain, coloring every moved line, since Git's conservative moved-block significance/zebra striping is not byte-reproducible; only affects colored output, and --color=always now forces diff color even when piped)/--no-color-moved (turn it off — the default, countermands an earlier --color-moved)/--relative[=<path>] (restrict the diff to a directory and strip that prefix from displayed paths; bare --relative uses the cwd)/--no-relative (a no-op on its own, but takes precedence over --relative when both are given)/--no-indent-heuristic (accepted no-op — Libra's diff applies no indent heuristic)/--textconv (run textconv filters, on by default like Git: a file whose diff=<driver> attribute from Git/Libra attributes sources (core.attributesFile, per-directory .gitattributes, .libra_attributes, and .git/info/attributes) names a driver with a configured diff.<driver>.textconv command has each side converted by that command before diffing — stat/numstat/name/JSON all reflect the converted content; matched via the shared attributes matcher, last-match-wins, with -diff/!diff/bare diff clearing an earlier driver, and a rename resolving each side's driver independently; a failing textconv command is a fatal error (LBR-IO-001, like Git's "unable to read files to diff") rather than a silent raw fall-back; skipped under --check and when diff.external is active)/--no-textconv (diff raw content; countermands --textconv)/-M[<n>] / --find-renames[=<n>] (rename detection — a deleted + added pair similar enough is folded into one rename with similarity index N% / rename from/to, and R<score> / brace-compacted old => new paths across name-status/numstat/stat/summary; the similarity index matches Git for real content (chunked like Git's rename spanhash but hashed with FNV-1a rather than Git's HASHBASE, so only contrived hash-collision inputs can differ); bare -M is 50%, a bare integer is read as 0.<digits> like Git so -M5=50%/-M100=10%, -M<n>% is a literal percent and -M100% is exact-only (integer threshold math, no float rounding), invalid scores are a usage error; enabled at 50% by default (matching Git), honoring diff.renames through the strict cascade — truthy or unset enables 50%, false disables detection, and copies degrades to renames; diff.context accepts Git integers and sets the default -U width; the rename index line uses mode 100644, rename pairing is score-ranked greedy with a same-basename tie-break (Git's diffcore-rename runs a same-basename pre-pass that can prefer a lower-scored same-basename pair, so the chosen old/new pairs for a multi-rename set can differ), and a pathspec cannot directly follow a bare -M/--find-renames — put it before the flag or after --)/--no-renames (turn rename detection off, overriding the default, diff.renames, and an earlier -M) and --ext-diff / diff.external (route each file's patch through an external diff driver via Git's GIT_EXTERNAL_DIFF protocol — cmd path old-file old-hex old-mode new-file new-hex new-mode, run through the shell; a working-tree new side reports an all-zero hash; --no-ext-diff disables it and --stat/name/numstat/-s/--check bypass it) and `--word-diff[=plain |
| diff-tree | partial | diff-tree <tree-a> <tree-b> [-- <path>...] diffs two trees by delegating to the shared engine (diff --old a --new b --no-renames); as Git plumbing it does not inherit porcelain diff.renames or default rename detection. Follows Git plumbing exit semantics — exit 1 when there are differences, 0 when clean, 128 on error. Path limiters require a -- separator (stricter than Git's bare paths). Single-commit diff-tree <commit> (vs parent), -r/-t/--stdin, raw output, and the porcelain flag surface are not exposed |
| diff-index | partial | diff-index <tree> [-- <path>...] diffs a tree against the working tree via the shared engine (diff --old <tree> --no-renames); as Git plumbing it ignores porcelain diff.renames. Exit 1 on differences / 0 clean / 128 error; path limiters require --. --cached (tree vs index) is not yet supported (exit 128; use diff --staged for HEAD vs the index); raw output / -m not exposed |
| diff-files | partial | diff-files [-- <path>...] shows the index-vs-working-tree diff via the shared engine (diff --no-renames); as Git plumbing it ignores porcelain diff.renames. Exit 1 on differences / 0 clean / 128 error; path limiters require --. Stage selection (-1/-2/-3), raw output, and the porcelain flag surface are not exposed |
| fast-export | partial | Read-only git fast-import stream export for default HEAD, multiple revisions, A..B/^A, or --all; all refs share marks, annotated commit tags retain tag records/marks, Libra notes become N records, and paths use Git C quoting. Commits use deleteall + full M trees (larger than Git parent diffs); excluded parents remain literal prerequisites. Note targets outside the exported graph fail closed instead of disappearing. Exit 0/128. Symmetric ranges, marks files, anonymize/filtering, and annotated non-commit targets are deferred; commit signing headers cannot be represented and are omitted |
| fast-import | partial | Imports counted/here-doc streams from stdin or --input: blob; commit mark/author/committer/data/from/merge/M/D/C/R/N/deleteall (M/N inline); annotated tag; reset update/delete; checkpoint/done; and lenient feature/option/progress. Quoted UTF-8 paths and mode↔object types are validated. Branch/tag/notes changes buffer and publish atomically per flush; Git tree-shaped notes commits translate to Libra notes rows; unsupported ref namespaces fail closed. Bounds: 1 GiB total (fastimport.maxInputSize, invalid/unreadable/zero config fails), 1 MiB command line, 1,000,000 top-level objects by default, hash-kind IDs, unique marks, readable typed targets. Exit 0/128. cat-blob/ls/get-mark, marks files, non-UTF-8 paths, and true multi-GiB payload streaming are deferred |
| grep | partial | tracked/index/tree search with common match/count/list/line flags, shared pathspec magic for repository searches (plain prefixes, default wildcards, :(top), :(exclude), :(icase), :(literal), :(glob)), Git grep exit contract (0 match / 1 no match / 2 command error), -A/-B/-C context, -E/-G regex aliases, explicit -P rejection (exit 2), -a/-I binary controls, --heading/--break/-z output grouping, -m/--max-count, and -o/--only-matching, --untracked (also search untracked, non-ignored working-tree files), --no-index (recursively grep the filesystem without a repository, including ignored files; keeps filesystem path roots, not repository pathspec magic), and --max-depth <DEPTH> (descend at most DEPTH directory levels below each pathspec — or below the search root with no pathspec; negative means no limit) supported; function display is not exposed |
| blame | partial | file blame with -L ranges (numeric N/START,END/START,+COUNT plus /regex/ start/end endpoints; a single endpoint spans to end-of-file, matching Git), ignore-rev inputs, --porcelain/-p, --line-porcelain, -e/--show-email, the display flags -l (full hash), -s (suppress author/date), -t (raw timestamp), -f/--show-name (show the filename after the hash), --abbrev <n>, --root (accepted no-op — Libra never prefixes boundary/root commits with ^), and -w/--ignore-whitespace (ignore-all-whitespace line attribution) supported; -L :<funcname>, reverse, incremental, complete porcelain boundary/previous metadata, and copy/move detection are not exposed |
| revert | partial | single/multi-commit revert, generated commits use current author/committer metadata (including GIT_AUTHOR_*/GIT_COMMITTER_* date/identity overrides) and derive Revert "<subject>" from the target commit's de-signed message body, -n/--no-commit, -m/--mainline merge-commit revert, -s/--signoff, -e/--edit (open the editor — $GIT_EDITOR/core.editor/$VISUAL/$EDITOR — on the generated revert message; unlike Git, Libra's revert does not open an editor by default, so --edit is opt-in and mutually exclusive with --no-edit; carried through a conflict via --continue), --no-edit (accepted no-op — the default), --cleanup=<mode> (strip/whitespace/verbatim/scissors/default, persisted across conflict/continue), repeatable last-wins `-X/--strategy-option ours |
| replace | partial | replace [-f] <object> <replacement> records an object substitution, -d <object>... deletes it, and -l [<pattern>] (the default) lists replaced ids. The peel is applied in load_object, so log / show / rev-parse peeling transparently honour it (not just one call site); types must match unless -f, an existing replacement needs -f, self-replacement is rejected. Stored as loose refs under .libra/refs/replace/<oid>. Exit 0 / 128 (no repo, invalid object, missing replacement, type mismatch or existing replacement without -f, IO). -l prints object ids only (Git's default short format) and filters by substring rather than glob. Listing through show-ref/for-each-ref, --format, --edit, --graft, and --convert-graft-file are deferred |
| rerere | partial | Records conflict resolutions and replays them on the identical conflict. rerere (no subcommand) records preimages / replays known resolutions / records postimages for tracked files that have been resolved; status, diff, forget <path>..., clear, gc (60-day resolved / 15-day unresolved TTL) supported. Storage under .libra/rerere/<id>/{preimage,postimage} + MERGE_RR, keyed by the SHA-256 of the conflicted file. Exit 0 / 128. Matching is whole-file byte-identical (Git's per-hunk normalisation / ours-theirs-swap independence not implemented). Automatic integration with merge/rebase/cherry-pick is implemented and gated on rerere.enabled (default off → those commands are byte-for-byte unchanged): a conflict auto-records the preimage and replays a known resolution, and resolving + committing / --continue auto-records the postimage. rerere.autoUpdate (or an effective --rerere-autoupdate on cherry-pick) additionally stages a replayed file |
| remote | partial | add/remove/rename/-v/show/get-url/set-url/prune, set-branches [--add], set-head, remote update, and add -f are supported. No-argument remote update resolves non-empty remotes.default first, expanding both names and remotes.<group> members; only an empty default falls back to every configured remote. update -p fetches every resolved remote before destination-aware remote prune, so a later fetch error cannot strand an earlier destructive prune. Rename transactionally migrates exact remote/SSH config sections, refspec destinations (not sources), branch upstreams, tracking refs, remote HEAD, and tracking reflogs; any target namespace conflict rolls back every change. remote show is live by default and --no-query is offline/cached. Cold add supports -t, -m, --tags/--no-tags, and informational --mirror. |
| hash-object | partial | Hashing for files, --stdin, and --stdin-paths (hash each newline-separated path from stdin); -t blob/commit/tree/tag typed hashing whose object id matches Git byte-for-byte, with --literally to skip content validation; -w writes the object; --path and --no-filters are accepted for raw-byte hashing. Path filters/attributes and arbitrary --literally type strings are unsupported |
| write-tree | partial | Writes the index out as a nested tree object (one tree per directory, modes preserved, hash kind followed) and prints the root tree id; an empty index yields the canonical empty tree; --json/--machine supported. Before writing, stage-0 index entries for regular/executable/symlink blobs and tree-mode entries are validated for object existence/type and fail closed with LBR-REPO-002; gitlinks (160000) are not validated because they reference submodule commits outside this object DB. --prefix/--missing-ok not exposed (deferred); --index-file <path> (lore.md 1.15, Libra flag standing in for Git's GIT_INDEX_FILE env) redirects the index read/write to a scratch file — a missing scratch file acts as an empty index — so revision composition never touches the shared .libra/index |
| read-tree | partial | Reads a <tree-ish> (tree id, commit/ref/tag/HEAD, including shared typed-peel syntax such as HEAD^{tree}) into the index, replacing it; index-only (the working tree is never touched), --json/--machine supported. Git's -m/-u/--reset/--prefix and multi-tree merges are not exposed (deferred); --index-file <path> (lore.md 1.15, Libra flag standing in for Git's GIT_INDEX_FILE env) redirects the index read/write to a scratch file — a missing scratch file acts as an empty index — so revision composition never touches the shared .libra/index |
| update-index | partial | Modifies the index directly: --add/--remove (re)stage or drop working-tree paths (--add records symlinks as mode 120000 with link target bytes and does not follow the target), and --cacheinfo <mode>,<object>,<path> registers an entry from an object id without reading the working tree (object need not exist at registration time; mode ∈ 100644/100755/120000/160000; oid length must match the hash format; absolute/.. paths rejected). Later write-tree / commit validate blob/tree object existence and type and fail with LBR-REPO-002 if a cacheinfo entry remains unresolved; --json/--machine supported. Bare-path stat refresh, --force-remove, --chmod, --assume-unchanged, --skip-worktree, --index-info not exposed (deferred); --index-file <path> (lore.md 1.15, Libra flag standing in for Git's GIT_INDEX_FILE env) redirects the index read/write to a scratch file — a missing scratch file acts as an empty index — so revision composition never touches the shared .libra/index |
| update-ref | partial | Updates/creates/deletes a refs/heads/<branch> ref with an optional compare-and-swap (<oldvalue>; the all-zero id means "must not exist"), -d delete, -m <reason> reflog message, and --json/--machine. The ref read, write/delete, and update-ref reflog entry run in one SQLite transaction (the <oldvalue> operand is never written to the reflog). Scoped to refs/heads/*: HEAD, refs/tags/*, refs/remotes/*, arbitrary namespaces, symbolic (ref:) values, --stdin batches, and --no-deref are rejected/deferred (use symbolic-ref/switch/tag); protect/archive branch metadata is now enforced INSIDE its txn for refs/heads/* updates AND deletes (LBR-POLICY-001, fail-closed — update-ref is no longer a policy bypass; it remains plumbing-sharp otherwise and may still move the checked-out branch, like git update-ref) |
| open | supported | |
| commit-tree | partial | Plumbing (lore.md 1.15): wraps tree+parents+message into a commit object with ZERO index/worktree/HEAD/ref side effects; -p repeatable (duplicates warn+ignored, parents must load as commits), -m repeatable paragraphs + -F/-F -/bare-piped-stdin (-m+-F combine in group order — argv interleaving not preserved); tree operand uses the shared strict tree-ish resolver, including commit/tag/ref and typed peel such as HEAD^{tree} (Libra superset). Intentionally different: empty messages refused (D-empty-message), always unsigned in v1 (git honors commit.gpgsign), no date-override envs yet (non-reproducible OIDs, recorded follow-up), TTY-without-message is a usage error. Exit 0 / 128 / 129 |
| config | partial | vault-backed local/global config is supported; section operations --remove-section <name> and --rename-section <old> <new> (transactional; use Git's section/subsection identity, so --remove-section branch deletes branch.<key> but not the branch.feature.* subsection; rename preserves each value's encryption flag and refuses an existing destination section; missing section exits 128, identical rename exits 2) are supported; -z/--null NUL-delimited output (value\0 for get/get-all, key\nvalue\0 for --get-regexp/--list, key\0 with --name-only, origin\0 prefix with --show-origin; rejected with the Libra-only --ssh-keys/--gpg-keys/--vault views) is supported; type canonicalization `--type=<bool |
| credential | partial | Vault-backed Git credential helper: fill/store/erase speak the Git credential key/value protocol on stdin/stdout, storing secrets AES-256-GCM-encrypted in the repo config keyed by a SHA-256 digest of protocol/host/path (no clear-text host/username at rest). fill is side-channel free (hit and miss both exit 0; miss prints nothing) and works outside a repo (clean miss); entries carry an expiry (password_expiry_utc, default 30 days) and expired entries are a miss; store rejects an already-expired timestamp. Secrets are never logged/traced/echoed in errors. Exit 0 / 128 (store missing fields, expired timestamp, or no vault). credential-cache, multiple usernames per host, and the consumer-side credential.helper chain are not exposed (Libra is a helper); fill now falls back to the global libra auth token store (https only, silent misses, username pinning) — store/erase never manage auth tokens |
| op | intentionally-different | Libra command-level operation history inspection/restore extension, not a Git command |
| reflog | supported | show/delete/exists/expire subcommands. expire prunes by time + reachability + --stale-fix (--all/--expire/--expire-unreachable/--rewrite/--updateref/-n/-v), reads gc.reflogExpire/gc.reflogExpireUnreachable (90/30-day defaults, never written). Intentional differences: no-ref expire is an explicit error (exit 128) vs Git's silent no-op; --stale-fix checks only that the new value loads as a commit (no transitive object walk); --updateref skips symbolic HEAD / remote-tracking refs |
| worktree | intentionally-different | remove keeps disk dir by default (no implicit data loss); --delete-dir gives Git-style removal and refuses on a dirty worktree. Either way, removing a registered worktree GCs that worktree's private HEAD/HEAD-reflog rows (the scoped-row GC is unconditional, not gated on --delete-dir). lore.md 2.1: linked worktrees now have ISOLATED HEAD + index + HEAD-reflog — each has a real .libra/ (a commondir pointer to the shared db/objects/hooks + a stable worktree_id) and its own index; a commit/switch/reset in one worktree never moves another's HEAD or touches its index. A new worktree is created DETACHED at the source commit (avoiding a same-branch collision). Shared: the object store, refs/heads/refs/tags/refs/remotes, stash, config. worktree list --porcelain emits each worktree's OWN HEAD <sha> plus a branch <ref>/detached line (resolved from that worktree's scoped HEAD row); an entry with no resolvable HEAD (legacy layout / missing scope) omits those lines rather than being mislabeled. Deferred (see the deferred-features list): per-worktree ref namespaces (refs/bisect/refs/worktree), pseudo-refs (ORIG_HEAD/MERGE_HEAD/…), and the remaining IN-worktree sequencer op (rebase is REFUSED in a linked worktree — its state is still global — but works in the main worktree). cherry-pick, am, revert, merge, and bisect ARE allowed in a linked worktree: their state is fully worktree-scoped (cherry-pick/am via the sequence_state row keyed by worktree_id; revert via revert-state.json, merge via merge-state.json/merge-autostash.json, all in the local gitdir; bisect via the bisect_state row keyed by worktree_id, with its checkouts/reset moving only that worktree's scoped HEAD and files; the editor buffers CHERRY_PICK_MSG/REVERT_EDITMSG are local too), and the start-time sequencer mutex resolves each per-worktree, so two worktrees can run them on their own branches concurrently without interfering. Commands whose store is still repository-global are likewise REFUSED in a linked worktree until they are worktree-scoped: stash (all subcommands), layer, sparse-view, dirty, and pull --rebase (rebase state is still shared; pull in merge/ff mode IS allowed — its fetch phase writes only repository-scoped state (refs/remotes + objects; pull writes no FETCH_HEAD) and its merge runs on the scoped merge state, with the mode resolved after pull.rebase config so an implicit rebase cannot slip through). fetch is allowed in a linked worktree — its FETCH_HEAD is now worktree-local, and its other writes (refs/remotes/* and the object store) are repository-scoped by design; fetching into a branch checked out in another worktree is still refused. Legacy symlink-layout worktrees keep sharing HEAD/index (no isolation, no regression) until re-added |
| cloud | intentionally-different | Libra cloud backup/restore extension, not a Git command |
| publish | intentionally-different | Libra Cloudflare publish extension, not a Git command |
| agent | intentionally-different | Libra external-agent capture extension, not a Git command. Deferred parity (non-goals for the current wave, tracked canonically in docs/development/tracing/agent.md 「还未实现的功能」 with each item's handling + restart condition): the unstable agent add/remove --local-dev/--force flags stay unpublished (canonical enable/disable + their add/remove aliases only); provider-specific transcript compaction/reassemble traits (the writer already stores large transcripts as manifest-relative chunks, but no provider-specific compactor exists yet); the optional capability traits (ProtectedFilesProvider/TranscriptCompactor/HookResponseWriter/RestoredSessionPathResolver, …) beyond the landed DeclaredAgentCaps matrix; external-RPC method families beyond the v2 info/capability gate (undeclared capabilities stay fail-closed); and the non-first-batch roster — gemini/cursor/copilot/factory-ai stay supported=false (unsupported, not hook-installable, not launchable for review/investigate), vs the first batch claude-code/codex/opencode |
| login | intentionally-different | Libra host-scoped HTTP auth extension (session-token login), not a Git command |
| logout | intentionally-different | Libra host-scoped HTTP auth extension (session-token logout), not a Git command |
| whoami | intentionally-different | Libra host-scoped HTTP auth extension (session-token identity), not a Git command |
| review | intentionally-different | Libra read-only agent review extension (AG-22), not a Git command: fans a review prompt out to external reviewer CLIs (claude-code/codex/opencode) in an isolated workspace and records run state, redacted logs, and untrusted findings under .libra/sessions/agent-runs/<run_id>/; --fix fails closed with LBR-AGENT-010 until the internal fix bridge lands |
| investigate | intentionally-different | Libra read-only agent investigate extension (AG-23), not a Git command: drives a STRICT round-robin investigation (one investigator at a time, in agent order) of external CLIs (claude-code/codex/opencode) in an isolated workspace; persists round-robin run state (turn/next_agent_idx/stances/pending_turn/quorum), single-writer findings.md, and redacted logs under .libra/sessions/agent-runs/<run_id>/; runs reach quorum/max_turns/cancelled/timeout or PAUSE (stalled/agent_failure) resumable via continue; the topic is an untrusted seed (redacted + spotlit before prompt injection); an OS run-lock makes concurrent continue on the same run fail closed; fix fails closed with LBR-AGENT-010 until the internal fix bridge lands (LBR-AGENT-011 gates untrusted-seed mutation) |
| maintenance | partial | run / register / unregister / status / start / stop exposed; start/stop install/remove an OS scheduler entry (launchd LaunchAgents plist on macOS, cron fragment elsewhere; dir overridable via LIBRA_MAINTENANCE_AGENT_DIR); gc recursively traces SQLite refs/reflogs (both endpoints and annotated-tag targets), every index stage, every file-backed stash reflog entry, and held merge/rebase autostash sidecars before loose-object deletion, failing closed on malformed/unreadable roots or reachable objects; the reachability walk covers only the CURRENT worktree's index, so in a repository with linked worktrees gc skips the loose-object prune entirely (reported in its task message) rather than delete objects reachable only from another worktree's private index — a full per-worktree reachability-root inventory is required before pruning is re-enabled there (--dry-run still previews); the commit-graph task writes a Git-compatible v1 commit-graph file (OIDF/OIDL/CDAT chunks + topological generation numbers; octopus merges are written via the EDGE chunk, and SHA-256 repositories via 32-byte OIDs + a SHA-256 header version/trailer); the prefetch task fetches all configured remotes via the normal fetch path (refreshing standard remote-tracking refs rather than Git's refs/prefetch/ namespace — intentionally different; skipped when no remotes are configured) |
| hooks | intentionally-different | Repository hooks use .libra/hooks, never .git/hooks or core.hooksPath. Supported lifecycle: pre-commit, prepare-commit-msg, commit-msg, post-commit, post-checkout, pre-rebase, pre-merge-commit, post-merge, and post-rewrite; automatic merge commits run the message/post-commit hooks, and pull shares its selected merge/rebase lifecycle. Canonical extensionless names win over .sh (Unix) / .ps1 (Windows), with symlinks and non-executable Unix files rejected fail-closed. Hooks run from a private copy in the required workspace-only, network-denied sandbox; repository metadata is protected except the commit message file for message hooks, and caller environment secrets are removed via an explicit process/locale allowlist. Blocking pre/message hooks abort before the associated history mutation; post hooks are advisory; quiet/JSON/machine suppress captured hook output. commit --no-verify, merge --no-verify, and LIBRA_NO_HOOKS=1 are explicit policy bypasses. .git/hooks bridge remains deferred under D3. Customized Windows hooks currently fail closed because the restricted-token backend is not implemented; the exact shipped no-op template is skipped. See repository hooks. |
| archive | partial | Creates tar/tar.gz/tar.bz2/zip archives from a committed tree; --format, --output, --prefix, --list, -v/--verbose (lists archived paths on stderr), --add-file=<file> (inject an untracked working-tree file at its basename under the prefix, bypassing the pathspec filter; repeatable; nothing is written to the object store), --compression-level <0-9> (compression level for tar.gz/tar.bz2/zip — Git's -0..-9, which clap cannot model as bare numeric flags; plain tar ignores it, and bzip2 has no level 0 so 0 is treated as 1), --mtime <time> (set every entry's modification time — same date formats as --since/--until; without it the archived commit's committer time is used, instead of epoch 0), export-ignore filtering from the archived tree's .gitattributes / .libra_attributes blobs (uncommitted working-tree attributes do not affect an existing TREEISH archive), and TREEISH <path>... pathspec limiting supported; export-subst is not implemented |
| check-attr | partial | reports Git/Libra attributes for pathnames from core.attributesFile, per-directory .gitattributes, .libra_attributes, and .git/info/attributes (same-directory .libra_attributes overrides .gitattributes); supports check-attr <attr> <path>..., --all (all set/unset/value attributes), --stdin, -z (NUL framing), and --json/--machine. Intentional difference (see docs/development/commands/_compatibility.md#d5-git-lfs-gitattributes-filter--hooks-bridge): this is a read-only query and does not run Git .gitattributes smudge/clean filter drivers. Output `: : <value |
| check-mailmap | partial | Resolves Name <email> contacts (arguments or --stdin) through the worktree .mailmap and prints the canonical Name <email>; supports all four mailmap line forms with (name,email) rules taking precedence over email-only rules (email match is case-insensitive); --json/--machine emits { contacts }. Exit 0 / 128 (no repo, no contacts, or a contact missing <email>). mailmap.file/mailmap.blob config and the log/blame author-display integration are deferred |
| check-ignore | partial | reports pathnames excluded by Git/Libra ignore sources (.gitignore, .git/info/exclude, core.excludesFile, and .libraignore; same-directory .libraignore overrides .gitignore); <pathname>..., --stdin, -z (NUL input/output framing), -v/--verbose (<source>:<line>:<pattern>\t<path>; the line number is recovered by scanning the source ignore file because the matcher engine does not expose it), -n/--non-matching (requires -v), and --no-index (report a pattern match even for a tracked path) supported, plus --json/--machine. Exit 0 when at least one path is ignored, 1 when none are, 128 on a usage/repository error. Git's --exclude/--exclude-from/--exclude-per-directory and full pathspec magic are not exposed |
| cat-file | partial | -t, -s, -p, -e, AI object modes, and the --batch-check / --batch / --batch-command stdin modes (with optional =<format> atom expansion) supported. Git object modes share the strict resolver: full refs, @, parent/ancestor, typed/recursive peel, numeric reflog selectors, and REV:path; batch reports only unresolvable/nonexistent names as missing and fails closed on ref/storage corruption. --batch-command dispatches info/contents; --buffer enables flush; --batch-all-objects enumerates loose + packed objects in id order. -e --json/--machine emits { exists: bool } while preserving present 0 / absent 1 |
| fsck | partial | object/ref/index/reflog/connectivity checks supported with JSON/machine output via --json/--machine; --strict adds commit email/timezone, commit tree/parent existence+type, and tree entry existence/type/sort-order checks (intentionally narrower than Git: .gitmodules/pathname-charset checks and fsck.<msg-id> severity config are not implemented); --full/--no-full packfile verification supported (on by default, like Git — each .pack is checked against its trailing checksum and each .idx via the shared index parser, without decoding pack objects, so a body-corrupt pack is reported rather than crashing the decoder); --heal (Libra extension, not in Git) re-fetches missing/corrupt objects from the configured durable tier (LIBRA_STORAGE_*), verifies each fetched payload hashes to its OID before writing (never fabricates), skips objects marked intentionally absent, and emits a repair summary (heal object in --json); heal runs before the checks so the exit code reflects the post-repair state, and with no durable tier configured every candidate is reported unrecoverable |
| verify-pack | partial | validates one or more .idx files against matching .pack siblings; -s / --stat-only supported; --pack is available for a single explicit pack path |
| index-pack | partial | hidden plumbing command for pack file indexing; --stdin, --keep[=<msg>], Git-style --progress / --no-progress, and --fix-thin (accepted no-op — Libra's pack decoder requires self-contained packs and never produces thin packs, so a pack that indexes successfully has no external delta bases to complete, matching Git's no-op on a complete pack; resolving external delta bases is not supported) are accepted |
| repack | partial | consolidates objects into one pack-<checksum>.pack (+ .idx) via the single shared pack writer used by maintenance (so the result round-trips through index-pack/verify-pack); -a/--all packs all reachable objects (default: only reachable-loose ones), -d/--delete prunes the loose objects now in the pack (existing packs are never removed, so nothing is left unreferenced), -q/--quiet and --json/--machine supported. Reachability comes from refs/reflogs/index (like the gc task). Always writes a single undeltified pack: delta compression, --window/--depth, geometric repacking, bitmaps, and redundant-pack removal are not implemented |
| pack-objects | partial | hidden plumbing command sharing repack's writer; reads object ids from stdin (one per line, tolerant of rev-list --objects' <id> <path> form) and writes one pack into objects/pack (printing its pack-<checksum> stem) or streams raw pack bytes with --stdout. Intentionally minimal: no --revs/--all history walking, always undeltified, no thin-pack/bitmap options |
| checkout | partial | visible branch compatibility surface plus checkout - previous-checkout target shared with switch - (worktree-scoped HEAD reflog; missing/deleted/corrupt target fails closed), checkout <commit> / -d/--detach detached HEAD, -b/-B <branch> [<start-point>] branch creation/reset that leaves HEAD symbolic on the target branch, --orphan <branch> unborn branch creation (preserves index/worktree, first user commit has no parents; separate start-point currently rejected), -t/--track (accepted no-op — Libra always configures tracking for a remote-tracking checkout via DWIM), --ignore-other-worktrees (Part C W0, intentionally-different from Git: checkout/switch refuse a branch already checked out in another worktree since branches are shared, and this flag does NOT bypass the guard — Libra never allows the same branch checked out in two worktrees. The flag is accepted for CLI compatibility and is a silent no-op in a single-worktree repo; requesting it against a real collision is refused with a note that it is not honored), --no-progress (accepted no-op — Libra's checkout renders no progress meter), --no-overlay (accepted no-op — Libra's checkout is never in overlay mode, matching the Git default; --overlay is not implemented), and explicit checkout -- <path> restoration alias with shared pathspec magic and literal path/directory-prefix fallback (including symlink paths restored as symlinks via restore); prefer switch / restore for new code; patch modes still partial; same case-collision preflight as switch (lore.md 1.14) on both its branch and detached flows |
| auth | intentionally-different | Host-scoped HTTP token auth v1 (lore.md 1.6): login (token via hidden prompt or --with-token stdin ONLY — no --token flag by design), status (never prints secrets; --host scriptable exit 0-iff-valid), logout/clear (idempotent revoke, works after key rotation). AES-256-GCM at rest under the 0600 global vault key; auth.token.* locked away from config get/set/list/unset. Stored tokens attach only to matching https host:port scopes (http for loopback); https→http downgrade redirects are refused outright (reqwest strips credentials only on host/port changes); the interactive 401 prompt remains the process-global fallback; credential fill consults the store silently with username pinning. Git's nearest analogue is external credential helpers (credential-store = plaintext). Exit 0 / 1 / 128 / 129; lore.md 2.7: OS-keyring backend (auth.backend = file |
| bisect | partial | start / bad / good / reset / skip / log / run / view (with Git's visualize alias — a text state summary, not a gitk GUI, since Libra is terminal-native) and start --first-parent (restrict the candidate walk to first-parent history) supported; runs per-worktree — bisect_state is keyed by worktree_id, so each linked worktree bisects independently (v0.19.34); replay (see docs/development/commands/_compatibility.md#d6-bisect-replay) / terms (see docs/development/commands/_compatibility.md#d7-bisect-terms) deferred |
clone --depth, fetch --depth, and pull --depth are supported only for Git
transports that can advertise shallow boundaries. A local Libra source currently
cannot send shallow <oid> metadata, so these commands fail closed before object
transfer with LBR-REPO-002 instead of leaving refs that point at commits whose
parents are missing. rev-parse --is-shallow-repository is supported and reports
whether .libra/shallow contains at least one boundary.
Diff prefix configuration is part of the diff compatibility surface:
diff.noPrefix, diff.mnemonicPrefix, diff.srcPrefix, and diff.dstPrefix
honor the strict local → global → system cascade and Git precedence. Mnemonic
pairs cover index/worktree (i/w), commit/worktree (c/w), commit/index
(c/i), and commit/commit (c/c); -R swaps the pair, /dev/null remains
unprefixed, external-driver output remains verbatim, and commit -v uses the
built-in staged diff as Git does. The shared plumbing wrappers inherit these
display prefixes while continuing to suppress porcelain rename defaults.
Unreadable local/global stores fail closed; unreadable or unsupported system
scope is skipped under the established strict-config contract.
The diff row above remains partial, but the script-facing metadata slice now
supports --raw (including -z rename fields and mode/type changes),
--compact-summary, --diff-filter include/exclude/* semantics,
--full-index, and CLI --src-prefix/--dst-prefix overrides. --summary now
includes mode changes. Raw live-worktree object IDs are zero while repository
sides retain their IDs, including same-content mode-only changes. Pickaxe
-S <STRING> filters per file pair by changed non-overlapping occurrence count
(textconv output when active, raw bytes otherwise), while -G <REGEX> filters
by matching added/removed hunk content; invalid regexes fail before scanning and
both filters run before an external diff driver. P1-08c also supports
--color-words[=<regex>] as the redirected-output-aware color shorthand and
--word-diff-regex=<regex> as the standalone custom tokenizer; global
--color=never still wins, and --word-diff=plain remains bracketed under tty
or forced color. P1-08c now also exposes real Myers (the truthful default),
Myers-minimal, Patience, and Histogram backends through --algorithm plus
--minimal/--patience/--histogram. Repeatable --anchored=<text> adds
Git-compatible anchored Patience: qualifying lines must be unique on both sides
and start with a supplied prefix; selector retention/clearing follows Git.
| Command | Tier | Notes |
|---|---|---|
| submodule | unsupported | intentional product boundary (see docs/development/commands/_compatibility.md#d1-submodule-子命令族) |
| sparse-checkout | unsupported | no public sparse checkout command (see docs/development/commands/_compatibility.md#d10-clone---sparse-与顶层-sparse-checkout-命令) |
| send-email | unsupported | P2-04 / D19 policy: Libra exposes no mail-transport command and never reads sendemail.* configuration, SMTP credentials, or contacts a mail server. Generate messages with libra format-patch, then validate or send them with stock git send-email or another mailer (see docs/commands/send-email.md). |
- Stock Git hooks at
.git/hooks/core.hooksPath:unsupported(see docs/development/commands/_compatibility.md#d3-git-hooks-bridge-作为核心特性) - AI provider hooks:
intentionally-different(see docs/development/tracing/agent.md)
libra lfs:partialcommand compatibility. Libra uses built-in pointer / lock management and.libra_attributes.- Git LFS filter bridge (
.gitattributessmudge/clean filters +git-lfshook install):intentionally-different(see docs/development/commands/_compatibility.md#d5-git-lfs-gitattributes-filter--hooks-bridge). - Repository asset storage policy: current committed binaries remain inline.
Optional future Git LFS rules in
.gitattributesare tracked as a repository governance decision, not as thelibra lfscommand status.
The command rows above additionally honor history-changing Git defaults through the strict local → global → system cascade: merge.ff=true|false|only, merge.log=true|false|<n>, merge.verifySignatures=true|false, and commit.gpgSign=true|false. Merge CLI overrides (--ff/--no-ff/--ff-only, --verify-signatures/--no-verify-signatures) win; --no-gpg-sign wins over commit.gpgSign, which in turn wins over Libra's vault.signing default. Invalid local/global values fail before history mutation. This clarification supersedes the older commit-row wording that described signing as driven only by vault.signing.
The single Tier column above is deliberately coarse: a command marked
partial or supported can still hide a broken conflict view, an unparseable
--porcelain stream, an ignored config default, or a non-Git plumbing output.
This section splits the compatibility promise of every command reached by the
plan-20260708 P0/P1 work into a fixed
enumeration of sub-faces, each graded into one of the four tiers so
supported can no longer mask a defect. This is the machine-checked surface
(guard compat_subface_labels).
Fixed sub-face enumeration (a guard rejects any label outside this set):
| Sub-face | What it promises |
|---|---|
common-user-flow |
The normal, interactive human path works like Git. |
porcelain-machine |
--porcelain / -z / exit code / stdout-vs-stderr are script-parseable like Git. |
conflict-aware |
Unmerged index, sequencer state, and conflict-aware status/diff/restore are correct. |
config-aware |
Common Git config defaults (e.g. status.*, diff.renames, diff prefix controls, pull.rebase) are honored. |
plumbing-compatible |
Object / ref / rev-syntax / raw output is byte-close to Git. |
Bucket meaning (same four tiers as the top-level matrix): supported = solid
today; partial = works for common cases but a P0/P1 task is closing gaps;
unsupported = the Git-compatible behavior is absent today (a P0/P1 task will
introduce it) and MUST carry a governing plan/D number; intentionally-different
= Libra deliberately diverges (documented). A face not listed for a command in
any bucket is not separately graded (—). Every unsupported face is also
recorded, with its governing number, in
docs/development/commands/_compatibility.md.
| Command | Supported faces | Partial faces | Unsupported faces | Intentionally-different faces |
|---|---|---|---|---|
| add | — | common-user-flow, config-aware, plumbing-compatible | — | — |
| archive | common-user-flow | config-aware | — | — |
| branch | common-user-flow | config-aware, plumbing-compatible | — | — |
| bundle | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| cat-file | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| check-attr | common-user-flow, config-aware | — | — | — |
| check-ignore | common-user-flow, config-aware | plumbing-compatible | — | — |
| checkout | — | common-user-flow, plumbing-compatible | — | — |
| cherry-pick | — | common-user-flow, conflict-aware | — | — |
| clean | — | common-user-flow, config-aware | — | — |
| clone | — | common-user-flow, config-aware, plumbing-compatible | — | — |
| cloud | — | config-aware, porcelain-machine | — | common-user-flow |
| commit | — | common-user-flow, config-aware, plumbing-compatible | — | — |
| commit-tree | common-user-flow | plumbing-compatible | — | — |
| diff | — | common-user-flow, porcelain-machine, conflict-aware, config-aware, plumbing-compatible | — | — |
| fast-export | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| fast-import | — | common-user-flow, plumbing-compatible | — | — |
| fetch | — | common-user-flow, porcelain-machine, config-aware, plumbing-compatible | — | — |
| for-each-ref | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| grep | — | common-user-flow, porcelain-machine, plumbing-compatible | — | — |
| hooks | — | common-user-flow | — | config-aware |
| init | — | common-user-flow, config-aware | — | — |
| lfs | common-user-flow | — | — | config-aware |
| log | common-user-flow | porcelain-machine, config-aware, plumbing-compatible | — | — |
| ls-files | conflict-aware | common-user-flow, porcelain-machine, plumbing-compatible | — | — |
| ls-remote | common-user-flow | plumbing-compatible | — | — |
| media | — | config-aware | — | common-user-flow |
| merge | — | common-user-flow, conflict-aware, config-aware | — | — |
| pull | common-user-flow | porcelain-machine, config-aware | — | — |
| push | common-user-flow | porcelain-machine, config-aware | — | — |
| rebase | — | common-user-flow, conflict-aware | — | — |
| remote | — | common-user-flow, config-aware, plumbing-compatible | — | — |
| reset | — | common-user-flow, conflict-aware | — | — |
| restore | — | common-user-flow, conflict-aware | — | — |
| rev-parse | common-user-flow | plumbing-compatible | — | — |
| revert | — | common-user-flow, conflict-aware | — | — |
| rm | — | common-user-flow, plumbing-compatible | — | — |
| show | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| show-ref | common-user-flow | plumbing-compatible | — | — |
| shortlog | common-user-flow | porcelain-machine, plumbing-compatible | — | — |
| status | — | common-user-flow, porcelain-machine, conflict-aware, config-aware, plumbing-compatible | — | — |
| switch | — | common-user-flow | — | — |
| tag | common-user-flow | config-aware, plumbing-compatible | — | — |
| write-tree | common-user-flow | plumbing-compatible | — | — |
The graded command set is pinned to the plan's P0/P1 surface; the
compat_subface_labels guard fails if it drifts from that set or from
src/cli.rs::Commands, or if any cell names a label outside the fixed
enumeration.