feat: Enhance executable resolution by refreshing PATH (#1098)

* Refresh path on executable lookup

* Make resolve_executable_path async

* Handle task attempt start failure gracefully

* clippy fix

* Remove unused to_shell_string

* Lint

---------

Co-authored-by: Alex Netsch <alex@bloop.ai>
This commit is contained in:
Solomon
2025-11-03 15:57:53 +00:00
committed by GitHub
parent c59ffdd0ab
commit 99f7d9a4bc
21 changed files with 532 additions and 163 deletions

View File

@@ -23,7 +23,7 @@ use std::{
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use thiserror::Error;
use utils::shell::resolve_executable_path;
use utils::shell::resolve_executable_path_blocking; // TODO: make GitCli async
use crate::services::git::Commit;
@@ -497,14 +497,15 @@ impl GitCli {
/// Return true if there are staged changes (index differs from HEAD)
pub fn has_staged_changes(&self, repo_path: &Path) -> Result<bool, GitCliError> {
// `git diff --cached --quiet` returns exit code 1 if there are differences
let out = Command::new(resolve_executable_path("git").ok_or(GitCliError::NotAvailable)?)
.arg("-C")
.arg(repo_path)
.arg("diff")
.arg("--cached")
.arg("--quiet")
.output()
.map_err(|e| GitCliError::CommandFailed(e.to_string()))?;
let out =
Command::new(resolve_executable_path_blocking("git").ok_or(GitCliError::NotAvailable)?)
.arg("-C")
.arg(repo_path)
.arg("diff")
.arg("--cached")
.arg("--quiet")
.output()
.map_err(|e| GitCliError::CommandFailed(e.to_string()))?;
match out.status.code() {
Some(0) => Ok(false),
Some(1) => Ok(true),
@@ -625,7 +626,7 @@ impl GitCli {
/// Ensure `git` is available on PATH
fn ensure_available(&self) -> Result<(), GitCliError> {
let git = resolve_executable_path("git").ok_or(GitCliError::NotAvailable)?;
let git = resolve_executable_path_blocking("git").ok_or(GitCliError::NotAvailable)?;
let out = Command::new(&git)
.arg("--version")
.output()
@@ -656,7 +657,7 @@ impl GitCli {
S: AsRef<OsStr>,
{
self.ensure_available()?;
let git = resolve_executable_path("git").ok_or(GitCliError::NotAvailable)?;
let git = resolve_executable_path_blocking("git").ok_or(GitCliError::NotAvailable)?;
let mut cmd = Command::new(&git);
cmd.arg("-C").arg(repo_path);
for a in args {
@@ -684,7 +685,7 @@ impl GitCli {
S: AsRef<OsStr>,
{
self.ensure_available()?;
let git = resolve_executable_path("git").ok_or(GitCliError::NotAvailable)?;
let git = resolve_executable_path_blocking("git").ok_or(GitCliError::NotAvailable)?;
let mut cmd = Command::new(&git);
cmd.arg("-C").arg(repo_path);
for (k, v) in envs {

View File

@@ -7,7 +7,7 @@ use std::{
use git2::{Error as GitError, Repository};
use thiserror::Error;
use tracing::{debug, info};
use utils::shell::get_shell_command;
use utils::shell::resolve_executable_path;
use super::{
git::{GitService, GitServiceError},
@@ -429,35 +429,30 @@ impl WorktreeManager {
// Try using git rev-parse --git-common-dir from within the worktree
let worktree_path_owned = worktree_path.to_path_buf();
tokio::task::spawn_blocking(move || {
let (shell_cmd, shell_arg) = get_shell_command();
let git_command = "git rev-parse --git-common-dir";
let git_path = resolve_executable_path("git").await?;
let output = std::process::Command::new(shell_cmd)
.args([shell_arg, git_command])
.current_dir(&worktree_path_owned)
.output()
.ok()?;
let output = tokio::process::Command::new(git_path)
.args(["rev-parse", "--git-common-dir"])
.current_dir(&worktree_path_owned)
.output()
.await
.ok()?;
if output.status.success() {
let git_common_dir = String::from_utf8(output.stdout).ok()?.trim().to_string();
if output.status.success() {
let git_common_dir = String::from_utf8(output.stdout).ok()?.trim().to_string();
// git-common-dir gives us the path to the .git directory
// We need the working directory (parent of .git)
let git_dir_path = Path::new(&git_common_dir);
if git_dir_path.file_name() == Some(std::ffi::OsStr::new(".git")) {
git_dir_path.parent()?.to_str().map(PathBuf::from)
} else {
// In case of bare repo or unusual setup, use the git-common-dir as is
Some(PathBuf::from(git_common_dir))
}
// git-common-dir gives us the path to the .git directory
// We need the working directory (parent of .git)
let git_dir_path = Path::new(&git_common_dir);
if git_dir_path.file_name() == Some(std::ffi::OsStr::new(".git")) {
git_dir_path.parent()?.to_str().map(PathBuf::from)
} else {
None
// In case of bare repo or unusual setup, use the git-common-dir as is
Some(PathBuf::from(git_common_dir))
}
})
.await
.ok()
.flatten()
} else {
None
}
}
/// Simple worktree cleanup when we can't determine the main repo