use std::process::Command; use tempfile::TempDir; fn arc_cmd() -> Command { let mut cmd = Command::new(env!("CARGO_BIN_EXE_arc")); cmd.env("NO_COLOR", "1"); cmd } fn init_repo() -> TempDir { let dir = TempDir::new().unwrap(); arc_cmd() .arg("init") .current_dir(dir.path()) .output() .expect("failed to init"); dir } fn commit_file(dir: &TempDir, name: &str, content: &str, msg: &str) -> String { std::fs::write(dir.path().join(name), content).unwrap(); let output = arc_cmd() .args(["commit", msg]) .current_dir(dir.path()) .output() .expect("failed to commit"); assert!(output.status.success()); String::from_utf8_lossy(&output.stdout) .trim() .strip_prefix("✓ committed ") .unwrap() .to_string() } #[test] fn merge_diverged_branches() { let dir = init_repo(); commit_file(&dir, "base.txt", "base\n", "base commit"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "main-file.txt", "main\n", "main change"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "feature-file.txt", "feature\n", "feature change"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); let output = arc_cmd() .args(["merge", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("merged feature")); assert!(dir.path().join("base.txt").exists()); assert!(dir.path().join("main-file.txt").exists()); assert!(dir.path().join("feature-file.txt").exists()); } #[test] fn merge_creates_commit_with_two_parents() { let dir = init_repo(); commit_file(&dir, "base.txt", "base\n", "base"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "a.txt", "main\n", "main work"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "b.txt", "feature\n", "feature work"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); let output = arc_cmd() .args(["merge", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); let merged_line = stdout.trim(); assert!(merged_line.contains("merged")); let show_output = arc_cmd() .args(["show", "HEAD"]) .current_dir(dir.path()) .output() .expect("failed"); let show_stdout = String::from_utf8_lossy(&show_output.stdout); assert!(show_stdout.contains("parent")); } #[test] fn merge_fails_with_dirty_worktree() { let dir = init_repo(); commit_file(&dir, "a.txt", "a\n", "first"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "b.txt", "b\n", "main work"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "c.txt", "c\n", "feature work"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("dirty.txt"), "dirty\n").unwrap(); let output = arc_cmd() .args(["merge", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("uncommitted changes")); } #[test] fn merge_same_file_no_conflict() { let dir = init_repo(); std::fs::write(dir.path().join("a.txt"), "line1\nline2\nline3\n").unwrap(); arc_cmd() .args(["commit", "base"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("a.txt"), "line1\nline2\nline3\nmain-line\n").unwrap(); arc_cmd() .args(["commit", "main edit"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write( dir.path().join("a.txt"), "feature-line\nline1\nline2\nline3\n", ) .unwrap(); arc_cmd() .args(["commit", "feature edit"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); let output = arc_cmd() .args(["merge", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); } #[test] fn merge_conflict_reports_error() { let dir = init_repo(); commit_file(&dir, "a.txt", "original\n", "base"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("a.txt"), "main version\n").unwrap(); arc_cmd() .args(["commit", "main change"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("a.txt"), "feature version\n").unwrap(); arc_cmd() .args(["commit", "feature change"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); let output = arc_cmd() .args(["merge", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("conflict")); }