use super::commit::{remove_settled, wrapped_commit};
use super::support::{Fixture, Observed, direct, executable, fixture, git, observe, repository, stderr_of};
use std::ffi::OsStr;
use std::path::PathBuf;
struct Scene {
repo: PathBuf,
markers: PathBuf,
}
fn scene(fixture: &Fixture) -> Scene {
let repo: PathBuf = repository(fixture, OsStr::new("repo"));
git(fixture, repo.as_path(), &["branch", "other"]);
return Scene { repo, markers: fixture.root.join("markers.txt") };
}
fn markers(scene: &Scene) -> String {
return std::fs::read_to_string(&scene.markers).unwrap_or_default();
}
fn program(fixture: &Fixture, scene: &Scene, name: &str, word: &str, rest: &str) -> PathBuf {
let path: PathBuf = fixture.root.join(name);
executable(
path.as_path(),
format!("#!/bin/sh\necho {word} >> '{}'\n{rest}\n", scene.markers.display()).as_bytes(),
);
return path;
}
fn wrapped(fixture: &Fixture, scene: &Scene, arguments: &[&str]) -> Observed {
return observe(&mut wrapped_commit(fixture, scene.repo.as_path(), arguments), b"");
}
fn post_checkout_hooks(fixture: &Fixture, scene: &Scene) -> PathBuf {
let hooks: PathBuf = fixture.root.join("alias-hooks");
std::fs::create_dir(&hooks).expect("hooks directory");
executable(
hooks.join("post-checkout").as_path(),
format!("#!/bin/sh\necho file >> '{}'\n", scene.markers.display()).as_bytes(),
);
let configured: PathBuf = program(fixture, scene, "configured-hook", "configured", "");
git(fixture, scene.repo.as_path(), &["config", "hook.control.event", "post-checkout"]);
git(fixture, scene.repo.as_path(), &["config", "hook.control.command", configured.to_str().expect("UTF-8")]);
return hooks;
}
#[test]
fn an_alias_that_carries_hook_options_cannot_bring_a_hook_back() {
let fixture: Fixture = fixture("hooks-off-alias-escape");
let scene: Scene = scene(&fixture);
let hooks: PathBuf = post_checkout_hooks(&fixture, &scene);
let value: String = format!(
"-c core.hooksPath={} -c hook.post-checkout.enabled=true checkout --quiet other",
hooks.display()
);
git(&fixture, scene.repo.as_path(), &["config", "alias.escape", value.as_str()]);
let control = direct(&fixture).current_dir(&scene.repo).args(["escape"]).output().expect("git starts");
assert!(control.status.success(), "{}", String::from_utf8_lossy(&control.stderr));
let ran: String = markers(&scene);
assert!(ran.contains("file") && ran.contains("configured"), "real Git must run both hooks: {ran:?}");
std::fs::remove_file(&scene.markers).expect("clear the markers");
git(&fixture, scene.repo.as_path(), &["checkout", "--quiet", "--force", "main"]);
std::fs::remove_file(&scene.markers).expect("clear the markers of the reset");
let switched: Observed = wrapped(&fixture, &scene, &["escape"]);
assert_eq!(switched.code, Some(0), "{}", stderr_of(&switched));
assert_eq!(markers(&scene), "", "no hook runs through an alias");
remove_settled(&fixture);
}
#[test]
fn a_core_fsmonitor_program_is_left_as_configured() {
let fixture: Fixture = fixture("hooks-off-fsmonitor");
let scene: Scene = scene(&fixture);
let monitor: PathBuf = program(&fixture, &scene, "fsmonitor", "fsmonitor", "exit 1");
git(&fixture, scene.repo.as_path(), &["config", "core.fsmonitor", monitor.to_str().expect("UTF-8")]);
let control = direct(&fixture).current_dir(&scene.repo).args(["status", "--short"]).output().expect("git starts");
assert!(control.status.success());
let direct_runs: String = markers(&scene);
assert!(direct_runs.contains("fsmonitor"), "real Git must run the program");
std::fs::remove_file(&scene.markers).expect("clear the markers");
let switched: Observed = wrapped(&fixture, &scene, &["status", "--short"]);
assert_eq!(switched.code, Some(0), "{}", stderr_of(&switched));
assert_eq!(markers(&scene), direct_runs, "the wrapper must leave the program exactly as configured");
assert!(!stderr_of(&switched).contains("external-hooks-unsupported"), "{}", stderr_of(&switched));
remove_settled(&fixture);
}
#[test]
fn a_git_started_by_the_config_editor_runs_no_hook() {
let fixture: Fixture = fixture("hooks-off-config-edit");
let scene: Scene = scene(&fixture);
let configured: PathBuf = program(&fixture, &scene, "configured-hook", "configured", "");
git(&fixture, scene.repo.as_path(), &["config", "hook.control.event", "post-checkout"]);
git(&fixture, scene.repo.as_path(), &["config", "hook.control.command", configured.to_str().expect("UTF-8")]);
let editor: PathBuf = program(&fixture, &scene, "editor", "editor", "/usr/bin/git checkout --quiet other");
let mut control = direct(&fixture);
control.current_dir(&scene.repo).args(["config", "--edit"]).env("GIT_EDITOR", &editor).env("TERM", "xterm");
assert!(control.status().expect("git starts").success());
assert!(markers(&scene).contains("configured"), "real Git must run the hook: {:?}", markers(&scene));
std::fs::remove_file(&scene.markers).expect("clear the markers");
git(&fixture, scene.repo.as_path(), &["checkout", "--quiet", "main"]);
std::fs::remove_file(&scene.markers).expect("clear the markers of the reset");
let mut command = wrapped_commit(&fixture, scene.repo.as_path(), &["config", "--edit"]);
command.env("GIT_EDITOR", &editor).env("TERM", "xterm");
let switched: Observed = observe(&mut command, b"");
assert_eq!(switched.code, Some(0), "{}", stderr_of(&switched));
assert_eq!(markers(&scene), "editor\n", "the editor ran and its Git ran no hook");
remove_settled(&fixture);
}