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