diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 05c0da4611ff7d410a1c947c8a42c066a7247b69..a8aac4ce8d8e94a5c26e43ec9b32f481307d626a 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1162,8 +1162,42 @@ impl GitRepository for RealGitRepository { return Ok(()); } + let working_directory = working_directory?; + let mut child = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(env.iter()) + .args([ + "checkout", + &commit, + "--pathspec-from-file=-", + "--pathspec-file-nul", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .context("failed to spawn git checkout")?; + + let mut stdin = child.stdin.take().context("failed to get stdin")?; + for path in &paths { + stdin.write_all(path.as_unix_str().as_bytes()).await?; + stdin.write_all(b"\0").await?; + } + drop(stdin); + + let output = child.output().await?; + if output.status.success() { + return Ok(()); + } + + let stderr = String::from_utf8_lossy(&output.stderr); + if !stderr.contains("pathspec-from-file") { + anyhow::bail!("Failed to checkout files:\n{}", stderr); + } + + // Fallback for older git versions: pass paths as command-line arguments let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) + .current_dir(&working_directory) .envs(env.iter()) .args(["checkout", &commit, "--"]) .args(paths.iter().map(|path| path.as_unix_str())) @@ -1850,20 +1884,33 @@ impl GitRepository for RealGitRepository { let git_binary_path = self.any_git_binary_path.clone(); self.executor .spawn(async move { - if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) - .envs(env.iter()) - .args(["update-index", "--add", "--remove", "--"]) - .args(paths.iter().map(|p| p.as_unix_str())) - .output() - .await?; - anyhow::ensure!( - output.status.success(), - "Failed to stage paths:\n{}", - String::from_utf8_lossy(&output.stderr), - ); + if paths.is_empty() { + return Ok(()); + } + + let mut child = new_smol_command(&git_binary_path) + .current_dir(&working_directory?) + .envs(env.iter()) + .args(["update-index", "--add", "--remove", "-z", "--stdin"]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .context("failed to spawn git update-index")?; + + let mut stdin = child.stdin.take().context("failed to get stdin")?; + for path in &paths { + stdin.write_all(path.as_unix_str().as_bytes()).await?; + stdin.write_all(b"\0").await?; } + drop(stdin); + + let output = child.output().await?; + anyhow::ensure!( + output.status.success(), + "Failed to stage paths:\n{}", + String::from_utf8_lossy(&output.stderr), + ); Ok(()) }) .boxed() @@ -1879,21 +1926,57 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { - if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) - .envs(env.iter()) - .args(["reset", "--quiet", "--"]) - .args(paths.iter().map(|p| p.as_std_path())) - .output() - .await?; + if paths.is_empty() { + return Ok(()); + } - anyhow::ensure!( - output.status.success(), - "Failed to unstage:\n{}", - String::from_utf8_lossy(&output.stderr), - ); + let working_directory = working_directory?; + let mut child = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(env.iter()) + .args([ + "reset", + "--quiet", + "--pathspec-from-file=-", + "--pathspec-file-nul", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .context("failed to spawn git reset")?; + + let mut stdin = child.stdin.take().context("failed to get stdin")?; + for path in &paths { + stdin.write_all(path.as_unix_str().as_bytes()).await?; + stdin.write_all(b"\0").await?; + } + drop(stdin); + + let output = child.output().await?; + if output.status.success() { + return Ok(()); } + + let stderr = String::from_utf8_lossy(&output.stderr); + if !stderr.contains("pathspec-from-file") { + anyhow::bail!("Failed to unstage:\n{}", stderr); + } + + // Fallback for older git versions: pass paths as command-line arguments + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(env.iter()) + .args(["reset", "--quiet", "--"]) + .args(paths.iter().map(|p| p.as_std_path())) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "Failed to unstage:\n{}", + String::from_utf8_lossy(&output.stderr), + ); Ok(()) }) .boxed()