Clean up worktree setup in tests (#52934)
Release Notes: - N/A --------- Co-authored-by: Eric Holk <eric@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
1 parent
a0028ae0fa
commit
51bad89a6c
7 files changed
+499
-495
No files matched your search
@@ -394,29 +394,29 @@ async fn test_linked_worktrees_sync(
|
||||
)
|
||||
.await;
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.with_git_state(Path::new(path!("/project/.git")), true, |state| {
|
||||
state.worktrees.push(GitWorktree {
|
||||
path: PathBuf::from(path!("/project")),
|
||||
ref_name: Some("refs/heads/main".into()),
|
||||
sha: "aaa111".into(),
|
||||
is_main: false,
|
||||
});
|
||||
state.worktrees.push(GitWorktree {
|
||||
path: PathBuf::from(path!("/project/feature-branch")),
|
||||
ref_name: Some("refs/heads/feature-branch".into()),
|
||||
sha: "bbb222".into(),
|
||||
is_main: false,
|
||||
});
|
||||
state.worktrees.push(GitWorktree {
|
||||
path: PathBuf::from(path!("/project/bugfix-branch")),
|
||||
ref_name: Some("refs/heads/bugfix-branch".into()),
|
||||
sha: "ccc333".into(),
|
||||
is_main: false,
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
let fs = client_a.fs();
|
||||
fs.add_linked_worktree_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
true,
|
||||
GitWorktree {
|
||||
path: PathBuf::from(path!("/worktrees/feature-branch")),
|
||||
ref_name: Some("refs/heads/feature-branch".into()),
|
||||
sha: "bbb222".into(),
|
||||
is_main: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
fs.add_linked_worktree_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
true,
|
||||
GitWorktree {
|
||||
path: PathBuf::from(path!("/worktrees/bugfix-branch")),
|
||||
ref_name: Some("refs/heads/bugfix-branch".into()),
|
||||
sha: "ccc333".into(),
|
||||
is_main: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let (project_a, _) = client_a.build_local_project(path!("/project"), cx_a).await;
|
||||
|
||||
@@ -437,22 +437,22 @@ async fn test_linked_worktrees_sync(
|
||||
);
|
||||
assert_eq!(
|
||||
host_linked[0].path,
|
||||
PathBuf::from(path!("/project/feature-branch"))
|
||||
PathBuf::from(path!("/worktrees/bugfix-branch"))
|
||||
);
|
||||
assert_eq!(
|
||||
host_linked[0].ref_name,
|
||||
Some("refs/heads/feature-branch".into())
|
||||
Some("refs/heads/bugfix-branch".into())
|
||||
);
|
||||
assert_eq!(host_linked[0].sha.as_ref(), "bbb222");
|
||||
assert_eq!(host_linked[0].sha.as_ref(), "ccc333");
|
||||
assert_eq!(
|
||||
host_linked[1].path,
|
||||
PathBuf::from(path!("/project/bugfix-branch"))
|
||||
PathBuf::from(path!("/worktrees/feature-branch"))
|
||||
);
|
||||
assert_eq!(
|
||||
host_linked[1].ref_name,
|
||||
Some("refs/heads/bugfix-branch".into())
|
||||
Some("refs/heads/feature-branch".into())
|
||||
);
|
||||
assert_eq!(host_linked[1].sha.as_ref(), "ccc333");
|
||||
assert_eq!(host_linked[1].sha.as_ref(), "bbb222");
|
||||
|
||||
// Share the project and have client B join.
|
||||
let project_id = active_call_a
|
||||
@@ -478,15 +478,17 @@ async fn test_linked_worktrees_sync(
|
||||
// Now mutate: add a third linked worktree on the host side.
|
||||
client_a
|
||||
.fs()
|
||||
.with_git_state(Path::new(path!("/project/.git")), true, |state| {
|
||||
state.worktrees.push(GitWorktree {
|
||||
path: PathBuf::from(path!("/project/hotfix-branch")),
|
||||
.add_linked_worktree_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
true,
|
||||
GitWorktree {
|
||||
path: PathBuf::from(path!("/worktrees/hotfix-branch")),
|
||||
ref_name: Some("refs/heads/hotfix-branch".into()),
|
||||
sha: "ddd444".into(),
|
||||
is_main: false,
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait for the host to re-scan and propagate the update.
|
||||
executor.run_until_parked();
|
||||
@@ -504,7 +506,7 @@ async fn test_linked_worktrees_sync(
|
||||
);
|
||||
assert_eq!(
|
||||
host_linked_updated[2].path,
|
||||
PathBuf::from(path!("/project/hotfix-branch"))
|
||||
PathBuf::from(path!("/worktrees/hotfix-branch"))
|
||||
);
|
||||
|
||||
// Verify the guest also received the update.
|
||||
@@ -521,12 +523,12 @@ async fn test_linked_worktrees_sync(
|
||||
// Now mutate: remove one linked worktree from the host side.
|
||||
client_a
|
||||
.fs()
|
||||
.with_git_state(Path::new(path!("/project/.git")), true, |state| {
|
||||
state
|
||||
.worktrees
|
||||
.retain(|wt| wt.ref_name != Some("refs/heads/bugfix-branch".into()));
|
||||
})
|
||||
.unwrap();
|
||||
.remove_worktree_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
true,
|
||||
"refs/heads/bugfix-branch",
|
||||
)
|
||||
.await;
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
|
||||
@@ -469,7 +469,7 @@ async fn test_ssh_collaboration_git_worktrees(
|
||||
.unwrap();
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
|
||||
let worktree_directory = PathBuf::from("/project");
|
||||
let worktree_directory = PathBuf::from("/worktrees");
|
||||
cx_b.update(|cx| {
|
||||
repo_b.update(cx, |repo, _| {
|
||||
repo.create_worktree(
|
||||
@@ -536,8 +536,8 @@ async fn test_ssh_collaboration_git_worktrees(
|
||||
cx_a.update(|cx| {
|
||||
repo_a.update(cx, |repository, _| {
|
||||
repository.rename_worktree(
|
||||
PathBuf::from("/project/feature-branch"),
|
||||
PathBuf::from("/project/renamed-branch"),
|
||||
PathBuf::from("/worktrees/feature-branch"),
|
||||
PathBuf::from("/worktrees/renamed-branch"),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -559,7 +559,7 @@ async fn test_ssh_collaboration_git_worktrees(
|
||||
);
|
||||
assert_eq!(
|
||||
host_worktrees[1].path,
|
||||
PathBuf::from("/project/renamed-branch")
|
||||
PathBuf::from("/worktrees/renamed-branch")
|
||||
);
|
||||
|
||||
let server_worktrees = {
|
||||
@@ -588,13 +588,13 @@ async fn test_ssh_collaboration_git_worktrees(
|
||||
);
|
||||
assert_eq!(
|
||||
server_worktrees[1].path,
|
||||
PathBuf::from("/project/renamed-branch")
|
||||
PathBuf::from("/worktrees/renamed-branch")
|
||||
);
|
||||
|
||||
// Host (client A) removes the renamed worktree via SSH
|
||||
cx_a.update(|cx| {
|
||||
repo_a.update(cx, |repository, _| {
|
||||
repository.remove_worktree(PathBuf::from("/project/renamed-branch"), false)
|
||||
repository.remove_worktree(PathBuf::from("/worktrees/renamed-branch"), false)
|
||||
})
|
||||
})
|
||||
.await
|
||||
|
||||
+179
-86
@@ -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()
|
||||
|
||||
+120
-6
@@ -57,7 +57,7 @@ use collections::{BTreeMap, btree_map};
|
||||
use fake_git_repo::FakeGitRepositoryState;
|
||||
#[cfg(feature = "test-support")]
|
||||
use git::{
|
||||
repository::{InitialGraphCommitData, RepoPath, repo_path},
|
||||
repository::{InitialGraphCommitData, RepoPath, Worktree, repo_path},
|
||||
status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||
};
|
||||
#[cfg(feature = "test-support")]
|
||||
@@ -1892,11 +1892,15 @@ impl FakeFs {
|
||||
anyhow::bail!("gitfile points to a non-directory")
|
||||
};
|
||||
let common_dir = if let Some(child) = entries.get("commondir") {
|
||||
Path::new(
|
||||
std::str::from_utf8(child.file_content("commondir".as_ref())?)
|
||||
.context("commondir content")?,
|
||||
)
|
||||
.to_owned()
|
||||
let raw = std::str::from_utf8(child.file_content("commondir".as_ref())?)
|
||||
.context("commondir content")?
|
||||
.trim();
|
||||
let raw_path = Path::new(raw);
|
||||
if raw_path.is_relative() {
|
||||
normalize_path(&canonical_path.join(raw_path))
|
||||
} else {
|
||||
raw_path.to_owned()
|
||||
}
|
||||
} else {
|
||||
canonical_path.clone()
|
||||
};
|
||||
@@ -1960,6 +1964,116 @@ impl FakeFs {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn add_linked_worktree_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
emit_git_event: bool,
|
||||
worktree: Worktree,
|
||||
) {
|
||||
let ref_name = worktree
|
||||
.ref_name
|
||||
.as_ref()
|
||||
.expect("linked worktree must have a ref_name");
|
||||
let branch_name = ref_name
|
||||
.strip_prefix("refs/heads/")
|
||||
.unwrap_or(ref_name.as_ref());
|
||||
|
||||
// Create ref in git state.
|
||||
self.with_git_state(dot_git, false, |state| {
|
||||
state
|
||||
.refs
|
||||
.insert(ref_name.to_string(), worktree.sha.to_string());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Create .git/worktrees/<name>/ directory with HEAD, commondir, and gitdir.
|
||||
let worktrees_entry_dir = dot_git.join("worktrees").join(branch_name);
|
||||
self.create_dir(&worktrees_entry_dir).await.unwrap();
|
||||
|
||||
self.write_file_internal(
|
||||
worktrees_entry_dir.join("HEAD"),
|
||||
format!("ref: {ref_name}").into_bytes(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.write_file_internal(
|
||||
worktrees_entry_dir.join("commondir"),
|
||||
dot_git.to_string_lossy().into_owned().into_bytes(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let worktree_dot_git = worktree.path.join(".git");
|
||||
self.write_file_internal(
|
||||
worktrees_entry_dir.join("gitdir"),
|
||||
worktree_dot_git.to_string_lossy().into_owned().into_bytes(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create the worktree checkout directory with a .git file pointing back.
|
||||
self.create_dir(&worktree.path).await.unwrap();
|
||||
|
||||
self.write_file_internal(
|
||||
&worktree_dot_git,
|
||||
format!("gitdir: {}", worktrees_entry_dir.display()).into_bytes(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if emit_git_event {
|
||||
self.with_git_state(dot_git, true, |_| {}).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_worktree_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
emit_git_event: bool,
|
||||
ref_name: &str,
|
||||
) {
|
||||
let branch_name = ref_name.strip_prefix("refs/heads/").unwrap_or(ref_name);
|
||||
let worktrees_entry_dir = dot_git.join("worktrees").join(branch_name);
|
||||
|
||||
// Read gitdir to find the worktree checkout path.
|
||||
let gitdir_content = self
|
||||
.load_internal(worktrees_entry_dir.join("gitdir"))
|
||||
.await
|
||||
.unwrap();
|
||||
let gitdir_str = String::from_utf8(gitdir_content).unwrap();
|
||||
let worktree_path = PathBuf::from(gitdir_str.trim())
|
||||
.parent()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_default();
|
||||
|
||||
// Remove the worktree checkout directory.
|
||||
self.remove_dir(
|
||||
&worktree_path,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Remove the .git/worktrees/<name>/ directory.
|
||||
self.remove_dir(
|
||||
&worktrees_entry_dir,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if emit_git_event {
|
||||
self.with_git_state(dot_git, true, |_| {}).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_unmerged_paths_for_repo(
|
||||
&self,
|
||||
dot_git: &Path,
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::{
|
||||
transport::parse_platform,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct DockerConnectionOptions {
|
||||
pub name: String,
|
||||
pub container_id: String,
|
||||
|
||||
@@ -250,15 +250,17 @@ mod tests {
|
||||
}),
|
||||
)
|
||||
.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(
|
||||
std::path::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: "abc".into(),
|
||||
is_main: false,
|
||||
});
|
||||
})
|
||||
.expect("git state should be set");
|
||||
},
|
||||
)
|
||||
.await;
|
||||
fs
|
||||
}
|
||||
|
||||
|
||||
+142
-349
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"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",
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
".git": {},
|
||||
"src": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in new issue
Block a user