diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 6fb30c9..8bc0817 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -132,27 +132,40 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } } - Commands::New { - description: Some(description), - } => { + Commands::New { description } => { let mut issue = entomologist::issue::Issue::new(issues_dir)?; - issue.set_description(description)?; - println!("created new issue '{}'", issue.title()); - } - - Commands::New { description: None } => { - let mut issue = entomologist::issue::Issue::new(issues_dir)?; - issue.edit_description()?; - println!("created new issue '{}'", issue.title()); + let r = match description { + Some(description) => issue.set_description(description), + None => issue.edit_description(), + }; + match r { + Err(entomologist::issue::IssueError::EmptyDescription) => { + println!("no new issue created"); + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + Ok(()) => { + println!("created new issue '{}'", issue.title()); + } + } } Commands::Edit { issue_id } => { let mut issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; match issues.get_mut_issue(issue_id) { - Some(issue) => { - issue.edit_description()?; - } + Some(issue) => match issue.edit_description() { + Err(entomologist::issue::IssueError::EmptyDescription) => { + println!("aborted issue edit"); + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + Ok(()) => (), + }, None => { return Err(anyhow::anyhow!("issue {} not found", issue_id)); } @@ -227,12 +240,20 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( return Err(anyhow::anyhow!("issue {} not found", issue_id)); }; let mut comment = issue.new_comment()?; - match description { - Some(description) => { - comment.set_description(description)?; + let r = match description { + Some(description) => comment.set_description(description), + None => comment.edit_description(), + }; + match r { + Err(entomologist::comment::CommentError::EmptyDescription) => { + println!("aborted new comment"); + return Ok(()); } - None => { - comment.edit_description()?; + Err(e) => { + return Err(e.into()); + } + Ok(()) => { + println!("created new comment {}", &comment.uuid); } } } diff --git a/src/comment.rs b/src/comment.rs index d6ed66e..e6c95fd 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -22,6 +22,8 @@ pub enum CommentError { GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] EditorError, + #[error("supplied description is empty")] + EmptyDescription, } impl Comment { @@ -60,6 +62,9 @@ impl Comment { } pub fn set_description(&mut self, description: &str) -> Result<(), CommentError> { + if description.len() == 0 { + return Err(CommentError::EmptyDescription); + } self.description = String::from(description); let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); @@ -79,6 +84,7 @@ impl Comment { pub fn edit_description(&mut self) -> Result<(), CommentError> { let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); + let exists = description_filename.exists(); let result = std::process::Command::new("vi") .arg(&description_filename.as_mut_os_str()) .spawn()? @@ -88,8 +94,31 @@ impl Comment { println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); return Err(CommentError::EditorError); } - crate::git::git_commit_file(&description_filename)?; - self.read_description()?; + if description_filename.exists() && description_filename.metadata()?.len() > 0 { + crate::git::add_file(&description_filename)?; + } else { + // User saved an empty file, which means they changed their + // mind and no longer want to edit the description. + if exists { + crate::git::restore_file(&description_filename)?; + } + return Err(CommentError::EmptyDescription); + } + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "new description for comment {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + ), + )?; + self.read_description()?; + } Ok(()) } } diff --git a/src/git.rs b/src/git.rs index 6018392..3374542 100644 --- a/src/git.rs +++ b/src/git.rs @@ -20,9 +20,24 @@ pub struct Worktree { impl Drop for Worktree { fn drop(&mut self) { - let _result = std::process::Command::new("git") - .args(["worktree", "remove", &self.path.path().to_string_lossy()]) + let result = std::process::Command::new("git") + .args([ + "worktree", + "remove", + "--force", + &self.path.path().to_string_lossy(), + ]) .output(); + match result { + Err(e) => { + println!("failed to run git: {:#?}", e); + } + Ok(result) => { + if !result.status.success() { + println!("failed to remove git worktree: {:#?}", result); + } + } + } } } @@ -91,6 +106,56 @@ pub fn git_branch_exists(branch: &str) -> Result { return Ok(result.status.success()); } +pub fn worktree_is_dirty(dir: &str) -> Result { + // `git status --porcelain` prints a terse list of files added or + // modified (both staged and not), and new untracked files. So if + // says *anything at all* it means the worktree is dirty. + let result = std::process::Command::new("git") + .args(["status", "--porcelain", "--untracked-files=no"]) + .current_dir(dir) + .output()?; + return Ok(result.stdout.len() > 0); +} + +pub fn add_file(file: &std::path::Path) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["add", &file.to_string_lossy()]) + .current_dir(file.parent().unwrap()) + .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); + } + return Ok(()); +} + +pub fn restore_file(file: &std::path::Path) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["restore", &file.to_string_lossy()]) + .current_dir(file.parent().unwrap()) + .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); + } + return Ok(()); +} + +pub fn commit(dir: &std::path::Path, msg: &str) -> Result<(), GitError> { + let result = std::process::Command::new("git") + .args(["commit", "-m", msg]) + .current_dir(dir) + .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(()) +} + pub fn git_commit_file(file: &std::path::Path) -> Result<(), GitError> { let mut git_dir = std::path::PathBuf::from(file); git_dir.pop(); diff --git a/src/issue.rs b/src/issue.rs index 68cb8f9..7201871 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -47,6 +47,8 @@ pub enum IssueError { GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] EditorError, + #[error("supplied description is empty")] + EmptyDescription, } impl FromStr for State { @@ -195,6 +197,9 @@ impl Issue { } pub fn set_description(&mut self, description: &str) -> Result<(), IssueError> { + if description.len() == 0 { + return Err(IssueError::EmptyDescription); + } self.description = String::from(description); let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); @@ -214,6 +219,7 @@ impl Issue { pub fn edit_description(&mut self) -> Result<(), IssueError> { let mut description_filename = std::path::PathBuf::from(&self.dir); description_filename.push("description"); + let exists = description_filename.exists(); let result = std::process::Command::new("vi") .arg(&description_filename.as_mut_os_str()) .spawn()? @@ -223,8 +229,31 @@ impl Issue { println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); return Err(IssueError::EditorError); } - crate::git::git_commit_file(&description_filename)?; - self.read_description()?; + if description_filename.exists() && description_filename.metadata()?.len() > 0 { + crate::git::add_file(&description_filename)?; + } else { + // User saved an empty file, which means they changed their + // mind and no longer want to edit the description. + if exists { + crate::git::restore_file(&description_filename)?; + } + return Err(IssueError::EmptyDescription); + } + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "new description for issue {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + ), + )?; + self.read_description()?; + } Ok(()) }