From cfb09c2e317707111c6db3110fc0cf05d6817634 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 1 Apr 2026 15:31:41 -0400 Subject: [PATCH] Add allow_empty to CommitOptions, detached worktree support, and new git operations - Add allow_empty field to CommitOptions and proto CommitOptions - Change create_worktree trait method to accept Option branch name (None means detached HEAD via --detach) - Add update_ref, delete_ref, and stage_all_including_untracked to the GitRepository trait with implementations for RealGitRepository --- crates/git/src/repository.rs | 84 ++++++++++++++++++++++++++++++------ crates/proto/proto/git.proto | 1 + 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index b03fe1b0c63904bfc751ab7946f92a7c8595db00..48648809e5dee26f5e678ef8942c4932318ec314 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -329,6 +329,7 @@ impl Upstream { pub struct CommitOptions { pub amend: bool, pub signoff: bool, + pub allow_empty: bool, } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] @@ -715,7 +716,7 @@ pub trait GitRepository: Send + Sync { fn create_worktree( &self, - branch_name: String, + branch_name: Option, path: PathBuf, from_commit: Option, ) -> BoxFuture<'_, Result<()>>; @@ -916,6 +917,12 @@ pub trait GitRepository: Send + Sync { fn commit_data_reader(&self) -> Result; + fn update_ref(&self, ref_name: String, commit: String) -> BoxFuture<'_, Result<()>>; + + fn delete_ref(&self, ref_name: String) -> BoxFuture<'_, Result<()>>; + + fn stage_all_including_untracked(&self) -> BoxFuture<'_, Result<()>>; + fn set_trusted(&self, trusted: bool); fn is_trusted(&self) -> bool; } @@ -1660,19 +1667,20 @@ impl GitRepository for RealGitRepository { fn create_worktree( &self, - branch_name: String, + branch_name: Option, path: PathBuf, from_commit: Option, ) -> BoxFuture<'_, Result<()>> { let git_binary = self.git_binary(); - let mut args = vec![ - OsString::from("worktree"), - OsString::from("add"), - OsString::from("-b"), - OsString::from(branch_name.as_str()), - OsString::from("--"), - OsString::from(path.as_os_str()), - ]; + let mut args = vec![OsString::from("worktree"), OsString::from("add")]; + if let Some(branch_name) = &branch_name { + args.push(OsString::from("-b")); + args.push(OsString::from(branch_name.as_str())); + } else { + args.push(OsString::from("--detach")); + } + args.push(OsString::from("--")); + args.push(OsString::from(path.as_os_str())); if let Some(from_commit) = from_commit { args.push(OsString::from(from_commit)); } else { @@ -2165,6 +2173,10 @@ impl GitRepository for RealGitRepository { cmd.arg("--signoff"); } + if options.allow_empty { + cmd.arg("--allow-empty"); + } + if let Some((name, email)) = name_and_email { cmd.arg("--author").arg(&format!("{name} <{email}>")); } @@ -2176,6 +2188,50 @@ impl GitRepository for RealGitRepository { .boxed() } + fn update_ref(&self, ref_name: String, commit: String) -> BoxFuture<'_, Result<()>> { + let git_binary = self.git_binary(); + self.executor + .spawn(async move { + let args: Vec = vec![ + "--no-optional-locks".into(), + "update-ref".into(), + ref_name.into(), + commit.into(), + ]; + git_binary?.run(&args).await?; + Ok(()) + }) + .boxed() + } + + fn delete_ref(&self, ref_name: String) -> BoxFuture<'_, Result<()>> { + let git_binary = self.git_binary(); + self.executor + .spawn(async move { + let args: Vec = vec![ + "--no-optional-locks".into(), + "update-ref".into(), + "-d".into(), + ref_name.into(), + ]; + git_binary?.run(&args).await?; + Ok(()) + }) + .boxed() + } + + fn stage_all_including_untracked(&self) -> BoxFuture<'_, Result<()>> { + let git_binary = self.git_binary(); + self.executor + .spawn(async move { + let args: Vec = + vec!["--no-optional-locks".into(), "add".into(), "-A".into()]; + git_binary?.run(&args).await?; + Ok(()) + }) + .boxed() + } + fn push( &self, branch_name: String, @@ -4009,7 +4065,7 @@ mod tests { // Create a new worktree repo.create_worktree( - "test-branch".to_string(), + Some("test-branch".to_string()), worktree_path.clone(), Some("HEAD".to_string()), ) @@ -4068,7 +4124,7 @@ mod tests { // Create a worktree let worktree_path = worktrees_dir.join("worktree-to-remove"); repo.create_worktree( - "to-remove".to_string(), + Some("to-remove".to_string()), worktree_path.clone(), Some("HEAD".to_string()), ) @@ -4092,7 +4148,7 @@ mod tests { // Create a worktree let worktree_path = worktrees_dir.join("dirty-wt"); repo.create_worktree( - "dirty-wt".to_string(), + Some("dirty-wt".to_string()), worktree_path.clone(), Some("HEAD".to_string()), ) @@ -4162,7 +4218,7 @@ mod tests { // Create a worktree let old_path = worktrees_dir.join("old-worktree-name"); repo.create_worktree( - "old-name".to_string(), + Some("old-name".to_string()), old_path.clone(), Some("HEAD".to_string()), ) diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index cb878cade726002e7e09670cf7c190880d8e66cb..18a9afad221485976022c9bf03a57249c7b1d9dd 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -403,6 +403,7 @@ message Commit { message CommitOptions { bool amend = 1; bool signoff = 2; + bool allow_empty = 3; } }