mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-24 05:29:51 +03:00
integrate gitbutler_git::push() into gitbutler-app and wire up askpass mechanisms
This commit is contained in:
parent
56ca8b233d
commit
df2f81340b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1935,6 +1935,7 @@ dependencies = [
|
||||
"futures",
|
||||
"git2",
|
||||
"git2-hooks",
|
||||
"gitbutler-git",
|
||||
"governor",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
81
gitbutler-app/src/askpass.rs
Normal file
81
gitbutler-app/src/askpass.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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(())
|
||||
|
@ -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())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -4,7 +4,7 @@
|
||||
"beforeBuildCommand": "pnpm build:development",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../gitbutler-ui/build",
|
||||
"withGlobalTauri": false
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
"package": {
|
||||
"productName": "GitButler Dev"
|
||||
|
@ -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():");
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user