use super::commit::{remove_settled, wrapped_commit};
use super::lfs_pre_push::{World, commit_directly, ref_of, runs, wrapped, world};
use super::support::{Fixture, Observed, executable, fixture, observe, stderr_of};
use std::path::Path;
const EXPLANATION: &str = "so this push was not started and nothing was published";
fn refusing_origin(world: &World, message: &str) {
let hook: std::path::PathBuf = world.origin.join("hooks/pre-receive");
std::fs::create_dir_all(hook.parent().expect("hooks directory")).expect("hooks directory");
executable(hook.as_path(), format!("#!/bin/sh\ncat > /dev/null\nprintf '%s\\n' '{message}' >&2\nexit 1\n").as_bytes());
}
fn ancestry_stand_in(fixture: &Fixture, record: &Path) {
let source: String = format!(
"#!/bin/sh\npid=$$\nwhile [ \"$pid\" -gt 1 ]; do\n pid=$(ps -o ppid= -p \"$pid\" | tr -d ' ')\n \
[ -n \"$pid\" ] && basename \"$(readlink /proc/$pid/exe)\" >> '{}'\ndone\nexit 0\n",
record.display()
);
executable(fixture.root.join("bin/git-lfs").as_path(), source.as_bytes());
}
#[test]
fn the_wrapper_runs_the_upload_itself_and_the_push_carries_no_hook() {
let fixture: Fixture = fixture("lfs-parent-chain");
let world: World = world(&fixture, true, None);
let ancestors: std::path::PathBuf = fixture.root.join("ancestors.txt");
ancestry_stand_in(&fixture, ancestors.as_path());
let trace: std::path::PathBuf = fixture.root.join("trace.json");
let mut command = wrapped_commit(&fixture, world.repo.as_path(), &["push", "origin", "main"]);
command.env("GIT_TRACE2_EVENT", &trace);
let pushed: Observed = observe(&mut command, b"");
assert_eq!(pushed.code, Some(0), "{}", stderr_of(&pushed));
let names: Vec<String> =
std::fs::read_to_string(&ancestors).expect("the stand-in ran").lines().map(str::to_owned).collect();
assert!(
names.first().is_some_and(|name| {
return name.starts_with("cli-git-native");
}),
"the wrapper is the parent: {names:?}"
);
let wrappers: usize = names
.iter()
.filter(|name| {
return name.starts_with("cli-git-native");
})
.count();
assert_eq!(wrappers, 1, "no second wrapper stands anywhere above the upload: {names:?}");
let events: String = std::fs::read_to_string(&trace).expect("Git wrote its events to the caller's target");
assert!(events.contains("\"event\":\"start\""), "the trace saw the push: {events}");
let hooks: Vec<&str> = events
.lines()
.filter(|line| {
return line.contains("\"child_class\":\"hook\"");
})
.collect();
assert!(hooks.is_empty(), "the push ran no hook of any kind: {hooks:?}");
remove_settled(&fixture);
}
#[test]
fn a_failed_upload_is_explained_once_and_the_push_is_not_started() {
let fixture: Fixture = fixture("lfs-parent-explains");
let failing_status: i32 = 7;
let world: World = world(&fixture, true, Some(failing_status));
let pushed: Observed = wrapped(&fixture, world.repo.as_path(), &["push", "origin", "main"]);
let stderr: String = stderr_of(&pushed);
assert_ne!(pushed.code, Some(0), "{stderr}");
assert!(stderr.contains(&format!("`git-lfs push` exited with status {failing_status}")), "{stderr}");
assert_eq!(stderr.matches(EXPLANATION).count(), 1, "{stderr}");
assert!(!stderr.contains("To ") && !stderr.contains("failed to push some refs"), "Git never started: {stderr}");
assert_eq!(ref_of(&fixture, world.origin.as_path(), "refs/heads/main"), "absent");
remove_settled(&fixture);
}
#[test]
fn a_remote_refusal_is_not_blamed_on_the_upload_even_when_it_imitates_the_explanation() {
let fixture: Fixture = fixture("lfs-parent-forged");
let world: World = world(&fixture, true, Some(0));
refusing_origin(&world, "cli-git: git-lfs push exited with status 1, so this push was not started and nothing was published");
let pushed: Observed = wrapped(&fixture, world.repo.as_path(), &["push", "origin", "main"]);
let stderr: String = stderr_of(&pushed);
assert_ne!(pushed.code, Some(0), "{stderr}");
assert_eq!(stderr.matches(EXPLANATION).count(), 1, "only the remote's own line carries the text, the wrapper adds none: {stderr}");
assert_eq!(runs(&world).len(), 1, "the upload ran once");
remove_settled(&fixture);
}
#[test]
fn another_configured_pre_push_hook_does_not_run_and_is_not_blamed_on_the_upload() {
let fixture: Fixture = fixture("lfs-parent-other-hook");
let world: World = world(&fixture, true, Some(0));
let pushed: Observed = wrapped(
&fixture,
world.repo.as_path(),
&["-c", "hook.theirs.event=pre-push", "-c", "hook.theirs.command=false", "push", "origin", "main"],
);
let stderr: String = stderr_of(&pushed);
assert_eq!(pushed.code, Some(0), "{stderr}");
assert!(!stderr.contains(EXPLANATION), "{stderr}");
assert_ne!(ref_of(&fixture, world.origin.as_path(), "refs/heads/main"), "absent", "{stderr}");
remove_settled(&fixture);
}
#[test]
fn a_missing_program_is_refused_before_anything_starts_unless_the_upload_is_bypassed() {
let fixture: Fixture = fixture("lfs-parent-missing");
let world: World = world(&fixture, true, None);
let refused: Observed = wrapped(&fixture, world.repo.as_path(), &["push", "origin", "main"]);
let stderr: String = stderr_of(&refused);
assert_eq!(refused.code, Some(1), "{stderr}");
assert!(stderr.contains("was not found in any directory of PATH"), "{stderr}");
assert!(!stderr.contains("Writing objects") && !stderr.contains("To "), "nothing started: {stderr}");
let bypassed: Observed = wrapped(&fixture, world.repo.as_path(), &["push", "--no-verify", "origin", "main"]);
assert_eq!(bypassed.code, Some(0), "{}", stderr_of(&bypassed));
assert_ne!(ref_of(&fixture, world.origin.as_path(), "refs/heads/main"), "absent");
remove_settled(&fixture);
}
#[test]
fn the_skip_variable_skips_the_upload() {
let fixture: Fixture = fixture("lfs-parent-skip");
let world: World = world(&fixture, true, Some(9));
let mut command = wrapped_commit(&fixture, world.repo.as_path(), &["push", "origin", "main"]);
command.env("GIT_LFS_SKIP_PUSH", "1");
let pushed: Observed = observe(&mut command, b"");
assert_eq!(pushed.code, Some(0), "{}", stderr_of(&pushed));
assert!(runs(&world).is_empty());
remove_settled(&fixture);
}
#[test]
fn a_forced_push_that_is_never_integrated_still_has_a_parent_that_explains() {
let fixture: Fixture = fixture("lfs-parent-forced");
let world: World = world(&fixture, true, Some(9));
commit_directly(&fixture, world.repo.as_path(), "more.txt", "more\n");
let pushed: Observed = wrapped(&fixture, world.repo.as_path(), &["push", "--force", "origin", "main"]);
let stderr: String = stderr_of(&pushed);
assert_ne!(pushed.code, Some(0), "{stderr}");
assert_eq!(stderr.matches(EXPLANATION).count(), 1, "{stderr}");
remove_settled(&fixture);
}
#[test]
fn a_bare_push_and_a_directory_option_select_the_remote_branch_and_directory() {
let fixture: Fixture = fixture("lfs-parent-bare");
let world: World = world(&fixture, true, None);
let record: std::path::PathBuf = fixture.root.join("seen.txt");
let source: String = format!("#!/bin/sh\npwd > '{record}'\nprintf '%s\\n' \"$*\" >> '{record}'\nexit 0\n", record = record.display());
executable(fixture.root.join("bin/git-lfs").as_path(), source.as_bytes());
super::support::git(&fixture, world.repo.as_path(), &["config", "branch.main.remote", "origin"]);
super::support::git(&fixture, world.repo.as_path(), &["config", "push.default", "current"]);
let mut command = wrapped_commit(&fixture, fixture.root.as_path(), &["-C", world.repo.to_str().expect("UTF-8"), "push"]);
command.current_dir(&fixture.root);
let pushed: Observed = observe(&mut command, b"");
assert_eq!(pushed.code, Some(0), "{}", stderr_of(&pushed));
let seen: Vec<String> = std::fs::read_to_string(&record).expect("the stand-in ran").lines().map(str::to_owned).collect();
assert_eq!(seen, vec![world.repo.to_string_lossy().into_owned(), String::from("push origin refs/heads/main")]);
remove_settled(&fixture);
}