Skip to content

Commit de80601

Browse files
author
Stephan Dilly
authored
Push progress
closes #267
1 parent 17de5a9 commit de80601

File tree

9 files changed

+300
-39
lines changed

9 files changed

+300
-39
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## Unreleased
99

1010
### Added
11-
- push to origin support ([#265](https://github.com/extrawurst/gitui/issues/265))
11+
- push to remote ([#265](https://github.com/extrawurst/gitui/issues/265)) ([#267](https://github.com/extrawurst/gitui/issues/267))
12+
13+
![push](assets/push.gif)
1214

1315
### Changed
1416
- do not highlight selection in diff view when not focused ([#270](https://github.com/extrawurst/gitui/issues/270))

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- Inspect, commit, and amend changes (incl. hooks: _commit-msg_/_post-commit_)
3131
- Stage, unstage, revert and reset files and hunks
3232
- Stashing (save, apply, drop, and inspect)
33+
- Push to remote
3334
- Browse commit log, diff committed changes
3435
- Scalable terminal UI layout
3536
- Async [input polling](assets/perf_compare.jpg)
@@ -53,7 +54,7 @@ Over the last 2 years my go-to GUI tool for this was [fork](https://git-fork.com
5354

5455
# Known Limitations
5556

56-
- no support for push and pull yet (see [#90](https://github.com/extrawurst/gitui/issues/90))
57+
- no support for `pull` yet (see [#90](https://github.com/extrawurst/gitui/issues/90))
5758
- limited support for branching (see [#90](https://github.com/extrawurst/gitui/issues/91))
5859
- no support for [bare repositories](https://git-scm.com/book/en/v2/Git-on-the-Server-Getting-Git-on-a-Server) (see [#100](https://github.com/extrawurst/gitui/issues/100))
5960
- no support for [core.hooksPath](https://git-scm.com/docs/githooks) config

assets/push.gif

282 KB
Loading

asyncgit/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ mod tags;
2020
pub use crate::{
2121
commit_files::AsyncCommitFiles,
2222
diff::{AsyncDiff, DiffParams, DiffType},
23-
push::{AsyncPush, PushRequest},
23+
push::{AsyncPush, PushProgress, PushProgressState, PushRequest},
2424
revlog::{AsyncLog, FetchStatus},
2525
status::{AsyncStatus, StatusParams},
2626
sync::{

asyncgit/src/push.rs

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,83 @@ use crate::{
22
error::{Error, Result},
33
sync, AsyncNotification, CWD,
44
};
5-
use crossbeam_channel::Sender;
6-
use std::sync::{Arc, Mutex};
5+
use crossbeam_channel::{unbounded, Receiver, Sender};
6+
use git2::PackBuilderStage;
7+
use std::{
8+
cmp,
9+
sync::{Arc, Mutex},
10+
thread,
11+
time::Duration,
12+
};
13+
use sync::ProgressNotification;
14+
use thread::JoinHandle;
15+
16+
///
17+
#[derive(Clone, Debug)]
18+
pub enum PushProgressState {
19+
///
20+
PackingAddingObject,
21+
///
22+
PackingDeltafiction,
23+
///
24+
Pushing,
25+
}
726

27+
///
828
#[derive(Clone, Debug)]
9-
enum PushStates {
10-
None,
11-
// Packing,
12-
// Pushing(usize, usize),
29+
pub struct PushProgress {
30+
///
31+
pub state: PushProgressState,
32+
///
33+
pub progress: u8,
1334
}
1435

15-
impl Default for PushStates {
16-
fn default() -> Self {
17-
PushStates::None
36+
impl PushProgress {
37+
///
38+
pub fn new(
39+
state: PushProgressState,
40+
current: usize,
41+
total: usize,
42+
) -> Self {
43+
let total = cmp::max(current, total);
44+
let progress = current as f32 / total as f32 * 100.0;
45+
let progress = progress as u8;
46+
Self { state, progress }
47+
}
48+
}
49+
50+
impl From<ProgressNotification> for PushProgress {
51+
fn from(progress: ProgressNotification) -> Self {
52+
match progress {
53+
ProgressNotification::Packing {
54+
stage,
55+
current,
56+
total,
57+
} => match stage {
58+
PackBuilderStage::AddingObjects => PushProgress::new(
59+
PushProgressState::PackingAddingObject,
60+
current,
61+
total,
62+
),
63+
PackBuilderStage::Deltafication => PushProgress::new(
64+
PushProgressState::PackingDeltafiction,
65+
current,
66+
total,
67+
),
68+
},
69+
ProgressNotification::PushTransfer {
70+
current,
71+
total,
72+
..
73+
} => PushProgress::new(
74+
PushProgressState::Pushing,
75+
current,
76+
total,
77+
),
78+
ProgressNotification::Done => {
79+
PushProgress::new(PushProgressState::Pushing, 1, 1)
80+
}
81+
}
1882
}
1983
}
2084

@@ -30,13 +94,13 @@ pub struct PushRequest {
3094
#[derive(Default, Clone, Debug)]
3195
struct PushState {
3296
request: PushRequest,
33-
state: PushStates,
3497
}
3598

3699
///
37100
pub struct AsyncPush {
38101
state: Arc<Mutex<Option<PushState>>>,
39102
last_result: Arc<Mutex<Option<String>>>,
103+
progress: Arc<Mutex<Option<ProgressNotification>>>,
40104
sender: Sender<AsyncNotification>,
41105
}
42106

@@ -46,6 +110,7 @@ impl AsyncPush {
46110
Self {
47111
state: Arc::new(Mutex::new(None)),
48112
last_result: Arc::new(Mutex::new(None)),
113+
progress: Arc::new(Mutex::new(None)),
49114
sender: sender.clone(),
50115
}
51116
}
@@ -62,6 +127,12 @@ impl AsyncPush {
62127
Ok(res.clone())
63128
}
64129

130+
///
131+
pub fn progress(&self) -> Result<Option<PushProgress>> {
132+
let res = self.progress.lock()?;
133+
Ok(res.map(|progress| progress.into()))
134+
}
135+
65136
///
66137
pub fn request(&mut self, params: PushRequest) -> Result<()> {
67138
log::trace!("request");
@@ -71,19 +142,35 @@ impl AsyncPush {
71142
}
72143

73144
self.set_request(&params)?;
145+
Self::set_progress(self.progress.clone(), None)?;
74146

75147
let arc_state = Arc::clone(&self.state);
76148
let arc_res = Arc::clone(&self.last_result);
149+
let arc_progress = Arc::clone(&self.progress);
77150
let sender = self.sender.clone();
78151

79-
rayon_core::spawn(move || {
80-
//TODO: use channels to communicate progress
81-
let res = sync::push_origin(
152+
thread::spawn(move || {
153+
let (progress_sender, receiver) = unbounded();
154+
155+
let handle = Self::spawn_receiver_thread(
156+
sender.clone(),
157+
receiver,
158+
arc_progress,
159+
);
160+
161+
let res = sync::push(
82162
CWD,
83163
params.remote.as_str(),
84164
params.branch.as_str(),
165+
progress_sender.clone(),
85166
);
86167

168+
progress_sender
169+
.send(ProgressNotification::Done)
170+
.expect("closing send failed");
171+
172+
handle.join().expect("joining thread failed");
173+
87174
Self::set_result(arc_res, res).expect("result error");
88175

89176
Self::clear_request(arc_state).expect("clear error");
@@ -96,6 +183,44 @@ impl AsyncPush {
96183
Ok(())
97184
}
98185

186+
fn spawn_receiver_thread(
187+
sender: Sender<AsyncNotification>,
188+
receiver: Receiver<ProgressNotification>,
189+
progress: Arc<Mutex<Option<ProgressNotification>>>,
190+
) -> JoinHandle<()> {
191+
log::info!("push progress receiver spawned");
192+
193+
thread::spawn(move || loop {
194+
let incoming = receiver.recv();
195+
match incoming {
196+
Ok(update) => {
197+
Self::set_progress(
198+
progress.clone(),
199+
Some(update),
200+
)
201+
.expect("set prgoress failed");
202+
sender
203+
.send(AsyncNotification::Push)
204+
.expect("error sending push");
205+
206+
//NOTE: for better debugging
207+
thread::sleep(Duration::from_millis(300));
208+
209+
if let ProgressNotification::Done = update {
210+
break;
211+
}
212+
}
213+
Err(e) => {
214+
log::error!(
215+
"push progress receiver error: {}",
216+
e
217+
);
218+
break;
219+
}
220+
}
221+
})
222+
}
223+
99224
fn set_request(&self, params: &PushRequest) -> Result<()> {
100225
let mut state = self.state.lock()?;
101226

@@ -105,7 +230,6 @@ impl AsyncPush {
105230

106231
*state = Some(PushState {
107232
request: params.clone(),
108-
..PushState::default()
109233
});
110234

111235
Ok(())
@@ -121,6 +245,20 @@ impl AsyncPush {
121245
Ok(())
122246
}
123247

248+
fn set_progress(
249+
progress: Arc<Mutex<Option<ProgressNotification>>>,
250+
state: Option<ProgressNotification>,
251+
) -> Result<()> {
252+
let simple_progress: Option<PushProgress> =
253+
state.map(|prog| prog.into());
254+
log::info!("push progress: {:?}", simple_progress);
255+
let mut progress = progress.lock()?;
256+
257+
*progress = state;
258+
259+
Ok(())
260+
}
261+
124262
fn set_result(
125263
arc_result: Arc<Mutex<Option<String>>>,
126264
res: Result<()>,
@@ -138,3 +276,24 @@ impl AsyncPush {
138276
Ok(())
139277
}
140278
}
279+
280+
#[cfg(test)]
281+
mod tests {
282+
use super::*;
283+
284+
#[test]
285+
fn test_progress_zero_total() {
286+
let prog =
287+
PushProgress::new(PushProgressState::Pushing, 1, 0);
288+
289+
assert_eq!(prog.progress, 100);
290+
}
291+
292+
#[test]
293+
fn test_progress_rounding() {
294+
let prog =
295+
PushProgress::new(PushProgressState::Pushing, 2, 10);
296+
297+
assert_eq!(prog.progress, 20);
298+
}
299+
}

asyncgit/src/sync/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
3030
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
3131
pub use ignore::add_to_ignore;
3232
pub use logwalker::LogWalker;
33-
pub use remotes::{fetch_origin, get_remotes, push_origin};
33+
pub use remotes::{
34+
fetch_origin, get_remotes, push, ProgressNotification,
35+
};
3436
pub use reset::{reset_stage, reset_workdir};
3537
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
3638
pub use tags::{get_tags, CommitTags, Tags};

0 commit comments

Comments
 (0)