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