Fix worktree creation in WSL2 (#263)

This commit is contained in:
Solomon
2025-07-18 18:26:57 +01:00
committed by GitHub
parent 643d873703
commit 0b1979deba
3 changed files with 108 additions and 1 deletions

View File

@@ -34,6 +34,7 @@ rust-embed = "8.2"
mime_guess = "2.0"
directories = "6.0.0"
open = "5.3.2"
pathdiff = "0.2.1"
ignore = "0.4"
command-group = { version = "5.0", features = ["with-tokio"] }
nix = { version = "0.29", features = ["signal", "process"] }

View File

@@ -137,6 +137,17 @@ impl GitService {
// Create the worktree at the specified path
repo.worktree(branch_name, worktree_path, Some(&worktree_opts))?;
// Fix commondir for Windows/WSL compatibility
let worktree_name = worktree_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(branch_name);
if let Err(e) =
WorktreeManager::fix_worktree_commondir_for_windows_wsl(&self.repo_path, worktree_name)
{
tracing::warn!("Failed to fix worktree commondir for Windows/WSL: {}", e);
}
info!(
"Created worktree '{}' at path: {}",
branch_name,

View File

@@ -5,7 +5,7 @@ use std::{
};
use git2::{Error as GitError, Repository, WorktreeAddOptions};
use tracing::{debug, info};
use tracing::{debug, info, warn};
// Global synchronization for worktree creation to prevent race conditions
lazy_static::lazy_static! {
@@ -284,6 +284,15 @@ impl WorktreeManager {
"Successfully created worktree {} at {}",
branch_name, path_str
);
// Fix commondir for Windows/WSL compatibility
if let Err(e) = Self::fix_worktree_commondir_for_windows_wsl(
Path::new(&git_repo_path),
&worktree_name,
) {
warn!("Failed to fix worktree commondir for Windows/WSL: {}", e);
}
Ok(())
}
Err(e) if e.code() == git2::ErrorCode::Exists => {
@@ -317,6 +326,15 @@ impl WorktreeManager {
"Successfully created worktree {} at {} after metadata cleanup",
branch_name, path_str
);
// Fix commondir for Windows/WSL compatibility
if let Err(e) = Self::fix_worktree_commondir_for_windows_wsl(
Path::new(&git_repo_path),
&worktree_name,
) {
warn!("Failed to fix worktree commondir for Windows/WSL: {}", e);
}
Ok(())
}
Err(retry_error) => {
@@ -475,4 +493,81 @@ impl WorktreeManager {
.await
.map_err(|e| GitError::from_str(&format!("Task join error: {}", e)))?
}
/// Rewrite worktree's commondir file to use relative paths for WSL compatibility
///
/// This fixes Git repository corruption in WSL environments where git2/libgit2 creates
/// worktrees with absolute WSL paths (/mnt/c/...) that Windows Git cannot understand.
/// Git CLI creates relative paths (../../..) which work across both environments.
///
/// References:
/// - Git 2.48+ native support: https://git-scm.com/docs/git-config/2.48.0#Documentation/git-config.txt-worktreeuseRelativePaths
/// - WSL worktree absolute path issue: https://github.com/git-ecosystem/git-credential-manager/issues/1789
pub fn fix_worktree_commondir_for_windows_wsl(
git_repo_path: &Path,
worktree_name: &str,
) -> Result<(), std::io::Error> {
let commondir_path = git_repo_path
.join(".git")
.join("worktrees")
.join(worktree_name)
.join("commondir");
if !commondir_path.exists() {
debug!(
"commondir file does not exist: {}",
commondir_path.display()
);
return Ok(());
}
// Read current commondir content
let current_content = std::fs::read_to_string(&commondir_path)?.trim().to_string();
debug!("Current commondir content: {}", current_content);
// Skip if already relative
if !Path::new(&current_content).is_absolute() {
debug!("commondir already contains relative path, skipping");
return Ok(());
}
// Calculate relative path from worktree metadata dir to repo .git dir
let metadata_dir = commondir_path.parent().unwrap();
let target_git_dir = Path::new(&current_content);
if let Some(relative_path) = pathdiff::diff_paths(target_git_dir, metadata_dir) {
let relative_path_str = relative_path.to_string_lossy();
// Safety check: ensure the relative path resolves to the same absolute path
let resolved_path = metadata_dir.join(&relative_path);
if let (Ok(resolved_canonical), Ok(target_canonical)) =
(resolved_path.canonicalize(), target_git_dir.canonicalize())
{
if resolved_canonical == target_canonical {
// Write the relative path
std::fs::write(&commondir_path, format!("{}\n", relative_path_str))?;
info!(
"Rewrote commondir to relative path: {} -> {}",
current_content, relative_path_str
);
} else {
warn!(
"Safety check failed: relative path {} does not resolve to same target",
relative_path_str
);
}
} else {
warn!("Failed to canonicalize paths for safety check");
}
} else {
warn!(
"Failed to calculate relative path from {} to {}",
metadata_dir.display(),
target_git_dir.display()
);
}
Ok(())
}
}