libra pull
Command reference for `libra pull`
Fetch objects from a remote and integrate the fetched branch into the current branch.
Synopsis
libra pull [--ff-only] [--ff] [--no-ff] [--squash] [--no-commit] [--commit] [--autostash] [--no-progress] [--rebase] [--no-rebase] [--depth <n>] [<repository> [<refspec>]]
Description
libra pull combines fetch and the same merge engine used by libra merge. It downloads new objects, updates remote-tracking refs, and then integrates the selected upstream into the current branch.
With --rebase (-r), the integration step instead replays local-only commits on top of the fetched upstream tip. This is equivalent to libra fetch followed by libra rebase <upstream>.
With --ff-only, pull fetches the upstream but refuses to create a merge commit when local and remote histories have diverged. Fast-forward and already-up-to-date pulls still succeed. --ff-only conflicts with --rebase, --ff, and --no-ff.
With --no-ff, pull always records a real merge commit even when the upstream could be fast-forwarded, mirroring git pull --no-ff. --ff explicitly allows the default fast-forward behaviour. --ff, --no-ff, and --ff-only are mutually exclusive and conflict with --rebase.
With --depth <n>, the fetch phase is limited to a shallow history of n commits per tip before integration, mirroring git pull --depth. --depth is fetch-only and conflicts with --rebase.
When invoked with no arguments, the command reads the current branch tracking configuration (branch.<name>.remote and branch.<name>.merge). When <repository> is given alone, the current branch name is used as the remote branch. When both <repository> and <refspec> are given, the specified remote branch is fetched and merged.
Pull supports already-up-to-date, fast-forward, and three-way merge results,
including recursive virtual ancestors for multiple merge bases. The merge phase
inherits merge's unresolved-index guard. Without merge state, unresolved index
stages return LBR-CONFLICT-002 with phase: "merge" and plain-commit
guidance—even when the fetched target is already up to date. Fetch may already
have downloaded objects or advanced remote-tracking refs, but integration
preserves HEAD, the index, and working tree.
For a normal merge conflict, pull leaves merge state: resolve with
libra add <path> and libra merge --continue, or abort it with
libra merge --abort. A squash conflict leaves no merge state and keeps
HEAD unchanged; resolve and stage the paths, then use a plain libra commit
to create a single-parent commit.
The merge phase inherits directory-rename inference and
merge.directoryRenames=true|false|conflict. The default conflict suggests
and stages a relocated path as unresolved, true relocates automatically, and
false leaves additions below the old directory. A tied destination stops the
merge with affected additions at stage 0. Unknown values fail closed in the
merge phase with LBR-REPO-003; fetch may already have updated remote-tracking
refs before that phase-local refusal.
The merge path also inherits merge.renormalize=true|false. When enabled, the
shared three-way engine canonicalizes text / eol-attributed base, local, and
remote inputs before content merge, then preserves local (ours-side) line
endings. Pull exposes no -X override; use repository configuration. An invalid
value fails with LBR-REPO-003 only when integration reaches a real three-way
merge. Fetch may already have updated objects and remote-tracking refs;
fast-forward and already-up-to-date integration do not consume the setting.
Arbitrary clean/smudge filters remain unsupported.
With --squash, pull fetches and computes the merge but stages the merged tree without creating a commit, moving HEAD, or recording merge state—even when the merge conflicts. With --no-commit, pull performs the merge and stages the result but stops before committing, recording merge state so the two-parent commit can be finalized with libra merge --continue. --squash and --no-commit conflict with each other and with --rebase. --commit forces a merge commit (the default merge behavior) and is last-one-wins with --no-commit (the final flag on the command line decides); it conflicts with --squash and --rebase, matching git pull --commit.
With --autostash, pull stashes tracked changes before integration. The rebase
path restores them when the operation returns. A normal merge conflict holds
them until merge --continue/--abort; a squash conflict stores them directly
in stash list without applying them so the unresolved index remains intact.
After resolving and committing a squash, restore them with libra stash pop.
Untracked and ignored files remain in place.
Global Config Schema Guard
Configuration schema compatibility is role-scoped. Before libra pull trusts
configuration, it inspects GlobalConfig and SystemConfig metadata read-only. A
future configuration schema or an unregistered/mismatched migration receipt
fails closed with LBR-CONFIG-001 when that scope is required. Known
Repository-only receipts, including 2026090801 in the current manifest, do
not make a configuration store future; its supported values remain readable.
The configuration-owned legacy-reader barrier is recognized by this build;
see configuration compatibility.
Global configuration uses LIBRA_CONFIG_GLOBAL_DB or ~/.libra/config.db;
system configuration uses LIBRA_CONFIG_SYSTEM_DB or /etc/libra/config.db.
Complete process/repo-local storage settings can make GlobalConfig unnecessary
(cloud must also satisfy its D1 settings). They do not prove that SystemConfig
defaults are unnecessary. Diagnostics identify the affected scope, ledger and
version without printing configuration values or untrusted receipt names.
Unknown or unsupported state is upgrade-only here, not automatically repaired.
Install a compatible newer Libra binary:
curl --proto '=https' --tlsv1.2 -sSf https://download.libra.tools/install.sh | sh.
Do not delete or edit SQLite receipts manually. Use --offline or
LIBRA_READ_POLICY=offline|local only for intentional local-only object access;
these modes warn and are not authorization for remote synchronization.
Options
| Flag / Argument | Description | Example |
|---|---|---|
<repository> |
Remote name to pull from. When omitted, uses the current branch's configured upstream. | libra pull origin |
<refspec> |
Branch name on the remote. Requires <repository>. When omitted, uses the current branch name. |
libra pull origin main |
--ff-only |
Refuse to create a merge commit; allows only fast-forward or already-up-to-date integration. Unresolved index stages still refuse the merge phase. Conflicts with --rebase, --ff, --no-ff. |
libra pull --ff-only |
--ff |
Explicitly allow a fast-forward merge (the default). Conflicts with --no-ff, --ff-only, --rebase. |
libra pull --ff |
--no-ff |
Always create a merge commit even when a fast-forward is possible. Conflicts with --ff, --ff-only, --rebase. |
libra pull --no-ff |
--squash |
Stage the merged tree without committing, moving HEAD, or recording merge state, including on conflict. Resolve and stage conflicts, then use a plain single-parent libra commit. |
libra pull --squash |
--no-commit |
Merge and stage but stop before committing, recording merge state to finalize with libra merge --continue. Conflicts with --squash, --rebase. |
libra pull --no-commit |
--commit |
Force a merge commit (the default); last-one-wins with --no-commit. Conflicts with --squash, --rebase. |
libra pull --commit |
--autostash |
Stash tracked changes before integrating. Normal merge conflicts hold them for merge control; squash conflicts save them in stash list for a later stash pop. |
libra pull --autostash |
--no-progress |
Suppress the fetch progress meter (the "Receiving objects" spinner), matching git pull --no-progress. |
libra pull --no-progress |
--notes |
Forward to the fetch: also import the file-dependency graph (refs/notes/deps, lore.md 3.2) from a local Libra upstream. Default OFF (Git parity); a network/plain-Git upstream warns and imports nothing (deferred, D17). See libra fetch --notes. |
libra pull --notes |
--depth <n> |
Limit the fetch phase to a shallow history of n commits per tip. Conflicts with --rebase. |
libra pull --depth 1 |
-r, --rebase |
After fetching, rebase the current branch onto the upstream tip instead of merging. | libra pull --rebase |
--no-rebase |
Merge instead of rebasing (the default), countermanding an earlier --rebase/-r (last one wins). Pull merges by default, so on its own this is a no-op. |
libra pull --no-rebase |
--json |
Emit structured JSON envelope to stdout (global flag). | libra pull --json |
--machine |
Compact single-line JSON; suppresses progress (global flag). | libra pull --machine |
--quiet |
Suppress all progress and merge summary output. | libra pull --quiet |
Examples
libra pull
libra pull origin main
libra pull --ff-only
libra pull --no-ff
libra pull --depth 1
libra pull --rebase origin main
Human Output
Default human mode writes fetch progress to stderr and the pull summary to stdout.
Fast-forward:
From git@github.com:user/repo.git
abc1234..def5678 origin/main
Updating abc1234..def5678
Fast-forward
3 files changed
Clean three-way merge:
From git@github.com:user/repo.git
abc1234..def5678 origin/main
Updating abc1234..def5678
Merge made by the 'three-way' strategy.
2 files changed
Already up to date:
From git@github.com:user/repo.git
Already up to date.
No tracking information:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
libra pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
libra branch --set-upstream-to=origin/<branch> main
Rebase:
From git@github.com:user/repo.git
abc1234..def5678 origin/main
Successfully rebased 2 commits onto 'origin/main' (1111111..2222222).
--quiet suppresses all progress and merge summary output.
Structured Output
--json writes one success envelope to stdout. --machine writes the same schema as one compact JSON line. Success leaves stderr clean.
{
"ok": true,
"command": "pull",
"data": {
"branch": "main",
"upstream": "origin/main",
"fetch": {
"remote": "origin",
"url": "git@github.com:user/repo.git",
"refs_updated": [
{
"remote_ref": "refs/remotes/origin/main",
"old_oid": "abc1234...",
"new_oid": "def5678..."
}
],
"objects_fetched": 12,
"bytes_received": 2048
},
"merge": {
"strategy": "three-way",
"old_commit": "abc1234...",
"commit": "def5678...",
"files_changed": 2,
"up_to_date": false,
"parents": ["abc1234...", "fedcba9..."]
}
}
}
Rebase output omits merge and includes rebase:
{
"ok": true,
"command": "pull",
"data": {
"branch": "main",
"upstream": "origin/main",
"fetch": {
"remote": "origin",
"url": "git@github.com:user/repo.git",
"refs_updated": [],
"objects_fetched": 0,
"bytes_received": 0
},
"rebase": {
"status": "completed",
"old_commit": "1111111...",
"commit": "2222222...",
"replay_count": 2,
"up_to_date": false
}
}
}
Schema Notes
branchis the current local branch being updated.upstreamis the remote tracking branch name, such as"origin/main".fetch.refs_updatedlists remote refs that changed during fetch.- Exactly one of
mergeorrebaseis present, depending on whether--rebasewas passed. merge.old_commitis the pre-mergeHEAD; it isnullon the first pull into an empty local branch.merge.strategyis"fast-forward","three-way", or"already-up-to-date".merge.commitis the new HEAD commit after merge; it isnullwhen up to date.merge.parentsappears for successful three-way merge commits.merge.files_changedis the number of paths changed by the merge result.rebase.statusis"completed","fast-forwarded","already-up-to-date", or"no-commits".rebase.replay_countis the number of local commits replayed onto the upstream tip.rebase.up_to_dateistruewhen the rebase did not moveHEAD.
Parameter Comparison: Libra vs Git vs jj
| Parameter | Libra | Git | jj |
|---|---|---|---|
| Basic pull | libra pull |
git pull |
N/A (jj uses jj git fetch + working copy) |
| Pull from specific remote | libra pull origin main |
git pull origin main |
N/A |
| Fast-forward integration | Supported | Supported | N/A |
| Fast-forward-only pull | libra pull --ff-only |
git pull --ff-only |
N/A |
| Three-way integration | Supported through merge engine | Supported | N/A |
| Rebase on pull | libra pull --rebase |
git pull --rebase |
N/A |
| Force merge commit | libra pull --no-ff |
git pull --no-ff |
N/A |
| Shallow pull | libra pull --depth 1 |
git pull --depth 1 |
N/A |
| Squash | libra pull --squash |
git pull --squash |
N/A |
| No-commit | libra pull --no-commit (finalize with libra merge --continue) |
git pull --no-commit |
N/A |
| Force-commit override | libra pull --commit (last-one-wins with --no-commit) |
git pull --commit |
N/A |
| Autostash | libra pull --autostash |
git pull --autostash |
N/A |
| Suppress progress | libra pull --no-progress |
git pull --no-progress |
N/A |
| Structured output | --json / --machine |
No | No |
| Phase diagnostics | phase detail in error JSON |
No | No |
Error Handling
Every PullError variant maps to an explicit StableErrorCode. Fetch, merge, and rebase sub-errors are forwarded with a phase detail for diagnostics.
| Scenario | Error Code | Exit | Hint |
|---|---|---|---|
| HEAD is detached | LBR-REPO-003 |
128 | "checkout a branch before pulling" |
| No tracking info for branch | LBR-REPO-003 |
128 | Git-style advisory block with libra pull <remote> <branch> and libra branch --set-upstream-to=... |
| Remote not found | LBR-CLI-003 |
129 | "use 'libra remote -v' to see configured remotes" |
| Fetch: network unreachable / timeout | LBR-NET-001 |
128 | "check network connectivity and retry" |
| Fetch: packet-read connection reset / non-protocol IO failure | LBR-NET-001 |
128 | "check network connectivity and retry" |
| Fetch: authentication failed | LBR-AUTH-001 |
128 | "check SSH key or HTTP credentials" |
| Fetch: pkt-line discovery / transfer setup error | LBR-NET-002 |
128 | "check that the remote serves Git data and that a proxy has not altered the response" |
| Fetch: pkt-line truncation / sideband / checksum / pack protocol error | LBR-NET-002 |
128 | No additional hint, except for an incomplete pack: "the connection dropped mid-transfer — retry the pull" |
| Merge: conflicts, dirty worktree, or untracked overwrite | LBR-CONFLICT-002 |
128 | "resolve conflicts, then run 'libra merge --continue'" |
Merge: non-fast-forward rejected by --ff-only |
LBR-CONFLICT-002 |
128 | "run 'libra pull' without --ff-only to allow a merge commit" |
| Rebase: conflict during replay | LBR-CONFLICT-001 |
128 | "resolve conflicts, stage them, then run 'libra rebase --continue'" |
| Rebase: dirty worktree | LBR-REPO-003 |
128 | "commit or stash your changes before rebasing" |
| Merge: invalid target | LBR-CLI-003 |
129 | "verify the upstream ref and try again" |
| Merge: unrelated histories or invalid merge state | LBR-REPO-003 |
128 | "inspect branch history and merge state" |
| Merge: repository corruption | LBR-REPO-002 |
128 | "inspect repository state and object integrity" |
| Merge: read failure | LBR-IO-001 |
128 | "check repository metadata and permissions" |
| Merge: write failure | LBR-IO-002 |
128 | "check filesystem permissions and retry" |
Phase Detail
When a sub-operation fails, the error JSON includes a phase key in the details object ("fetch", "merge", or "rebase") so agents can distinguish which stage failed.
Malformed HTTP(S) discovery responses
During HTTP(S) reference discovery, Libra rejects a zero-byte advertisement and
malformed pkt-line frames, including short or non-hexadecimal headers, frame
lengths below four, and truncated payloads. A valid 0000 flush remains distinct
from an absent response; a valid empty-repository advertisement is supported.
An unsupported object-format capability reports the fixed message
Unsupported object format capability without echoing its remote value.
Check that the URL points to a Git smart HTTP service and that a proxy has not
truncated or replaced the response; then retry.
pkt-line error classification
Detected pkt-line framing errors return LBR-NET-002 (exit 128), including an
empty HTTP(S) discovery advertisement. Ordinary connection failures, resets and
timeouts return LBR-NET-001 (exit 128). Verify the Git service and any proxy
response when a protocol error occurs. Discovery framing errors use the hint
check that the remote serves Git data and that a proxy has not altered the response.
The fetch phase uses this classification for discovery and object-transfer
setup. A truncated header or payload while reading the fetch stream returns
LBR-NET-002 with no extra CLI hint; an incomplete pack at a clean frame boundary
retains its byte count and the connection dropped mid-transfer — retry the pull
hint. A packet-read connection reset is LBR-NET-001. These errors keep
details.phase = "fetch" in JSON output.
An upload-pack EOF at a frame boundary before pack data begins, including a
zero-byte POST response, returns LBR-NET-001 with
check network connectivity and retry. An empty discovery advertisement remains
LBR-NET-002.
Git and SSH advertisement frame boundaries
The Git/SSH pkt-line advertisement readers reject declared lengths 0001 through
0003, incomplete four-byte headers, and EOF inside a declared payload. Flush
0000, empty-payload 0004 and maximum-size ffff frames retain their behavior.
Their typed pkt-line errors classify as LBR-NET-002 at the reader boundary;
ordinary transport IO and idle timeouts remain LBR-NET-001 when classified.
During the git:// object-fetch advertisement, fetch, clone and pull already
report these failures as LBR-NET-002, including a zero-byte advertisement. The
hint is check that the remote serves Git data and that a proxy has not altered the response.
Lengths 1–3 previously could panic; truncated advertisements previously returned
LBR-NET-001 with a network/transfer hint. Git discovery can still report LBR-NET-001. SSH advertisement propagation
and bounded cleanup are described below; complete ASCII-hex header validation
remains separate work.
This advertisement is distinct from an upload-pack response after negotiation: HTTP(S) framing and empty upload-pack response classifications are unchanged. Tests exercise real local TCP object-fetch advertisement reads and the public fetch/clone/pull error conversions, without claiming full command execution or bounded SSH cleanup. Check the remote Git service or proxy for malformed frames.
SSH advertisement error handling
SSH advertisement frames with lengths 0001 through 0003, incomplete headers
(including zero-byte EOF), or truncated payloads return LBR-NET-002. The fixed
protocol reason and its marker are retained; captured SSH stdout/stderr is never
inserted into that protocol error.
A missing advertisement can also mean SSH failed before Git negotiation, for
example because of connectivity, host trust, authentication or repository access.
This release still reports that incomplete advertisement as LBR-NET-002. When
SSH's local non-zero exit status is available, the message adds only
SSH exited with status N and fixed guidance to check SSH connectivity, trusted
host keys, ssh-agent authentication and remote repository access. The original
SSH stderr is not shown in this protocol diagnostic; specific host-key guidance
is not yet provided by this path.
After an incomplete required header, Libra allows up to 100 milliseconds for SSH to report its exit status, then requests termination if it is still running. Other advertisement read errors request termination immediately. The status window and direct-child cleanup share a two-second total budget. Cleanup failure does not replace the primary protocol reason. This does not promise cleanup of arbitrary descendant processes.
Ordinary IO and timeout errors keep their transport classification. Cleanup can terminate a running child, so its reported exit status and the amount of available diagnostics can change; a local cleanup warning is appended to any collected process result. Interactive stderr inheritance and other SSH process-error diagnostics retain their existing behavior, so this change does not suppress every SSH terminal message.
The git:// object-fetch path already reports these frame errors as LBR-NET-002.
Git discovery can still report them as LBR-NET-001. Non-ASCII/non-hex headers
retain their existing classification. HTTP(S) behavior is unchanged.