Git askpass (#25953)
Supersedes #25848 Release Notes: - git: Supporting push/pull/fetch when remote requires auth --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
6fdb666bb7
commit
c34357e2ab
29 changed files with 864 additions and 379 deletions
|
@ -4,8 +4,10 @@ use crate::{
|
|||
Project, ProjectItem, ProjectPath,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use askpass::{AskPassDelegate, AskPassSession};
|
||||
use buffer_diff::BufferDiffEvent;
|
||||
use client::ProjectId;
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
StreamExt as _,
|
||||
|
@ -22,6 +24,7 @@ use gpui::{
|
|||
WeakEntity,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use parking_lot::Mutex;
|
||||
use rpc::{
|
||||
proto::{self, git_reset, ToProto},
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
|
@ -34,13 +37,13 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use text::BufferId;
|
||||
use util::{maybe, ResultExt};
|
||||
use util::{debug_panic, maybe, ResultExt};
|
||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
|
||||
|
||||
pub struct GitStore {
|
||||
buffer_store: Entity<BufferStore>,
|
||||
pub(super) project_id: Option<ProjectId>,
|
||||
pub(super) client: Option<AnyProtoClient>,
|
||||
pub(super) client: AnyProtoClient,
|
||||
repositories: Vec<Entity<Repository>>,
|
||||
active_index: Option<usize>,
|
||||
update_sender: mpsc::UnboundedSender<GitJob>,
|
||||
|
@ -55,6 +58,8 @@ pub struct Repository {
|
|||
pub git_repo: GitRepo,
|
||||
pub merge_message: Option<String>,
|
||||
job_sender: mpsc::UnboundedSender<GitJob>,
|
||||
askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
|
||||
latest_askpass_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -92,7 +97,7 @@ impl GitStore {
|
|||
pub fn new(
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
client: Option<AnyProtoClient>,
|
||||
client: AnyProtoClient,
|
||||
project_id: Option<ProjectId>,
|
||||
cx: &mut Context<'_, Self>,
|
||||
) -> Self {
|
||||
|
@ -129,6 +134,7 @@ impl GitStore {
|
|||
client.add_entity_request_handler(Self::handle_checkout_files);
|
||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||
client.add_entity_request_handler(Self::handle_askpass);
|
||||
client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
|
||||
}
|
||||
|
||||
|
@ -164,7 +170,7 @@ impl GitStore {
|
|||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
let client = client.clone()?;
|
||||
let client = client.clone();
|
||||
let project_id = project_id?;
|
||||
Some((
|
||||
GitRepo::Remote {
|
||||
|
@ -216,6 +222,8 @@ impl GitStore {
|
|||
cx.new(|_| Repository {
|
||||
git_store: this.clone(),
|
||||
worktree_id,
|
||||
askpass_delegates: Default::default(),
|
||||
latest_askpass_id: 0,
|
||||
repository_entry: repo.clone(),
|
||||
git_repo,
|
||||
job_sender: self.update_sender.clone(),
|
||||
|
@ -362,9 +370,21 @@ impl GitStore {
|
|||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let askpass_id = envelope.payload.askpass_id;
|
||||
|
||||
let askpass = make_remote_delegate(
|
||||
this,
|
||||
envelope.payload.project_id,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
askpass_id,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
let remote_output = repository_handle
|
||||
.update(&mut cx, |repository_handle, _cx| repository_handle.fetch())?
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.fetch(askpass, cx)
|
||||
})?
|
||||
.await??;
|
||||
|
||||
Ok(proto::RemoteMessageResponse {
|
||||
|
@ -383,6 +403,16 @@ impl GitStore {
|
|||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let askpass_id = envelope.payload.askpass_id;
|
||||
let askpass = make_remote_delegate(
|
||||
this,
|
||||
envelope.payload.project_id,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
askpass_id,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
let options = envelope
|
||||
.payload
|
||||
.options
|
||||
|
@ -396,8 +426,8 @@ impl GitStore {
|
|||
let remote_name = envelope.payload.remote_name.into();
|
||||
|
||||
let remote_output = repository_handle
|
||||
.update(&mut cx, |repository_handle, _cx| {
|
||||
repository_handle.push(branch_name, remote_name, options)
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.push(branch_name, remote_name, options, askpass, cx)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::RemoteMessageResponse {
|
||||
|
@ -415,15 +445,25 @@ impl GitStore {
|
|||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let askpass_id = envelope.payload.askpass_id;
|
||||
let askpass = make_remote_delegate(
|
||||
this,
|
||||
envelope.payload.project_id,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
askpass_id,
|
||||
&mut cx,
|
||||
);
|
||||
|
||||
let branch_name = envelope.payload.branch_name.into();
|
||||
let remote_name = envelope.payload.remote_name.into();
|
||||
|
||||
let remote_message = repository_handle
|
||||
.update(&mut cx, |repository_handle, _cx| {
|
||||
repository_handle.pull(branch_name, remote_name)
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.pull(branch_name, remote_name, askpass, cx)
|
||||
})?
|
||||
.await??;
|
||||
|
||||
Ok(proto::RemoteMessageResponse {
|
||||
stdout: remote_message.stdout,
|
||||
stderr: remote_message.stderr,
|
||||
|
@ -719,6 +759,31 @@ impl GitStore {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_askpass(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::AskPassRequest>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::AskPassResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
|
||||
let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
|
||||
debug_panic!("no askpass found");
|
||||
return Err(anyhow::anyhow!("no askpass found"));
|
||||
};
|
||||
|
||||
let response = askpass.ask_password(envelope.payload.prompt).await?;
|
||||
|
||||
delegates
|
||||
.lock()
|
||||
.insert(envelope.payload.askpass_id, askpass);
|
||||
|
||||
Ok(proto::AskPassResponse { response })
|
||||
}
|
||||
|
||||
async fn handle_check_for_pushed_commits(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::CheckForPushedCommits>,
|
||||
|
@ -765,6 +830,33 @@ impl GitStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn make_remote_delegate(
|
||||
this: Entity<GitStore>,
|
||||
project_id: u64,
|
||||
worktree_id: WorktreeId,
|
||||
work_directory_id: ProjectEntryId,
|
||||
askpass_id: u64,
|
||||
cx: &mut AsyncApp,
|
||||
) -> AskPassDelegate {
|
||||
AskPassDelegate::new(cx, move |prompt, tx, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
let response = this.client.request(proto::AskPassRequest {
|
||||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
askpass_id,
|
||||
prompt,
|
||||
});
|
||||
cx.spawn(|_, _| async move {
|
||||
tx.send(response.await?.response).ok();
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
|
||||
impl GitRepo {}
|
||||
|
||||
impl Repository {
|
||||
|
@ -1286,21 +1378,39 @@ impl Repository {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn fetch(&self) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
|
||||
self.send_job(|git_repo| async move {
|
||||
pub fn fetch(
|
||||
&mut self,
|
||||
askpass: AskPassDelegate,
|
||||
cx: &App,
|
||||
) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
|
||||
let executor = cx.background_executor().clone();
|
||||
let askpass_delegates = self.askpass_delegates.clone();
|
||||
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
|
||||
|
||||
self.send_job(move |git_repo| async move {
|
||||
match git_repo {
|
||||
GitRepo::Local(git_repository) => git_repository.fetch(),
|
||||
GitRepo::Local(git_repository) => {
|
||||
let askpass = AskPassSession::new(&executor, askpass).await?;
|
||||
git_repository.fetch(askpass)
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
askpass_delegates.lock().insert(askpass_id, askpass);
|
||||
let _defer = util::defer(|| {
|
||||
let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
|
||||
debug_assert!(askpass_delegate.is_some());
|
||||
});
|
||||
|
||||
let response = client
|
||||
.request(proto::Fetch {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
askpass_id,
|
||||
})
|
||||
.await
|
||||
.context("sending fetch request")?;
|
||||
|
@ -1315,25 +1425,40 @@ impl Repository {
|
|||
}
|
||||
|
||||
pub fn push(
|
||||
&self,
|
||||
&mut self,
|
||||
branch: SharedString,
|
||||
remote: SharedString,
|
||||
options: Option<PushOptions>,
|
||||
askpass: AskPassDelegate,
|
||||
cx: &App,
|
||||
) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
|
||||
let executor = cx.background_executor().clone();
|
||||
let askpass_delegates = self.askpass_delegates.clone();
|
||||
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
|
||||
|
||||
self.send_job(move |git_repo| async move {
|
||||
match git_repo {
|
||||
GitRepo::Local(git_repository) => git_repository.push(&branch, &remote, options),
|
||||
GitRepo::Local(git_repository) => {
|
||||
let askpass = AskPassSession::new(&executor, askpass).await?;
|
||||
git_repository.push(&branch, &remote, options, askpass)
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
askpass_delegates.lock().insert(askpass_id, askpass);
|
||||
let _defer = util::defer(|| {
|
||||
let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
|
||||
debug_assert!(askpass_delegate.is_some());
|
||||
});
|
||||
let response = client
|
||||
.request(proto::Push {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
askpass_id,
|
||||
branch_name: branch.to_string(),
|
||||
remote_name: remote.to_string(),
|
||||
options: options.map(|options| match options {
|
||||
|
@ -1354,24 +1479,38 @@ impl Repository {
|
|||
}
|
||||
|
||||
pub fn pull(
|
||||
&self,
|
||||
&mut self,
|
||||
branch: SharedString,
|
||||
remote: SharedString,
|
||||
askpass: AskPassDelegate,
|
||||
cx: &App,
|
||||
) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
|
||||
self.send_job(|git_repo| async move {
|
||||
let executor = cx.background_executor().clone();
|
||||
let askpass_delegates = self.askpass_delegates.clone();
|
||||
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
|
||||
self.send_job(move |git_repo| async move {
|
||||
match git_repo {
|
||||
GitRepo::Local(git_repository) => git_repository.pull(&branch, &remote),
|
||||
GitRepo::Local(git_repository) => {
|
||||
let askpass = AskPassSession::new(&executor, askpass).await?;
|
||||
git_repository.pull(&branch, &remote, askpass)
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
askpass_delegates.lock().insert(askpass_id, askpass);
|
||||
let _defer = util::defer(|| {
|
||||
let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
|
||||
debug_assert!(askpass_delegate.is_some());
|
||||
});
|
||||
let response = client
|
||||
.request(proto::Pull {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
askpass_id,
|
||||
branch_name: branch.to_string(),
|
||||
remote_name: remote.to_string(),
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue