use crate::config_snapshot::ConfigSnapshot;
use std::ffi::OsString;
pub type Query<'a> = &'a mut dyn FnMut(&[&str]) -> Result<String, String>;
pub type Settings = Vec<(String, String)>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanError(pub String);
pub const SETTINGS_REGEXP: &str =
"^(branch\\..*\\.(pushremote|remote)|remote\\.pushdefault|remote\\..*\\.(push|pushurl|mirror|url)|push\\.default|url\\..*\\.pushinsteadof)$";
pub(crate) fn wanted_key(key: &str) -> bool {
let remote_variable = |suffixes: &[&str]| -> bool {
return key.strip_prefix("remote.").is_some_and(|rest| {
return rest.rsplit_once('.').is_some_and(|(_name, variable)| {
return suffixes.contains(&variable);
});
});
};
let branch_variable: bool = key.strip_prefix("branch.").is_some_and(|rest| {
return rest.rsplit_once('.').is_some_and(|(_name, variable)| {
return variable == "pushremote" || variable == "remote";
});
});
let push_rewrite: bool = key.strip_prefix("url.").is_some_and(|rest| {
return rest.ends_with(".pushinsteadof");
});
return branch_variable
|| push_rewrite
|| key == "remote.pushdefault"
|| key == "push.default"
|| remote_variable(&["push", "pushurl", "mirror", "url"]);
}
pub fn settings_from_snapshot(snapshot: &ConfigSnapshot) -> Settings {
return snapshot
.entries()
.iter()
.filter(|entry| {
return wanted_key(entry.key.as_str());
})
.map(|entry| {
let value: String = entry
.value
.as_deref()
.map(|bytes| {
return String::from_utf8_lossy(bytes).into_owned();
})
.unwrap_or_default();
return (entry.key.clone(), value);
})
.collect();
}
pub fn read_settings(query: Query<'_>) -> Result<Settings, PlanError> {
let listing: String = query(&["config", "--get-regexp", SETTINGS_REGEXP])
.map_err(|why| {
return PlanError(format!("git config --get-regexp: {why}"));
})?;
return Ok(listing
.lines()
.map(|line| {
let (key, value) = line.split_once(' ').unwrap_or((line, ""));
return (key.to_owned(), value.trim().to_owned());
})
.collect());
}
pub(crate) fn setting<'a>(all: &'a [(String, String)], key: &str) -> Option<&'a str> {
return all
.iter()
.rev()
.find(|(name, _)| {
return name == key;
})
.map(|(_, value)| {
return value.as_str();
});
}
pub(crate) fn values<'a>(all: &'a [(String, String)], key: &str) -> Vec<&'a str> {
return all
.iter()
.filter(|(name, _)| {
return name == key;
})
.map(|(_, value)| {
return value.as_str();
})
.collect();
}
pub(crate) fn current_branch(query: Query<'_>) -> Result<Option<String>, PlanError> {
let full: String = query(&["symbolic-ref", "--quiet", "HEAD"]).map_err(|why| {
return PlanError(format!("git symbolic-ref HEAD: {why}"));
})?;
return Ok(full.strip_prefix("refs/heads/").map(str::to_owned));
}
pub(crate) fn default_remote(branch: Option<&str>, all: &[(String, String)]) -> String {
let named = |variable: &str| -> Option<String> {
return branch.and_then(|name| {
return setting(all, &format!("branch.{name}.{variable}")).map(str::to_owned);
});
};
return named("pushremote")
.or_else(|| {
return setting(all, "remote.pushdefault").map(str::to_owned);
})
.or_else(|| {
return named("remote");
})
.unwrap_or_else(|| {
return String::from("origin");
});
}
pub fn resolve_push_remote(named: Option<OsString>, query: Query<'_>) -> Result<OsString, PlanError> {
if let Some(found) = named {
return Ok(found);
}
let branch: Option<String> = current_branch(query)?;
let all: Settings = read_settings(query)?;
return Ok(OsString::from(default_remote(branch.as_deref(), all.as_slice())));
}
pub(crate) fn reads_as_true(value: &str) -> bool {
return ["true", "yes", "on", "1", ""].iter().any(|spelling| {
return value.eq_ignore_ascii_case(spelling);
});
}