integrate gitbutler_git::push() into gitbutler-app and wire up askpass mechanisms

This commit is contained in:
Josh Junon 2024-03-19 17:00:00 +01:00 committed by Kiril Videlov
parent 56ca8b233d
commit df2f81340b
17 changed files with 342 additions and 67 deletions

1
Cargo.lock generated
View File

@ -1935,6 +1935,7 @@ dependencies = [
"futures",
"git2",
"git2-hooks",
"gitbutler-git",
"governor",
"itertools 0.12.1",
"lazy_static",

View File

@ -74,6 +74,7 @@ uuid.workspace = true
walkdir = "2.3.2"
zip = "0.6.5"
tempfile = "3.10"
gitbutler-git = { path = "../gitbutler-git" }
[features]
# by default Tauri runs in production mode

View File

@ -3,12 +3,15 @@ use std::{collections::HashMap, path};
use anyhow::{Context, Result};
use crate::{
askpass::AskpassBroker,
gb_repository, git,
project_repository::{self, conflicts},
projects::{self, ProjectId},
reader,
sessions::{self, SessionId},
users, watcher,
users,
virtual_branches::BranchId,
watcher,
};
#[derive(Clone)]
@ -124,11 +127,12 @@ impl App {
remote_name: &str,
branch_name: &str,
credentials: &git::credentials::Helper,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<(), Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::open(&project)?;
project_repository
.git_test_push(credentials, remote_name, branch_name)
.git_test_push(credentials, remote_name, branch_name, askpass)
.map_err(Error::Other)
}

View File

@ -0,0 +1,81 @@
use std::{collections::HashMap, sync::Arc};
use serde::Serialize;
use tauri::{AppHandle, Manager};
use tokio::sync::{oneshot, Mutex};
use crate::id::Id;
pub struct AskpassRequest {
sender: oneshot::Sender<Option<String>>,
}
#[derive(Clone)]
pub struct AskpassBroker {
pending_requests: Arc<Mutex<HashMap<Id<AskpassRequest>, AskpassRequest>>>,
handle: AppHandle,
}
#[derive(Debug, Clone, serde::Serialize)]
struct PromptEvent<C: Serialize + Clone> {
id: Id<AskpassRequest>,
prompt: String,
context: C,
}
impl AskpassBroker {
pub fn init(handle: AppHandle) -> Self {
Self {
pending_requests: Arc::new(Mutex::new(HashMap::new())),
handle,
}
}
pub async fn submit_prompt<C: Serialize + Clone>(
&self,
prompt: String,
context: C,
) -> Option<String> {
let (sender, receiver) = oneshot::channel();
let id = Id::generate();
let request = AskpassRequest { sender };
self.pending_requests.lock().await.insert(id, request);
self.handle
.emit_all(
"git_prompt",
PromptEvent {
id,
prompt,
context,
},
)
.expect("failed to emit askpass event");
receiver.await.unwrap()
}
pub async fn handle_response(&self, id: Id<AskpassRequest>, response: Option<String>) {
let mut pending_requests = self.pending_requests.lock().await;
if let Some(request) = pending_requests.remove(&id) {
let _ = request.sender.send(response);
} else {
log::warn!("received response for unknown askpass request: {}", id);
}
}
}
pub mod commands {
use super::*;
#[tauri::command(async)]
#[tracing::instrument(skip(handle))]
pub async fn submit_prompt_response(
handle: AppHandle,
id: Id<AskpassRequest>,
response: Option<String>,
) -> Result<(), ()> {
handle
.state::<AskpassBroker>()
.handle_response(id, response)
.await;
Ok(())
}
}

View File

@ -76,7 +76,17 @@ pub async fn git_test_push(
code: Code::Validation,
message: "Malformed project id".to_string(),
})?;
app.git_test_push(&project_id, remote_name, branch_name, &helper)
let askpass_broker = handle
.state::<crate::askpass::AskpassBroker>()
.inner()
.clone();
app.git_test_push(
&project_id,
remote_name,
branch_name,
&helper,
Some((askpass_broker, None)),
)
.map_err(|e| Error::UserError {
code: Code::Unknown,
message: e.to_string(),

View File

@ -185,6 +185,10 @@ impl Helper {
.collect())
}
projects::AuthKey::Default => self.default_flow(remote, project_repository),
projects::AuthKey::SystemExecutable => {
tracing::error!("WARNING: FIXME: this codepath should NEVER be hit. Something is seriously wrong.");
self.default_flow(remote, project_repository)
}
}
}

View File

@ -3,6 +3,7 @@
pub(crate) mod analytics;
pub(crate) mod app;
pub(crate) mod askpass;
pub(crate) mod assets;
pub(crate) mod commands;
pub(crate) mod database;
@ -103,6 +104,9 @@ fn main() {
tracing::info!(version = %app_handle.package_info().version, name = %app_handle.package_info().name, "starting app");
let askpass_broker = askpass::AskpassBroker::init(app_handle.clone());
app_handle.manage(askpass_broker);
let storage_controller = storage::Storage::new(&app_data_dir);
app_handle.manage(storage_controller.clone());
@ -282,6 +286,7 @@ fn main() {
keys::commands::get_public_key,
github::commands::init_device_oauth,
github::commands::check_auth_status,
askpass::commands::submit_prompt_response,
])
.menu(menu::build(tauri_context.package_info()))
.on_menu_event(|event|menu::handle_event(&event))

View File

@ -7,9 +7,12 @@ use std::{
use anyhow::{Context, Result};
use crate::{
askpass::AskpassBroker,
git::{self, credentials::HelpError, Url},
keys, projects, ssh, users,
virtual_branches::Branch,
keys,
projects::{self, AuthKey},
ssh, users,
virtual_branches::{Branch, BranchId},
};
use super::conflicts;
@ -167,6 +170,7 @@ impl Repository {
credentials: &git::credentials::Helper,
remote_name: &str,
branch_name: &str,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<()> {
let target_branch_refname =
git::Refname::from_str(&format!("refs/remotes/{}/{}", remote_name, branch_name))?;
@ -185,13 +189,27 @@ impl Repository {
remote_name, branch_name,
))?;
match self.push(&commit_id, &refname, false, credentials, None) {
match self.push(
&commit_id,
&refname,
false,
credentials,
None,
askpass.clone(),
) {
Ok(()) => Ok(()),
Err(e) => Err(anyhow::anyhow!(e.to_string())),
}?;
let empty_refspec = Some(format!(":refs/heads/{}", branch_name));
match self.push(&commit_id, &refname, false, credentials, empty_refspec) {
match self.push(
&commit_id,
&refname,
false,
credentials,
empty_refspec,
askpass,
) {
Ok(()) => Ok(()),
Err(e) => Err(anyhow::anyhow!(e.to_string())),
}?;
@ -434,6 +452,7 @@ impl Repository {
with_force: bool,
credentials: &git::credentials::Helper,
refspec: Option<String>,
askpass_broker: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<(), RemoteError> {
let refspec = refspec.unwrap_or_else(|| {
if with_force {
@ -443,6 +462,32 @@ impl Repository {
}
});
// NOTE(qix-): This is a nasty hack, however the codebase isn't structured
// NOTE(qix-): in a way that allows us to really incorporate new backends
// NOTE(qix-): without a lot of work. This is a temporary measure to
// NOTE(qix-): work around a time-sensitive change that was necessary
// NOTE(qix-): without having to refactor a large portion of the codebase.
if self.project.preferred_key == AuthKey::SystemExecutable {
let path = self.path().to_path_buf();
let remote = branch.remote().to_string();
return std::thread::spawn(move || {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(gitbutler_git::push(
path,
gitbutler_git::tokio::TokioExecutor,
&remote,
gitbutler_git::RefSpec::parse(refspec).unwrap(),
with_force,
handle_git_prompt,
askpass_broker,
))
})
.join()
.unwrap()
.map_err(|e| RemoteError::Other(e.into()));
}
let auth_flows = credentials.help(self, branch.remote())?;
for (mut remote, callbacks) in auth_flows {
if let Some(url) = remote.url().context("failed to get remote url")? {
@ -577,3 +622,24 @@ pub enum LogUntil {
#[cfg(test)]
End,
}
#[derive(Debug, Clone, serde::Serialize)]
struct AskpassPromptContext {
branch_id: Option<BranchId>,
}
#[allow(unused_variables)]
async fn handle_git_prompt(
prompt: String,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Option<String> {
if let Some((askpass_broker, branch_id)) = askpass {
tracing::info!("received prompt for branch {branch_id:?}: {prompt:?}");
askpass_broker
.submit_prompt(prompt, AskpassPromptContext { branch_id })
.await
} else {
tracing::warn!("received askpass prompt but no broker was supplied; returning None");
None
}
}

View File

@ -4,12 +4,13 @@ use serde::{Deserialize, Serialize};
use crate::{git, id::Id, types::default_true::DefaultTrue};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum AuthKey {
#[default]
Default,
Generated,
SystemExecutable,
GitCredentialsHelper,
Local {
private_key_path: path::PathBuf,

View File

@ -108,6 +108,7 @@ mod unapply_ownership {
}
mod create_commit {
use super::*;
#[tokio::test]
@ -219,7 +220,7 @@ mod create_commit {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -518,6 +519,7 @@ mod references {
}
mod push_virtual_branch {
use super::*;
#[tokio::test]
@ -552,7 +554,7 @@ mod references {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.push_virtual_branch(&project_id, &branch1_id, false, None)
.await
.unwrap();
@ -605,7 +607,7 @@ mod references {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.push_virtual_branch(&project_id, &branch1_id, false, None)
.await
.unwrap();
branch1_id
@ -642,7 +644,7 @@ mod references {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch2_id, false)
.push_virtual_branch(&project_id, &branch2_id, false, None)
.await
.unwrap();
branch2_id
@ -1603,6 +1605,7 @@ mod update_base_branch {
use super::*;
mod unapplied_branch {
use super::*;
#[tokio::test]
@ -1810,7 +1813,7 @@ mod update_base_branch {
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -2170,7 +2173,7 @@ mod update_base_branch {
fs::write(repository.path().join("file2.txt"), "other").unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -2333,7 +2336,7 @@ mod update_base_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
}
@ -2367,6 +2370,7 @@ mod update_base_branch {
}
mod applied_branch {
use super::*;
#[tokio::test]
@ -2565,7 +2569,7 @@ mod update_base_branch {
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -2828,7 +2832,7 @@ mod update_base_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -2900,7 +2904,7 @@ mod update_base_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -3064,7 +3068,7 @@ mod update_base_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -3187,7 +3191,7 @@ mod update_base_branch {
// push the branch
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -3283,7 +3287,7 @@ mod update_base_branch {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -3365,7 +3369,7 @@ mod update_base_branch {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -3502,7 +3506,7 @@ mod update_base_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
}
@ -3800,6 +3804,7 @@ mod reset_virtual_branch {
}
mod upstream {
use super::*;
#[tokio::test]
@ -3841,7 +3846,7 @@ mod upstream {
// push
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.push_virtual_branch(&project_id, &branch1_id, false, None)
.await
.unwrap();
@ -3908,7 +3913,7 @@ mod upstream {
// push
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.push_virtual_branch(&project_id, &branch1_id, false, None)
.await
.unwrap();
@ -3955,6 +3960,7 @@ mod cherry_pick {
use super::*;
mod cleanly {
use super::*;
#[tokio::test]
@ -3993,7 +3999,7 @@ mod cherry_pick {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -4065,7 +4071,7 @@ mod cherry_pick {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -4175,6 +4181,7 @@ mod cherry_pick {
}
mod with_conflicts {
use super::*;
#[tokio::test]
@ -4221,7 +4228,7 @@ mod cherry_pick {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -4335,6 +4342,7 @@ mod cherry_pick {
}
mod amend {
use super::*;
#[tokio::test]
@ -4416,7 +4424,7 @@ mod amend {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -4483,7 +4491,7 @@ mod amend {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -4904,6 +4912,7 @@ mod init {
}
mod squash {
use super::*;
#[tokio::test]
@ -5096,7 +5105,7 @@ mod squash {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -5179,7 +5188,7 @@ mod squash {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -5263,6 +5272,7 @@ mod squash {
}
mod update_commit_message {
use super::*;
#[tokio::test]
@ -5450,7 +5460,7 @@ mod update_commit_message {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -5520,7 +5530,7 @@ mod update_commit_message {
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -5680,7 +5690,7 @@ mod create_virtual_branch_from_branch {
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();
@ -5731,7 +5741,7 @@ mod create_virtual_branch_from_branch {
{
// merge branch into master
controller
.push_virtual_branch(&project_id, &branch_id, false)
.push_virtual_branch(&project_id, &branch_id, false, None)
.await
.unwrap();

View File

@ -406,9 +406,15 @@ pub async fn push_virtual_branch(
code: Code::Validation,
message: "Malformed branch id".to_string(),
})?;
let askpass_broker = handle.state::<crate::askpass::AskpassBroker>();
handle
.state::<Controller>()
.push_virtual_branch(&project_id, &branch_id, with_force)
.push_virtual_branch(
&project_id,
&branch_id,
with_force,
Some((askpass_broker.inner().clone(), Some(branch_id))),
)
.await?;
emit_vbranches(&handle, &project_id).await;
Ok(())

View File

@ -1,9 +1,10 @@
use std::{collections::HashMap, path, sync::Arc};
use anyhow::Context;
use tokio::sync::Semaphore;
use tokio::{sync::Semaphore, task::JoinHandle};
use crate::{
askpass::AskpassBroker,
error::Error,
gb_repository, git, keys, project_repository,
projects::{self, ProjectId},
@ -305,10 +306,11 @@ impl Controller {
project_id: &ProjectId,
branch_id: &BranchId,
with_force: bool,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<(), ControllerError<errors::PushError>> {
self.inner(project_id)
.await
.push_virtual_branch(project_id, branch_id, with_force)
.push_virtual_branch(project_id, branch_id, with_force, askpass)
.await
}
@ -863,19 +865,25 @@ impl ControllerInner {
project_id: &ProjectId,
branch_id: &BranchId,
with_force: bool,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<(), ControllerError<errors::PushError>> {
let _permit = self.semaphore.acquire().await;
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
let helper = self.helper.clone();
let project_id = *project_id;
let branch_id = *branch_id;
self.with_verify_branch_async(&project_id, move |gb_repository, project_repository, _| {
super::push(
project_repository,
gb_repository,
branch_id,
&branch_id,
with_force,
&self.helper,
&helper,
askpass,
)
.map_err(Into::into)
})
})?
.await
.map_err(|e| ControllerError::Other(e.into()))?
.map_err(ControllerError::Action)
}
pub async fn cherry_pick(
@ -1074,4 +1082,29 @@ impl ControllerInner {
super::integration::verify_branch(&gb_repository, &project_repository)?;
action(&gb_repository, &project_repository, user.as_ref()).map_err(ControllerError::Action)
}
fn with_verify_branch_async<T: Send + 'static, E: Into<Error> + Send + 'static>(
&self,
project_id: &ProjectId,
action: impl FnOnce(
&gb_repository::Repository,
&project_repository::Repository,
Option<&users::User>,
) -> Result<T, E>
+ Send
+ 'static,
) -> Result<JoinHandle<Result<T, E>>, ControllerError<E>> {
let local_data_dir = self.local_data_dir.clone();
let project = self.projects.get(project_id).map_err(Error::from)?;
let project_repository =
project_repository::Repository::open(&project).map_err(Error::from)?;
let user = self.users.get_user().map_err(Error::from)?;
let gb_repository =
gb_repository::Repository::open(&local_data_dir, &project_repository, user.as_ref())
.context("failed to open gitbutler repository")?;
super::integration::verify_branch(&gb_repository, &project_repository)?;
Ok(tokio::task::spawn_blocking(move || {
action(&gb_repository, &project_repository, user.as_ref())
}))
}
}

View File

@ -15,6 +15,7 @@ use regex::Regex;
use serde::Serialize;
use crate::{
askpass::AskpassBroker,
dedup::{dedup, dedup_fmt},
gb_repository,
git::{
@ -2593,6 +2594,7 @@ pub fn push(
branch_id: &BranchId,
with_force: bool,
credentials: &git::credentials::Helper,
askpass: Option<(AskpassBroker, Option<BranchId>)>,
) -> Result<(), errors::PushError> {
let current_session = gb_repository
.get_or_create_current_session()
@ -2649,7 +2651,14 @@ pub fn push(
))
};
project_repository.push(&vbranch.head, &remote_branch, with_force, credentials, None)?;
project_repository.push(
&vbranch.head,
&remote_branch,
with_force,
credentials,
None,
askpass,
)?;
vbranch.upstream = Some(remote_branch.clone());
vbranch.upstream_head = Some(vbranch.head);

View File

@ -4,7 +4,7 @@
"beforeBuildCommand": "pnpm build:development",
"devPath": "http://localhost:1420",
"distDir": "../gitbutler-ui/build",
"withGlobalTauri": false
"withGlobalTauri": true
},
"package": {
"productName": "GitButler Dev"

View File

@ -7,10 +7,10 @@ pub fn main(sock_path: &str, secret: &str, prompt: &str) {
// Set a timer for 10s.
raw_stream
.set_read_timeout(Some(std::time::Duration::from_secs(10)))
.expect("set_read_timeout():");
.expect("set_read_timeout(Some):");
let mut reader = BufReader::new(raw_stream.try_clone().unwrap());
let mut writer = BufWriter::new(raw_stream);
let mut writer = BufWriter::new(raw_stream.try_clone().unwrap());
// Write the secret.
writeln!(writer, "{secret}").expect("write(secret):");
@ -20,6 +20,11 @@ pub fn main(sock_path: &str, secret: &str, prompt: &str) {
writer.flush().expect("flush():");
// Clear the timeout (it's now time for the user to provide a response)
raw_stream
.set_read_timeout(None)
.expect("set_read_timeout(None):");
// Wait for the response.
let mut password = String::new();
let nread = reader.read_line(&mut password).expect("read_line():");

View File

@ -24,7 +24,7 @@ unsafe impl super::GitExecutor for TokioExecutor {
// Output the command being executed to stderr, for debugging purposes
// (only on test configs).
#[cfg(test)]
#[cfg(any(test, debug_assertions))]
{
let mut envs_str = String::new();
if let Some(envs) = &envs {
@ -46,6 +46,16 @@ unsafe impl super::GitExecutor for TokioExecutor {
let output = cmd.output().await?;
#[cfg(any(test, debug_assertions))]
{
eprintln!(
"\n\n GIT STDOUT:\n\n{}\n\nGIT STDERR:\n\n{}\n\nGIT EXIT CODE: {}\n",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
output.status.code().unwrap_or(127) as usize
);
}
Ok((
output.status.code().unwrap_or(127) as usize,
String::from_utf8_lossy(&output.stdout).trim().into(),

View File

@ -59,18 +59,20 @@ pub type Error<E> = RepositoryError<
>;
#[cold]
async fn execute_with_auth_harness<P, F, Fut, E>(
async fn execute_with_auth_harness<P, F, Fut, E, Extra>(
repo_path: P,
executor: E,
args: &[&str],
envs: Option<HashMap<String, String>>,
on_prompt: F,
mut on_prompt: F,
extra: Extra,
) -> Result<(usize, String, String), Error<E>>
where
P: AsRef<Path>,
E: GitExecutor,
F: Fn(&str) -> Fut,
F: FnMut(String, Extra) -> Fut,
Fut: std::future::Future<Output = Option<String>>,
Extra: Send + Clone,
{
let path = std::env::current_exe().map_err(Error::<E>::NoSelfExe)?;
@ -136,10 +138,17 @@ where
envs.insert("DISPLAY".into(), ":".into());
}
let base_ssh_command = match envs.get("GIT_SSH_COMMAND") {
Some(v) => v.clone(),
None => get_core_sshcommand(&executor, &repo_path)
.await
.unwrap_or_else(|| "ssh".into()),
};
envs.insert(
"GIT_SSH_COMMAND".into(),
format!(
"{}{} -o StrictHostKeyChecking=accept-new -o KbdInteractiveAuthentication=no{}",
"{}{base_ssh_command} -o StrictHostKeyChecking=accept-new -o KbdInteractiveAuthentication=no{}",
{
#[cfg(not(target_os = "windows"))]
{
@ -150,7 +159,6 @@ where
""
}
},
envs.get("GIT_SSH_COMMAND").unwrap_or(&"ssh".into()),
{
// In test environments, we don't want to pollute the user's known hosts file.
// So, we just use /dev/null instead.
@ -180,7 +188,7 @@ where
res = child_process => {
return res;
},
res = sock_server.accept(Some(Duration::from_secs(60))).fuse() => {
res = sock_server.accept(Some(Duration::from_secs(120))).fuse() => {
let mut sock = res.map_err(Error::<E>::AskpassServer)?;
// get the PID of the peer
@ -222,7 +230,7 @@ where
let prompt = sock.read_line().await.map_err(Error::<E>::AskpassIo)?;
// call the prompt handler
let response = on_prompt(&prompt).await;
let response = on_prompt(prompt.clone(), extra.clone()).await;
if let Some(response) = response {
sock.write_line(&response).await.map_err(Error::<E>::AskpassIo)?;
} else {
@ -238,18 +246,20 @@ where
/// callback `on_prompt` which should return the user's response or `None` if the
/// operation should be aborted, in which case an `Err` value is returned from this
/// function.
pub async fn fetch<P, F, Fut, E>(
pub async fn fetch<P, F, Fut, E, Extra>(
repo_path: P,
executor: E,
remote: &str,
refspec: RefSpec,
on_prompt: F,
extra: Extra,
) -> Result<(), crate::Error<Error<E>>>
where
P: AsRef<Path>,
E: GitExecutor,
F: Fn(&str) -> Fut,
F: FnMut(String, Extra) -> Fut,
Fut: std::future::Future<Output = Option<String>>,
Extra: Send + Clone,
{
let mut args = vec!["fetch", "--quiet", "--no-write-fetch-head"];
@ -259,7 +269,7 @@ where
args.push(&refspec);
let (status, stdout, stderr) =
execute_with_auth_harness(repo_path, executor, &args, None, on_prompt).await?;
execute_with_auth_harness(repo_path, executor, &args, None, on_prompt, extra).await?;
if status == 0 {
Ok(())
@ -293,19 +303,21 @@ where
/// Any prompts for the user are passed to the asynchronous callback `on_prompt`,
/// which should return the user's response or `None` if the operation should be
/// aborted, in which case an `Err` value is returned from this function.
pub async fn push<P, F, Fut, E>(
pub async fn push<P, F, Fut, E, Extra>(
repo_path: P,
executor: E,
remote: &str,
refspec: RefSpec,
force: bool,
on_prompt: F,
extra: Extra,
) -> Result<(), crate::Error<Error<E>>>
where
P: AsRef<Path>,
E: GitExecutor,
F: Fn(&str) -> Fut,
F: FnMut(String, Extra) -> Fut,
Fut: std::future::Future<Output = Option<String>>,
Extra: Send + Clone,
{
let mut args = vec!["push", "--quiet"];
@ -319,7 +331,7 @@ where
}
let (status, stdout, stderr) =
execute_with_auth_harness(repo_path, executor, &args, None, on_prompt).await?;
execute_with_auth_harness(repo_path, executor, &args, None, on_prompt, extra).await?;
if status == 0 {
Ok(())
@ -348,3 +360,20 @@ where
}
}
}
async fn get_core_sshcommand<E: GitExecutor, P: AsRef<Path>>(
executor: &E,
cwd: P,
) -> Option<String> {
executor
.execute(&["config", "--get", "core.sshCommand"], cwd, None)
.await
.map(|(status, stdout, _)| {
if status != 0 {
None
} else {
Some(stdout.trim().to_string())
}
})
.unwrap_or(None)
}