Skip to main content

git_lfs_transfer/
config.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4/// Optional URL transform applied to every action `href` returned by the
5/// batch endpoint before the transfer adapter dials it. Used to plumb
6/// `lfs.transfer.enablehrefrewrite` + `url.<base>.insteadOf` from the
7/// caller (which has the git-config context) down into the queue.
8pub type UrlRewriter = Arc<dyn Fn(&str) -> String + Send + Sync>;
9
10/// Tunables for the transfer queue.
11///
12/// Defaults aim at "sensible for a developer laptop on a corporate VPN" —
13/// not too aggressive on concurrency, generous retries for flaky links.
14/// Upstream Git LFS scales `concurrency` to CPU count (commit `aa08c37f`);
15/// we hard-code 8 for v0 and let callers override.
16#[derive(Clone)]
17pub struct TransferConfig {
18    /// Max number of concurrent in-flight transfers.
19    pub concurrency: usize,
20    /// Total attempts per object — including the first. So 9 means "try
21    /// once, then up to 8 retries". Matches upstream's
22    /// `defaultMaxRetries = 8` in `tq/manifest.go` (upstream counts
23    /// retries; we count attempts, hence +1).
24    pub max_attempts: u32,
25    /// Sleep before the first retry. Doubled before each subsequent retry,
26    /// capped at [`backoff_max`](Self::backoff_max).
27    pub initial_backoff: Duration,
28    /// Upper bound for exponential backoff between retries.
29    pub backoff_max: Duration,
30    /// Optional rewriter applied to every action URL before transfer.
31    /// `None` ⇒ use action URLs verbatim.
32    pub url_rewriter: Option<UrlRewriter>,
33    /// Max objects per `POST /objects/batch` call. The transfer queue
34    /// chunks the input list into runs of this size and issues one
35    /// batch API call per chunk. Default: 100 (matches upstream's
36    /// `lfs.transfer.batchSize` default). Values < 1 are clamped to 1.
37    pub batch_size: usize,
38    /// `lfs.<url>.contenttype` — when `true` (default), the basic
39    /// upload adapter sniffs the first 512 bytes of each object and
40    /// sets the `Content-Type` header on the action PUT to the
41    /// detected MIME type. When `false`, the adapter sends
42    /// `application/octet-stream` so a misconfigured CDN can't reject
43    /// the upload based on its content sniffing. Honored only when
44    /// the batch response didn't already set a `Content-Type` in
45    /// `action.header`.
46    pub detect_content_type: bool,
47}
48
49impl Default for TransferConfig {
50    fn default() -> Self {
51        Self {
52            concurrency: 8,
53            max_attempts: 9,
54            initial_backoff: Duration::from_millis(100),
55            backoff_max: Duration::from_secs(30),
56            url_rewriter: None,
57            batch_size: 100,
58            detect_content_type: true,
59        }
60    }
61}
62
63impl std::fmt::Debug for TransferConfig {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("TransferConfig")
66            .field("concurrency", &self.concurrency)
67            .field("max_attempts", &self.max_attempts)
68            .field("initial_backoff", &self.initial_backoff)
69            .field("backoff_max", &self.backoff_max)
70            .field("url_rewriter", &self.url_rewriter.as_ref().map(|_| "<fn>"))
71            .field("batch_size", &self.batch_size)
72            .field("detect_content_type", &self.detect_content_type)
73            .finish()
74    }
75}