Skip to content

Commit 91fb64b

Browse files
committed
fix(worktree): normalize Windows managed paths
1 parent 397afdc commit 91fb64b

1 file changed

Lines changed: 34 additions & 0 deletions

File tree

  • src/crates/assembly/core/src/service/worktree

‎src/crates/assembly/core/src/service/worktree/mod.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,13 @@ async fn managed_target_path(
16801680
format!("Failed to resolve the managed worktree root: {io_error}"),
16811681
)
16821682
})?;
1683+
// `std::fs::canonicalize` (and Tokio's wrapper) returns a verbatim
1684+
// `\\?\C:\...` path on Windows. Git for Windows does not accept that form
1685+
// as a `worktree add` target after the Git adapter normalizes separators,
1686+
// because it becomes `//?/C:/...`. Keep the resolved path used for the
1687+
// containment checks, but prefer the ordinary drive-letter representation
1688+
// whenever it can address the same path.
1689+
let canonical_root = dunce::simplified(&canonical_root).to_path_buf();
16831690
let repository_root = canonical_root.join(repository_id);
16841691
match tokio::fs::symlink_metadata(&repository_root).await {
16851692
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => {
@@ -1715,6 +1722,7 @@ async fn managed_target_path(
17151722
format!("Failed to resolve the repository worktree root: {io_error}"),
17161723
)
17171724
})?;
1725+
let canonical_repository_root = dunce::simplified(&canonical_repository_root).to_path_buf();
17181726
if !canonical_repository_root.starts_with(&canonical_root) {
17191727
return Err(error(
17201728
WorktreeErrorCode::InvalidPath,
@@ -2092,6 +2100,32 @@ mod tests {
20922100
assert_eq!(collision.code, WorktreeErrorCode::InvalidPath);
20932101
}
20942102

2103+
#[cfg(windows)]
2104+
#[tokio::test]
2105+
async fn managed_target_path_is_compatible_with_git_for_windows() {
2106+
let root = tempfile::tempdir().expect("temp root");
2107+
let settings = WorktreeSettings {
2108+
root_path: root.path().join("managed-worktrees").display().to_string(),
2109+
..WorktreeSettings::default()
2110+
};
2111+
2112+
let target = managed_target_path(
2113+
&settings,
2114+
"repository-id",
2115+
&root.path().join("projects/BitFun"),
2116+
"48e8b457e87649aebf801b408698f46c",
2117+
)
2118+
.await
2119+
.expect("managed target path");
2120+
let git_argument = target.to_string_lossy().replace('\\', "/");
2121+
2122+
assert!(
2123+
!git_argument.starts_with("//?/"),
2124+
"managed worktree targets passed to Git must not use a Windows verbatim path: {}",
2125+
target.display()
2126+
);
2127+
}
2128+
20952129
#[test]
20962130
fn managed_directory_name_falls_back_for_paths_without_a_project_name() {
20972131
assert!(managed_worktree_directory_name(

0 commit comments

Comments
 (0)