mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 08:58:21 +00:00
Async fetch (#552)
* async fetch * reuse remote progress for fetch * prvent push/fetch popup from closing too soon
This commit is contained in:
parent
c96feb0fe6
commit
39fb65b396
15 changed files with 660 additions and 220 deletions
|
|
@ -1,2 +1,3 @@
|
|||
msrv = "1.50.0"
|
||||
cognitive-complexity-threshold = 18
|
||||
cognitive-complexity-threshold = 18
|
||||
too-many-lines-threshold = 105
|
||||
158
asyncgit/src/fetch.rs
Normal file
158
asyncgit/src/fetch.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
use crate::{
|
||||
error::{Error, Result},
|
||||
sync::{
|
||||
cred::BasicAuthCredential,
|
||||
remotes::{fetch_origin, push::ProgressNotification},
|
||||
},
|
||||
AsyncNotification, RemoteProgress, CWD,
|
||||
};
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
///
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct FetchRequest {
|
||||
///
|
||||
pub remote: String,
|
||||
///
|
||||
pub branch: String,
|
||||
///
|
||||
pub basic_credential: Option<BasicAuthCredential>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct FetchState {
|
||||
request: FetchRequest,
|
||||
}
|
||||
|
||||
///
|
||||
pub struct AsyncFetch {
|
||||
state: Arc<Mutex<Option<FetchState>>>,
|
||||
last_result: Arc<Mutex<Option<(usize, String)>>>,
|
||||
progress: Arc<Mutex<Option<ProgressNotification>>>,
|
||||
sender: Sender<AsyncNotification>,
|
||||
}
|
||||
|
||||
impl AsyncFetch {
|
||||
///
|
||||
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(None)),
|
||||
last_result: Arc::new(Mutex::new(None)),
|
||||
progress: Arc::new(Mutex::new(None)),
|
||||
sender: sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn is_pending(&self) -> Result<bool> {
|
||||
let state = self.state.lock()?;
|
||||
Ok(state.is_some())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn last_result(&self) -> Result<Option<(usize, String)>> {
|
||||
let res = self.last_result.lock()?;
|
||||
Ok(res.clone())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn progress(&self) -> Result<Option<RemoteProgress>> {
|
||||
let res = self.progress.lock()?;
|
||||
Ok(res.as_ref().map(|progress| progress.clone().into()))
|
||||
}
|
||||
|
||||
///
|
||||
pub fn request(&mut self, params: FetchRequest) -> Result<()> {
|
||||
log::trace!("request");
|
||||
|
||||
if self.is_pending()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.set_request(¶ms)?;
|
||||
RemoteProgress::set_progress(self.progress.clone(), None)?;
|
||||
|
||||
let arc_state = Arc::clone(&self.state);
|
||||
let arc_res = Arc::clone(&self.last_result);
|
||||
let arc_progress = Arc::clone(&self.progress);
|
||||
let sender = self.sender.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let (progress_sender, receiver) = unbounded();
|
||||
|
||||
let handle = RemoteProgress::spawn_receiver_thread(
|
||||
sender.clone(),
|
||||
receiver,
|
||||
arc_progress,
|
||||
);
|
||||
|
||||
let res = fetch_origin(
|
||||
CWD,
|
||||
¶ms.branch,
|
||||
params.basic_credential,
|
||||
Some(progress_sender.clone()),
|
||||
);
|
||||
|
||||
progress_sender
|
||||
.send(ProgressNotification::Done)
|
||||
.expect("closing send failed");
|
||||
|
||||
handle.join().expect("joining thread failed");
|
||||
|
||||
Self::set_result(arc_res, res).expect("result error");
|
||||
|
||||
Self::clear_request(arc_state).expect("clear error");
|
||||
|
||||
sender
|
||||
.send(AsyncNotification::Fetch)
|
||||
.expect("AsyncNotification error");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_request(&self, params: &FetchRequest) -> Result<()> {
|
||||
let mut state = self.state.lock()?;
|
||||
|
||||
if state.is_some() {
|
||||
return Err(Error::Generic("pending request".into()));
|
||||
}
|
||||
|
||||
*state = Some(FetchState {
|
||||
request: params.clone(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_request(
|
||||
state: Arc<Mutex<Option<FetchState>>>,
|
||||
) -> Result<()> {
|
||||
let mut state = state.lock()?;
|
||||
|
||||
*state = None;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_result(
|
||||
arc_result: Arc<Mutex<Option<(usize, String)>>>,
|
||||
res: Result<usize>,
|
||||
) -> Result<()> {
|
||||
let mut last_res = arc_result.lock()?;
|
||||
|
||||
*last_res = match res {
|
||||
Ok(bytes) => Some((bytes, String::new())),
|
||||
Err(e) => {
|
||||
log::error!("fetch error: {}", e);
|
||||
Some((0, e.to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,9 @@ pub mod cached;
|
|||
mod commit_files;
|
||||
mod diff;
|
||||
mod error;
|
||||
mod fetch;
|
||||
mod push;
|
||||
pub mod remote_progress;
|
||||
mod revlog;
|
||||
mod status;
|
||||
pub mod sync;
|
||||
|
|
@ -23,7 +25,9 @@ mod tags;
|
|||
pub use crate::{
|
||||
commit_files::AsyncCommitFiles,
|
||||
diff::{AsyncDiff, DiffParams, DiffType},
|
||||
push::{AsyncPush, PushProgress, PushProgressState, PushRequest},
|
||||
fetch::{AsyncFetch, FetchRequest},
|
||||
push::{AsyncPush, PushRequest},
|
||||
remote_progress::{RemoteProgress, RemoteProgressState},
|
||||
revlog::{AsyncLog, FetchStatus},
|
||||
status::{AsyncStatus, StatusParams},
|
||||
sync::{
|
||||
|
|
@ -54,6 +58,8 @@ pub enum AsyncNotification {
|
|||
Tags,
|
||||
///
|
||||
Push,
|
||||
///
|
||||
Fetch,
|
||||
}
|
||||
|
||||
/// current working director `./`
|
||||
|
|
|
|||
|
|
@ -4,85 +4,13 @@ use crate::{
|
|||
cred::BasicAuthCredential, remotes::push::push,
|
||||
remotes::push::ProgressNotification,
|
||||
},
|
||||
AsyncNotification, CWD,
|
||||
AsyncNotification, RemoteProgress, CWD,
|
||||
};
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use git2::PackBuilderStage;
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use std::{
|
||||
cmp,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use thread::JoinHandle;
|
||||
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PushProgressState {
|
||||
///
|
||||
PackingAddingObject,
|
||||
///
|
||||
PackingDeltafiction,
|
||||
///
|
||||
Pushing,
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PushProgress {
|
||||
///
|
||||
pub state: PushProgressState,
|
||||
///
|
||||
pub progress: u8,
|
||||
}
|
||||
|
||||
impl PushProgress {
|
||||
///
|
||||
pub fn new(
|
||||
state: PushProgressState,
|
||||
current: usize,
|
||||
total: usize,
|
||||
) -> Self {
|
||||
let total = cmp::max(current, total) as f32;
|
||||
let progress = current as f32 / total * 100.0;
|
||||
let progress = progress as u8;
|
||||
Self { state, progress }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProgressNotification> for PushProgress {
|
||||
fn from(progress: ProgressNotification) -> Self {
|
||||
match progress {
|
||||
ProgressNotification::Packing {
|
||||
stage,
|
||||
current,
|
||||
total,
|
||||
} => match stage {
|
||||
PackBuilderStage::AddingObjects => PushProgress::new(
|
||||
PushProgressState::PackingAddingObject,
|
||||
current,
|
||||
total,
|
||||
),
|
||||
PackBuilderStage::Deltafication => PushProgress::new(
|
||||
PushProgressState::PackingDeltafiction,
|
||||
current,
|
||||
total,
|
||||
),
|
||||
},
|
||||
ProgressNotification::PushTransfer {
|
||||
current,
|
||||
total,
|
||||
..
|
||||
} => PushProgress::new(
|
||||
PushProgressState::Pushing,
|
||||
current,
|
||||
total,
|
||||
),
|
||||
//ProgressNotification::Done |
|
||||
_ => PushProgress::new(PushProgressState::Pushing, 1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
|
@ -134,7 +62,7 @@ impl AsyncPush {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn progress(&self) -> Result<Option<PushProgress>> {
|
||||
pub fn progress(&self) -> Result<Option<RemoteProgress>> {
|
||||
let res = self.progress.lock()?;
|
||||
Ok(res.as_ref().map(|progress| progress.clone().into()))
|
||||
}
|
||||
|
|
@ -148,7 +76,7 @@ impl AsyncPush {
|
|||
}
|
||||
|
||||
self.set_request(¶ms)?;
|
||||
Self::set_progress(self.progress.clone(), None)?;
|
||||
RemoteProgress::set_progress(self.progress.clone(), None)?;
|
||||
|
||||
let arc_state = Arc::clone(&self.state);
|
||||
let arc_res = Arc::clone(&self.last_result);
|
||||
|
|
@ -158,7 +86,7 @@ impl AsyncPush {
|
|||
thread::spawn(move || {
|
||||
let (progress_sender, receiver) = unbounded();
|
||||
|
||||
let handle = Self::spawn_receiver_thread(
|
||||
let handle = RemoteProgress::spawn_receiver_thread(
|
||||
sender.clone(),
|
||||
receiver,
|
||||
arc_progress,
|
||||
|
|
@ -191,44 +119,6 @@ impl AsyncPush {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_receiver_thread(
|
||||
sender: Sender<AsyncNotification>,
|
||||
receiver: Receiver<ProgressNotification>,
|
||||
progress: Arc<Mutex<Option<ProgressNotification>>>,
|
||||
) -> JoinHandle<()> {
|
||||
log::info!("push progress receiver spawned");
|
||||
|
||||
thread::spawn(move || loop {
|
||||
let incoming = receiver.recv();
|
||||
match incoming {
|
||||
Ok(update) => {
|
||||
Self::set_progress(
|
||||
progress.clone(),
|
||||
Some(update.clone()),
|
||||
)
|
||||
.expect("set prgoress failed");
|
||||
sender
|
||||
.send(AsyncNotification::Push)
|
||||
.expect("error sending push");
|
||||
|
||||
//NOTE: for better debugging
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
|
||||
if let ProgressNotification::Done = update {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"push progress receiver error: {}",
|
||||
e
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_request(&self, params: &PushRequest) -> Result<()> {
|
||||
let mut state = self.state.lock()?;
|
||||
|
||||
|
|
@ -253,20 +143,6 @@ impl AsyncPush {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn set_progress(
|
||||
progress: Arc<Mutex<Option<ProgressNotification>>>,
|
||||
state: Option<ProgressNotification>,
|
||||
) -> Result<()> {
|
||||
let simple_progress: Option<PushProgress> =
|
||||
state.as_ref().map(|prog| prog.clone().into());
|
||||
log::info!("push progress: {:?}", simple_progress);
|
||||
let mut progress = progress.lock()?;
|
||||
|
||||
*progress = state;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_result(
|
||||
arc_result: Arc<Mutex<Option<String>>>,
|
||||
res: Result<()>,
|
||||
|
|
@ -284,24 +160,3 @@ impl AsyncPush {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_progress_zero_total() {
|
||||
let prog =
|
||||
PushProgress::new(PushProgressState::Pushing, 1, 0);
|
||||
|
||||
assert_eq!(prog.progress, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_progress_rounding() {
|
||||
let prog =
|
||||
PushProgress::new(PushProgressState::Pushing, 2, 10);
|
||||
|
||||
assert_eq!(prog.progress, 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
161
asyncgit/src/remote_progress.rs
Normal file
161
asyncgit/src/remote_progress.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
//!
|
||||
|
||||
use crate::{
|
||||
error::Result, sync::remotes::push::ProgressNotification,
|
||||
AsyncNotification,
|
||||
};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use git2::PackBuilderStage;
|
||||
use std::{
|
||||
cmp,
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RemoteProgressState {
|
||||
///
|
||||
PackingAddingObject,
|
||||
///
|
||||
PackingDeltafiction,
|
||||
///
|
||||
Pushing,
|
||||
///
|
||||
Done,
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteProgress {
|
||||
///
|
||||
pub state: RemoteProgressState,
|
||||
///
|
||||
pub progress: u8,
|
||||
}
|
||||
|
||||
impl RemoteProgress {
|
||||
///
|
||||
pub fn new(
|
||||
state: RemoteProgressState,
|
||||
current: usize,
|
||||
total: usize,
|
||||
) -> Self {
|
||||
let total = cmp::max(current, total) as f32;
|
||||
let progress = current as f32 / total * 100.0;
|
||||
let progress = progress as u8;
|
||||
Self { state, progress }
|
||||
}
|
||||
|
||||
pub(crate) fn set_progress(
|
||||
progress: Arc<Mutex<Option<ProgressNotification>>>,
|
||||
state: Option<ProgressNotification>,
|
||||
) -> Result<()> {
|
||||
let simple_progress: Option<RemoteProgress> =
|
||||
state.as_ref().map(|prog| prog.clone().into());
|
||||
log::info!("remote progress: {:?}", simple_progress);
|
||||
let mut progress = progress.lock()?;
|
||||
|
||||
*progress = state;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_receiver_thread(
|
||||
sender: Sender<AsyncNotification>,
|
||||
receiver: Receiver<ProgressNotification>,
|
||||
progress: Arc<Mutex<Option<ProgressNotification>>>,
|
||||
) -> JoinHandle<()> {
|
||||
log::info!("push progress receiver spawned");
|
||||
|
||||
thread::spawn(move || loop {
|
||||
let incoming = receiver.recv();
|
||||
match incoming {
|
||||
Ok(update) => {
|
||||
Self::set_progress(
|
||||
progress.clone(),
|
||||
Some(update.clone()),
|
||||
)
|
||||
.expect("set prgoress failed");
|
||||
sender
|
||||
.send(AsyncNotification::Push)
|
||||
.expect("error sending push");
|
||||
|
||||
//NOTE: for better debugging
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
if let ProgressNotification::Done = update {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"push progress receiver error: {}",
|
||||
e
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProgressNotification> for RemoteProgress {
|
||||
fn from(progress: ProgressNotification) -> Self {
|
||||
match progress {
|
||||
ProgressNotification::Packing {
|
||||
stage,
|
||||
current,
|
||||
total,
|
||||
} => match stage {
|
||||
PackBuilderStage::AddingObjects => {
|
||||
RemoteProgress::new(
|
||||
RemoteProgressState::PackingAddingObject,
|
||||
current,
|
||||
total,
|
||||
)
|
||||
}
|
||||
PackBuilderStage::Deltafication => {
|
||||
RemoteProgress::new(
|
||||
RemoteProgressState::PackingDeltafiction,
|
||||
current,
|
||||
total,
|
||||
)
|
||||
}
|
||||
},
|
||||
ProgressNotification::PushTransfer {
|
||||
current,
|
||||
total,
|
||||
..
|
||||
} => RemoteProgress::new(
|
||||
RemoteProgressState::Pushing,
|
||||
current,
|
||||
total,
|
||||
),
|
||||
_ => RemoteProgress::new(RemoteProgressState::Done, 1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::remote_progress::RemoteProgressState;
|
||||
|
||||
#[test]
|
||||
fn test_progress_zero_total() {
|
||||
let prog =
|
||||
RemoteProgress::new(RemoteProgressState::Pushing, 1, 0);
|
||||
|
||||
assert_eq!(prog.progress, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_progress_rounding() {
|
||||
let prog =
|
||||
RemoteProgress::new(RemoteProgressState::Pushing, 2, 10);
|
||||
|
||||
assert_eq!(prog.progress, 20);
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +48,8 @@ pub fn branch_merge_upstream_fastforward(
|
|||
mod test {
|
||||
use super::*;
|
||||
use crate::sync::{
|
||||
commit, fetch_origin,
|
||||
remotes::push::push,
|
||||
commit,
|
||||
remotes::{fetch_origin, push::push},
|
||||
stage_add_file,
|
||||
tests::{
|
||||
debug_cmd_print, get_commit_ids, repo_clone,
|
||||
|
|
@ -137,6 +137,8 @@ mod test {
|
|||
let bytes = fetch_origin(
|
||||
clone1_dir.path().to_str().unwrap(),
|
||||
"master",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(bytes > 0);
|
||||
|
|
@ -144,6 +146,8 @@ mod test {
|
|||
let bytes = fetch_origin(
|
||||
clone1_dir.path().to_str().unwrap(),
|
||||
"master",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bytes, 0);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ pub use hooks::{
|
|||
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
|
||||
pub use ignore::add_to_ignore;
|
||||
pub use logwalker::LogWalker;
|
||||
pub use remotes::{fetch_origin, get_default_remote, get_remotes};
|
||||
pub use remotes::{get_default_remote, get_remotes};
|
||||
pub use reset::{reset_stage, reset_workdir};
|
||||
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
|
||||
pub use state::{repo_state, RepoState};
|
||||
|
|
|
|||
|
|
@ -6,10 +6,15 @@ use crate::{
|
|||
error::{Error, Result},
|
||||
sync::utils,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use git2::{FetchOptions, Repository};
|
||||
use push::remote_callbacks;
|
||||
use scopetime::scope_time;
|
||||
|
||||
use self::push::ProgressNotification;
|
||||
|
||||
use super::cred::BasicAuthCredential;
|
||||
|
||||
/// origin
|
||||
pub const DEFAULT_REMOTE_NAME: &str = "origin";
|
||||
|
||||
|
|
@ -67,7 +72,12 @@ pub(crate) fn get_default_remote_in_repo(
|
|||
}
|
||||
|
||||
///
|
||||
pub fn fetch_origin(repo_path: &str, branch: &str) -> Result<usize> {
|
||||
pub(crate) fn fetch_origin(
|
||||
repo_path: &str,
|
||||
branch: &str,
|
||||
basic_credential: Option<BasicAuthCredential>,
|
||||
progress_sender: Option<Sender<ProgressNotification>>,
|
||||
) -> Result<usize> {
|
||||
scope_time!("fetch_origin");
|
||||
|
||||
let repo = utils::repo(repo_path)?;
|
||||
|
|
@ -75,7 +85,10 @@ pub fn fetch_origin(repo_path: &str, branch: &str) -> Result<usize> {
|
|||
repo.find_remote(&get_default_remote_in_repo(&repo)?)?;
|
||||
|
||||
let mut options = FetchOptions::new();
|
||||
options.remote_callbacks(remote_callbacks(None, None));
|
||||
options.remote_callbacks(remote_callbacks(
|
||||
progress_sender,
|
||||
basic_credential,
|
||||
));
|
||||
|
||||
remote.fetch(&[branch], Some(&mut options), None)?;
|
||||
|
||||
|
|
@ -104,7 +117,7 @@ mod tests {
|
|||
|
||||
assert_eq!(remotes, vec![String::from("origin")]);
|
||||
|
||||
fetch_origin(repo_path, "master").unwrap();
|
||||
fetch_origin(repo_path, "master", None, None).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
18
src/app.rs
18
src/app.rs
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
event_pump, BranchListComponent, CommandBlocking,
|
||||
CommandInfo, CommitComponent, Component,
|
||||
CreateBranchComponent, DrawableComponent,
|
||||
ExternalEditorComponent, HelpComponent,
|
||||
ExternalEditorComponent, FetchComponent, HelpComponent,
|
||||
InspectCommitComponent, MsgComponent, PushComponent,
|
||||
RenameBranchComponent, ResetComponent, StashMsgComponent,
|
||||
TagCommitComponent,
|
||||
|
|
@ -45,6 +45,7 @@ pub struct App {
|
|||
inspect_commit_popup: InspectCommitComponent,
|
||||
external_editor_popup: ExternalEditorComponent,
|
||||
push_popup: PushComponent,
|
||||
fetch_popup: FetchComponent,
|
||||
tag_commit_popup: TagCommitComponent,
|
||||
create_branch_popup: CreateBranchComponent,
|
||||
rename_branch_popup: RenameBranchComponent,
|
||||
|
|
@ -74,7 +75,6 @@ impl App {
|
|||
theme_path: PathBuf,
|
||||
) -> Self {
|
||||
let queue = Queue::default();
|
||||
|
||||
let theme = Rc::new(Theme::init(theme_path));
|
||||
let key_config = Rc::new(KeyConfig::init());
|
||||
|
||||
|
|
@ -111,6 +111,12 @@ impl App {
|
|||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
fetch_popup: FetchComponent::new(
|
||||
&queue,
|
||||
sender,
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
tag_commit_popup: TagCommitComponent::new(
|
||||
queue.clone(),
|
||||
theme.clone(),
|
||||
|
|
@ -302,6 +308,7 @@ impl App {
|
|||
self.revlog.update_git(ev)?;
|
||||
self.inspect_commit_popup.update_git(ev)?;
|
||||
self.push_popup.update_git(ev)?;
|
||||
self.fetch_popup.update_git(ev)?;
|
||||
|
||||
//TODO: better system for this
|
||||
// can we simply process the queue here and everyone just uses the queue to schedule a cmd update?
|
||||
|
|
@ -347,6 +354,7 @@ impl App {
|
|||
inspect_commit_popup,
|
||||
external_editor_popup,
|
||||
push_popup,
|
||||
fetch_popup,
|
||||
tag_commit_popup,
|
||||
create_branch_popup,
|
||||
rename_branch_popup,
|
||||
|
|
@ -543,6 +551,10 @@ impl App {
|
|||
self.push_popup.push(branch, force)?;
|
||||
flags.insert(NeedsUpdate::ALL)
|
||||
}
|
||||
InternalEvent::Fetch(branch) => {
|
||||
self.fetch_popup.fetch(branch)?;
|
||||
flags.insert(NeedsUpdate::ALL)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(flags)
|
||||
|
|
@ -603,6 +615,7 @@ impl App {
|
|||
|| self.tag_commit_popup.is_visible()
|
||||
|| self.create_branch_popup.is_visible()
|
||||
|| self.push_popup.is_visible()
|
||||
|| self.fetch_popup.is_visible()
|
||||
|| self.select_branch_popup.is_visible()
|
||||
|| self.rename_branch_popup.is_visible()
|
||||
}
|
||||
|
|
@ -632,6 +645,7 @@ impl App {
|
|||
self.create_branch_popup.draw(f, size)?;
|
||||
self.rename_branch_popup.draw(f, size)?;
|
||||
self.push_popup.draw(f, size)?;
|
||||
self.fetch_popup.draw(f, size)?;
|
||||
self.reset.draw(f, size)?;
|
||||
self.msg.draw(f, size)?;
|
||||
|
||||
|
|
|
|||
255
src/components/fetch.rs
Normal file
255
src/components/fetch.rs
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
use crate::{
|
||||
components::{
|
||||
cred::CredComponent, visibility_blocking, CommandBlocking,
|
||||
CommandInfo, Component, DrawableComponent,
|
||||
},
|
||||
keys::SharedKeyConfig,
|
||||
queue::{InternalEvent, Queue},
|
||||
strings,
|
||||
ui::{self, style::SharedTheme},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{
|
||||
self,
|
||||
cred::{
|
||||
extract_username_password, need_username_password,
|
||||
BasicAuthCredential,
|
||||
},
|
||||
get_default_remote,
|
||||
},
|
||||
AsyncFetch, AsyncNotification, FetchRequest, RemoteProgress, CWD,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::Rect,
|
||||
text::Span,
|
||||
widgets::{Block, BorderType, Borders, Clear, Gauge},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::PushComponent;
|
||||
|
||||
///
|
||||
pub struct FetchComponent {
|
||||
visible: bool,
|
||||
git_fetch: AsyncFetch,
|
||||
progress: Option<RemoteProgress>,
|
||||
pending: bool,
|
||||
branch: String,
|
||||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
input_cred: CredComponent,
|
||||
}
|
||||
|
||||
impl FetchComponent {
|
||||
///
|
||||
pub fn new(
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncNotification>,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
queue: queue.clone(),
|
||||
pending: false,
|
||||
visible: false,
|
||||
branch: String::new(),
|
||||
git_fetch: AsyncFetch::new(sender),
|
||||
progress: None,
|
||||
input_cred: CredComponent::new(
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
theme,
|
||||
key_config,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn fetch(&mut self, branch: String) -> Result<()> {
|
||||
self.branch = branch;
|
||||
self.show()?;
|
||||
if need_username_password()? {
|
||||
let cred =
|
||||
extract_username_password().unwrap_or_else(|_| {
|
||||
BasicAuthCredential::new(None, None)
|
||||
});
|
||||
if cred.is_complete() {
|
||||
self.fetch_from_remote(Some(cred))
|
||||
} else {
|
||||
self.input_cred.set_cred(cred);
|
||||
self.input_cred.show()
|
||||
}
|
||||
} else {
|
||||
self.fetch_from_remote(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_from_remote(
|
||||
&mut self,
|
||||
cred: Option<BasicAuthCredential>,
|
||||
) -> Result<()> {
|
||||
self.pending = true;
|
||||
self.progress = None;
|
||||
self.git_fetch.request(FetchRequest {
|
||||
remote: get_default_remote(CWD)?,
|
||||
branch: self.branch.clone(),
|
||||
basic_credential: cred,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn update_git(
|
||||
&mut self,
|
||||
ev: AsyncNotification,
|
||||
) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
if let AsyncNotification::Fetch = ev {
|
||||
self.update()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
fn update(&mut self) -> Result<()> {
|
||||
self.pending = self.git_fetch.is_pending()?;
|
||||
self.progress = self.git_fetch.progress()?;
|
||||
|
||||
if !self.pending {
|
||||
if let Some((_bytes, err)) =
|
||||
self.git_fetch.last_result()?
|
||||
{
|
||||
if err.is_empty() {
|
||||
let merge_res =
|
||||
sync::branch_merge_upstream_fastforward(
|
||||
CWD,
|
||||
&self.branch,
|
||||
);
|
||||
if let Err(err) = merge_res {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"merge failed:\n{}",
|
||||
err
|
||||
)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"fetch failed:\n{}",
|
||||
err
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.hide();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for FetchComponent {
|
||||
fn draw<B: Backend>(
|
||||
&self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
if self.visible {
|
||||
let (state, progress) =
|
||||
PushComponent::get_progress(&self.progress);
|
||||
|
||||
let area = ui::centered_rect_absolute(30, 3, f.size());
|
||||
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
Gauge::default()
|
||||
.label(state.as_str())
|
||||
.block(
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
strings::FETCH_POPUP_MSG,
|
||||
self.theme.title(true),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Thick)
|
||||
.border_style(self.theme.block(true)),
|
||||
)
|
||||
.gauge_style(self.theme.push_gauge())
|
||||
.percent(u16::from(progress)),
|
||||
area,
|
||||
);
|
||||
self.input_cred.draw(f, rect)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for FetchComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.is_visible() {
|
||||
out.clear();
|
||||
}
|
||||
|
||||
if self.input_cred.is_visible() {
|
||||
self.input_cred.commands(out, force_all)
|
||||
} else {
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::close_msg(&self.key_config),
|
||||
!self.pending,
|
||||
self.visible,
|
||||
));
|
||||
visibility_blocking(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
if let Event::Key(e) = ev {
|
||||
if self.input_cred.is_visible() {
|
||||
if self.input_cred.event(ev)? {
|
||||
return Ok(true);
|
||||
} else if self.input_cred.get_cred().is_complete()
|
||||
{
|
||||
self.fetch_from_remote(Some(
|
||||
self.input_cred.get_cred().clone(),
|
||||
))?;
|
||||
self.input_cred.hide();
|
||||
}
|
||||
} else if e == self.key_config.exit_popup
|
||||
&& !self.pending
|
||||
{
|
||||
self.hide();
|
||||
}
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ mod create_branch;
|
|||
mod cred;
|
||||
mod diff;
|
||||
mod externaleditor;
|
||||
mod fetch;
|
||||
mod filetree;
|
||||
mod help;
|
||||
mod inspect_commit;
|
||||
|
|
@ -29,6 +30,7 @@ pub use commitlist::CommitList;
|
|||
pub use create_branch::CreateBranchComponent;
|
||||
pub use diff::DiffComponent;
|
||||
pub use externaleditor::ExternalEditorComponent;
|
||||
pub use fetch::FetchComponent;
|
||||
pub use filetree::FileTreeComponent;
|
||||
pub use help::HelpComponent;
|
||||
pub use inspect_commit::InspectCommitComponent;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ use asyncgit::{
|
|||
},
|
||||
get_default_remote,
|
||||
},
|
||||
AsyncNotification, AsyncPush, PushProgress, PushProgressState,
|
||||
PushRequest, CWD,
|
||||
AsyncNotification, AsyncPush, PushRequest, RemoteProgress,
|
||||
RemoteProgressState, CWD,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
|
|
@ -35,7 +35,7 @@ pub struct PushComponent {
|
|||
visible: bool,
|
||||
force: bool,
|
||||
git_push: AsyncPush,
|
||||
progress: Option<PushProgress>,
|
||||
progress: Option<RemoteProgress>,
|
||||
pending: bool,
|
||||
branch: String,
|
||||
queue: Queue,
|
||||
|
|
@ -144,8 +144,10 @@ impl PushComponent {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_progress(&self) -> (String, u8) {
|
||||
self.progress.as_ref().map_or(
|
||||
pub fn get_progress(
|
||||
progress: &Option<RemoteProgress>,
|
||||
) -> (String, u8) {
|
||||
progress.as_ref().map_or(
|
||||
(strings::PUSH_POPUP_PROGRESS_NONE.into(), 0),
|
||||
|progress| {
|
||||
(
|
||||
|
|
@ -156,17 +158,20 @@ impl PushComponent {
|
|||
)
|
||||
}
|
||||
|
||||
fn progress_state_name(state: &PushProgressState) -> String {
|
||||
fn progress_state_name(state: &RemoteProgressState) -> String {
|
||||
match state {
|
||||
PushProgressState::PackingAddingObject => {
|
||||
RemoteProgressState::PackingAddingObject => {
|
||||
strings::PUSH_POPUP_STATES_ADDING
|
||||
}
|
||||
PushProgressState::PackingDeltafiction => {
|
||||
RemoteProgressState::PackingDeltafiction => {
|
||||
strings::PUSH_POPUP_STATES_DELTAS
|
||||
}
|
||||
PushProgressState::Pushing => {
|
||||
RemoteProgressState::Pushing => {
|
||||
strings::PUSH_POPUP_STATES_PUSHING
|
||||
}
|
||||
RemoteProgressState::Done => {
|
||||
strings::PUSH_POPUP_STATES_DONE
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
@ -179,7 +184,8 @@ impl DrawableComponent for PushComponent {
|
|||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
if self.visible {
|
||||
let (state, progress) = self.get_progress();
|
||||
let (state, progress) =
|
||||
Self::get_progress(&self.progress);
|
||||
|
||||
let area = ui::centered_rect_absolute(30, 3, f.size());
|
||||
|
||||
|
|
@ -237,23 +243,21 @@ impl Component for PushComponent {
|
|||
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||
if self.visible {
|
||||
if let Event::Key(e) = ev {
|
||||
if e == self.key_config.exit_popup {
|
||||
self.hide();
|
||||
}
|
||||
if self.input_cred.event(ev)? {
|
||||
return Ok(true);
|
||||
} else if e == self.key_config.enter {
|
||||
if self.input_cred.is_visible()
|
||||
&& self.input_cred.get_cred().is_complete()
|
||||
if self.input_cred.is_visible() {
|
||||
if self.input_cred.event(ev)? {
|
||||
return Ok(true);
|
||||
} else if self.input_cred.get_cred().is_complete()
|
||||
{
|
||||
self.push_to_remote(
|
||||
Some(self.input_cred.get_cred().clone()),
|
||||
self.force,
|
||||
)?;
|
||||
self.input_cred.hide();
|
||||
} else {
|
||||
self.hide();
|
||||
}
|
||||
} else if e == self.key_config.exit_popup
|
||||
&& !self.pending
|
||||
{
|
||||
self.hide();
|
||||
}
|
||||
}
|
||||
return Ok(true);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ pub enum InternalEvent {
|
|||
OpenExternalEditor(Option<String>),
|
||||
///
|
||||
Push(String, bool),
|
||||
///
|
||||
Fetch(String),
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ pub mod order {
|
|||
|
||||
pub static PUSH_POPUP_MSG: &str = "Push";
|
||||
pub static FORCE_PUSH_POPUP_MSG: &str = "Force Push";
|
||||
pub static FETCH_POPUP_MSG: &str = "Fetch";
|
||||
pub static PUSH_POPUP_PROGRESS_NONE: &str = "preparing...";
|
||||
pub static PUSH_POPUP_STATES_ADDING: &str = "adding objects (1/3)";
|
||||
pub static PUSH_POPUP_STATES_DELTAS: &str = "deltas (2/3)";
|
||||
pub static PUSH_POPUP_STATES_PUSHING: &str = "pushing (3/3)";
|
||||
pub static PUSH_POPUP_STATES_DONE: &str = "done";
|
||||
|
||||
pub static SELECT_BRANCH_POPUP_MSG: &str = "Switch Branch";
|
||||
|
||||
|
|
|
|||
|
|
@ -416,46 +416,9 @@ impl Status {
|
|||
|
||||
fn fetch(&self) {
|
||||
if let Some(branch) = self.git_branch_name.last() {
|
||||
match sync::fetch_origin(CWD, branch.as_str()) {
|
||||
Err(e) => {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"fetch error:\n{}",
|
||||
e
|
||||
)),
|
||||
);
|
||||
}
|
||||
Ok(bytes) => {
|
||||
if bytes > 0
|
||||
|| self
|
||||
.git_branch_state
|
||||
.as_ref()
|
||||
.map(|state| state.behind > 0)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
let merge_res =
|
||||
sync::branch_merge_upstream_fastforward(
|
||||
CWD, &branch,
|
||||
);
|
||||
let msg = match merge_res {
|
||||
Err(err) => {
|
||||
format!("merge failed:\n{}", err)
|
||||
}
|
||||
Ok(_) => "merged".to_string(),
|
||||
};
|
||||
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(msg),
|
||||
);
|
||||
} else {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(
|
||||
"nothing fetched".to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Fetch(branch));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue