use std::path::Path;
use thiserror::Error;
use crate::git::{self, GitError};
pub const AGENT_NAME: &str = "git-lfs-object-store";
const KEY_STANDALONE: &str = "lfs.standalonetransferagent";
const KEY_PATH: &str = concat!("lfs.customtransfer.", "git-lfs-object-store", ".path");
const KEY_ARGS: &str = concat!("lfs.customtransfer.", "git-lfs-object-store", ".args");
#[cfg(test)]
const _: () = {
let agent = AGENT_NAME.as_bytes();
let needle = b"git-lfs-object-store";
assert!(agent.len() == needle.len());
let mut i = 0;
while i < needle.len() {
assert!(agent[i] == needle[i]);
i += 1;
}
};
#[derive(Debug, Error)]
pub enum InstallError {
#[error(transparent)]
Git(#[from] GitError),
}
pub fn install(cwd: &Path) -> Result<(), InstallError> {
git::config_set_many(cwd, &[(KEY_PATH, AGENT_NAME), (KEY_STANDALONE, AGENT_NAME)])?;
Ok(())
}
pub fn enable_debug(cwd: &Path) -> Result<(), InstallError> {
git::config_set(cwd, KEY_ARGS, "debug")?;
Ok(())
}
pub fn disable_debug(cwd: &Path) -> Result<(), InstallError> {
git::config_unset_if_present(cwd, KEY_ARGS)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{AGENT_NAME, KEY_ARGS, KEY_PATH};
#[test]
fn key_path_embeds_agent_name() {
assert!(
KEY_PATH.contains(AGENT_NAME),
"KEY_PATH = {KEY_PATH:?} must contain AGENT_NAME = {AGENT_NAME:?}",
);
}
#[test]
fn key_args_embeds_agent_name() {
assert!(
KEY_ARGS.contains(AGENT_NAME),
"KEY_ARGS = {KEY_ARGS:?} must contain AGENT_NAME = {AGENT_NAME:?}",
);
}
#[test]
fn key_shapes_match_lfs_customtransfer_namespace() {
assert_eq!(KEY_PATH, format!("lfs.customtransfer.{AGENT_NAME}.path"));
assert_eq!(KEY_ARGS, format!("lfs.customtransfer.{AGENT_NAME}.args"));
}
use std::path::Path;
use tempfile::TempDir;
use super::{KEY_STANDALONE, disable_debug, enable_debug, install};
use crate::git;
fn empty_repo() -> (TempDir, std::path::PathBuf) {
let dir = TempDir::new().expect("tempdir");
gix::init(dir.path()).expect("gix::init");
let path = dir.path().to_path_buf();
(dir, path)
}
fn config_values(cwd: &Path, key: &str) -> Vec<String> {
let repo = gix::discover(cwd).expect("discover");
let path = repo.common_dir().join("config");
let bytes = std::fs::read(&path).expect("read config");
let file = gix::config::File::from_bytes_no_includes(
&bytes,
gix::config::file::Metadata::api(),
gix::config::file::init::Options::default(),
)
.expect("parse");
file.raw_values(key)
.map(|values| {
values
.into_iter()
.map(|v| v.into_owned().to_string())
.collect()
})
.unwrap_or_default()
}
#[test]
fn install_is_idempotent_on_repeated_calls() {
let (_dir, cwd) = empty_repo();
install(&cwd).expect("first install");
install(&cwd).expect("second install");
install(&cwd).expect("third install");
assert_eq!(config_values(&cwd, KEY_PATH), vec![AGENT_NAME.to_owned()],);
assert_eq!(
config_values(&cwd, KEY_STANDALONE),
vec![AGENT_NAME.to_owned()],
);
}
#[test]
fn install_collapses_legacy_duplicate_entries() {
let (_dir, cwd) = empty_repo();
git::config_add(&cwd, KEY_PATH, AGENT_NAME).expect("seed 1");
git::config_add(&cwd, KEY_PATH, AGENT_NAME).expect("seed 2");
git::config_add(&cwd, KEY_STANDALONE, AGENT_NAME).expect("seed 1");
git::config_add(&cwd, KEY_STANDALONE, AGENT_NAME).expect("seed 2");
assert_eq!(
config_values(&cwd, KEY_PATH),
vec![AGENT_NAME.to_owned(), AGENT_NAME.to_owned()],
"seed step must produce two values for the collapse test to be meaningful",
);
assert_eq!(
config_values(&cwd, KEY_STANDALONE),
vec![AGENT_NAME.to_owned(), AGENT_NAME.to_owned()],
);
install(&cwd).expect("install");
assert_eq!(config_values(&cwd, KEY_PATH), vec![AGENT_NAME.to_owned()],);
assert_eq!(
config_values(&cwd, KEY_STANDALONE),
vec![AGENT_NAME.to_owned()],
);
}
#[test]
fn enable_debug_is_idempotent_on_repeated_calls() {
let (_dir, cwd) = empty_repo();
enable_debug(&cwd).expect("first");
enable_debug(&cwd).expect("second");
assert_eq!(config_values(&cwd, KEY_ARGS), vec!["debug".to_owned()]);
}
#[test]
fn disable_debug_is_idempotent_when_already_absent() {
let (_dir, cwd) = empty_repo();
disable_debug(&cwd).expect("disable on empty repo");
assert!(config_values(&cwd, KEY_ARGS).is_empty());
}
#[test]
fn disable_debug_then_disable_debug_succeeds() {
let (_dir, cwd) = empty_repo();
enable_debug(&cwd).expect("enable");
disable_debug(&cwd).expect("first disable");
disable_debug(&cwd).expect("second disable");
assert!(config_values(&cwd, KEY_ARGS).is_empty());
}
}