mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
since it does not support hard-whitespaces its the only way to make whitespaces consisitent and checked
223 lines
5.3 KiB
Rust
223 lines
5.3 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use super::push::ProgressNotification;
|
|
use crate::{error::Result, sync::cred::BasicAuthCredential};
|
|
use crossbeam_channel::Sender;
|
|
use git2::{Cred, Error as GitError, RemoteCallbacks};
|
|
use std::sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc, Mutex,
|
|
};
|
|
|
|
///
|
|
#[derive(Default, Clone)]
|
|
pub struct CallbackStats {
|
|
pub push_rejected_msg: Option<(String, String)>,
|
|
}
|
|
|
|
///
|
|
#[derive(Clone)]
|
|
pub struct Callbacks {
|
|
sender: Option<Sender<ProgressNotification>>,
|
|
basic_credential: Option<BasicAuthCredential>,
|
|
stats: Arc<Mutex<CallbackStats>>,
|
|
first_call_to_credentials: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl Callbacks {
|
|
///
|
|
pub fn new(
|
|
sender: Option<Sender<ProgressNotification>>,
|
|
basic_credential: Option<BasicAuthCredential>,
|
|
) -> Self {
|
|
let stats = Arc::new(Mutex::new(CallbackStats::default()));
|
|
|
|
Self {
|
|
sender,
|
|
basic_credential,
|
|
stats,
|
|
first_call_to_credentials: Arc::new(AtomicBool::new(
|
|
true,
|
|
)),
|
|
}
|
|
}
|
|
|
|
///
|
|
pub fn get_stats(&self) -> Result<CallbackStats> {
|
|
let stats = self.stats.lock()?;
|
|
Ok(stats.clone())
|
|
}
|
|
|
|
///
|
|
pub fn callbacks<'a>(&self) -> RemoteCallbacks<'a> {
|
|
let mut callbacks = RemoteCallbacks::new();
|
|
|
|
let this = self.clone();
|
|
callbacks.push_transfer_progress(
|
|
move |current, total, bytes| {
|
|
this.push_transfer_progress(current, total, bytes);
|
|
},
|
|
);
|
|
|
|
let this = self.clone();
|
|
callbacks.update_tips(move |name, a, b| {
|
|
this.update_tips(name, a, b);
|
|
true
|
|
});
|
|
|
|
let this = self.clone();
|
|
callbacks.transfer_progress(move |p| {
|
|
this.transfer_progress(&p);
|
|
true
|
|
});
|
|
|
|
let this = self.clone();
|
|
callbacks.pack_progress(move |stage, current, total| {
|
|
this.pack_progress(stage, total, current);
|
|
});
|
|
|
|
let this = self.clone();
|
|
callbacks.push_update_reference(move |reference, msg| {
|
|
this.push_update_reference(reference, msg);
|
|
Ok(())
|
|
});
|
|
|
|
let this = self.clone();
|
|
callbacks.credentials(
|
|
move |url, username_from_url, allowed_types| {
|
|
this.credentials(
|
|
url,
|
|
username_from_url,
|
|
allowed_types,
|
|
)
|
|
},
|
|
);
|
|
|
|
callbacks
|
|
}
|
|
|
|
fn push_update_reference(
|
|
&self,
|
|
reference: &str,
|
|
msg: Option<&str>,
|
|
) {
|
|
log::debug!(
|
|
"push_update_reference: '{}' {:?}",
|
|
reference,
|
|
msg
|
|
);
|
|
|
|
if let Ok(mut stats) = self.stats.lock() {
|
|
stats.push_rejected_msg = msg
|
|
.map(|msg| (reference.to_string(), msg.to_string()));
|
|
}
|
|
}
|
|
|
|
fn pack_progress(
|
|
&self,
|
|
stage: git2::PackBuilderStage,
|
|
total: usize,
|
|
current: usize,
|
|
) {
|
|
log::debug!("packing: {:?} - {}/{}", stage, current, total);
|
|
self.sender.clone().map(|sender| {
|
|
sender.send(ProgressNotification::Packing {
|
|
stage,
|
|
total,
|
|
current,
|
|
})
|
|
});
|
|
}
|
|
|
|
fn transfer_progress(&self, p: &git2::Progress) {
|
|
log::debug!(
|
|
"transfer: {}/{}",
|
|
p.received_objects(),
|
|
p.total_objects()
|
|
);
|
|
self.sender.clone().map(|sender| {
|
|
sender.send(ProgressNotification::Transfer {
|
|
objects: p.received_objects(),
|
|
total_objects: p.total_objects(),
|
|
})
|
|
});
|
|
}
|
|
|
|
fn update_tips(&self, name: &str, a: git2::Oid, b: git2::Oid) {
|
|
log::debug!("update tips: '{}' [{}] [{}]", name, a, b);
|
|
self.sender.clone().map(|sender| {
|
|
sender.send(ProgressNotification::UpdateTips {
|
|
name: name.to_string(),
|
|
a: a.into(),
|
|
b: b.into(),
|
|
})
|
|
});
|
|
}
|
|
|
|
fn push_transfer_progress(
|
|
&self,
|
|
current: usize,
|
|
total: usize,
|
|
bytes: usize,
|
|
) {
|
|
log::debug!("progress: {}/{} ({} B)", current, total, bytes,);
|
|
self.sender.clone().map(|sender| {
|
|
sender.send(ProgressNotification::PushTransfer {
|
|
current,
|
|
total,
|
|
bytes,
|
|
})
|
|
});
|
|
}
|
|
|
|
// If credentials are bad, we don't ask the user to re-fill their creds. We push an error and they will be able to restart their action (for example a push) and retype their creds.
|
|
// This behavior is explained in a issue on git2-rs project : https://github.com/rust-lang/git2-rs/issues/347
|
|
// An implementation reference is done in cargo : https://github.com/rust-lang/cargo/blob/9fb208dddb12a3081230a5fd8f470e01df8faa25/src/cargo/sources/git/utils.rs#L588
|
|
// There is also a guide about libgit2 authentication : https://libgit2.org/docs/guides/authentication/
|
|
fn credentials(
|
|
&self,
|
|
url: &str,
|
|
username_from_url: Option<&str>,
|
|
allowed_types: git2::CredentialType,
|
|
) -> std::result::Result<Cred, GitError> {
|
|
log::debug!(
|
|
"creds: '{}' {:?} ({:?})",
|
|
url,
|
|
username_from_url,
|
|
allowed_types
|
|
);
|
|
|
|
// This boolean is used to avoid multiple calls to credentials callback.
|
|
if self.first_call_to_credentials.load(Ordering::Relaxed) {
|
|
self.first_call_to_credentials
|
|
.store(false, Ordering::Relaxed);
|
|
} else {
|
|
return Err(GitError::from_str("Bad credentials."));
|
|
}
|
|
|
|
match &self.basic_credential {
|
|
_ if allowed_types.is_ssh_key() => {
|
|
match username_from_url {
|
|
Some(username) => {
|
|
Cred::ssh_key_from_agent(username)
|
|
}
|
|
None => Err(GitError::from_str(
|
|
" Couldn't extract username from url.",
|
|
)),
|
|
}
|
|
}
|
|
Some(BasicAuthCredential {
|
|
username: Some(user),
|
|
password: Some(pwd),
|
|
}) if allowed_types.is_user_pass_plaintext() => {
|
|
Cred::userpass_plaintext(user, pwd)
|
|
}
|
|
Some(BasicAuthCredential {
|
|
username: Some(user),
|
|
password: _,
|
|
}) if allowed_types.is_username() => Cred::username(user),
|
|
_ if allowed_types.is_default() => Cred::default(),
|
|
_ => Err(GitError::from_str("Couldn't find credentials")),
|
|
}
|
|
}
|
|
}
|