git_policy_cli/
lfs_upload_settings.rs1use super::config_snapshot::ConfigSnapshot;
15use std::ffi::OsString;
17
18pub type Query<'a> = &'a mut dyn FnMut(&[&str]) -> Result<String, String>;
21
22pub type Settings = Vec<(String, String)>;
24
25#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct PlanError(pub String);
33
34pub const SETTINGS_REGEXP: &str =
36 "^(branch\\..*\\.(pushremote|remote)|remote\\.pushdefault|remote\\..*\\.(push|pushurl|mirror|url)|push\\.default|url\\..*\\.pushinsteadof)$";
37
38pub(super) fn wanted_key(key: &str) -> bool {
40 let remote_variable = |suffixes: &[&str]| -> bool {
41 return key.strip_prefix("remote.").is_some_and(|rest| {
42 return rest.rsplit_once('.').is_some_and(|(_name, variable)| {
43 return suffixes.contains(&variable);
44 });
45 });
46 };
47 let branch_variable: bool = key.strip_prefix("branch.").is_some_and(|rest| {
48 return rest.rsplit_once('.').is_some_and(|(_name, variable)| {
49 return variable == "pushremote" || variable == "remote";
50 });
51 });
52 let push_rewrite: bool = key.strip_prefix("url.").is_some_and(|rest| {
53 return rest.ends_with(".pushinsteadof");
54 });
55 return branch_variable
56 || push_rewrite
57 || key == "remote.pushdefault"
58 || key == "push.default"
59 || remote_variable(&["push", "pushurl", "mirror", "url"]);
60}
61
62pub fn settings_from_snapshot(snapshot: &ConfigSnapshot) -> Settings {
69 return snapshot
70 .entries()
71 .iter()
72 .filter(|entry| {
73 return wanted_key(entry.key.as_str());
74 })
75 .map(|entry| {
76 let value: String = entry
77 .value
78 .as_deref()
79 .map(|bytes| {
80 return String::from_utf8_lossy(bytes).into_owned();
81 })
82 .unwrap_or_default();
83 return (entry.key.clone(), value);
84 })
85 .collect();
86}
87
88pub fn read_settings(query: Query<'_>) -> Result<Settings, PlanError> {
95 let listing: String = query(&["config", "--get-regexp", SETTINGS_REGEXP])
96 .map_err(|why| {
97 return PlanError(format!("git config --get-regexp: {why}"));
98 })?;
99 return Ok(listing
100 .lines()
101 .map(|line| {
102 let (key, value) = line.split_once(' ').unwrap_or((line, ""));
104 return (key.to_owned(), value.trim().to_owned());
105 })
106 .collect());
107}
108
109pub(super) fn setting<'a>(all: &'a [(String, String)], key: &str) -> Option<&'a str> {
111 return all
112 .iter()
113 .rev()
114 .find(|(name, _)| {
115 return name == key;
116 })
117 .map(|(_, value)| {
118 return value.as_str();
119 });
120}
121
122pub(super) fn values<'a>(all: &'a [(String, String)], key: &str) -> Vec<&'a str> {
124 return all
125 .iter()
126 .filter(|(name, _)| {
127 return name == key;
128 })
129 .map(|(_, value)| {
130 return value.as_str();
131 })
132 .collect();
133}
134
135pub(super) fn current_branch(query: Query<'_>) -> Result<Option<String>, PlanError> {
137 let full: String = query(&["symbolic-ref", "--quiet", "HEAD"]).map_err(|why| {
138 return PlanError(format!("git symbolic-ref HEAD: {why}"));
139 })?;
140 return Ok(full.strip_prefix("refs/heads/").map(str::to_owned));
141}
142
143pub(super) fn default_remote(branch: Option<&str>, all: &[(String, String)]) -> String {
146 let named = |variable: &str| -> Option<String> {
147 return branch.and_then(|name| {
148 return setting(all, &format!("branch.{name}.{variable}")).map(str::to_owned);
149 });
150 };
151 return named("pushremote")
152 .or_else(|| {
153 return setting(all, "remote.pushdefault").map(str::to_owned);
154 })
155 .or_else(|| {
156 return named("remote");
157 })
158 .unwrap_or_else(|| {
159 return String::from("origin");
160 });
161}
162
163pub fn resolve_push_remote(named: Option<OsString>, query: Query<'_>) -> Result<OsString, PlanError> {
173 if let Some(found) = named {
174 return Ok(found);
175 }
176 let branch: Option<String> = current_branch(query)?;
177 let all: Settings = read_settings(query)?;
178 return Ok(OsString::from(default_remote(branch.as_deref(), all.as_slice())));
179}
180
181pub(super) fn reads_as_true(value: &str) -> bool {
183 return ["true", "yes", "on", "1", ""].iter().any(|spelling| {
184 return value.eq_ignore_ascii_case(spelling);
185 });
186}