diff --git a/Cargo.toml b/Cargo.toml index addd833..1f7941d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ edition = "2024" [dependencies] anyhow = "1.0.95" clap = { version = "4.5.26", features = ["derive"] } -mktemp = "0.5.1" rand = "0.9.1" serde = { version = "1.0.217", features = ["derive"] } +tempfile = "3.20.0" thiserror = "2.0.11" toml = "0.8.19" diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 53ecf9d..86dfa95 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -4,8 +4,12 @@ use clap::Parser; #[command(version, about, long_about = None)] struct Args { /// Directory containing issues. - #[arg(short, long)] - issues_dir: String, + #[arg(short = 'd', long)] + issues_dir: Option, + + /// Branch containing issues. + #[arg(short = 'b', long)] + issues_branch: Option, /// Type of behavior/output. #[command(subcommand)] @@ -24,14 +28,11 @@ enum Commands { }, } -fn main() -> anyhow::Result<()> { - let args: Args = Args::parse(); - // println!("{:?}", args); - - match args.command { +fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { + match &args.command { Commands::List => { let issues = - entomologist::issues::Issues::new_from_dir(std::path::Path::new(&args.issues_dir))?; + entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; for (uuid, issue) in issues.issues.iter() { println!("{} {} ({:?})", uuid, issue.title, issue.state); } @@ -46,3 +47,31 @@ fn main() -> anyhow::Result<()> { Ok(()) } + +fn main() -> anyhow::Result<()> { + let args: Args = Args::parse(); + // println!("{:?}", args); + + if let (Some(_), Some(_)) = (&args.issues_dir, &args.issues_branch) { + return Err(anyhow::anyhow!( + "don't specify both `--issues-dir` and `--issues-branch`" + )); + } + + if let Some(dir) = &args.issues_dir { + let dir = std::path::Path::new(dir); + handle_command(&args, dir)?; + } else { + let branch = match &args.issues_branch { + Some(branch) => branch, + None => "entomologist-data", + }; + if !entomologist::git::git_branch_exists(branch)? { + entomologist::git::create_orphan_branch(branch)?; + } + let worktree = entomologist::git::Worktree::new(branch)?; + handle_command(&args, worktree.path())?; + } + + Ok(()) +} diff --git a/src/git.rs b/src/git.rs index 8d1ce46..2b03b5a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -8,6 +8,41 @@ pub enum GitError { Oops, } +#[derive(Debug)] +/// `Worktree` is a struct that manages a temporary directory containing +/// a checkout of a specific branch. The worktree is removed and pruned +/// when the `Worktree` struct is dropped. +pub struct Worktree { + path: tempfile::TempDir, +} + +impl Drop for Worktree { + fn drop(&mut self) { + let _result = std::process::Command::new("git") + .args(["worktree", "remove", &self.path.path().to_string_lossy()]) + .output(); + } +} + +impl Worktree { + pub fn new(branch: &str) -> Result { + let path = tempfile::tempdir()?; + let result = std::process::Command::new("git") + .args(["worktree", "add", &path.path().to_string_lossy(), branch]) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + Ok(Self { path }) + } + + pub fn path(&self) -> &std::path::Path { + self.path.as_ref() + } +} + pub fn checkout_branch_in_worktree( branch: &str, worktree_dir: &std::path::Path, @@ -47,10 +82,17 @@ pub fn git_remove_branch(branch: &str) -> Result<(), GitError> { Ok(()) } +pub fn git_branch_exists(branch: &str) -> Result { + let result = std::process::Command::new("git") + .args(["show-ref", "--quiet", branch]) + .output()?; + return Ok(result.status.success()); +} + pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { { - let tmp_worktree = mktemp::Temp::new_path(); - create_orphan_branch_at_path(branch, &tmp_worktree.to_path_buf())?; + let tmp_worktree = tempfile::tempdir().unwrap(); + create_orphan_branch_at_path(branch, tmp_worktree.path())?; } // The temp dir is now removed / cleaned up. @@ -65,16 +107,14 @@ pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { Ok(()) } -pub fn create_orphan_branch_at_path( - branch: &str, - worktree_path: &std::path::PathBuf, -) -> Result<(), GitError> { - let Some(worktree_dir) = worktree_path.to_str() else { - return Err(GitError::Oops); - }; +fn create_orphan_branch_at_path( + branch: &str, + worktree_path: &std::path::Path, +) -> Result<(), GitError> { + let worktree_dir = worktree_path.to_string_lossy(); let result = std::process::Command::new("git") - .args(["worktree", "add", "--orphan", "-b", branch, worktree_dir]) + .args(["worktree", "add", "--orphan", "-b", branch, &worktree_dir]) .output()?; if !result.status.success() { println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); @@ -82,7 +122,7 @@ pub fn create_orphan_branch_at_path( return Err(GitError::Oops); } - let mut readme_filename = worktree_path.clone(); + let mut readme_filename = std::path::PathBuf::from(worktree_path); readme_filename.push("README.md"); let mut readme = std::fs::File::create(readme_filename)?; write!( @@ -92,7 +132,7 @@ pub fn create_orphan_branch_at_path( let result = std::process::Command::new("git") .args(["add", "README.md"]) - .current_dir(worktree_dir) + .current_dir(worktree_path) .output()?; if !result.status.success() { println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); @@ -119,12 +159,19 @@ mod tests { #[test] fn test_worktree() { + let mut p = std::path::PathBuf::new(); { - let temp_worktree = mktemp::Temp::new_path(); - checkout_branch_in_worktree("origin/main", temp_worktree.as_path()).unwrap(); - // The temporary worktree directory is removed when the Temp variable is dropped. + let worktree = Worktree::new("origin/main").unwrap(); + + p.push(worktree.path()); + assert!(p.exists()); + + let mut p2 = p.clone(); + p2.push("README.md"); + assert!(p2.exists()); } - git_worktree_prune().unwrap(); + // The temporary worktree directory is removed when the Temp variable is dropped. + assert!(!p.exists()); } #[test] @@ -135,4 +182,19 @@ mod tests { create_orphan_branch(&branch).unwrap(); git_remove_branch(&branch).unwrap(); } + + #[test] + fn test_branch_exists_0() { + let r = git_branch_exists("main").unwrap(); + assert_eq!(r, true); + } + + #[test] + fn test_branch_exists_1() { + let rnd: u128 = rand::random(); + let mut branch = std::string::String::from("entomologist-missing-branch-"); + branch.push_str(&format!("{:0x}", rnd)); + let r = git_branch_exists(&branch).unwrap(); + assert_eq!(r, false); + } }