diff --git a/.clippy.toml b/.clippy.toml index 940e1282..87f21c64 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,3 @@ msrv = "1.50.0" -cognitive-complexity-threshold = 18 \ No newline at end of file +cognitive-complexity-threshold = 18 +too-many-lines-threshold = 105 \ No newline at end of file diff --git a/asyncgit/src/fetch.rs b/asyncgit/src/fetch.rs new file mode 100644 index 00000000..2b80a623 --- /dev/null +++ b/asyncgit/src/fetch.rs @@ -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, +} + +#[derive(Default, Clone, Debug)] +struct FetchState { + request: FetchRequest, +} + +/// +pub struct AsyncFetch { + state: Arc>>, + last_result: Arc>>, + progress: Arc>>, + sender: Sender, +} + +impl AsyncFetch { + /// + pub fn new(sender: &Sender) -> 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 { + let state = self.state.lock()?; + Ok(state.is_some()) + } + + /// + pub fn last_result(&self) -> Result> { + let res = self.last_result.lock()?; + Ok(res.clone()) + } + + /// + pub fn progress(&self) -> Result> { + 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>>, + ) -> Result<()> { + let mut state = state.lock()?; + + *state = None; + + Ok(()) + } + + fn set_result( + arc_result: Arc>>, + res: Result, + ) -> 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(()) + } +} diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 8fc89937..4dc3b01f 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -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 `./` diff --git a/asyncgit/src/push.rs b/asyncgit/src/push.rs index 38990685..7c032a3e 100644 --- a/asyncgit/src/push.rs +++ b/asyncgit/src/push.rs @@ -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 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> { + pub fn progress(&self) -> Result> { 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, - receiver: Receiver, - progress: Arc>>, - ) -> 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>>, - state: Option, - ) -> Result<()> { - let simple_progress: Option = - 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>>, 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); - } -} diff --git a/asyncgit/src/remote_progress.rs b/asyncgit/src/remote_progress.rs new file mode 100644 index 00000000..ae293947 --- /dev/null +++ b/asyncgit/src/remote_progress.rs @@ -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>>, + state: Option, + ) -> Result<()> { + let simple_progress: Option = + 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, + receiver: Receiver, + progress: Arc>>, + ) -> 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 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); + } +} diff --git a/asyncgit/src/sync/branch/merge.rs b/asyncgit/src/sync/branch/merge.rs index 3f3db70a..b5354228 100644 --- a/asyncgit/src/sync/branch/merge.rs +++ b/asyncgit/src/sync/branch/merge.rs @@ -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); diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index c188fbff..aeaf91d7 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -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}; diff --git a/asyncgit/src/sync/remotes/mod.rs b/asyncgit/src/sync/remotes/mod.rs index dbe2fc3c..6143b32c 100644 --- a/asyncgit/src/sync/remotes/mod.rs +++ b/asyncgit/src/sync/remotes/mod.rs @@ -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 { +pub(crate) fn fetch_origin( + repo_path: &str, + branch: &str, + basic_credential: Option, + progress_sender: Option>, +) -> Result { scope_time!("fetch_origin"); let repo = utils::repo(repo_path)?; @@ -75,7 +85,10 @@ pub fn fetch_origin(repo_path: &str, branch: &str) -> Result { 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] diff --git a/src/app.rs b/src/app.rs index af0a1635..7aa8f904 100644 --- a/src/app.rs +++ b/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)?; diff --git a/src/components/fetch.rs b/src/components/fetch.rs new file mode 100644 index 00000000..bb9f928d --- /dev/null +++ b/src/components/fetch.rs @@ -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, + pending: bool, + branch: String, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + input_cred: CredComponent, +} + +impl FetchComponent { + /// + pub fn new( + queue: &Queue, + sender: &Sender, + 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, + ) -> 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( + &self, + f: &mut Frame, + 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, + 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 { + 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(()) + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 045d26f1..bd3e318e 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -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; diff --git a/src/components/push.rs b/src/components/push.rs index 68fa6c8c..822840b1 100644 --- a/src/components/push.rs +++ b/src/components/push.rs @@ -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, + progress: Option, 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, + ) -> (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 { 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); diff --git a/src/queue.rs b/src/queue.rs index dc9c6503..aa4694c0 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -62,6 +62,8 @@ pub enum InternalEvent { OpenExternalEditor(Option), /// Push(String, bool), + /// + Fetch(String), } /// diff --git a/src/strings.rs b/src/strings.rs index b854d64b..401382af 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -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"; diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 32c6f63c..69d7ad79 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -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)); } }