Compare commits
1 Commits
namespace_
...
fix-git-ht
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2dffc792 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -5350,6 +5350,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
|
"tempfile",
|
||||||
"text",
|
"text",
|
||||||
"time",
|
"time",
|
||||||
"unindent",
|
"unindent",
|
||||||
@@ -16850,6 +16851,7 @@ dependencies = [
|
|||||||
"tasks_ui",
|
"tasks_ui",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"telemetry_events",
|
"telemetry_events",
|
||||||
|
"tempfile",
|
||||||
"terminal_view",
|
"terminal_view",
|
||||||
"theme",
|
"theme",
|
||||||
"theme_extension",
|
"theme_extension",
|
||||||
@@ -16866,6 +16868,7 @@ dependencies = [
|
|||||||
"vim",
|
"vim",
|
||||||
"vim_mode_setting",
|
"vim_mode_setting",
|
||||||
"welcome",
|
"welcome",
|
||||||
|
"which 6.0.3",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
"winresource",
|
"winresource",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
|||||||
@@ -275,7 +275,11 @@ async fn run_evaluation(
|
|||||||
let db_path = Path::new(EVAL_DB_PATH);
|
let db_path = Path::new(EVAL_DB_PATH);
|
||||||
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
||||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
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 clock = Arc::new(RealSystemClock);
|
||||||
let client = cx
|
let client = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ impl From<MTime> for proto::Timestamp {
|
|||||||
pub struct RealFs {
|
pub struct RealFs {
|
||||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
git_binary_path: Option<PathBuf>,
|
git_binary_path: Option<PathBuf>,
|
||||||
|
askpass_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
||||||
@@ -302,10 +303,12 @@ impl RealFs {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
git_binary_path: Option<PathBuf>,
|
git_binary_path: Option<PathBuf>,
|
||||||
|
askpass_path: PathBuf,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
git_hosting_provider_registry,
|
git_hosting_provider_registry,
|
||||||
git_binary_path,
|
git_binary_path,
|
||||||
|
askpass_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,6 +772,7 @@ impl Fs for RealFs {
|
|||||||
Some(Arc::new(RealGitRepository::new(
|
Some(Arc::new(RealGitRepository::new(
|
||||||
repo,
|
repo,
|
||||||
self.git_binary_path.clone(),
|
self.git_binary_path.clone(),
|
||||||
|
self.askpass_path.to_owned(),
|
||||||
self.git_hosting_provider_registry.clone(),
|
self.git_hosting_provider_registry.clone(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ schemars.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
sum_tree.workspace = true
|
sum_tree.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ use rope::Rope;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
use std::env::temp_dir;
|
||||||
use std::io::Write as _;
|
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::sync::LazyLock;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
@@ -200,6 +203,7 @@ impl std::fmt::Debug for dyn GitRepository {
|
|||||||
pub struct RealGitRepository {
|
pub struct RealGitRepository {
|
||||||
pub repository: Mutex<git2::Repository>,
|
pub repository: Mutex<git2::Repository>,
|
||||||
pub git_binary_path: PathBuf,
|
pub git_binary_path: PathBuf,
|
||||||
|
pub askpass_path: PathBuf,
|
||||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,11 +211,13 @@ impl RealGitRepository {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
repository: git2::Repository,
|
repository: git2::Repository,
|
||||||
git_binary_path: Option<PathBuf>,
|
git_binary_path: Option<PathBuf>,
|
||||||
|
askpass_path: PathBuf,
|
||||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
repository: Mutex::new(repository),
|
repository: Mutex::new(repository),
|
||||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||||
|
askpass_path,
|
||||||
hosting_provider_registry,
|
hosting_provider_registry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -608,7 +614,10 @@ impl GitRepository for RealGitRepository {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let working_directory = self.working_directory()?;
|
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)
|
.current_dir(&working_directory)
|
||||||
.args(["push", "--quiet"])
|
.args(["push", "--quiet"])
|
||||||
.args(options.map(|option| match option {
|
.args(options.map(|option| match option {
|
||||||
@@ -632,9 +641,12 @@ impl GitRepository for RealGitRepository {
|
|||||||
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
|
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
|
||||||
let working_directory = self.working_directory()?;
|
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)
|
.current_dir(&working_directory)
|
||||||
.args(["pull", "--quiet"])
|
.args(["pull"])
|
||||||
.arg(remote_name)
|
.arg(remote_name)
|
||||||
.arg(branch_name)
|
.arg(branch_name)
|
||||||
.output()?;
|
.output()?;
|
||||||
@@ -652,7 +664,10 @@ impl GitRepository for RealGitRepository {
|
|||||||
fn fetch(&self) -> Result<()> {
|
fn fetch(&self) -> Result<()> {
|
||||||
let working_directory = self.working_directory()?;
|
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)
|
.current_dir(&working_directory)
|
||||||
.args(["fetch", "--quiet", "--all"])
|
.args(["fetch", "--quiet", "--all"])
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ task.workspace = true
|
|||||||
tasks_ui.workspace = true
|
tasks_ui.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
theme_extension.workspace = true
|
theme_extension.workspace = true
|
||||||
@@ -130,6 +131,7 @@ uuid.workspace = true
|
|||||||
vim.workspace = true
|
vim.workspace = true
|
||||||
vim_mode_setting.workspace = true
|
vim_mode_setting.workspace = true
|
||||||
welcome.workspace = true
|
welcome.workspace = true
|
||||||
|
which.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
zeta.workspace = true
|
zeta.workspace = true
|
||||||
|
|||||||
@@ -256,9 +256,11 @@ fn main() {
|
|||||||
};
|
};
|
||||||
log::info!("Using git binary path: {:?}", git_binary_path);
|
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(
|
let fs = Arc::new(RealFs::new(
|
||||||
git_hosting_provider_registry.clone(),
|
git_hosting_provider_registry.clone(),
|
||||||
git_binary_path,
|
git_binary_path,
|
||||||
|
git_askpass_path.clone(),
|
||||||
));
|
));
|
||||||
let user_settings_file_rx = watch_config_file(
|
let user_settings_file_rx = watch_config_file(
|
||||||
&app.background_executor(),
|
&app.background_executor(),
|
||||||
@@ -301,6 +303,7 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.run(move |cx| {
|
app.run(move |cx| {
|
||||||
|
zed::git_askpass::setup_git_askpass(git_askpass_path, cx);
|
||||||
release_channel::init(app_version, cx);
|
release_channel::init(app_version, cx);
|
||||||
gpui_tokio::init(cx);
|
gpui_tokio::init(cx);
|
||||||
if let Some(app_commit_sha) = app_commit_sha {
|
if let Some(app_commit_sha) = app_commit_sha {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod app_menus;
|
mod app_menus;
|
||||||
|
pub mod git_askpass;
|
||||||
pub mod inline_completion_registry;
|
pub mod inline_completion_registry;
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
pub(crate) mod linux_prompts;
|
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