1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! **Git LFS over pure SSH.** The git-lfs 3.0 `git-lfs-transfer` protocol,
//! server side.
//!
//! No HTTP server, no second endpoint, no second authorisation path. A client
//! execs a third command over the SSH connection it is already authenticated on,
//! and speaks a pkt-line protocol there. The session's grant was decided once,
//! by [`gunnar_ssh::command::parse`](../gunnar_ssh/command/fn.parse.html), from
//! the same key table `git-upload-pack` and `git-receive-pack` use.
//!
//! # What the specification actually says, verified 2026-08-03
//!
//! The LFS design said to confirm the protocol against the git-lfs spec before building to
//! it. That was done against `git-lfs/docs/proposals/ssh_adapter.md`, against the
//! **client implementation** (`git-lfs/ssh/{ssh,connection,protocol}.go`,
//! `git-lfs/tq/ssh.go`, `git-lfs/pktline`), and against the reference server
//! (`charmbracelet/git-lfs-transfer`). That description is correct. Four details
//! it does not carry, all of which change the code:
//!
//! | detail | what it is |
//! |---|---|
//! | the exec line | `git-lfs-transfer <path> <operation>`: **three tokens, path unquoted**, `operation ∈ {upload, download}` |
//! | the grant | therefore **not a constant per verb**: `upload` needs Write, `download` needs Read. See [`Operation`] |
//! | the version | **1.** `version=1` capability, `version 1` command. The design's "v2, not v1" is gunnar's release phase, not the wire |
//! | locking | **optional.** `lock`/`unlock`/`list-lock` only if the server advertises `locking`. gunnar does not; see [`wire::CAPABILITIES`] |
//!
//! # The conversation
//!
//! ```text
//! server → version=1 capability advertisement, flush
//! client → version 1 flush
//! server → status 200 delim, flush
//! client → batch transfer=ssh hash-algo=sha256 delim, <oid> <size>…, flush
//! server → status 200 delim, <oid> <size> <action>…, flush
//! action ∈ upload | download | noop
//! a `download` batch never answers `noop`; see `Session::batch`
//! client → get-object <oid> flush (download only)
//! server → status 200 size=<n> delim, data…, flush
//! client → put-object <oid> size=<n> delim, data…, flush (upload only)
//! server → status 200 flush
//! client → verify-object <oid> size=<n> flush (upload only)
//! server → status 200 flush
//! client → quit flush
//! server → status 200 flush ← no delim; see `wire`
//! ```
//!
//! # Streaming is the design, not a tuning goal
//!
//! An LFS object is a video, a firmware image, a dataset. Nothing on this path
//! allocates from an object's size: every byte in both directions passes through
//! one [`CHUNK_BYTES`] buffer, and a declared size is used only to *refuse* and
//! to *verify*. `tests/streaming.rs` asserts that against the process's real
//! resident-memory high-water mark while moving a gigabyte, because a test in
//! this tree OOM'd a 499 GB machine by allocating what a header claimed, and
//! this is the code path most likely to repeat it.
//!
//! # Objects are stored raw. Measured.
//!
//! Level-6 DEFLATE over large high-entropy content, which is what LFS content
//! is, was measured at **ratio 1.000, 48.2 MB/s**, against **2027 MB/s** for
//! SHA-1 over the same bytes. Compressing an LFS object is 42× slower than the
//! hash that has to run over it anyway and returns nothing. Nothing here
//! compresses; see [`fs_store`].
//!
//! And the hash cannot be parallelised: a single object's SHA-256 is serial by
//! construction. The win for large objects is I/O, which is why the chunk is a
//! full pkt-line payload and why the `fs` backend hands out a bare `File`.
//!
//! # Verification (measure, never assert)
//!
//! * `tests/interop.rs` drives the **real `git-lfs` binary**. It hard-fails if
//! `git lfs version` is absent rather than skipping: a self-skipping test that
//! reports success is the failure this project keeps finding.
//! * `tests/authorisation.rs` watches both directions go red: a `download`
//! session is refused `put-object`, an `upload` session is refused
//! `get-object`.
//! * `tests/streaming.rs` asserts bounded RSS while a gigabyte moves.
pub use Allowance;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use Operation;
pub use ;
pub use ;
pub use VerifyingSink;
pub use FsLfsStore;
/// The tracing target every line in this crate carries, so it is filterable with
/// the same `RUST_LOG=gunnar::lfs=info` the rest of the server answers to.
///
/// # Why the string and not `gunnar_core::observe::TARGET_LFS`
///
/// That is where it is *named*, and this is a second spelling of it, which one
/// implementation is normally against. The edge that would remove the second
/// spelling runs the wrong way: this crate has no `gunnar-core` dependency,
/// and pulling one in to reach a `&str` would put the identity table, the ACL
/// and `aws-lc-rs` into its graph. `gunnar-wire`'s `GIT_TARGET` records the
/// same trade.
///
/// The two spellings are held together by a guard where both crates are already
/// linked — `gunnar-cli`'s `tests/observability.rs` — which costs nothing and
/// cannot be forgotten by a build that does not link them.
///
/// ⚠ It was **`"gunnar.lfs"`** until 2026-08-05, with a dot instead of `::`, so
/// `RUST_LOG=gunnar::lfs=…` selected none of these lines and neither did any other
/// documented filter. Nothing was broken about the logging; it was simply
/// unreachable.
pub const LFS_TARGET: &str = "gunnar::lfs";
/// The exec verb a client sends to open one of these sessions.
///
/// Named here rather than spelled as a literal in `gunnar-ssh`'s dispatch table,
/// so the verb and the protocol that answers it cannot drift apart.
pub const TRANSFER_VERB: &str = "git-lfs-transfer";