windows: Implement shell environment loading for git operations (#39019)

Xiaobo Liu created

Fixes the "failed to get working directory environment for repository"
error on Windows by implementing proper shell environment variable
capture.

Release Notes:

- Fixed failed to get working directory environment for repository

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>

Change summary

crates/project/src/environment.rs | 23 ++++++++++++++++--
crates/util/src/shell_env.rs      | 41 +++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 3 deletions(-)

Detailed changes

crates/project/src/environment.rs 🔗

@@ -227,14 +227,31 @@ async fn load_shell_environment(
 
 #[cfg(all(target_os = "windows", not(any(test, feature = "test-support"))))]
 async fn load_shell_environment(
-    _dir: &Path,
+    dir: &Path,
     _load_direnv: &DirenvSettings,
 ) -> (
     Option<HashMap<String, String>>,
     Option<EnvironmentErrorMessage>,
 ) {
-    // TODO the current code works with Unix $SHELL only, implement environment loading on windows
-    (None, None)
+    use util::shell_env;
+
+    let envs = match shell_env::capture(dir).await {
+        Ok(envs) => envs,
+        Err(err) => {
+            util::log_err(&err);
+            return (
+                None,
+                Some(EnvironmentErrorMessage(format!(
+                    "Failed to load environment variables: {}",
+                    err
+                ))),
+            );
+        }
+    };
+
+    // Note: direnv is not available on Windows, so we skip direnv processing
+    // and just return the shell environment
+    (Some(envs), None)
 }
 
 #[cfg(not(any(target_os = "windows", test, feature = "test-support")))]

crates/util/src/shell_env.rs 🔗

@@ -99,6 +99,47 @@ async fn spawn_and_read_fd(
     Ok((buffer, process.output().await?))
 }
 
+/// Capture all environment variables from the shell on Windows.
+#[cfg(windows)]
+pub async fn capture(directory: &std::path::Path) -> Result<collections::HashMap<String, String>> {
+    use std::process::Stdio;
+
+    let zed_path =
+        std::env::current_exe().context("Failed to determine current zed executable path.")?;
+
+    // Use PowerShell to get environment variables in the directory context
+    let mut command = std::process::Command::new(crate::get_windows_system_shell());
+    command
+        .arg("-NonInteractive")
+        .arg("-NoProfile")
+        .arg("-Command")
+        .arg(format!(
+            "Set-Location '{}'; & '{}' --printenv",
+            directory.display(),
+            zed_path.display()
+        ))
+        .stdin(Stdio::null())
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped());
+
+    let output = smol::process::Command::from(command).output().await?;
+
+    anyhow::ensure!(
+        output.status.success(),
+        "PowerShell command failed with {}. stdout: {:?}, stderr: {:?}",
+        output.status,
+        String::from_utf8_lossy(&output.stdout),
+        String::from_utf8_lossy(&output.stderr),
+    );
+
+    let env_output = String::from_utf8_lossy(&output.stdout);
+
+    // Parse the JSON output from zed --printenv
+    let env_map: collections::HashMap<String, String> = serde_json::from_str(&env_output)
+        .with_context(|| "Failed to deserialize environment variables from json")?;
+    Ok(env_map)
+}
+
 pub fn print_env() {
     let env_vars: HashMap<String, String> = std::env::vars().collect();
     let json = serde_json::to_string_pretty(&env_vars).unwrap_or_else(|err| {