@@ -53,7 +53,6 @@ pub struct FakeGitRepositoryState {
pub simulated_create_worktree_error: Option<String>,
pub refs: HashMap<String, String>,
pub graph_commits: Vec<Arc<InitialGraphCommitData>>,
- pub worktrees: Vec<Worktree>,
}
impl FakeGitRepositoryState {
@@ -73,7 +72,6 @@ impl FakeGitRepositoryState {
oids: Default::default(),
remotes: HashMap::default(),
graph_commits: Vec::new(),
- worktrees: Vec::new(),
}
}
}
@@ -409,32 +407,78 @@ impl GitRepository for FakeGitRepository {
}
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>> {
- let dot_git_path = self.dot_git_path.clone();
- self.with_state_async(false, move |state| {
- let work_dir = dot_git_path
- .parent()
- .map(PathBuf::from)
- .unwrap_or(dot_git_path);
- let head_sha = state
- .refs
- .get("HEAD")
- .cloned()
- .unwrap_or_else(|| "0000000".to_string());
- let branch_ref = state
- .current_branch_name
- .as_ref()
- .map(|name| format!("refs/heads/{name}"))
- .unwrap_or_else(|| "refs/heads/main".to_string());
- let main_worktree = Worktree {
- path: work_dir,
- ref_name: Some(branch_ref.into()),
- sha: head_sha.into(),
- is_main: true,
- };
+ let fs = self.fs.clone();
+ let common_dir_path = self.common_dir_path.clone();
+ let executor = self.executor.clone();
+
+ async move {
+ executor.simulate_random_delay().await;
+
+ let (main_worktree, refs) = fs.with_git_state(&common_dir_path, false, |state| {
+ let work_dir = common_dir_path
+ .parent()
+ .map(PathBuf::from)
+ .unwrap_or_else(|| common_dir_path.clone());
+ let head_sha = state
+ .refs
+ .get("HEAD")
+ .cloned()
+ .unwrap_or_else(|| "0000000".to_string());
+ let branch_ref = state
+ .current_branch_name
+ .as_ref()
+ .map(|name| format!("refs/heads/{name}"))
+ .unwrap_or_else(|| "refs/heads/main".to_string());
+ let main_wt = Worktree {
+ path: work_dir,
+ ref_name: Some(branch_ref.into()),
+ sha: head_sha.into(),
+ is_main: true,
+ };
+ (main_wt, state.refs.clone())
+ })?;
+
let mut all = vec![main_worktree];
- all.extend(state.worktrees.iter().cloned());
+
+ let worktrees_dir = common_dir_path.join("worktrees");
+ if let Ok(mut entries) = fs.read_dir(&worktrees_dir).await {
+ use futures::StreamExt;
+ while let Some(Ok(entry_path)) = entries.next().await {
+ let head_content = match fs.load(&entry_path.join("HEAD")).await {
+ Ok(content) => content,
+ Err(_) => continue,
+ };
+ let gitdir_content = match fs.load(&entry_path.join("gitdir")).await {
+ Ok(content) => content,
+ Err(_) => continue,
+ };
+
+ let ref_name = head_content
+ .strip_prefix("ref: ")
+ .map(|s| s.trim().to_string());
+ let sha = ref_name
+ .as_ref()
+ .and_then(|r| refs.get(r))
+ .cloned()
+ .unwrap_or_else(|| head_content.trim().to_string());
+
+ let worktree_path = PathBuf::from(gitdir_content.trim())
+ .parent()
+ .map(PathBuf::from)
+ .unwrap_or_default();
+
+ all.push(Worktree {
+ path: worktree_path,
+ ref_name: ref_name.map(Into::into),
+ sha: sha.into(),
+ is_main: false,
+ });
+ }
+ }
+
Ok(all)
- })
+ }
+ .boxed()
}
fn create_worktree(
@@ -446,36 +490,58 @@ impl GitRepository for FakeGitRepository {
let fs = self.fs.clone();
let executor = self.executor.clone();
let dot_git_path = self.dot_git_path.clone();
+ let common_dir_path = self.common_dir_path.clone();
async move {
executor.simulate_random_delay().await;
- // Check for simulated error before any side effects
+ // Check for simulated error and duplicate branch before any side effects.
fs.with_git_state(&dot_git_path, false, |state| {
if let Some(message) = &state.simulated_create_worktree_error {
anyhow::bail!("{message}");
}
+ if state.branches.contains(&branch_name) {
+ bail!("a branch named '{}' already exists", branch_name);
+ }
Ok(())
})??;
- // Create directory before updating state so state is never
- // inconsistent with the filesystem
+
+ // Create the worktree checkout directory.
fs.create_dir(&path).await?;
- fs.with_git_state(&dot_git_path, true, {
- let path = path.clone();
- move |state| {
- if state.branches.contains(&branch_name) {
- bail!("a branch named '{}' already exists", branch_name);
- }
- let ref_name = format!("refs/heads/{branch_name}");
- let sha = from_commit.unwrap_or_else(|| "fake-sha".to_string());
- state.refs.insert(ref_name.clone(), sha.clone());
- state.worktrees.push(Worktree {
- path,
- ref_name: Some(ref_name.into()),
- sha: sha.into(),
- is_main: false,
- });
- state.branches.insert(branch_name);
- Ok::<(), anyhow::Error>(())
- }
+
+ // Create .git/worktrees/<name>/ directory with HEAD, commondir, gitdir.
+ let ref_name = format!("refs/heads/{branch_name}");
+ let worktrees_entry_dir = common_dir_path.join("worktrees").join(&branch_name);
+ fs.create_dir(&worktrees_entry_dir).await?;
+
+ fs.write_file_internal(
+ worktrees_entry_dir.join("HEAD"),
+ format!("ref: {ref_name}").into_bytes(),
+ false,
+ )?;
+ fs.write_file_internal(
+ worktrees_entry_dir.join("commondir"),
+ common_dir_path.to_string_lossy().into_owned().into_bytes(),
+ false,
+ )?;
+ let worktree_dot_git = path.join(".git");
+ fs.write_file_internal(
+ worktrees_entry_dir.join("gitdir"),
+ worktree_dot_git.to_string_lossy().into_owned().into_bytes(),
+ false,
+ )?;
+
+ // Create .git file in the worktree checkout.
+ fs.write_file_internal(
+ &worktree_dot_git,
+ format!("gitdir: {}", worktrees_entry_dir.display()).into_bytes(),
+ false,
+ )?;
+
+ // Update git state: add ref and branch.
+ let sha = from_commit.unwrap_or_else(|| "fake-sha".to_string());
+ fs.with_git_state(&dot_git_path, true, move |state| {
+ state.refs.insert(ref_name, sha);
+ state.branches.insert(branch_name);
+ Ok::<(), anyhow::Error>(())
})??;
Ok(())
}
@@ -485,20 +551,23 @@ impl GitRepository for FakeGitRepository {
fn remove_worktree(&self, path: PathBuf, _force: bool) -> BoxFuture<'_, Result<()>> {
let fs = self.fs.clone();
let executor = self.executor.clone();
- let dot_git_path = self.dot_git_path.clone();
+ let common_dir_path = self.common_dir_path.clone();
async move {
executor.simulate_random_delay().await;
- // Validate the worktree exists in state before touching the filesystem
- fs.with_git_state(&dot_git_path, false, {
- let path = path.clone();
- move |state| {
- if !state.worktrees.iter().any(|w| w.path == path) {
- bail!("no worktree found at path: {}", path.display());
- }
- Ok(())
- }
- })??;
- // Now remove the directory
+
+ // Read the worktree's .git file to find its entry directory.
+ let dot_git_file = path.join(".git");
+ let content = fs
+ .load(&dot_git_file)
+ .await
+ .with_context(|| format!("no worktree found at path: {}", path.display()))?;
+ let gitdir = content
+ .strip_prefix("gitdir:")
+ .context("invalid .git file in worktree")?
+ .trim();
+ let worktree_entry_dir = PathBuf::from(gitdir);
+
+ // Remove the worktree checkout directory.
fs.remove_dir(
&path,
RemoveOptions {
@@ -507,11 +576,21 @@ impl GitRepository for FakeGitRepository {
},
)
.await?;
- // Update state
- fs.with_git_state(&dot_git_path, true, move |state| {
- state.worktrees.retain(|worktree| worktree.path != path);
- Ok::<(), anyhow::Error>(())
- })??;
+
+ // Remove the .git/worktrees/<name>/ directory.
+ fs.remove_dir(
+ &worktree_entry_dir,
+ RemoveOptions {
+ recursive: true,
+ ignore_if_not_exists: false,
+ },
+ )
+ .await?;
+
+ // Emit a git event on the main .git directory so the scanner
+ // notices the change.
+ fs.with_git_state(&common_dir_path, true, |_| {})?;
+
Ok(())
}
.boxed()
@@ -520,20 +599,23 @@ impl GitRepository for FakeGitRepository {
fn rename_worktree(&self, old_path: PathBuf, new_path: PathBuf) -> BoxFuture<'_, Result<()>> {
let fs = self.fs.clone();
let executor = self.executor.clone();
- let dot_git_path = self.dot_git_path.clone();
+ let common_dir_path = self.common_dir_path.clone();
async move {
executor.simulate_random_delay().await;
- // Validate the worktree exists in state before touching the filesystem
- fs.with_git_state(&dot_git_path, false, {
- let old_path = old_path.clone();
- move |state| {
- if !state.worktrees.iter().any(|w| w.path == old_path) {
- bail!("no worktree found at path: {}", old_path.display());
- }
- Ok(())
- }
- })??;
- // Now move the directory
+
+ // Read the worktree's .git file to find its entry directory.
+ let dot_git_file = old_path.join(".git");
+ let content = fs
+ .load(&dot_git_file)
+ .await
+ .with_context(|| format!("no worktree found at path: {}", old_path.display()))?;
+ let gitdir = content
+ .strip_prefix("gitdir:")
+ .context("invalid .git file in worktree")?
+ .trim();
+ let worktree_entry_dir = PathBuf::from(gitdir);
+
+ // Move the worktree checkout directory.
fs.rename(
&old_path,
&new_path,
@@ -544,16 +626,27 @@ impl GitRepository for FakeGitRepository {
},
)
.await?;
- // Update state
- fs.with_git_state(&dot_git_path, true, move |state| {
- let worktree = state
- .worktrees
- .iter_mut()
- .find(|worktree| worktree.path == old_path)
- .expect("worktree was validated above");
- worktree.path = new_path;
- Ok::<(), anyhow::Error>(())
- })??;
+
+ // Update the gitdir file in .git/worktrees/<name>/ to point to the
+ // new location.
+ let new_dot_git = new_path.join(".git");
+ fs.write_file_internal(
+ worktree_entry_dir.join("gitdir"),
+ new_dot_git.to_string_lossy().into_owned().into_bytes(),
+ false,
+ )?;
+
+ // Update the .git file in the moved worktree checkout.
+ fs.write_file_internal(
+ &new_dot_git,
+ format!("gitdir: {}", worktree_entry_dir.display()).into_bytes(),
+ false,
+ )?;
+
+ // Emit a git event on the main .git directory so the scanner
+ // notices the change.
+ fs.with_git_state(&common_dir_path, true, |_| {})?;
+
Ok(())
}
.boxed()
@@ -12,7 +12,10 @@ use gpui::TestAppContext;
use pretty_assertions::assert_eq;
use project::AgentId;
use settings::SettingsStore;
-use std::{path::PathBuf, sync::Arc};
+use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+};
use util::path_list::PathList;
fn init_test(cx: &mut TestAppContext) {
@@ -2435,38 +2438,24 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
.await;
// Worktree checkout pointing back to the main repo.
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
- "src": {},
- }),
- )
- .await;
-
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -2573,15 +2562,17 @@ async fn test_search_matches_worktree_name(cx: &mut TestAppContext) {
let (project, fs) = init_test_project_with_git("/project", cx).await;
fs.as_fake()
- .with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ .add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt/rosewood"),
ref_name: Some("refs/heads/rosewood".into()),
sha: "abc".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
project
.update(cx, |project, cx| project.git_scans_complete(cx))
@@ -2635,15 +2626,17 @@ async fn test_git_worktree_added_live_updates_sidebar(cx: &mut TestAppContext) {
// Now add the worktree to the git state and trigger a rescan.
fs.as_fake()
- .with_git_state(std::path::Path::new("/project/.git"), true, |state| {
- state.worktrees.push(git::repository::Worktree {
+ .add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ true,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt/rosewood"),
ref_name: Some("refs/heads/rosewood".into()),
sha: "abc".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.run_until_parked();
@@ -2667,16 +2660,6 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC
"/project",
serde_json::json!({
".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- "feature-b": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-b",
- },
- },
},
"src": {},
}),
@@ -2684,20 +2667,26 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC
.await;
// Two worktree checkouts whose .git files point back to the main repo.
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
- "src": {},
- }),
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
+ path: std::path::PathBuf::from("/wt-feature-a"),
+ ref_name: Some("refs/heads/feature-a".into()),
+ sha: "aaa".into(),
+ is_main: false,
+ },
)
.await;
- fs.insert_tree(
- "/wt-feature-b",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-b",
- "src": {},
- }),
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
+ path: std::path::PathBuf::from("/wt-feature-b"),
+ ref_name: Some("refs/heads/feature-b".into()),
+ sha: "bbb".into(),
+ is_main: false,
+ },
)
.await;
@@ -2735,24 +2724,6 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC
]
);
- // Configure the main repo to list both worktrees before opening
- // it so the initial git scan picks them up.
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
- path: std::path::PathBuf::from("/wt-feature-a"),
- ref_name: Some("refs/heads/feature-a".into()),
- sha: "aaa".into(),
- is_main: false,
- });
- state.worktrees.push(git::repository::Worktree {
- path: std::path::PathBuf::from("/wt-feature-b"),
- ref_name: Some("refs/heads/feature-b".into()),
- sha: "bbb".into(),
- is_main: false,
- });
- })
- .unwrap();
-
let main_project = project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
main_project
.update(cx, |p, cx| p.git_scans_complete(cx))
@@ -2788,54 +2759,33 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- "feature-b": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-b",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
- "src": {},
- }),
- )
- .await;
- fs.insert_tree(
- "/wt-feature-b",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-b",
+ ".git": {},
"src": {},
}),
)
.await;
-
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- state.worktrees.push(git::repository::Worktree {
+ },
+ )
+ .await;
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-b"),
ref_name: Some("refs/heads/feature-b".into()),
sha: "bbb".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -2884,18 +2834,7 @@ async fn test_multi_worktree_thread_shows_multiple_chips(cx: &mut TestAppContext
fs.insert_tree(
"/project_a",
serde_json::json!({
- ".git": {
- "worktrees": {
- "olivetti": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/olivetti",
- },
- "selectric": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/selectric",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
@@ -2903,56 +2842,28 @@ async fn test_multi_worktree_thread_shows_multiple_chips(cx: &mut TestAppContext
fs.insert_tree(
"/project_b",
serde_json::json!({
- ".git": {
- "worktrees": {
- "olivetti": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/olivetti",
- },
- "selectric": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/selectric",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
.await;
// Worktree checkouts.
- for (repo, branch) in &[
- ("project_a", "olivetti"),
- ("project_a", "selectric"),
- ("project_b", "olivetti"),
- ("project_b", "selectric"),
- ] {
- let worktree_path = format!("/worktrees/{repo}/{branch}/{repo}");
- let gitdir = format!("gitdir: /{repo}/.git/worktrees/{branch}");
- fs.insert_tree(
- &worktree_path,
- serde_json::json!({
- ".git": gitdir,
- "src": {},
- }),
- )
- .await;
- }
-
- // Register linked worktrees.
for repo in &["project_a", "project_b"] {
let git_path = format!("/{repo}/.git");
- fs.with_git_state(std::path::Path::new(&git_path), false, |state| {
- for branch in &["olivetti", "selectric"] {
- state.worktrees.push(git::repository::Worktree {
+ for branch in &["olivetti", "selectric"] {
+ fs.add_linked_worktree_for_repo(
+ Path::new(&git_path),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from(format!("/worktrees/{repo}/{branch}/{repo}")),
ref_name: Some(format!("refs/heads/{branch}").into()),
sha: "aaa".into(),
is_main: false,
- });
- }
- })
- .unwrap();
+ },
+ )
+ .await;
+ }
}
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3005,14 +2916,7 @@ async fn test_same_named_worktree_chips_are_deduplicated(cx: &mut TestAppContext
fs.insert_tree(
"/project_a",
serde_json::json!({
- ".git": {
- "worktrees": {
- "olivetti": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/olivetti",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
@@ -3020,41 +2924,25 @@ async fn test_same_named_worktree_chips_are_deduplicated(cx: &mut TestAppContext
fs.insert_tree(
"/project_b",
serde_json::json!({
- ".git": {
- "worktrees": {
- "olivetti": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/olivetti",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
.await;
for repo in &["project_a", "project_b"] {
- let worktree_path = format!("/worktrees/{repo}/olivetti/{repo}");
- let gitdir = format!("gitdir: /{repo}/.git/worktrees/olivetti");
- fs.insert_tree(
- &worktree_path,
- serde_json::json!({
- ".git": gitdir,
- "src": {},
- }),
- )
- .await;
-
let git_path = format!("/{repo}/.git");
- fs.with_git_state(std::path::Path::new(&git_path), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new(&git_path),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from(format!("/worktrees/{repo}/olivetti/{repo}")),
ref_name: Some("refs/heads/olivetti".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
}
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3114,38 +3002,24 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
+ ".git": {},
"src": {},
}),
)
.await;
// Worktree checkout pointing back to the main repo.
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
- "src": {},
- }),
- )
- .await;
-
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3231,37 +3105,23 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
-
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
.await;
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3338,37 +3198,23 @@ async fn test_clicking_worktree_thread_opens_workspace_when_none_exists(cx: &mut
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
-
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
.await;
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3444,37 +3290,23 @@ async fn test_clicking_worktree_thread_does_not_briefly_render_as_separate_proje
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
-
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
.await;
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -3595,37 +3427,23 @@ async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace(
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
-
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
.await;
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -4190,37 +4008,23 @@ async fn test_archive_thread_uses_next_threads_own_workspace(cx: &mut TestAppCon
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
-
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
.await;
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -4354,22 +4158,7 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test
fs.insert_tree(
"/project",
serde_json::json!({
- ".git": {
- "worktrees": {
- "feature-a": {
- "commondir": "../../",
- "HEAD": "ref: refs/heads/feature-a",
- },
- },
- },
- "src": {},
- }),
- )
- .await;
- fs.insert_tree(
- "/wt-feature-a",
- serde_json::json!({
- ".git": "gitdir: /project/.git/worktrees/feature-a",
+ ".git": {},
"src": {},
}),
)
@@ -4384,15 +4173,17 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test
.await;
// Register the linked worktree in the main repo.
- fs.with_git_state(std::path::Path::new("/project/.git"), false, |state| {
- state.worktrees.push(git::repository::Worktree {
+ fs.add_linked_worktree_for_repo(
+ Path::new("/project/.git"),
+ false,
+ git::repository::Worktree {
path: std::path::PathBuf::from("/wt-feature-a"),
ref_name: Some("refs/heads/feature-a".into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
@@ -5191,15 +4982,17 @@ mod property_test {
let worktree_pathbuf = std::path::PathBuf::from(&worktree_path);
state
.fs
- .with_git_state(dot_git_path, false, |git_state| {
- git_state.worktrees.push(git::repository::Worktree {
+ .add_linked_worktree_for_repo(
+ dot_git_path,
+ false,
+ git::repository::Worktree {
path: worktree_pathbuf,
ref_name: Some(format!("refs/heads/{}", worktree_name).into()),
sha: "aaa".into(),
is_main: false,
- });
- })
- .unwrap();
+ },
+ )
+ .await;
// Re-scan the main workspace's project so it discovers the new worktree.
let main_workspace =