git: Add git worktree picker (#38719)
Related discussions #26084 Worktree creations are implemented similar to how branch creations are handled on the branch picker (the user types a new name that's not on the list and a new entry option appears to create a new branch with that name). https://github.com/user-attachments/assets/39e58983-740c-4a91-be88-57ef95aed85b With this picker you have a few workflows: - Open the picker and type the name of a branch that's checked out on an existing worktree: - Press enter to open the worktree on a new window - Press ctrl-enter to open the worktree and replace the current window - Open the picker and type the name of a new branch or an existing one that's not checked out in another worktree: - Press enter to create the worktree and open in a new window. If the branch doesn't exists, we will create a new one based on the branch you have currently checked out. If the branch does exists then we create a worktree with that branch checked out. - Press ctrl-enter to do everything on the previous point but instead, replace the current window with the new worktre. - Open the picker and type the name of a new branch or an existing one that's not checked out in another worktree: - If a default branch is detected on the repo, you can create a new worktree based on that branch by pressing ctrl-enter or ctrl-shift-enter. The first one will open a new window and the last one will replace the current one. Note: If you preffer to not use the system prompt for choosing a directory, you can set `"use_system_path_prompts": false` in zed settings. Release Notes: - Added git worktree picker to open a git worktree on a new window or replace the current one - Added git worktree creation action --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
co-authored by
Cole Miller
parent
57adf42492
commit
e5660d25f1
@@ -72,6 +72,50 @@ impl Branch {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Worktree {
|
||||
pub path: PathBuf,
|
||||
pub ref_name: SharedString,
|
||||
pub sha: SharedString,
|
||||
}
|
||||
|
||||
impl Worktree {
|
||||
pub fn branch(&self) -> &str {
|
||||
self.ref_name
|
||||
.as_ref()
|
||||
.strip_prefix("refs/heads/")
|
||||
.or_else(|| self.ref_name.as_ref().strip_prefix("refs/remotes/"))
|
||||
.unwrap_or(self.ref_name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_worktrees_from_str<T: AsRef<str>>(raw_worktrees: T) -> Vec<Worktree> {
|
||||
let mut worktrees = Vec::new();
|
||||
let entries = raw_worktrees.as_ref().split("\n\n");
|
||||
for entry in entries {
|
||||
let mut parts = entry.splitn(3, '\n');
|
||||
let path = parts
|
||||
.next()
|
||||
.and_then(|p| p.split_once(' ').map(|(_, path)| path.to_string()));
|
||||
let sha = parts
|
||||
.next()
|
||||
.and_then(|p| p.split_once(' ').map(|(_, sha)| sha.to_string()));
|
||||
let ref_name = parts
|
||||
.next()
|
||||
.and_then(|p| p.split_once(' ').map(|(_, ref_name)| ref_name.to_string()));
|
||||
|
||||
if let (Some(path), Some(sha), Some(ref_name)) = (path, sha, ref_name) {
|
||||
worktrees.push(Worktree {
|
||||
path: PathBuf::from(path),
|
||||
ref_name: ref_name.into(),
|
||||
sha: sha.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
worktrees
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Upstream {
|
||||
pub ref_name: SharedString,
|
||||
@@ -390,6 +434,15 @@ pub trait GitRepository: Send + Sync {
|
||||
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
|
||||
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>;
|
||||
|
||||
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>>;
|
||||
|
||||
fn create_worktree(
|
||||
&self,
|
||||
name: String,
|
||||
directory: PathBuf,
|
||||
from_commit: Option<String>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
|
||||
fn reset(
|
||||
&self,
|
||||
commit: String,
|
||||
@@ -1206,6 +1259,66 @@ impl GitRepository for RealGitRepository {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>> {
|
||||
let git_binary_path = self.any_git_binary_path.clone();
|
||||
let working_directory = self.working_directory();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let output = new_smol_command(&git_binary_path)
|
||||
.current_dir(working_directory?)
|
||||
.args(&["--no-optional-locks", "worktree", "list", "--porcelain"])
|
||||
.output()
|
||||
.await?;
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
Ok(parse_worktrees_from_str(&stdout))
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("git worktree list failed: {stderr}");
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn create_worktree(
|
||||
&self,
|
||||
name: String,
|
||||
directory: PathBuf,
|
||||
from_commit: Option<String>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
let git_binary_path = self.any_git_binary_path.clone();
|
||||
let working_directory = self.working_directory();
|
||||
let final_path = directory.join(&name);
|
||||
let mut args = vec![
|
||||
OsString::from("--no-optional-locks"),
|
||||
OsString::from("worktree"),
|
||||
OsString::from("add"),
|
||||
OsString::from(final_path.as_os_str()),
|
||||
];
|
||||
if let Some(from_commit) = from_commit {
|
||||
args.extend([
|
||||
OsString::from("-b"),
|
||||
OsString::from(name.as_str()),
|
||||
OsString::from(from_commit),
|
||||
]);
|
||||
}
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let output = new_smol_command(&git_binary_path)
|
||||
.current_dir(working_directory?)
|
||||
.args(args)
|
||||
.output()
|
||||
.await?;
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("git worktree list failed: {stderr}");
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
|
||||
let repo = self.repository.clone();
|
||||
let working_directory = self.working_directory();
|
||||
|
||||
Reference in New Issue
Block a user