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

  • branch is the current local branch being updated.
  • upstream is the remote tracking branch name, such as "origin/main".
  • fetch.refs_updated lists remote refs that changed during fetch.
  • Exactly one of merge or rebase is present, depending on whether --rebase was passed.
  • merge.old_commit is the pre-merge HEAD; it is null on the first pull into an empty local branch.
  • merge.strategy is "fast-forward", "three-way", or "already-up-to-date".
  • merge.commit is the new HEAD commit after merge; it is null when up to date.
  • merge.parents appears for successful three-way merge commits.
  • merge.files_changed is the number of paths changed by the merge result.
  • rebase.status is "completed", "fast-forwarded", "already-up-to-date", or "no-commits".
  • rebase.replay_count is the number of local commits replayed onto the upstream tip.
  • rebase.up_to_date is true when the rebase did not move HEAD.

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.