use std::{error::Error, fs, process::Command}; use git2::{Repository, Signature}; use tempfile::TempDir; // shared test helpers mod common; use common::{ init_bare_repo, init_repo_with_commit, pakker_bin_path, push_to_remote, }; #[test] fn happy_path_from_path() -> Result<(), Box> { let tmp = TempDir::new()?; let tmp_path = tmp.path().to_path_buf(); let upstream = tmp_path.join("upstream.git"); init_bare_repo(&upstream)?; let work = tmp_path.join("work_repo"); fs::create_dir_all(&work)?; let work_repo = init_repo_with_commit(&work, "README.md", "hello")?; push_to_remote(&work_repo, "origin", upstream.to_str().unwrap())?; // Clone upstream to local path let local = tmp_path.join("local_clone"); Repository::clone(upstream.to_str().unwrap(), &local)?; // Now create a new project dir where pakker will be initialized let project = tmp_path.join("project_dir"); fs::create_dir_all(&project)?; let pakker = pakker_bin_path(); let status = Command::new(pakker) .args([ "fork", "init", "--from-path", local.to_str().unwrap(), "--ref-name", "master", ]) .current_dir(&project) .status()?; assert!(status.success()); let parent = project.join(".pakku").join("parent"); assert!(parent.exists()); Ok(()) } #[test] fn fails_with_uncommitted_changes() -> Result<(), Box> { let tmp = TempDir::new()?; let tmp_path = tmp.path().to_path_buf(); let upstream = tmp_path.join("upstream2.git"); init_bare_repo(&upstream)?; let work = tmp_path.join("work2"); fs::create_dir_all(&work)?; let work_repo = init_repo_with_commit(&work, "file.txt", "a")?; work_repo.remote("origin", upstream.to_str().unwrap())?; push_to_remote(&work_repo, "origin", upstream.to_str().unwrap())?; let local = tmp_path.join("local2"); Repository::clone(upstream.to_str().unwrap(), &local)?; // Make an uncommitted change fs::write(local.join("UNCOMMITTED.md"), "oops")?; let project = tmp_path.join("project2"); fs::create_dir_all(&project)?; let pakker = pakker_bin_path(); let output = Command::new(pakker) .args([ "fork", "init", "--from-path", local.to_str().unwrap(), "--ref-name", "master", ]) .current_dir(&project) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.to_lowercase().contains("uncommitted") || stderr.to_lowercase().contains("dirty") ); Ok(()) } #[test] fn fails_when_local_ahead() -> Result<(), Box> { let tmp = TempDir::new()?; let tmp_path = tmp.path().to_path_buf(); let upstream = tmp_path.join("upstream3.git"); init_bare_repo(&upstream)?; let work = tmp_path.join("work3"); fs::create_dir_all(&work)?; let work_repo = init_repo_with_commit(&work, "f.txt", "1")?; work_repo.remote("origin", upstream.to_str().unwrap())?; push_to_remote(&work_repo, "origin", upstream.to_str().unwrap())?; let local = tmp_path.join("local3"); Repository::clone(upstream.to_str().unwrap(), &local)?; // Create a new commit locally that is not pushed { let repo = Repository::open(&local)?; let workdir = repo.workdir().ok_or("no workdir")?; fs::write(workdir.join("f.txt"), "2")?; let mut index = repo.index()?; index.add_path(std::path::Path::new("f.txt"))?; index.write()?; let tree_oid = index.write_tree()?; let tree = repo.find_tree(tree_oid)?; let sig = Signature::now("Test User", "test@example.com")?; let head = repo.head()?; let parent = head.peel_to_commit()?; repo.commit(Some("HEAD"), &sig, &sig, "local change", &tree, &[&parent])?; } let project = tmp_path.join("project3"); fs::create_dir_all(&project)?; let pakker = pakker_bin_path(); let output = Command::new(pakker) .args([ "fork", "init", "--from-path", local.to_str().unwrap(), "--ref-name", "master", ]) .current_dir(&project) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.to_lowercase().contains("commits not present") || stderr.to_lowercase().contains("not present on upstream") ); Ok(()) } #[test] fn warns_on_fetch_failure_and_proceeds() -> Result<(), Box> { let tmp = TempDir::new()?; let tmp_path = tmp.path().to_path_buf(); let upstream = tmp_path.join("upstream4.git"); init_bare_repo(&upstream)?; let work = tmp_path.join("work4"); fs::create_dir_all(&work)?; let work_repo = init_repo_with_commit(&work, "a.txt", "1")?; work_repo.remote("origin", upstream.to_str().unwrap())?; push_to_remote(&work_repo, "origin", upstream.to_str().unwrap())?; let local = tmp_path.join("local4"); Repository::clone(upstream.to_str().unwrap(), &local)?; // Set an invalid URL for origin to force fetch failure let repo = Repository::open(&local)?; repo.remote_set_url("origin", "git@invalid:nonexistent/repo.git")?; let project = tmp_path.join("project4"); fs::create_dir_all(&project)?; let pakker = pakker_bin_path(); let status = Command::new(pakker) .args([ "fork", "init", "--from-path", local.to_str().unwrap(), "--ref-name", "master", ]) .current_dir(&project) .status()?; assert!(status.success()); let parent = project.join(".pakku").join("parent"); assert!(parent.exists()); Ok(()) }