diff --git a/crates/gitbutler-core/src/askpass.rs b/crates/gitbutler-core/src/askpass.rs index be6dc22db..6b85f7cc4 100644 --- a/crates/gitbutler-core/src/askpass.rs +++ b/crates/gitbutler-core/src/askpass.rs @@ -5,6 +5,31 @@ use tokio::sync::{oneshot, Mutex}; use crate::{id::Id, virtual_branches::BranchId}; +static mut GLOBAL_ASKPASS_BROKER: Option = None; + +/// Initialize the global askpass broker. +/// +/// # Safety +/// This function **must** be called **at least once**, from only one thread at a time, +/// before any other function from this module is called. **Calls to [`get`] before [`init`] will panic.** +/// +/// This function is **NOT** thread safe. +pub unsafe fn init(submit_prompt: impl Fn(PromptEvent) + Send + Sync + 'static) { + GLOBAL_ASKPASS_BROKER.replace(AskpassBroker::init(submit_prompt)); +} + +/// Get the global askpass broker. +/// +/// # Panics +/// Will panic if [`init`] was not called before this function. +pub fn get_broker() -> &'static AskpassBroker { + unsafe { + GLOBAL_ASKPASS_BROKER + .as_ref() + .expect("askpass broker not initialized") + } +} + pub struct AskpassRequest { sender: oneshot::Sender>, } diff --git a/crates/gitbutler-core/src/project_repository/repository.rs b/crates/gitbutler-core/src/project_repository/repository.rs index be8639d7d..5ce7a964f 100644 --- a/crates/gitbutler-core/src/project_repository/repository.rs +++ b/crates/gitbutler-core/src/project_repository/repository.rs @@ -9,9 +9,7 @@ use anyhow::{Context, Result}; use super::conflicts; use crate::error::{AnyhowContextExt, Code, ErrorWithContext}; use crate::{ - askpass, - askpass::AskpassBroker, - error, + askpass, error, git::{self, credentials::HelpError, Url}, keys, projects::{self, AuthKey}, @@ -171,7 +169,7 @@ impl Repository { credentials: &git::credentials::Helper, remote_name: &str, branch_name: &str, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Result<()> { let target_branch_refname = git::Refname::from_str(&format!("refs/remotes/{}/{}", remote_name, branch_name))?; @@ -184,14 +182,7 @@ impl Repository { let refname = git::RemoteRefname::from_str(&format!("refs/remotes/{remote_name}/{branch_name}",))?; - match self.push( - &commit_id, - &refname, - false, - credentials, - None, - askpass.clone(), - ) { + match self.push(&commit_id, &refname, false, credentials, None, askpass) { Ok(()) => Ok(()), Err(e) => Err(anyhow::anyhow!(e.to_string())), }?; @@ -446,7 +437,7 @@ impl Repository { with_force: bool, credentials: &git::credentials::Helper, refspec: Option, - askpass_broker: Option<(AskpassBroker, Option)>, + askpass_broker: Option>, ) -> Result<(), RemoteError> { let refspec = refspec.unwrap_or_else(|| { if with_force { @@ -545,7 +536,7 @@ impl Repository { &self, remote_name: &str, credentials: &git::credentials::Helper, - askpass: Option<(AskpassBroker, String)>, + askpass: Option, ) -> Result<(), RemoteError> { let refspec = format!("+refs/heads/*:refs/remotes/{}/*", remote_name); @@ -658,11 +649,11 @@ pub enum LogUntil { async fn handle_git_prompt_push( prompt: String, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Option { - if let Some((askpass_broker, branch_id)) = askpass { + if let Some(branch_id) = askpass { tracing::info!("received prompt for branch push {branch_id:?}: {prompt:?}"); - askpass_broker + askpass::get_broker() .submit_prompt(prompt, askpass::Context::Push { branch_id }) .await } else { @@ -671,13 +662,10 @@ async fn handle_git_prompt_push( } } -async fn handle_git_prompt_fetch( - prompt: String, - askpass: Option<(AskpassBroker, String)>, -) -> Option { - if let Some((askpass_broker, action)) = askpass { +async fn handle_git_prompt_fetch(prompt: String, askpass: Option) -> Option { + if let Some(action) = askpass { tracing::info!("received prompt for fetch with action {action:?}: {prompt:?}"); - askpass_broker + askpass::get_broker() .submit_prompt(prompt, askpass::Context::Fetch { action }) .await } else { diff --git a/crates/gitbutler-core/src/virtual_branches/controller.rs b/crates/gitbutler-core/src/virtual_branches/controller.rs index 62c592c46..07576029a 100644 --- a/crates/gitbutler-core/src/virtual_branches/controller.rs +++ b/crates/gitbutler-core/src/virtual_branches/controller.rs @@ -16,7 +16,6 @@ use super::{ target, target_to_base_branch, BaseBranch, RemoteBranchFile, VirtualBranchesHandle, }; use crate::{ - askpass::AskpassBroker, git, keys, project_repository, projects::{self, ProjectId}, users, @@ -336,7 +335,7 @@ impl Controller { project_id: &ProjectId, branch_id: &BranchId, with_force: bool, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Result<(), Error> { self.inner(project_id) .await @@ -403,7 +402,7 @@ impl Controller { pub async fn fetch_from_target( &self, project_id: &ProjectId, - askpass: Option<(AskpassBroker, String)>, + askpass: Option, ) -> Result { self.inner(project_id) .await @@ -923,7 +922,7 @@ impl ControllerInner { project_id: &ProjectId, branch_id: &BranchId, with_force: bool, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Result<(), Error> { let _permit = self.semaphore.acquire().await; let helper = self.helper.clone(); @@ -1019,7 +1018,7 @@ impl ControllerInner { pub async fn fetch_from_target( &self, project_id: &ProjectId, - askpass: Option<(AskpassBroker, String)>, + askpass: Option, ) -> Result { let project = self.projects.get(project_id)?; let mut project_repository = project_repository::Repository::open(&project)?; diff --git a/crates/gitbutler-core/src/virtual_branches/virtual.rs b/crates/gitbutler-core/src/virtual_branches/virtual.rs index e21c39e9b..969be78db 100644 --- a/crates/gitbutler-core/src/virtual_branches/virtual.rs +++ b/crates/gitbutler-core/src/virtual_branches/virtual.rs @@ -27,7 +27,6 @@ use super::{ use crate::git::diff::{diff_files_into_hunks, trees, FileDiff}; use crate::virtual_branches::branch::HunkHash; use crate::{ - askpass::AskpassBroker, dedup::{dedup, dedup_fmt}, git::{ self, @@ -2441,7 +2440,7 @@ pub fn push( branch_id: &BranchId, with_force: bool, credentials: &git::credentials::Helper, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Result<(), errors::PushError> { let vb_state = project_repository.project().virtual_branches(); @@ -2505,7 +2504,7 @@ pub fn push( with_force, credentials, None, - askpass.clone(), + askpass, )?; vbranch.upstream = Some(remote_branch.clone()); @@ -2516,7 +2515,7 @@ pub fn push( project_repository.fetch( remote_branch.remote(), credentials, - askpass.map(|(broker, _)| (broker, "modal".to_string())), + askpass.map(|_| "modal".to_string()), )?; Ok(()) diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index d3e0e45fa..c52528ecb 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -3,7 +3,6 @@ use std::{collections::HashMap, path}; use anyhow::{Context, Result}; use gitbutler_core::error::Error as CoreError; use gitbutler_core::{ - askpass::AskpassBroker, gb_repository, git, project_repository::{self, conflicts}, projects::{self, ProjectId}, @@ -92,7 +91,7 @@ impl App { remote_name: &str, branch_name: &str, credentials: &git::credentials::Helper, - askpass: Option<(AskpassBroker, Option)>, + askpass: Option>, ) -> Result<(), CoreError> { let project = self.projects.get(project_id)?; let project_repository = project_repository::Repository::open(&project)?; @@ -104,7 +103,7 @@ impl App { project_id: &ProjectId, remote_name: &str, credentials: &git::credentials::Helper, - askpass: Option<(AskpassBroker, String)>, + askpass: Option, ) -> Result<(), CoreError> { let project = self.projects.get(project_id)?; let project_repository = project_repository::Repository::open(&project)?; diff --git a/crates/gitbutler-tauri/src/askpass.rs b/crates/gitbutler-tauri/src/askpass.rs index 57e9105f2..9bc3e7bb0 100644 --- a/crates/gitbutler-tauri/src/askpass.rs +++ b/crates/gitbutler-tauri/src/askpass.rs @@ -1,21 +1,16 @@ pub mod commands { use gitbutler_core::{ - askpass::{AskpassBroker, AskpassRequest}, + askpass::{self, AskpassRequest}, id::Id, }; - use tauri::{AppHandle, Manager}; #[tauri::command(async)] - #[tracing::instrument(skip(handle, response))] + #[tracing::instrument(skip(response))] pub async fn submit_prompt_response( - handle: AppHandle, id: Id, response: Option, ) -> Result<(), ()> { - handle - .state::() - .handle_response(id, response) - .await; + askpass::get_broker().handle_response(id, response).await; Ok(()) } } diff --git a/crates/gitbutler-tauri/src/commands.rs b/crates/gitbutler-tauri/src/commands.rs index 61fe3f26c..74b7a60b2 100644 --- a/crates/gitbutler-tauri/src/commands.rs +++ b/crates/gitbutler-tauri/src/commands.rs @@ -48,16 +48,13 @@ pub async fn git_test_push( ) -> Result<(), Error> { let app = handle.state::(); let helper = handle.state::(); - let askpass_broker = handle - .state::() - .inner() - .clone(); Ok(app.git_test_push( &project_id, remote_name, branch_name, &helper, - Some((askpass_broker, None)), + // Run askpass, but don't pass any action + Some(None), )?) } @@ -71,15 +68,11 @@ pub async fn git_test_fetch( ) -> Result<(), Error> { let app = handle.state::(); let helper = handle.state::(); - let askpass_broker = handle - .state::() - .inner() - .clone(); Ok(app.git_test_fetch( &project_id, remote_name, &helper, - Some((askpass_broker, action.unwrap_or_else(|| "test".to_string()))), + Some(action.unwrap_or_else(|| "test".to_string())), )?) } diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 18258c2f3..5beb3fcf2 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -68,6 +68,17 @@ fn main() { logs::init(&app_handle); + // SAFETY(qix-): This is safe because we're initializing the askpass broker here, + // SAFETY(qix-): before any other threads would ever access it. + unsafe { + gitbutler_core::askpass::init({ + let handle = app_handle.clone(); + move |event| { + handle.emit_all("git_prompt", event).expect("tauri event emission doesn't fail in practice") + } + }); + } + let app_data_dir = app_handle.path_resolver().app_data_dir().expect("missing app data dir"); let app_cache_dir = app_handle.path_resolver().app_cache_dir().expect("missing app cache dir"); let app_log_dir = app_handle.path_resolver().app_log_dir().expect("missing app log dir"); @@ -77,14 +88,6 @@ fn main() { tracing::info!(version = %app_handle.package_info().version, name = %app_handle.package_info().name, "starting app"); - let askpass_broker = gitbutler_core::askpass::AskpassBroker::init({ - let handle = app_handle.clone(); - move |event| { - handle.emit_all("git_prompt", event).expect("tauri event emission doesn't fail in practice") - } - }); - app_handle.manage(askpass_broker); - let storage_controller = storage::Storage::new(&app_data_dir); app_handle.manage(storage_controller.clone()); diff --git a/crates/gitbutler-tauri/src/virtual_branches.rs b/crates/gitbutler-tauri/src/virtual_branches.rs index 7ec4e691b..cb203d30b 100644 --- a/crates/gitbutler-tauri/src/virtual_branches.rs +++ b/crates/gitbutler-tauri/src/virtual_branches.rs @@ -2,7 +2,6 @@ pub mod commands { use crate::error::Error; use anyhow::Context; use gitbutler_core::{ - askpass::AskpassBroker, assets, error::Code, git, projects, @@ -266,15 +265,9 @@ pub mod commands { branch_id: BranchId, with_force: bool, ) -> Result<(), Error> { - let askpass_broker = handle.state::(); handle .state::() - .push_virtual_branch( - &project_id, - &branch_id, - with_force, - Some((askpass_broker.inner().clone(), Some(branch_id))), - ) + .push_virtual_branch(&project_id, &branch_id, with_force, Some(Some(branch_id))) .await .map_err(|err| err.context(Code::Unknown))?; emit_vbranches(&handle, &project_id).await; @@ -499,15 +492,11 @@ pub mod commands { project_id: ProjectId, action: Option, ) -> Result { - let askpass_broker = handle.state::().inner().clone(); let base_branch = handle .state::() .fetch_from_target( &project_id, - Some(( - askpass_broker, - action.unwrap_or_else(|| "unknown".to_string()), - )), + Some(action.unwrap_or_else(|| "unknown".to_string())), ) .await?; emit_vbranches(&handle, &project_id).await;