git_lfs_filter/lib.rs
1//! Clean and smudge filters for git-lfs.
2//!
3//! See `docs/spec.md` § "Intercepting Git" for the protocol contract.
4
5use std::io::{self, Read};
6
7use git_lfs_pointer::{MAX_POINTER_SIZE, Pointer};
8
9mod clean;
10mod filter_process;
11mod smudge;
12
13pub use clean::{CleanError, CleanExtension, CleanOutcome, clean};
14pub use filter_process::{FilterProcessError, filter_process};
15pub use smudge::{SmudgeError, SmudgeOutcome, smudge, smudge_with_fetch};
16
17/// Boxed error returned by the on-demand fetch closure passed to
18/// [`smudge_with_fetch`] / [`filter_process`].
19///
20/// Kept as a boxed trait object so callers can plug in any error type
21/// (HTTP failures, missing config, custom-transfer breakage, …) without
22/// the filter crate needing to know about it. The typed
23/// [`git_lfs_transfer::TransferError`] is the most common payload — it
24/// converts via `Into` since it implements `std::error::Error + Send + Sync`.
25pub type FetchError = Box<dyn std::error::Error + Send + Sync>;
26
27/// Read up to [`MAX_POINTER_SIZE`] bytes from `input` and try to parse them
28/// as a pointer.
29///
30/// The returned `Vec` is the buffered head: callers that fall through to a
31/// content path need to prepend it to whatever's still in the stream. The
32/// `Option<Pointer>` is `Some` iff the head fit entirely in the buffer (i.e.
33/// total input was strictly less than [`MAX_POINTER_SIZE`]) **and** parsed
34/// as a valid pointer.
35fn detect_pointer<R: Read>(input: &mut R) -> io::Result<(Vec<u8>, Option<Pointer>)> {
36 let mut head = vec![0u8; MAX_POINTER_SIZE];
37 let mut filled = 0;
38 while filled < head.len() {
39 match input.read(&mut head[filled..])? {
40 0 => break,
41 n => filled += n,
42 }
43 }
44 head.truncate(filled);
45
46 let pointer = if filled < MAX_POINTER_SIZE {
47 Pointer::parse(&head).ok()
48 } else {
49 None
50 };
51 Ok((head, pointer))
52}