use super::commit::{remove_settled, wrapped_commit};
use super::service_commit::{Running, start};
use super::service_landing_support::{Case, case, compare, copy_beside, finished, text_of};
use super::support::{Fixture, direct, fixture, git, observe, repository};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
fn write(repo: &Path, name: &str, text: &str) {
std::fs::write(repo.join(name), text).expect("write");
}
fn numbered() -> String {
return (1..=30).map(|number| { return format!("line {number}\n"); }).collect();
}
pub(super) fn base_repository(fixture: &Fixture) -> PathBuf {
let repo: PathBuf = repository(fixture, OsStr::new("repo"));
write(&repo, "a.txt", numbered().as_str());
write(&repo, "b.txt", "other\n");
write(&repo, "c.txt", "third\n");
git(fixture, &repo, &["add", "a.txt", "b.txt", "c.txt"]);
git(fixture, &repo, &["commit", "--quiet", "--message=base"]);
git(fixture, &repo, &["config", "maintenance.autoDetach", "false"]);
return repo;
}
fn two_hunks_and_a_staged_file(fixture: &Fixture, repo: &Path) {
write(repo, "a.txt", numbered().replace("line 1\n", "line 1 changed\n").replace("line 30\n", "line 30 changed\n").as_str());
write(repo, "b.txt", "other\nstaged\n");
git(fixture, repo, &["add", "b.txt"]);
}
#[test]
fn include_patch_and_interactive_selections_equal_the_native_commit() {
let fixture: Fixture = fixture("landing-selection");
let base: PathBuf = base_repository(&fixture);
two_hunks_and_a_staged_file(&fixture, &base);
let name: String = format!("landing-selection-{}", std::process::id());
let mut service: Running = start(&fixture, name.as_str());
let cases: Vec<Case> = vec![
case("short include", &["commit", "-i", "-m", "included", "--", "a.txt"]),
case("long include", &["commit", "--include", "-m", "included", "--", "a.txt"]),
Case { index_commit: true, input: b"y\nn\n", ..case("short patch, first hunk only", &["commit", "-p", "-m", "patched"]) },
Case { index_commit: true, input: b"n\ny\n", ..case("long patch, second hunk only", &["commit", "--patch", "-m", "patched"]) },
Case { index_commit: true, input: b"y\ny\n", ..case("patch of every hunk", &["commit", "-p", "-m", "patched"]) },
Case { input: b"y\nn\n", ..case("patch of a named path", &["commit", "-p", "-m", "patched", "--", "a.txt"]) },
Case { index_commit: true, input: b"u\n1\n\nq\n", ..case("interactive update of one file", &["commit", "--interactive", "-m", "chosen"]) },
case("include with a message from a file", &["commit", "-i", "-F", "../message.txt", "--", "a.txt"]),
];
std::fs::write(fixture.root.join("message.txt"), "from a file\n\nbody\n").expect("message file");
for entry in &cases {
compare(&fixture, &base, name.as_str(), entry);
}
let _killed = service.kill();
let _waited = service.wait();
remove_settled(&fixture);
}
#[test]
fn a_pathspec_file_selects_the_paths_it_names() {
let fixture: Fixture = fixture("landing-pathspec-file");
let base: PathBuf = base_repository(&fixture);
write(&base, "a.txt", "changed a\n");
write(&base, "b.txt", "changed b\n");
write(&base, "c.txt", "changed c\n");
write(&base, "list.txt", "a.txt\nc.txt\n");
std::fs::write(base.join("list-nul.txt"), b"b.txt\0c.txt\0").expect("nul list");
git(&fixture, &base, &["add", "list.txt", "list-nul.txt"]);
git(&fixture, &base, &["commit", "--quiet", "--message=lists"]);
write(&base, "a.txt", "changed a\nagain\n");
write(&base, "b.txt", "changed b\nagain\n");
write(&base, "c.txt", "changed c\nagain\n");
let name: String = format!("landing-pathspec-{}", std::process::id());
let mut service: Running = start(&fixture, name.as_str());
let cases: Vec<Case> = vec![
case("a list file", &["commit", "-m", "listed", "--pathspec-from-file=list.txt"]),
case("a list file given as a separate word", &["commit", "-m", "listed", "--pathspec-from-file", "list.txt"]),
case("a NUL-separated list file", &["commit", "-m", "listed", "--pathspec-from-file=list-nul.txt", "--pathspec-file-nul"]),
Case { input: b"a.txt\nb.txt\n", ..case("a list from standard input", &["commit", "-m", "listed", "--pathspec-from-file=-"]) },
Case { input: b"b.txt\0", ..case("a NUL list from standard input", &["commit", "-m", "listed", "--pathspec-from-file=-", "--pathspec-file-nul"]) },
case("a list file with an editor-free amend", &["commit", "--amend", "--no-edit", "--pathspec-from-file=list.txt"]),
];
for entry in &cases {
compare(&fixture, &base, name.as_str(), entry);
}
let _killed = service.kill();
let _waited = service.wait();
remove_settled(&fixture);
}
#[test]
fn a_dry_run_and_the_status_formats_need_neither_the_transaction_nor_the_service() {
let fixture: Fixture = fixture("landing-dry-run");
let base: PathBuf = base_repository(&fixture);
write(&base, "a.txt", "changed a\n");
write(&base, "d.txt", "untracked\n");
git(&fixture, &base, &["add", "a.txt"]);
let head: String = text_of(&fixture, &base, &["rev-parse", "HEAD"]);
let lines: Vec<&[&str]> = vec![
&["commit", "--dry-run", "-m", "x", "--", "a.txt"],
&["commit", "--dry-run", "--no-enforce-only"],
&["commit", "--short", "--no-enforce-only"],
&["commit", "--short", "--branch", "--no-enforce-only"],
&["commit", "--porcelain", "--no-enforce-only"],
&["commit", "--long", "--no-enforce-only"],
&["commit", "-z", "--no-enforce-only"],
&["commit", "--porcelain", "-z", "--ahead-behind", "--no-enforce-only"],
&["commit", "--dry-run", "--untracked-files=no", "--no-enforce-only"],
];
for line in lines {
let served_repo: PathBuf = copy_beside(&fixture, &base, "dry");
let native_line: Vec<&str> = line.iter().copied().filter(|word| { return *word != "--no-enforce-only"; }).collect();
let mut native = direct(&fixture);
native.current_dir(&base).args(native_line.as_slice());
let expected = observe(&mut native, b"");
let mut wrapped = wrapped_commit(&fixture, &served_repo, line);
wrapped.env_remove("XDG_RUNTIME_DIR");
let found = observe(&mut wrapped, b"");
assert_eq!(found.code, expected.code, "{line:?}: {}", String::from_utf8_lossy(&found.stderr));
assert_eq!(found.stdout, expected.stdout, "{line:?}");
assert_eq!(text_of(&fixture, &served_repo, &["rev-parse", "HEAD"]), head, "{line:?}: nothing was committed");
assert!(!served_repo.join(".git/cli-git").exists(), "{line:?}: the transaction's area was not made");
assert!(std::fs::read_dir(served_repo.join(".git")).expect("git dir").flatten().all(|entry| {
return !entry.file_name().to_string_lossy().starts_with("cli-git-service-");
}), "{line:?}: no scratch directory of the service was made");
std::fs::remove_dir_all(&served_repo).expect("remove the copy");
}
assert_eq!(finished(&fixture), 0, "no landing happened");
remove_settled(&fixture);
}