mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28: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"
|
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 commit_files;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod fetch;
|
||||||
mod push;
|
mod push;
|
||||||
|
pub mod remote_progress;
|
||||||
mod revlog;
|
mod revlog;
|
||||||
mod status;
|
mod status;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
|
@ -23,7 +25,9 @@ mod tags;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
commit_files::AsyncCommitFiles,
|
commit_files::AsyncCommitFiles,
|
||||||
diff::{AsyncDiff, DiffParams, DiffType},
|
diff::{AsyncDiff, DiffParams, DiffType},
|
||||||
push::{AsyncPush, PushProgress, PushProgressState, PushRequest},
|
fetch::{AsyncFetch, FetchRequest},
|
||||||
|
push::{AsyncPush, PushRequest},
|
||||||
|
remote_progress::{RemoteProgress, RemoteProgressState},
|
||||||
revlog::{AsyncLog, FetchStatus},
|
revlog::{AsyncLog, FetchStatus},
|
||||||
status::{AsyncStatus, StatusParams},
|
status::{AsyncStatus, StatusParams},
|
||||||
sync::{
|
sync::{
|
||||||
|
|
@ -54,6 +58,8 @@ pub enum AsyncNotification {
|
||||||
Tags,
|
Tags,
|
||||||
///
|
///
|
||||||
Push,
|
Push,
|
||||||
|
///
|
||||||
|
Fetch,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// current working director `./`
|
/// current working director `./`
|
||||||
|
|
|
||||||
|
|
@ -4,85 +4,13 @@ use crate::{
|
||||||
cred::BasicAuthCredential, remotes::push::push,
|
cred::BasicAuthCredential, remotes::push::push,
|
||||||
remotes::push::ProgressNotification,
|
remotes::push::ProgressNotification,
|
||||||
},
|
},
|
||||||
AsyncNotification, CWD,
|
AsyncNotification, RemoteProgress, CWD,
|
||||||
};
|
};
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Sender};
|
||||||
use git2::PackBuilderStage;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
thread,
|
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)]
|
#[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()?;
|
let res = self.progress.lock()?;
|
||||||
Ok(res.as_ref().map(|progress| progress.clone().into()))
|
Ok(res.as_ref().map(|progress| progress.clone().into()))
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +76,7 @@ impl AsyncPush {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_request(¶ms)?;
|
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_state = Arc::clone(&self.state);
|
||||||
let arc_res = Arc::clone(&self.last_result);
|
let arc_res = Arc::clone(&self.last_result);
|
||||||
|
|
@ -158,7 +86,7 @@ impl AsyncPush {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let (progress_sender, receiver) = unbounded();
|
let (progress_sender, receiver) = unbounded();
|
||||||
|
|
||||||
let handle = Self::spawn_receiver_thread(
|
let handle = RemoteProgress::spawn_receiver_thread(
|
||||||
sender.clone(),
|
sender.clone(),
|
||||||
receiver,
|
receiver,
|
||||||
arc_progress,
|
arc_progress,
|
||||||
|
|
@ -191,44 +119,6 @@ impl AsyncPush {
|
||||||
Ok(())
|
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<()> {
|
fn set_request(&self, params: &PushRequest) -> Result<()> {
|
||||||
let mut state = self.state.lock()?;
|
let mut state = self.state.lock()?;
|
||||||
|
|
||||||
|
|
@ -253,20 +143,6 @@ impl AsyncPush {
|
||||||
Ok(())
|
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(
|
fn set_result(
|
||||||
arc_result: Arc<Mutex<Option<String>>>,
|
arc_result: Arc<Mutex<Option<String>>>,
|
||||||
res: Result<()>,
|
res: Result<()>,
|
||||||
|
|
@ -284,24 +160,3 @@ impl AsyncPush {
|
||||||
Ok(())
|
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 {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::sync::{
|
use crate::sync::{
|
||||||
commit, fetch_origin,
|
commit,
|
||||||
remotes::push::push,
|
remotes::{fetch_origin, push::push},
|
||||||
stage_add_file,
|
stage_add_file,
|
||||||
tests::{
|
tests::{
|
||||||
debug_cmd_print, get_commit_ids, repo_clone,
|
debug_cmd_print, get_commit_ids, repo_clone,
|
||||||
|
|
@ -137,6 +137,8 @@ mod test {
|
||||||
let bytes = fetch_origin(
|
let bytes = fetch_origin(
|
||||||
clone1_dir.path().to_str().unwrap(),
|
clone1_dir.path().to_str().unwrap(),
|
||||||
"master",
|
"master",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(bytes > 0);
|
assert!(bytes > 0);
|
||||||
|
|
@ -144,6 +146,8 @@ mod test {
|
||||||
let bytes = fetch_origin(
|
let bytes = fetch_origin(
|
||||||
clone1_dir.path().to_str().unwrap(),
|
clone1_dir.path().to_str().unwrap(),
|
||||||
"master",
|
"master",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(bytes, 0);
|
assert_eq!(bytes, 0);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ pub use hooks::{
|
||||||
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
|
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
|
||||||
pub use ignore::add_to_ignore;
|
pub use ignore::add_to_ignore;
|
||||||
pub use logwalker::LogWalker;
|
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 reset::{reset_stage, reset_workdir};
|
||||||
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
|
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
|
||||||
pub use state::{repo_state, RepoState};
|
pub use state::{repo_state, RepoState};
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,15 @@ use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
sync::utils,
|
sync::utils,
|
||||||
};
|
};
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
use git2::{FetchOptions, Repository};
|
use git2::{FetchOptions, Repository};
|
||||||
use push::remote_callbacks;
|
use push::remote_callbacks;
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
|
|
||||||
|
use self::push::ProgressNotification;
|
||||||
|
|
||||||
|
use super::cred::BasicAuthCredential;
|
||||||
|
|
||||||
/// origin
|
/// origin
|
||||||
pub const DEFAULT_REMOTE_NAME: &str = "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");
|
scope_time!("fetch_origin");
|
||||||
|
|
||||||
let repo = utils::repo(repo_path)?;
|
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)?)?;
|
repo.find_remote(&get_default_remote_in_repo(&repo)?)?;
|
||||||
|
|
||||||
let mut options = FetchOptions::new();
|
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)?;
|
remote.fetch(&[branch], Some(&mut options), None)?;
|
||||||
|
|
||||||
|
|
@ -104,7 +117,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(remotes, vec![String::from("origin")]);
|
assert_eq!(remotes, vec![String::from("origin")]);
|
||||||
|
|
||||||
fetch_origin(repo_path, "master").unwrap();
|
fetch_origin(repo_path, "master", None, None).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
18
src/app.rs
18
src/app.rs
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
event_pump, BranchListComponent, CommandBlocking,
|
event_pump, BranchListComponent, CommandBlocking,
|
||||||
CommandInfo, CommitComponent, Component,
|
CommandInfo, CommitComponent, Component,
|
||||||
CreateBranchComponent, DrawableComponent,
|
CreateBranchComponent, DrawableComponent,
|
||||||
ExternalEditorComponent, HelpComponent,
|
ExternalEditorComponent, FetchComponent, HelpComponent,
|
||||||
InspectCommitComponent, MsgComponent, PushComponent,
|
InspectCommitComponent, MsgComponent, PushComponent,
|
||||||
RenameBranchComponent, ResetComponent, StashMsgComponent,
|
RenameBranchComponent, ResetComponent, StashMsgComponent,
|
||||||
TagCommitComponent,
|
TagCommitComponent,
|
||||||
|
|
@ -45,6 +45,7 @@ pub struct App {
|
||||||
inspect_commit_popup: InspectCommitComponent,
|
inspect_commit_popup: InspectCommitComponent,
|
||||||
external_editor_popup: ExternalEditorComponent,
|
external_editor_popup: ExternalEditorComponent,
|
||||||
push_popup: PushComponent,
|
push_popup: PushComponent,
|
||||||
|
fetch_popup: FetchComponent,
|
||||||
tag_commit_popup: TagCommitComponent,
|
tag_commit_popup: TagCommitComponent,
|
||||||
create_branch_popup: CreateBranchComponent,
|
create_branch_popup: CreateBranchComponent,
|
||||||
rename_branch_popup: RenameBranchComponent,
|
rename_branch_popup: RenameBranchComponent,
|
||||||
|
|
@ -74,7 +75,6 @@ impl App {
|
||||||
theme_path: PathBuf,
|
theme_path: PathBuf,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let queue = Queue::default();
|
let queue = Queue::default();
|
||||||
|
|
||||||
let theme = Rc::new(Theme::init(theme_path));
|
let theme = Rc::new(Theme::init(theme_path));
|
||||||
let key_config = Rc::new(KeyConfig::init());
|
let key_config = Rc::new(KeyConfig::init());
|
||||||
|
|
||||||
|
|
@ -111,6 +111,12 @@ impl App {
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
),
|
),
|
||||||
|
fetch_popup: FetchComponent::new(
|
||||||
|
&queue,
|
||||||
|
sender,
|
||||||
|
theme.clone(),
|
||||||
|
key_config.clone(),
|
||||||
|
),
|
||||||
tag_commit_popup: TagCommitComponent::new(
|
tag_commit_popup: TagCommitComponent::new(
|
||||||
queue.clone(),
|
queue.clone(),
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
|
|
@ -302,6 +308,7 @@ impl App {
|
||||||
self.revlog.update_git(ev)?;
|
self.revlog.update_git(ev)?;
|
||||||
self.inspect_commit_popup.update_git(ev)?;
|
self.inspect_commit_popup.update_git(ev)?;
|
||||||
self.push_popup.update_git(ev)?;
|
self.push_popup.update_git(ev)?;
|
||||||
|
self.fetch_popup.update_git(ev)?;
|
||||||
|
|
||||||
//TODO: better system for this
|
//TODO: better system for this
|
||||||
// can we simply process the queue here and everyone just uses the queue to schedule a cmd update?
|
// 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,
|
inspect_commit_popup,
|
||||||
external_editor_popup,
|
external_editor_popup,
|
||||||
push_popup,
|
push_popup,
|
||||||
|
fetch_popup,
|
||||||
tag_commit_popup,
|
tag_commit_popup,
|
||||||
create_branch_popup,
|
create_branch_popup,
|
||||||
rename_branch_popup,
|
rename_branch_popup,
|
||||||
|
|
@ -543,6 +551,10 @@ impl App {
|
||||||
self.push_popup.push(branch, force)?;
|
self.push_popup.push(branch, force)?;
|
||||||
flags.insert(NeedsUpdate::ALL)
|
flags.insert(NeedsUpdate::ALL)
|
||||||
}
|
}
|
||||||
|
InternalEvent::Fetch(branch) => {
|
||||||
|
self.fetch_popup.fetch(branch)?;
|
||||||
|
flags.insert(NeedsUpdate::ALL)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(flags)
|
Ok(flags)
|
||||||
|
|
@ -603,6 +615,7 @@ impl App {
|
||||||
|| self.tag_commit_popup.is_visible()
|
|| self.tag_commit_popup.is_visible()
|
||||||
|| self.create_branch_popup.is_visible()
|
|| self.create_branch_popup.is_visible()
|
||||||
|| self.push_popup.is_visible()
|
|| self.push_popup.is_visible()
|
||||||
|
|| self.fetch_popup.is_visible()
|
||||||
|| self.select_branch_popup.is_visible()
|
|| self.select_branch_popup.is_visible()
|
||||||
|| self.rename_branch_popup.is_visible()
|
|| self.rename_branch_popup.is_visible()
|
||||||
}
|
}
|
||||||
|
|
@ -632,6 +645,7 @@ impl App {
|
||||||
self.create_branch_popup.draw(f, size)?;
|
self.create_branch_popup.draw(f, size)?;
|
||||||
self.rename_branch_popup.draw(f, size)?;
|
self.rename_branch_popup.draw(f, size)?;
|
||||||
self.push_popup.draw(f, size)?;
|
self.push_popup.draw(f, size)?;
|
||||||
|
self.fetch_popup.draw(f, size)?;
|
||||||
self.reset.draw(f, size)?;
|
self.reset.draw(f, size)?;
|
||||||
self.msg.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 cred;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod externaleditor;
|
mod externaleditor;
|
||||||
|
mod fetch;
|
||||||
mod filetree;
|
mod filetree;
|
||||||
mod help;
|
mod help;
|
||||||
mod inspect_commit;
|
mod inspect_commit;
|
||||||
|
|
@ -29,6 +30,7 @@ pub use commitlist::CommitList;
|
||||||
pub use create_branch::CreateBranchComponent;
|
pub use create_branch::CreateBranchComponent;
|
||||||
pub use diff::DiffComponent;
|
pub use diff::DiffComponent;
|
||||||
pub use externaleditor::ExternalEditorComponent;
|
pub use externaleditor::ExternalEditorComponent;
|
||||||
|
pub use fetch::FetchComponent;
|
||||||
pub use filetree::FileTreeComponent;
|
pub use filetree::FileTreeComponent;
|
||||||
pub use help::HelpComponent;
|
pub use help::HelpComponent;
|
||||||
pub use inspect_commit::InspectCommitComponent;
|
pub use inspect_commit::InspectCommitComponent;
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ use asyncgit::{
|
||||||
},
|
},
|
||||||
get_default_remote,
|
get_default_remote,
|
||||||
},
|
},
|
||||||
AsyncNotification, AsyncPush, PushProgress, PushProgressState,
|
AsyncNotification, AsyncPush, PushRequest, RemoteProgress,
|
||||||
PushRequest, CWD,
|
RemoteProgressState, CWD,
|
||||||
};
|
};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct PushComponent {
|
||||||
visible: bool,
|
visible: bool,
|
||||||
force: bool,
|
force: bool,
|
||||||
git_push: AsyncPush,
|
git_push: AsyncPush,
|
||||||
progress: Option<PushProgress>,
|
progress: Option<RemoteProgress>,
|
||||||
pending: bool,
|
pending: bool,
|
||||||
branch: String,
|
branch: String,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
|
@ -144,8 +144,10 @@ impl PushComponent {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_progress(&self) -> (String, u8) {
|
pub fn get_progress(
|
||||||
self.progress.as_ref().map_or(
|
progress: &Option<RemoteProgress>,
|
||||||
|
) -> (String, u8) {
|
||||||
|
progress.as_ref().map_or(
|
||||||
(strings::PUSH_POPUP_PROGRESS_NONE.into(), 0),
|
(strings::PUSH_POPUP_PROGRESS_NONE.into(), 0),
|
||||||
|progress| {
|
|progress| {
|
||||||
(
|
(
|
||||||
|
|
@ -156,17 +158,20 @@ impl PushComponent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress_state_name(state: &PushProgressState) -> String {
|
fn progress_state_name(state: &RemoteProgressState) -> String {
|
||||||
match state {
|
match state {
|
||||||
PushProgressState::PackingAddingObject => {
|
RemoteProgressState::PackingAddingObject => {
|
||||||
strings::PUSH_POPUP_STATES_ADDING
|
strings::PUSH_POPUP_STATES_ADDING
|
||||||
}
|
}
|
||||||
PushProgressState::PackingDeltafiction => {
|
RemoteProgressState::PackingDeltafiction => {
|
||||||
strings::PUSH_POPUP_STATES_DELTAS
|
strings::PUSH_POPUP_STATES_DELTAS
|
||||||
}
|
}
|
||||||
PushProgressState::Pushing => {
|
RemoteProgressState::Pushing => {
|
||||||
strings::PUSH_POPUP_STATES_PUSHING
|
strings::PUSH_POPUP_STATES_PUSHING
|
||||||
}
|
}
|
||||||
|
RemoteProgressState::Done => {
|
||||||
|
strings::PUSH_POPUP_STATES_DONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +184,8 @@ impl DrawableComponent for PushComponent {
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.visible {
|
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());
|
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> {
|
fn event(&mut self, ev: Event) -> Result<bool> {
|
||||||
if self.visible {
|
if self.visible {
|
||||||
if let Event::Key(e) = ev {
|
if let Event::Key(e) = ev {
|
||||||
if e == self.key_config.exit_popup {
|
if self.input_cred.is_visible() {
|
||||||
self.hide();
|
if self.input_cred.event(ev)? {
|
||||||
}
|
return Ok(true);
|
||||||
if self.input_cred.event(ev)? {
|
} else if self.input_cred.get_cred().is_complete()
|
||||||
return Ok(true);
|
|
||||||
} else if e == self.key_config.enter {
|
|
||||||
if self.input_cred.is_visible()
|
|
||||||
&& self.input_cred.get_cred().is_complete()
|
|
||||||
{
|
{
|
||||||
self.push_to_remote(
|
self.push_to_remote(
|
||||||
Some(self.input_cred.get_cred().clone()),
|
Some(self.input_cred.get_cred().clone()),
|
||||||
self.force,
|
self.force,
|
||||||
)?;
|
)?;
|
||||||
self.input_cred.hide();
|
self.input_cred.hide();
|
||||||
} else {
|
|
||||||
self.hide();
|
|
||||||
}
|
}
|
||||||
|
} else if e == self.key_config.exit_popup
|
||||||
|
&& !self.pending
|
||||||
|
{
|
||||||
|
self.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ pub enum InternalEvent {
|
||||||
OpenExternalEditor(Option<String>),
|
OpenExternalEditor(Option<String>),
|
||||||
///
|
///
|
||||||
Push(String, bool),
|
Push(String, bool),
|
||||||
|
///
|
||||||
|
Fetch(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ pub mod order {
|
||||||
|
|
||||||
pub static PUSH_POPUP_MSG: &str = "Push";
|
pub static PUSH_POPUP_MSG: &str = "Push";
|
||||||
pub static FORCE_PUSH_POPUP_MSG: &str = "Force 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_PROGRESS_NONE: &str = "preparing...";
|
||||||
pub static PUSH_POPUP_STATES_ADDING: &str = "adding objects (1/3)";
|
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_DELTAS: &str = "deltas (2/3)";
|
||||||
pub static PUSH_POPUP_STATES_PUSHING: &str = "pushing (3/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";
|
pub static SELECT_BRANCH_POPUP_MSG: &str = "Switch Branch";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -416,46 +416,9 @@ impl Status {
|
||||||
|
|
||||||
fn fetch(&self) {
|
fn fetch(&self) {
|
||||||
if let Some(branch) = self.git_branch_name.last() {
|
if let Some(branch) = self.git_branch_name.last() {
|
||||||
match sync::fetch_origin(CWD, branch.as_str()) {
|
self.queue
|
||||||
Err(e) => {
|
.borrow_mut()
|
||||||
self.queue.borrow_mut().push_back(
|
.push_back(InternalEvent::Fetch(branch));
|
||||||
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(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue