Async fetch (#552)

* async fetch
* reuse remote progress for fetch
* prvent push/fetch popup from closing too soon
This commit is contained in:
Stephan Dilly 2021-02-28 19:24:05 +01:00 committed by GitHub
parent c96feb0fe6
commit 39fb65b396
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 660 additions and 220 deletions

View file

@ -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
View 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(&params)?;
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,
&params.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(())
}
}

View file

@ -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 `./`

View file

@ -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(&params)?;
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);
}
}

View 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);
}
}

View file

@ -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);

View file

@ -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};

View file

@ -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]

View file

@ -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
View 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(())
}
}

View file

@ -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;

View file

@ -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);

View file

@ -62,6 +62,8 @@ pub enum InternalEvent {
OpenExternalEditor(Option<String>),
///
Push(String, bool),
///
Fetch(String),
}
///

View file

@ -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";

View file

@ -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));
}
}