1use std::path::Path;
7
8use crate::config;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct FetchPruneConfig {
15 pub fetch_recent_refs_days: i64,
18 pub fetch_recent_refs_include_remotes: bool,
21 pub fetch_recent_commits_days: i64,
24 pub fetch_recent_always: bool,
26 pub prune_offset_days: i64,
30 pub prune_verify_remote_always: bool,
32 pub prune_verify_unreachable_always: bool,
34 pub prune_remote_name: String,
37}
38
39impl FetchPruneConfig {
40 pub fn from_repo(cwd: &Path) -> Self {
44 Self {
45 fetch_recent_refs_days: get_int(cwd, "lfs.fetchrecentrefsdays", 7),
46 fetch_recent_refs_include_remotes: get_bool(cwd, "lfs.fetchrecentremoterefs", true),
47 fetch_recent_commits_days: get_int(cwd, "lfs.fetchrecentcommitsdays", 0),
48 fetch_recent_always: get_bool(cwd, "lfs.fetchrecentalways", false),
49 prune_offset_days: get_int(cwd, "lfs.pruneoffsetdays", 3),
50 prune_verify_remote_always: get_bool(cwd, "lfs.pruneverifyremotealways", false),
51 prune_verify_unreachable_always: get_bool(
52 cwd,
53 "lfs.pruneverifyunreachablealways",
54 false,
55 ),
56 prune_remote_name: get_string(cwd, "lfs.pruneremotetocheck", "origin"),
57 }
58 }
59}
60
61fn get_int(cwd: &Path, key: &str, default: i64) -> i64 {
62 config::get_effective(cwd, key)
63 .ok()
64 .flatten()
65 .and_then(|s| s.trim().parse().ok())
66 .unwrap_or(default)
67}
68
69fn get_bool(cwd: &Path, key: &str, default: bool) -> bool {
74 let Ok(Some(raw)) = config::get_effective(cwd, key) else {
75 return default;
76 };
77 match raw.trim().to_ascii_lowercase().as_str() {
78 "true" | "yes" | "on" | "1" => true,
79 "false" | "no" | "off" | "0" | "" => false,
80 _ => default,
81 }
82}
83
84fn get_string(cwd: &Path, key: &str, default: &str) -> String {
85 let raw = config::get_effective(cwd, key).ok().flatten();
86 match raw {
87 Some(s) if !s.trim().is_empty() => s.trim().to_owned(),
88 _ => default.to_owned(),
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::tests::commit_helper;
96
97 fn set(cwd: &Path, key: &str, value: &str) {
98 std::process::Command::new("git")
99 .arg("-C")
100 .arg(cwd)
101 .args(["config", key, value])
102 .status()
103 .unwrap();
104 }
105
106 #[test]
107 fn defaults_match_upstream() {
108 let tmp = commit_helper::init_repo();
109 let cfg = FetchPruneConfig::from_repo(tmp.path());
110 assert_eq!(cfg.fetch_recent_refs_days, 7);
111 assert!(cfg.fetch_recent_refs_include_remotes);
112 assert_eq!(cfg.fetch_recent_commits_days, 0);
113 assert!(!cfg.fetch_recent_always);
114 assert_eq!(cfg.prune_offset_days, 3);
115 assert!(!cfg.prune_verify_remote_always);
116 assert!(!cfg.prune_verify_unreachable_always);
117 assert_eq!(cfg.prune_remote_name, "origin");
118 }
119
120 #[test]
121 fn reads_overrides() {
122 let tmp = commit_helper::init_repo();
123 set(tmp.path(), "lfs.fetchrecentrefsdays", "14");
124 set(tmp.path(), "lfs.fetchrecentcommitsdays", "30");
125 set(tmp.path(), "lfs.fetchrecentremoterefs", "false");
126 set(tmp.path(), "lfs.fetchrecentalways", "yes");
127 set(tmp.path(), "lfs.pruneoffsetdays", "0");
128 set(tmp.path(), "lfs.pruneremotetocheck", "upstream");
129 let cfg = FetchPruneConfig::from_repo(tmp.path());
130 assert_eq!(cfg.fetch_recent_refs_days, 14);
131 assert_eq!(cfg.fetch_recent_commits_days, 30);
132 assert!(!cfg.fetch_recent_refs_include_remotes);
133 assert!(cfg.fetch_recent_always);
134 assert_eq!(cfg.prune_offset_days, 0);
135 assert_eq!(cfg.prune_remote_name, "upstream");
136 }
137
138 #[test]
139 fn bool_accepts_git_styles() {
140 let tmp = commit_helper::init_repo();
141 for (raw, expected) in [
142 ("true", true),
143 ("TRUE", true),
144 ("yes", true),
145 ("On", true),
146 ("1", true),
147 ("false", false),
148 ("no", false),
149 ("OFF", false),
150 ("0", false),
151 ] {
152 set(tmp.path(), "lfs.fetchrecentalways", raw);
153 let cfg = FetchPruneConfig::from_repo(tmp.path());
154 assert_eq!(cfg.fetch_recent_always, expected, "raw={raw:?}");
155 }
156 }
157}