Compare commits

..

14 commits

3 changed files with 24 additions and 115 deletions

View file

@ -6,8 +6,8 @@ edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.95" anyhow = "1.0.95"
clap = { version = "4.5.26", features = ["derive"] } clap = { version = "4.5.26", features = ["derive"] }
mktemp = "0.5.1"
rand = "0.9.1" rand = "0.9.1"
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }
tempfile = "3.20.0"
thiserror = "2.0.11" thiserror = "2.0.11"
toml = "0.8.19" toml = "0.8.19"

View file

@ -4,12 +4,8 @@ use clap::Parser;
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Args { struct Args {
/// Directory containing issues. /// Directory containing issues.
#[arg(short = 'd', long)] #[arg(short, long)]
issues_dir: Option<String>, issues_dir: String,
/// Branch containing issues.
#[arg(short = 'b', long)]
issues_branch: Option<String>,
/// Type of behavior/output. /// Type of behavior/output.
#[command(subcommand)] #[command(subcommand)]
@ -28,11 +24,14 @@ enum Commands {
}, },
} }
fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
match &args.command { let args: Args = Args::parse();
// println!("{:?}", args);
match args.command {
Commands::List => { Commands::List => {
let issues = let issues =
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; entomologist::issues::Issues::new_from_dir(std::path::Path::new(&args.issues_dir))?;
for (uuid, issue) in issues.issues.iter() { for (uuid, issue) in issues.issues.iter() {
println!("{} {} ({:?})", uuid, issue.title, issue.state); println!("{} {} ({:?})", uuid, issue.title, issue.state);
} }
@ -47,31 +46,3 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<(
Ok(()) 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(())
}

View file

@ -8,41 +8,6 @@ pub enum GitError {
Oops, 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<Worktree, GitError> {
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( pub fn checkout_branch_in_worktree(
branch: &str, branch: &str,
worktree_dir: &std::path::Path, worktree_dir: &std::path::Path,
@ -82,17 +47,10 @@ pub fn git_remove_branch(branch: &str) -> Result<(), GitError> {
Ok(()) Ok(())
} }
pub fn git_branch_exists(branch: &str) -> Result<bool, GitError> {
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> { pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> {
{ {
let tmp_worktree = tempfile::tempdir().unwrap(); let tmp_worktree = mktemp::Temp::new_path();
create_orphan_branch_at_path(branch, tmp_worktree.path())?; create_orphan_branch_at_path(branch, &tmp_worktree.to_path_buf())?;
} }
// The temp dir is now removed / cleaned up. // The temp dir is now removed / cleaned up.
@ -107,14 +65,16 @@ pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> {
Ok(()) Ok(())
} }
pub fn create_orphan_branch_at_path(
fn create_orphan_branch_at_path(
branch: &str, branch: &str,
worktree_path: &std::path::Path, worktree_path: &std::path::PathBuf,
) -> Result<(), GitError> { ) -> Result<(), GitError> {
let worktree_dir = worktree_path.to_string_lossy(); let Some(worktree_dir) = worktree_path.to_str() else {
return Err(GitError::Oops);
};
let result = std::process::Command::new("git") let result = std::process::Command::new("git")
.args(["worktree", "add", "--orphan", "-b", branch, &worktree_dir]) .args(["worktree", "add", "--orphan", "-b", branch, worktree_dir])
.output()?; .output()?;
if !result.status.success() { if !result.status.success() {
println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap());
@ -122,7 +82,7 @@ fn create_orphan_branch_at_path(
return Err(GitError::Oops); return Err(GitError::Oops);
} }
let mut readme_filename = std::path::PathBuf::from(worktree_path); let mut readme_filename = worktree_path.clone();
readme_filename.push("README.md"); readme_filename.push("README.md");
let mut readme = std::fs::File::create(readme_filename)?; let mut readme = std::fs::File::create(readme_filename)?;
write!( write!(
@ -132,7 +92,7 @@ fn create_orphan_branch_at_path(
let result = std::process::Command::new("git") let result = std::process::Command::new("git")
.args(["add", "README.md"]) .args(["add", "README.md"])
.current_dir(worktree_path) .current_dir(worktree_dir)
.output()?; .output()?;
if !result.status.success() { if !result.status.success() {
println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap());
@ -159,19 +119,12 @@ mod tests {
#[test] #[test]
fn test_worktree() { fn test_worktree() {
let mut p = std::path::PathBuf::new();
{ {
let worktree = Worktree::new("origin/main").unwrap(); let temp_worktree = mktemp::Temp::new_path();
checkout_branch_in_worktree("origin/main", temp_worktree.as_path()).unwrap();
p.push(worktree.path()); // The temporary worktree directory is removed when the Temp variable is dropped.
assert!(p.exists());
let mut p2 = p.clone();
p2.push("README.md");
assert!(p2.exists());
} }
// The temporary worktree directory is removed when the Temp variable is dropped. git_worktree_prune().unwrap();
assert!(!p.exists());
} }
#[test] #[test]
@ -182,19 +135,4 @@ mod tests {
create_orphan_branch(&branch).unwrap(); create_orphan_branch(&branch).unwrap();
git_remove_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);
}
} }