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 switch_to_bookmark() { let dir = init_repo(); commit_file(&dir, "a.txt", "v1\n", "first on main"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "a.txt", "v2\n", "second on main"); let output = arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("switched to bookmark feature")); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "v1\n"); } #[test] fn switch_updates_head_to_attached() { let dir = init_repo(); commit_file(&dir, "a.txt", "v1\n", "first"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "a.txt", "v2\n", "second"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); let head = std::fs::read_to_string(dir.path().join(".arc").join("HEAD")).unwrap(); assert!(head.contains("attached")); assert!(head.contains("feature")); } #[test] fn switch_to_tag() { let dir = init_repo(); commit_file(&dir, "a.txt", "v1\n", "first"); arc_cmd() .args(["tag", "add", "v1.0"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "a.txt", "v2\n", "second"); let output = arc_cmd() .args(["switch", "v1.0"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("switched to tag v1.0")); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "v1\n"); let head = std::fs::read_to_string(dir.path().join(".arc").join("HEAD")).unwrap(); assert!(head.contains("detached")); } #[test] fn switch_to_commit_prefix() { let dir = init_repo(); let id1 = commit_file(&dir, "a.txt", "v1\n", "first"); commit_file(&dir, "a.txt", "v2\n", "second"); let short = &id1[..12]; let output = arc_cmd() .args(["switch", short]) .current_dir(dir.path()) .output() .expect("failed"); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("switched to commit")); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "v1\n"); } #[test] fn switch_fails_with_dirty_worktree() { let dir = init_repo(); commit_file(&dir, "a.txt", "v1\n", "first"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("a.txt"), "modified\n").unwrap(); let output = arc_cmd() .args(["switch", "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 switch_removes_deleted_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "a\n", "first"); commit_file(&dir, "b.txt", "b\n", "add b"); arc_cmd() .args(["mark", "add", "with-b"]) .current_dir(dir.path()) .output() .expect("failed"); let id1_output = arc_cmd() .args(["log"]) .current_dir(dir.path()) .output() .expect("failed"); let _ = String::from_utf8_lossy(&id1_output.stdout); let first_id = { let log_output = arc_cmd() .args(["log"]) .current_dir(dir.path()) .output() .expect("failed"); let stdout = String::from_utf8_lossy(&log_output.stdout); let lines: Vec<&str> = stdout.lines().collect(); let last_commit_line = lines.iter().rev().find(|l| l.starts_with("●")).unwrap(); let parts: Vec<&str> = last_commit_line.split_whitespace().collect(); parts[1].to_string() }; arc_cmd() .args(["mark", "add", "just-a", &first_id]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "just-a"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(dir.path().join("a.txt").exists()); assert!(!dir.path().join("b.txt").exists()); } #[test] fn switch_adds_new_files() { let dir = init_repo(); commit_file(&dir, "a.txt", "a\n", "first"); arc_cmd() .args(["mark", "add", "no-b"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "b.txt", "b\n", "add b"); arc_cmd() .args(["mark", "add", "with-b"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "no-b"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(!dir.path().join("b.txt").exists()); arc_cmd() .args(["switch", "with-b"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(dir.path().join("b.txt").exists()); let content = std::fs::read_to_string(dir.path().join("b.txt")).unwrap(); assert_eq!(content, "b\n"); } #[test] fn switch_preserves_ignored_files() { let dir = init_repo(); std::fs::write(dir.path().join(".arcignore"), "*.log\n").unwrap(); commit_file(&dir, "a.txt", "a\n", "first"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::write(dir.path().join("debug.log"), "log data\n").unwrap(); commit_file(&dir, "a.txt", "v2\n", "second"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(dir.path().join("debug.log").exists()); } #[test] fn switch_back_and_forth() { let dir = init_repo(); commit_file(&dir, "a.txt", "main-v1\n", "main first"); arc_cmd() .args(["mark", "add", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); commit_file(&dir, "a.txt", "main-v2\n", "main second"); arc_cmd() .args(["switch", "feature"]) .current_dir(dir.path()) .output() .expect("failed"); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "main-v1\n"); arc_cmd() .args(["switch", "main"]) .current_dir(dir.path()) .output() .expect("failed"); let content = std::fs::read_to_string(dir.path().join("a.txt")).unwrap(); assert_eq!(content, "main-v2\n"); } #[test] fn switch_cleans_empty_directories() { let dir = init_repo(); std::fs::create_dir_all(dir.path().join("src")).unwrap(); std::fs::write(dir.path().join("src/main.rs"), "fn main() {}\n").unwrap(); commit_file(&dir, "root.txt", "root\n", "first"); arc_cmd() .args(["mark", "add", "with-src"]) .current_dir(dir.path()) .output() .expect("failed"); std::fs::remove_file(dir.path().join("src/main.rs")).unwrap(); std::fs::remove_dir(dir.path().join("src")).unwrap(); commit_file(&dir, "root.txt", "root v2\n", "remove src"); arc_cmd() .args(["mark", "add", "no-src"]) .current_dir(dir.path()) .output() .expect("failed"); arc_cmd() .args(["switch", "with-src"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(dir.path().join("src/main.rs").exists()); arc_cmd() .args(["switch", "no-src"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(!dir.path().join("src").exists()); } #[test] fn switch_fails_for_unknown_target() { let dir = init_repo(); commit_file(&dir, "a.txt", "hello\n", "first"); let output = arc_cmd() .args(["switch", "nonexistent"]) .current_dir(dir.path()) .output() .expect("failed"); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("unknown revision")); }