Skip to content

Commit 8e8c5fa

Browse files
committed
make fetching tree files async
1 parent bc15b5d commit 8e8c5fa

File tree

7 files changed

+185
-45
lines changed

7 files changed

+185
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixes
1111
* improve performance by requesting branches info asynchronous ([92f63d1](https://github.com/extrawurst/gitui/commit/92f63d107c1dca1f10139668ff5b3ca752261b0f))
1212
* fix app startup delay due to using file watcher ([#1436](https://github.com/extrawurst/gitui/issues/1436))
13+
* make git tree file fetch async ([#734](https://github.com/extrawurst/gitui/issues/734))
1314

1415
## [0.22.0] - 2022-11-19
1516

asyncgit/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ mod revlog;
4040
mod status;
4141
pub mod sync;
4242
mod tags;
43+
mod treefiles;
4344

4445
pub use crate::{
4546
blame::{AsyncBlame, BlameParams},
@@ -61,6 +62,7 @@ pub use crate::{
6162
status::{StatusItem, StatusItemType},
6263
},
6364
tags::AsyncTags,
65+
treefiles::AsyncTreeFilesJob,
6466
};
6567
pub use git2::message_prettify;
6668
use std::{
@@ -99,6 +101,8 @@ pub enum AsyncGitNotification {
99101
Fetch,
100102
///
101103
Branches,
104+
///
105+
TreeFiles,
102106
}
103107

104108
/// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait

asyncgit/src/treefiles.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::{
2+
asyncjob::{AsyncJob, RunParams},
3+
error::Result,
4+
sync::{tree_files, CommitId, RepoPath, TreeFile},
5+
AsyncGitNotification,
6+
};
7+
use std::sync::{Arc, Mutex};
8+
9+
enum JobState {
10+
Request { commit: CommitId, repo: RepoPath },
11+
Response(Result<Vec<TreeFile>>),
12+
}
13+
14+
///
15+
#[derive(Clone, Default)]
16+
pub struct AsyncTreeFilesJob {
17+
state: Arc<Mutex<Option<JobState>>>,
18+
}
19+
20+
///
21+
impl AsyncTreeFilesJob {
22+
///
23+
pub fn new(repo: RepoPath, commit: CommitId) -> Self {
24+
Self {
25+
state: Arc::new(Mutex::new(Some(JobState::Request {
26+
repo,
27+
commit,
28+
}))),
29+
}
30+
}
31+
32+
///
33+
pub fn result(&self) -> Option<Result<Vec<TreeFile>>> {
34+
if let Ok(mut state) = self.state.lock() {
35+
if let Some(state) = state.take() {
36+
return match state {
37+
JobState::Request { .. } => None,
38+
JobState::Response(result) => Some(result),
39+
};
40+
}
41+
}
42+
43+
None
44+
}
45+
}
46+
47+
impl AsyncJob for AsyncTreeFilesJob {
48+
type Notification = AsyncGitNotification;
49+
type Progress = ();
50+
51+
fn run(
52+
&mut self,
53+
_params: RunParams<Self::Notification, Self::Progress>,
54+
) -> Result<Self::Notification> {
55+
if let Ok(mut state) = self.state.lock() {
56+
*state = state.take().map(|state| match state {
57+
JobState::Request { commit, repo } => {
58+
let files = tree_files(&repo, commit);
59+
60+
std::thread::sleep(
61+
std::time::Duration::from_secs(2),
62+
);
63+
JobState::Response(files)
64+
}
65+
JobState::Response(result) => {
66+
JobState::Response(result)
67+
}
68+
});
69+
}
70+
71+
Ok(AsyncGitNotification::TreeFiles)
72+
}
73+
}

src/app.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ impl App {
162162
repo.clone(),
163163
&queue,
164164
sender_app,
165+
sender.clone(),
165166
theme.clone(),
166167
key_config.clone(),
167168
),
@@ -306,6 +307,7 @@ impl App {
306307
files_tab: FilesTab::new(
307308
repo.clone(),
308309
sender_app,
310+
sender.clone(),
309311
&queue,
310312
theme.clone(),
311313
key_config.clone(),
@@ -508,8 +510,8 @@ impl App {
508510
self.select_branch_popup.update_git(ev)?;
509511
}
510512

511-
self.files_tab.update_async(ev);
512-
self.revision_files_popup.update(ev);
513+
self.files_tab.update_async(ev)?;
514+
self.revision_files_popup.update(ev)?;
513515
self.tags_popup.update(ev);
514516

515517
//TODO: better system for this

src/components/revision_files.rs

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ use crate::{
1111
AsyncAppNotification, AsyncNotification,
1212
};
1313
use anyhow::Result;
14-
use asyncgit::sync::{
15-
self, get_commit_info, CommitId, CommitInfo, RepoPathRef,
16-
TreeFile,
14+
use asyncgit::{
15+
asyncjob::AsyncSingleJob,
16+
sync::{
17+
get_commit_info, CommitId, CommitInfo, RepoPathRef, TreeFile,
18+
},
19+
AsyncGitNotification, AsyncTreeFilesJob,
1720
};
1821
use crossbeam_channel::Sender;
1922
use crossterm::event::Event;
2023
use filetreelist::{FileTree, FileTreeItem};
21-
use std::fmt::Write;
24+
use std::{borrow::Cow, fmt::Write};
2225
use std::{
2326
collections::BTreeSet,
2427
convert::From,
@@ -44,7 +47,8 @@ pub struct RevisionFilesComponent {
4447
queue: Queue,
4548
theme: SharedTheme,
4649
//TODO: store TreeFiles in `tree`
47-
files: Vec<TreeFile>,
50+
files: Option<Vec<TreeFile>>,
51+
async_treefiles: AsyncSingleJob<AsyncTreeFilesJob>,
4852
current_file: SyntaxTextComponent,
4953
tree: FileTree,
5054
scroll: VerticalScroll,
@@ -60,6 +64,7 @@ impl RevisionFilesComponent {
6064
repo: RepoPathRef,
6165
queue: &Queue,
6266
sender: &Sender<AsyncAppNotification>,
67+
sender_git: Sender<AsyncGitNotification>,
6368
theme: SharedTheme,
6469
key_config: SharedKeyConfig,
6570
) -> Self {
@@ -73,8 +78,9 @@ impl RevisionFilesComponent {
7378
key_config.clone(),
7479
theme.clone(),
7580
),
81+
async_treefiles: AsyncSingleJob::new(sender_git),
7682
theme,
77-
files: Vec::new(),
83+
files: None,
7884
revision: None,
7985
focus: Focus::Tree,
8086
key_config,
@@ -89,13 +95,15 @@ impl RevisionFilesComponent {
8995

9096
let same_id =
9197
self.revision.as_ref().map_or(false, |c| c.id == commit);
98+
9299
if !same_id {
93-
self.files =
94-
sync::tree_files(&self.repo.borrow(), commit)?;
95-
let filenames: Vec<&Path> =
96-
self.files.iter().map(|f| f.path.as_path()).collect();
97-
self.tree = FileTree::new(&filenames, &BTreeSet::new())?;
98-
self.tree.collapse_but_root();
100+
self.files = None;
101+
102+
self.async_treefiles.spawn(AsyncTreeFilesJob::new(
103+
self.repo.borrow().clone(),
104+
commit,
105+
));
106+
99107
self.revision =
100108
Some(get_commit_info(&self.repo.borrow(), &commit)?);
101109
}
@@ -114,13 +122,35 @@ impl RevisionFilesComponent {
114122
}
115123

116124
///
117-
pub fn update(&mut self, ev: AsyncNotification) {
125+
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
118126
self.current_file.update(ev);
127+
128+
if matches!(
129+
ev,
130+
AsyncNotification::Git(AsyncGitNotification::TreeFiles)
131+
) {
132+
if let Some(last) = self.async_treefiles.take_last() {
133+
if let Some(Ok(last)) = last.result() {
134+
let filenames: Vec<&Path> = last
135+
.iter()
136+
.map(|f| f.path.as_path())
137+
.collect();
138+
self.tree =
139+
FileTree::new(&filenames, &BTreeSet::new())?;
140+
self.tree.collapse_but_root();
141+
142+
self.files = Some(last);
143+
}
144+
}
145+
}
146+
147+
Ok(())
119148
}
120149

121150
///
122151
pub fn any_work_pending(&self) -> bool {
123152
self.current_file.any_work_pending()
153+
|| self.async_treefiles.is_pending()
124154
}
125155

126156
fn tree_item_to_span<'a>(
@@ -190,8 +220,9 @@ impl RevisionFilesComponent {
190220
}
191221

192222
fn open_finder(&self) {
193-
self.queue
194-
.push(InternalEvent::OpenFileFinder(self.files.clone()));
223+
self.queue.push(InternalEvent::OpenFileFinder(
224+
self.files.clone().unwrap_or_default(),
225+
));
195226
}
196227

197228
pub fn find_file(&mut self, file: &Option<PathBuf>) {
@@ -221,18 +252,20 @@ impl RevisionFilesComponent {
221252
fn selection_changed(&mut self) {
222253
//TODO: retrieve TreeFile from tree datastructure
223254
if let Some(file) = self.selected_file_path_with_prefix() {
224-
let path = Path::new(&file);
225-
if let Some(item) =
226-
self.files.iter().find(|f| f.path == path)
227-
{
228-
if let Ok(path) = path.strip_prefix("./") {
229-
return self.current_file.load_file(
230-
path.to_string_lossy().to_string(),
231-
item,
232-
);
255+
if let Some(files) = &self.files {
256+
let path = Path::new(&file);
257+
if let Some(item) =
258+
files.iter().find(|f| f.path == path)
259+
{
260+
if let Ok(path) = path.strip_prefix("./") {
261+
return self.current_file.load_file(
262+
path.to_string_lossy().to_string(),
263+
item,
264+
);
265+
}
233266
}
267+
self.current_file.clear();
234268
}
235-
self.current_file.clear();
236269
}
237270
}
238271

@@ -268,18 +301,30 @@ impl RevisionFilesComponent {
268301
let is_tree_focused = matches!(self.focus, Focus::Tree);
269302

270303
let title = self.title_within(tree_width);
271-
ui::draw_list_block(
272-
f,
273-
area,
274-
Block::default()
275-
.title(Span::styled(
276-
title,
277-
self.theme.title(is_tree_focused),
278-
))
279-
.borders(Borders::ALL)
280-
.border_style(self.theme.block(is_tree_focused)),
281-
items,
282-
);
304+
let block = Block::default()
305+
.title(Span::styled(
306+
title,
307+
self.theme.title(is_tree_focused),
308+
))
309+
.borders(Borders::ALL)
310+
.border_style(self.theme.block(is_tree_focused));
311+
312+
if self.files.is_some() {
313+
ui::draw_list_block(f, area, block, items);
314+
} else {
315+
ui::draw_list_block(
316+
f,
317+
area,
318+
block,
319+
vec![Span::styled(
320+
Cow::from(strings::loading_text(
321+
&self.key_config,
322+
)),
323+
self.theme.text(false, false),
324+
)]
325+
.into_iter(),
326+
);
327+
}
283328

284329
if is_tree_focused {
285330
self.scroll.draw(f, area, &self.theme);

src/components/revision_files_popup.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use crate::{
1313
AsyncAppNotification, AsyncNotification,
1414
};
1515
use anyhow::Result;
16-
use asyncgit::sync::{CommitId, RepoPathRef};
16+
use asyncgit::{
17+
sync::{CommitId, RepoPathRef},
18+
AsyncGitNotification,
19+
};
1720
use crossbeam_channel::Sender;
1821
use crossterm::event::Event;
1922
use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame};
@@ -47,6 +50,7 @@ impl RevisionFilesPopup {
4750
repo: RepoPathRef,
4851
queue: &Queue,
4952
sender: &Sender<AsyncAppNotification>,
53+
sender_git: Sender<AsyncGitNotification>,
5054
theme: SharedTheme,
5155
key_config: SharedKeyConfig,
5256
) -> Self {
@@ -55,6 +59,7 @@ impl RevisionFilesPopup {
5559
repo,
5660
queue,
5761
sender,
62+
sender_git,
5863
theme,
5964
key_config.clone(),
6065
),
@@ -75,8 +80,8 @@ impl RevisionFilesPopup {
7580
}
7681

7782
///
78-
pub fn update(&mut self, ev: AsyncNotification) {
79-
self.files.update(ev);
83+
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
84+
self.files.update(ev)
8085
}
8186

8287
///

0 commit comments

Comments
 (0)