use crate::config_snapshot::ConfigSnapshot;
use std::ffi::OsString;
pub trait GitQuery {
fn ask(&mut self, arguments: &[&str]) -> Result<String, String>;
}
impl<Answer: FnMut(&[&str]) -> Result<String, String>> GitQuery for Answer {
fn ask(&mut self, arguments: &[&str]) -> Result<String, String> {
return self(arguments);
}
}
pub type Query<'a> = &'a mut dyn GitQuery;
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)$";
fn last_variable<'a>(key: &'a str, prefix: &str) -> Option<&'a str> {
let rest: &str = key.strip_prefix(prefix)?;
let (_name, variable) = rest.rsplit_once('.')?;
return Some(variable);
}
pub(crate) fn wanted_key(key: &str) -> bool {
let remote_variable: bool = match last_variable(key, "remote.") {
Some(variable) => ["push", "pushurl", "mirror", "url"].contains(&variable),
None => false,
};
let branch_variable: bool = match last_variable(key, "branch.") {
Some(variable) => variable == "pushremote" || variable == "remote",
None => false,
};
let push_rewrite: bool = match key.strip_prefix("url.") {
Some(rest) => rest.ends_with(".pushinsteadof"),
None => false,
};
return branch_variable
|| push_rewrite
|| key == "remote.pushdefault"
|| key == "push.default"
|| remote_variable;
}
pub fn settings_from_snapshot(snapshot: &ConfigSnapshot) -> Settings {
let mut settings: Settings = Vec::new();
for entry in snapshot.entries() {
if !wanted_key(entry.key.as_str()) {
continue;
}
let value: String = match entry.value.as_deref() {
Some(bytes) => String::from_utf8_lossy(bytes).into_owned(),
None => String::new(),
};
settings.push((entry.key.clone(), value));
}
return settings;
}
pub fn read_settings(query: Query<'_>) -> Result<Settings, PlanError> {
let listing: String = match query.ask(&["config", "--get-regexp", SETTINGS_REGEXP]) {
Ok(text) => text,
Err(why) => return Err(PlanError(format!("git config --get-regexp: {why}"))),
};
let mut settings: Settings = Vec::new();
for line in listing.lines() {
let (key, value) = line.split_once(' ').unwrap_or((line, ""));
settings.push((key.to_owned(), value.trim().to_owned()));
}
return Ok(settings);
}
pub(crate) fn setting<'a>(all: &'a [(String, String)], key: &str) -> Option<&'a str> {
for (name, value) in all.iter().rev() {
if name == key {
return Some(value.as_str());
}
}
return None;
}
pub(crate) fn values<'a>(all: &'a [(String, String)], key: &str) -> Vec<&'a str> {
let mut found: Vec<&'a str> = Vec::new();
for (name, value) in all {
if name == key {
found.push(value.as_str());
}
}
return found;
}
pub(crate) fn current_branch(query: Query<'_>) -> Result<Option<String>, PlanError> {
let full: String = match query.ask(&["symbolic-ref", "--quiet", "HEAD"]) {
Ok(text) => text,
Err(why) => return Err(PlanError(format!("git symbolic-ref HEAD: {why}"))),
};
return Ok(full.strip_prefix("refs/heads/").map(str::to_owned));
}
fn branch_setting(branch: Option<&str>, all: &[(String, String)], variable: &str) -> Option<String> {
let name: &str = branch?;
return setting(all, &format!("branch.{name}.{variable}")).map(str::to_owned);
}
pub(crate) fn default_remote(branch: Option<&str>, all: &[(String, String)]) -> String {
if let Some(found) = branch_setting(branch, all, "pushremote") {
return found;
}
if let Some(found) = setting(all, "remote.pushdefault") {
return found.to_owned();
}
if let Some(found) = branch_setting(branch, all, "remote") {
return found;
}
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 {
for spelling in ["true", "yes", "on", "1", ""] {
if value.eq_ignore_ascii_case(spelling) {
return true;
}
}
return false;
}