mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
Merge 898b4d7e92 into 09165c15dc
This commit is contained in:
commit
3c20723f04
3 changed files with 79 additions and 71 deletions
|
|
@ -20,12 +20,18 @@ use futures::{
|
|||
};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, Task};
|
||||
use smol::fs;
|
||||
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt, shell::ShellKind};
|
||||
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use util::shell::ShellKind;
|
||||
|
||||
/// Path to the program used for askpass
|
||||
///
|
||||
/// On Unix and remote servers, this defaults to the current executable
|
||||
/// On Windows, this is set to the CLI variant of zed
|
||||
/// On Unix and remote servers, this defaults to the current executable.
|
||||
/// On Windows, this must be set to the CLI variant of zed via set_askpass_program(),
|
||||
/// because SSH_ASKPASS must point to a directly executable binary. The CLI binary
|
||||
/// handles the ZED_ASKPASS_SOCKET env var to communicate with Zed over a Unix socket
|
||||
/// without needing a wrapper script.
|
||||
static ASKPASS_PROGRAM: OnceLock<std::path::PathBuf> = OnceLock::new();
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
|
|
@ -80,11 +86,8 @@ pub struct AskPassSession {
|
|||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
const ASKPASS_SCRIPT_NAME: &str = if cfg!(target_os = "windows") {
|
||||
"askpass.ps1"
|
||||
} else {
|
||||
"askpass.sh"
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const ASKPASS_SCRIPT_NAME: &str = "askpass.sh";
|
||||
|
||||
impl AskPassSession {
|
||||
/// This will create a new AskPassSession.
|
||||
|
|
@ -177,17 +180,34 @@ impl AskPassSession {
|
|||
self.secret.lock().ok()?.clone()
|
||||
}
|
||||
|
||||
/// Returns the value to set as SSH_ASKPASS.
|
||||
/// On Unix this is the path to the generated shell script.
|
||||
/// On Windows this is the path to cli.exe directly — no script needed.
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
self.askpass_task.script_path()
|
||||
}
|
||||
|
||||
/// Returns the socket path to set as ZED_ASKPASS_SOCKET.
|
||||
///
|
||||
/// On Windows, SSH_ASKPASS points directly to cli.exe. SSH passes only
|
||||
/// the prompt string as argv[1] with no mechanism for extra arguments,
|
||||
/// so the socket path is communicated via this environment variable instead.
|
||||
/// cli.exe must check ZED_ASKPASS_SOCKET before clap parses args.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn socket_path(&self) -> impl AsRef<OsStr> {
|
||||
self.askpass_task.socket_path()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PasswordProxy {
|
||||
_task: Task<()>,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
/// On Unix: path to the generated .sh askpass script (set as SSH_ASKPASS).
|
||||
/// On Windows: path to cli.exe (set as SSH_ASKPASS directly — no script needed).
|
||||
askpass_script_path: std::path::PathBuf,
|
||||
/// On Windows only: path to the Unix socket, passed as ZED_ASKPASS_SOCKET
|
||||
/// so cli.exe can find it without --askpass argument parsing.
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper: String,
|
||||
askpass_socket_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl PasswordProxy {
|
||||
|
|
@ -202,19 +222,21 @@ impl PasswordProxy {
|
|||
) -> Result<Self> {
|
||||
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
|
||||
let askpass_socket = temp_dir.path().join("askpass.sock");
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
let current_exec =
|
||||
std::env::current_exe().context("Failed to determine current zed executable path.")?;
|
||||
|
||||
// TODO: inferred from the use of powershell.exe in askpass_helper_script
|
||||
let shell_kind = if cfg!(windows) {
|
||||
ShellKind::PowerShell
|
||||
} else {
|
||||
ShellKind::Posix
|
||||
};
|
||||
let askpass_program = ASKPASS_PROGRAM.get_or_init(|| current_exec);
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = generate_askpass_script(shell_kind, askpass_program, &askpass_socket)?;
|
||||
|
||||
// Unix: SSH_ASKPASS = path to generated .sh script in temp dir.
|
||||
// Windows: SSH_ASKPASS = path to cli.exe directly. No script is written.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_script_path = askpass_program.to_path_buf();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_socket_path = askpass_socket.clone();
|
||||
|
||||
let _task = executor.spawn(async move {
|
||||
maybe!(async move {
|
||||
let listener =
|
||||
|
|
@ -253,43 +275,44 @@ impl PasswordProxy {
|
|||
.log_err();
|
||||
});
|
||||
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("marking askpass script executable at {askpass_script_path:?}")
|
||||
})?;
|
||||
// todo(shell): There might be no powershell on the system
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File \"{}\"",
|
||||
askpass_script_path.display()
|
||||
);
|
||||
// Unix only: write the shell script and mark it executable.
|
||||
// On Windows cli.exe is invoked directly, so no script is needed.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let askpass_script = generate_askpass_script(askpass_program, &askpass_socket)?;
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("marking askpass script executable at {askpass_script_path:?}")
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
_task,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
askpass_script_path,
|
||||
#[cfg(target_os = "windows")]
|
||||
askpass_helper,
|
||||
askpass_socket_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> impl AsRef<OsStr> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
&self.askpass_script_path
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
&self.askpass_helper
|
||||
}
|
||||
&self.askpass_script_path
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn socket_path(&self) -> impl AsRef<OsStr> {
|
||||
&self.askpass_socket_path
|
||||
}
|
||||
}
|
||||
|
||||
/// The main function for when Zed is running in netcat mode for use in askpass.
|
||||
/// Called from both the remote server binary and the zed binary in their respective main functions.
|
||||
///
|
||||
/// On Windows, the socket path is passed via ZED_ASKPASS_SOCKET rather than --askpass,
|
||||
/// because SSH_ASKPASS points directly to cli.exe and SSH only passes the prompt as argv[1].
|
||||
pub fn main(socket: &str) {
|
||||
use net::UnixStream;
|
||||
use std::io::{self, Read, Write};
|
||||
|
|
@ -340,13 +363,14 @@ pub fn set_askpass_program(path: std::path::PathBuf) {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Generates the Unix shell askpass script.
|
||||
/// Not used on Windows — cli.exe is invoked directly as SSH_ASKPASS.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn generate_askpass_script(
|
||||
shell_kind: ShellKind,
|
||||
askpass_program: &std::path::Path,
|
||||
askpass_socket: &std::path::Path,
|
||||
) -> Result<String> {
|
||||
let shell_kind = ShellKind::Posix;
|
||||
let askpass_program = shell_kind.prepend_command_prefix(
|
||||
askpass_program
|
||||
.to_str()
|
||||
|
|
@ -364,29 +388,3 @@ fn generate_askpass_script(
|
|||
"{shebang}\n{print_args} | {askpass_program} --askpass={askpass_socket} 2> /dev/null \n",
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn generate_askpass_script(
|
||||
shell_kind: ShellKind,
|
||||
askpass_program: &std::path::Path,
|
||||
askpass_socket: &std::path::Path,
|
||||
) -> Result<String> {
|
||||
let askpass_program = shell_kind.prepend_command_prefix(
|
||||
askpass_program
|
||||
.to_str()
|
||||
.context("Askpass program is on a non-utf8 path")?,
|
||||
);
|
||||
let askpass_program = shell_kind
|
||||
.try_quote_prefix_aware(&askpass_program)
|
||||
.context("Failed to shell-escape Askpass program path")?;
|
||||
let askpass_socket = askpass_socket
|
||||
.try_shell_safe(shell_kind)
|
||||
.context("Failed to shell-escape Askpass socket path")?;
|
||||
Ok(format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop';
|
||||
($args -join [char]0) | {askpass_program} --askpass={askpass_socket} 2> $null
|
||||
"#,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,6 +487,14 @@ fn run() -> Result<()> {
|
|||
return mac_os::spawn_channel_cli(channel, std::env::args().skip(2).collect());
|
||||
}
|
||||
}
|
||||
|
||||
// Must happen before clap — SSH invokes cli.exe directly as SSH_ASKPASS
|
||||
// and passes the socket path via env var to avoid argument parsing.
|
||||
if let Ok(socket) = std::env::var("ZED_ASKPASS_SOCKET") {
|
||||
askpass::main(&socket);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
// `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
|
||||
|
|
|
|||
|
|
@ -3495,6 +3495,8 @@ async fn run_git_command(
|
|||
.env("GIT_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force");
|
||||
#[cfg(target_os = "windows")]
|
||||
command.env("ZED_ASKPASS_SOCKET", ask_pass.socket_path());
|
||||
let git_process = command.spawn()?;
|
||||
|
||||
run_askpass_command(ask_pass, git_process).await
|
||||
|
|
|
|||
Loading…
Reference in a new issue