Compare commits
1 Commits
v0.204.4
...
fix-git-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2dffc792 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -5350,6 +5350,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"text",
|
||||
"time",
|
||||
"unindent",
|
||||
@@ -16850,6 +16851,7 @@ dependencies = [
|
||||
"tasks_ui",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"tempfile",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
@@ -16866,6 +16868,7 @@ dependencies = [
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
"which 6.0.3",
|
||||
"windows 0.58.0",
|
||||
"winresource",
|
||||
"workspace",
|
||||
|
||||
@@ -275,7 +275,11 @@ async fn run_evaluation(
|
||||
let db_path = Path::new(EVAL_DB_PATH);
|
||||
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>;
|
||||
let fs = Arc::new(RealFs::new(
|
||||
git_hosting_provider_registry,
|
||||
None,
|
||||
PathBuf::from("/non/existent/askpass"),
|
||||
)) as Arc<dyn Fs>;
|
||||
let clock = Arc::new(RealSystemClock);
|
||||
let client = cx
|
||||
.update(|cx| {
|
||||
|
||||
@@ -248,6 +248,7 @@ impl From<MTime> for proto::Timestamp {
|
||||
pub struct RealFs {
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
}
|
||||
|
||||
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
||||
@@ -302,10 +303,12 @@ impl RealFs {
|
||||
pub fn new(
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
git_hosting_provider_registry,
|
||||
git_binary_path,
|
||||
askpass_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,6 +772,7 @@ impl Fs for RealFs {
|
||||
Some(Arc::new(RealGitRepository::new(
|
||||
repo,
|
||||
self.git_binary_path.clone(),
|
||||
self.askpass_path.to_owned(),
|
||||
self.git_hosting_provider_registry.clone(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
@@ -10,8 +10,11 @@ use rope::Rope;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Borrow;
|
||||
use std::env::temp_dir;
|
||||
use std::io::Write as _;
|
||||
use std::process::Stdio;
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
@@ -200,6 +203,7 @@ impl std::fmt::Debug for dyn GitRepository {
|
||||
pub struct RealGitRepository {
|
||||
pub repository: Mutex<git2::Repository>,
|
||||
pub git_binary_path: PathBuf,
|
||||
pub askpass_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
}
|
||||
|
||||
@@ -207,11 +211,13 @@ impl RealGitRepository {
|
||||
pub fn new(
|
||||
repository: git2::Repository,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
repository: Mutex::new(repository),
|
||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||
askpass_path,
|
||||
hosting_provider_registry,
|
||||
}
|
||||
}
|
||||
@@ -608,7 +614,10 @@ impl GitRepository for RealGitRepository {
|
||||
) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["push", "--quiet"])
|
||||
.args(options.map(|option| match option {
|
||||
@@ -632,9 +641,12 @@ impl GitRepository for RealGitRepository {
|
||||
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["pull", "--quiet"])
|
||||
.args(["pull"])
|
||||
.arg(remote_name)
|
||||
.arg(branch_name)
|
||||
.output()?;
|
||||
@@ -652,7 +664,10 @@ impl GitRepository for RealGitRepository {
|
||||
fn fetch(&self) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["fetch", "--quiet", "--all"])
|
||||
.output()?;
|
||||
|
||||
@@ -116,6 +116,7 @@ task.workspace = true
|
||||
tasks_ui.workspace = true
|
||||
telemetry.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
tempfile.workspace = true
|
||||
terminal_view.workspace = true
|
||||
theme.workspace = true
|
||||
theme_extension.workspace = true
|
||||
@@ -130,6 +131,7 @@ uuid.workspace = true
|
||||
vim.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
welcome.workspace = true
|
||||
which.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zeta.workspace = true
|
||||
|
||||
@@ -256,9 +256,11 @@ fn main() {
|
||||
};
|
||||
log::info!("Using git binary path: {:?}", git_binary_path);
|
||||
|
||||
let git_askpass_path = zed::git_askpass::get_askpass_dir();
|
||||
let fs = Arc::new(RealFs::new(
|
||||
git_hosting_provider_registry.clone(),
|
||||
git_binary_path,
|
||||
git_askpass_path.clone(),
|
||||
));
|
||||
let user_settings_file_rx = watch_config_file(
|
||||
&app.background_executor(),
|
||||
@@ -301,6 +303,7 @@ fn main() {
|
||||
});
|
||||
|
||||
app.run(move |cx| {
|
||||
zed::git_askpass::setup_git_askpass(git_askpass_path, cx);
|
||||
release_channel::init(app_version, cx);
|
||||
gpui_tokio::init(cx);
|
||||
if let Some(app_commit_sha) = app_commit_sha {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod app_menus;
|
||||
pub mod git_askpass;
|
||||
pub mod inline_completion_registry;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub(crate) mod linux_prompts;
|
||||
|
||||
137
crates/zed/src/zed/git_askpass.rs
Normal file
137
crates/zed/src/zed/git_askpass.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use std::{os::unix::fs::PermissionsExt, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gpui::AsyncApp;
|
||||
use smol::{
|
||||
io::{AsyncWriteExt as _, BufReader},
|
||||
net::unix::UnixListener,
|
||||
};
|
||||
use ui::{App, Window};
|
||||
use util::{maybe, ResultExt as _};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub fn get_askpass_dir() -> PathBuf {
|
||||
// TODO: bundle this script instead of creating it
|
||||
let temp_dir = tempfile::Builder::new()
|
||||
.prefix("zed-git-askpass-session")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
// Create a domain socket listener to handle requests from the askpass program.
|
||||
let askpass_socket = temp_dir.path().join("git_askpass.sock");
|
||||
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = format!(
|
||||
"{shebang}\n{print_args} | {nc} -U {askpass_socket} 2> /dev/null \n",
|
||||
// on macOS `brew install netcat` provides the GNU netcat implementation
|
||||
// which does not support -U.
|
||||
nc = if cfg!(target_os = "macos") {
|
||||
"/usr/bin/nc"
|
||||
} else {
|
||||
"nc"
|
||||
},
|
||||
askpass_socket = askpass_socket.display(),
|
||||
print_args = "printf '%s\\0' \"$@\"",
|
||||
shebang = "#!/bin/sh",
|
||||
);
|
||||
let askpass_script_path = temp_dir.path().join("askpass.sh");
|
||||
std::fs::write(&askpass_script_path, &askpass_script).unwrap();
|
||||
std::fs::set_permissions(&askpass_script_path, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
|
||||
PathBuf::from(askpass_script)
|
||||
}
|
||||
|
||||
pub fn setup_git_askpass(askpasss_file: PathBuf, cx: &mut App) {
|
||||
maybe!({
|
||||
anyhow::ensure!(
|
||||
which::which("nc").is_ok(),
|
||||
"Cannot find `nc` command (netcat), which is required to connect over SSH."
|
||||
);
|
||||
|
||||
// TODO: REMOVE THIS ONCE WE HAVE A WAY OF BUNDLING AN ASKPASS SCRIPT
|
||||
let askpass_socket = askpasss_file.parent().unwrap().join("git_askpass.sock");
|
||||
|
||||
let listener =
|
||||
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
|
||||
|
||||
cx.spawn({
|
||||
|mut cx| async move {
|
||||
while let Ok((mut stream, _)) = listener.accept().await {
|
||||
let mut buffer = Vec::new();
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
if smol::io::AsyncBufReadExt::read_until(&mut reader, b'\0', &mut buffer)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
buffer.clear();
|
||||
}
|
||||
let password_prompt = String::from_utf8_lossy(&buffer);
|
||||
if let Some(Ok(password)) = ask_password(&password_prompt, &mut cx)
|
||||
.await
|
||||
.context("failed to get ssh password")
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
} else {
|
||||
stream.write("\n".as_bytes()).await.log_err();
|
||||
}
|
||||
|
||||
stream.flush().await.log_err();
|
||||
stream.close().await.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
async fn ask_password(prompt: &str, cx: &mut AsyncApp) -> Option<Result<String>> {
|
||||
let mut workspace = get_workspace(cx, |window| window.is_window_active());
|
||||
if workspace.is_none() {
|
||||
workspace = get_workspace(cx, |_| true);
|
||||
}
|
||||
|
||||
let Some(workspace) = workspace else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// DO THINGS WITH THE WORKSPACE
|
||||
// pop the askpass modal, get the output out of a oneshot, and we're good to go
|
||||
None
|
||||
}
|
||||
|
||||
fn get_workspace(
|
||||
cx: &mut AsyncApp,
|
||||
predicate: impl Fn(&mut Window) -> bool,
|
||||
) -> Option<gpui::Entity<Workspace>> {
|
||||
let workspace = cx
|
||||
.update(|cx| {
|
||||
for window in cx.windows() {
|
||||
let workspace = window
|
||||
.update(cx, |view, window, _| {
|
||||
if predicate(window) {
|
||||
if let Ok(workspace) = view.downcast::<Workspace>() {
|
||||
return Some(workspace);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if let Some(workspace) = workspace {
|
||||
return Some(workspace);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
workspace
|
||||
}
|
||||
Reference in New Issue
Block a user