mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
make fetching tree files async
This commit is contained in:
parent
bc15b5d550
commit
8e8c5fad55
7 changed files with 185 additions and 45 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Fixes
|
### Fixes
|
||||||
* improve performance by requesting branches info asynchronous ([92f63d1](https://github.com/extrawurst/gitui/commit/92f63d107c1dca1f10139668ff5b3ca752261b0f))
|
* improve performance by requesting branches info asynchronous ([92f63d1](https://github.com/extrawurst/gitui/commit/92f63d107c1dca1f10139668ff5b3ca752261b0f))
|
||||||
* fix app startup delay due to using file watcher ([#1436](https://github.com/extrawurst/gitui/issues/1436))
|
* fix app startup delay due to using file watcher ([#1436](https://github.com/extrawurst/gitui/issues/1436))
|
||||||
|
* make git tree file fetch async ([#734](https://github.com/extrawurst/gitui/issues/734))
|
||||||
|
|
||||||
## [0.22.0] - 2022-11-19
|
## [0.22.0] - 2022-11-19
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ mod revlog;
|
||||||
mod status;
|
mod status;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
mod tags;
|
mod tags;
|
||||||
|
mod treefiles;
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
blame::{AsyncBlame, BlameParams},
|
blame::{AsyncBlame, BlameParams},
|
||||||
|
|
@ -61,6 +62,7 @@ pub use crate::{
|
||||||
status::{StatusItem, StatusItemType},
|
status::{StatusItem, StatusItemType},
|
||||||
},
|
},
|
||||||
tags::AsyncTags,
|
tags::AsyncTags,
|
||||||
|
treefiles::AsyncTreeFilesJob,
|
||||||
};
|
};
|
||||||
pub use git2::message_prettify;
|
pub use git2::message_prettify;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -99,6 +101,8 @@ pub enum AsyncGitNotification {
|
||||||
Fetch,
|
Fetch,
|
||||||
///
|
///
|
||||||
Branches,
|
Branches,
|
||||||
|
///
|
||||||
|
TreeFiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait
|
/// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait
|
||||||
|
|
|
||||||
73
asyncgit/src/treefiles.rs
Normal file
73
asyncgit/src/treefiles.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::{
|
||||||
|
asyncjob::{AsyncJob, RunParams},
|
||||||
|
error::Result,
|
||||||
|
sync::{tree_files, CommitId, RepoPath, TreeFile},
|
||||||
|
AsyncGitNotification,
|
||||||
|
};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
enum JobState {
|
||||||
|
Request { commit: CommitId, repo: RepoPath },
|
||||||
|
Response(Result<Vec<TreeFile>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct AsyncTreeFilesJob {
|
||||||
|
state: Arc<Mutex<Option<JobState>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
impl AsyncTreeFilesJob {
|
||||||
|
///
|
||||||
|
pub fn new(repo: RepoPath, commit: CommitId) -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(Some(JobState::Request {
|
||||||
|
repo,
|
||||||
|
commit,
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn result(&self) -> Option<Result<Vec<TreeFile>>> {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
if let Some(state) = state.take() {
|
||||||
|
return match state {
|
||||||
|
JobState::Request { .. } => None,
|
||||||
|
JobState::Response(result) => Some(result),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncJob for AsyncTreeFilesJob {
|
||||||
|
type Notification = AsyncGitNotification;
|
||||||
|
type Progress = ();
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
_params: RunParams<Self::Notification, Self::Progress>,
|
||||||
|
) -> Result<Self::Notification> {
|
||||||
|
if let Ok(mut state) = self.state.lock() {
|
||||||
|
*state = state.take().map(|state| match state {
|
||||||
|
JobState::Request { commit, repo } => {
|
||||||
|
let files = tree_files(&repo, commit);
|
||||||
|
|
||||||
|
std::thread::sleep(
|
||||||
|
std::time::Duration::from_secs(2),
|
||||||
|
);
|
||||||
|
JobState::Response(files)
|
||||||
|
}
|
||||||
|
JobState::Response(result) => {
|
||||||
|
JobState::Response(result)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AsyncGitNotification::TreeFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -162,6 +162,7 @@ impl App {
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
&queue,
|
&queue,
|
||||||
sender_app,
|
sender_app,
|
||||||
|
sender.clone(),
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
),
|
),
|
||||||
|
|
@ -306,6 +307,7 @@ impl App {
|
||||||
files_tab: FilesTab::new(
|
files_tab: FilesTab::new(
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
sender_app,
|
sender_app,
|
||||||
|
sender.clone(),
|
||||||
&queue,
|
&queue,
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
|
|
@ -508,8 +510,8 @@ impl App {
|
||||||
self.select_branch_popup.update_git(ev)?;
|
self.select_branch_popup.update_git(ev)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.files_tab.update_async(ev);
|
self.files_tab.update_async(ev)?;
|
||||||
self.revision_files_popup.update(ev);
|
self.revision_files_popup.update(ev)?;
|
||||||
self.tags_popup.update(ev);
|
self.tags_popup.update(ev);
|
||||||
|
|
||||||
//TODO: better system for this
|
//TODO: better system for this
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,17 @@ use crate::{
|
||||||
AsyncAppNotification, AsyncNotification,
|
AsyncAppNotification, AsyncNotification,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::sync::{
|
use asyncgit::{
|
||||||
self, get_commit_info, CommitId, CommitInfo, RepoPathRef,
|
asyncjob::AsyncSingleJob,
|
||||||
TreeFile,
|
sync::{
|
||||||
|
get_commit_info, CommitId, CommitInfo, RepoPathRef, TreeFile,
|
||||||
|
},
|
||||||
|
AsyncGitNotification, AsyncTreeFilesJob,
|
||||||
};
|
};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use filetreelist::{FileTree, FileTreeItem};
|
use filetreelist::{FileTree, FileTreeItem};
|
||||||
use std::fmt::Write;
|
use std::{borrow::Cow, fmt::Write};
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeSet,
|
collections::BTreeSet,
|
||||||
convert::From,
|
convert::From,
|
||||||
|
|
@ -44,7 +47,8 @@ pub struct RevisionFilesComponent {
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
//TODO: store TreeFiles in `tree`
|
//TODO: store TreeFiles in `tree`
|
||||||
files: Vec<TreeFile>,
|
files: Option<Vec<TreeFile>>,
|
||||||
|
async_treefiles: AsyncSingleJob<AsyncTreeFilesJob>,
|
||||||
current_file: SyntaxTextComponent,
|
current_file: SyntaxTextComponent,
|
||||||
tree: FileTree,
|
tree: FileTree,
|
||||||
scroll: VerticalScroll,
|
scroll: VerticalScroll,
|
||||||
|
|
@ -60,6 +64,7 @@ impl RevisionFilesComponent {
|
||||||
repo: RepoPathRef,
|
repo: RepoPathRef,
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
sender: &Sender<AsyncAppNotification>,
|
sender: &Sender<AsyncAppNotification>,
|
||||||
|
sender_git: Sender<AsyncGitNotification>,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
key_config: SharedKeyConfig,
|
key_config: SharedKeyConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -73,8 +78,9 @@ impl RevisionFilesComponent {
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
),
|
),
|
||||||
|
async_treefiles: AsyncSingleJob::new(sender_git),
|
||||||
theme,
|
theme,
|
||||||
files: Vec::new(),
|
files: None,
|
||||||
revision: None,
|
revision: None,
|
||||||
focus: Focus::Tree,
|
focus: Focus::Tree,
|
||||||
key_config,
|
key_config,
|
||||||
|
|
@ -89,13 +95,15 @@ impl RevisionFilesComponent {
|
||||||
|
|
||||||
let same_id =
|
let same_id =
|
||||||
self.revision.as_ref().map_or(false, |c| c.id == commit);
|
self.revision.as_ref().map_or(false, |c| c.id == commit);
|
||||||
|
|
||||||
if !same_id {
|
if !same_id {
|
||||||
self.files =
|
self.files = None;
|
||||||
sync::tree_files(&self.repo.borrow(), commit)?;
|
|
||||||
let filenames: Vec<&Path> =
|
self.async_treefiles.spawn(AsyncTreeFilesJob::new(
|
||||||
self.files.iter().map(|f| f.path.as_path()).collect();
|
self.repo.borrow().clone(),
|
||||||
self.tree = FileTree::new(&filenames, &BTreeSet::new())?;
|
commit,
|
||||||
self.tree.collapse_but_root();
|
));
|
||||||
|
|
||||||
self.revision =
|
self.revision =
|
||||||
Some(get_commit_info(&self.repo.borrow(), &commit)?);
|
Some(get_commit_info(&self.repo.borrow(), &commit)?);
|
||||||
}
|
}
|
||||||
|
|
@ -114,13 +122,35 @@ impl RevisionFilesComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn update(&mut self, ev: AsyncNotification) {
|
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
|
||||||
self.current_file.update(ev);
|
self.current_file.update(ev);
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
ev,
|
||||||
|
AsyncNotification::Git(AsyncGitNotification::TreeFiles)
|
||||||
|
) {
|
||||||
|
if let Some(last) = self.async_treefiles.take_last() {
|
||||||
|
if let Some(Ok(last)) = last.result() {
|
||||||
|
let filenames: Vec<&Path> = last
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.path.as_path())
|
||||||
|
.collect();
|
||||||
|
self.tree =
|
||||||
|
FileTree::new(&filenames, &BTreeSet::new())?;
|
||||||
|
self.tree.collapse_but_root();
|
||||||
|
|
||||||
|
self.files = Some(last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn any_work_pending(&self) -> bool {
|
pub fn any_work_pending(&self) -> bool {
|
||||||
self.current_file.any_work_pending()
|
self.current_file.any_work_pending()
|
||||||
|
|| self.async_treefiles.is_pending()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tree_item_to_span<'a>(
|
fn tree_item_to_span<'a>(
|
||||||
|
|
@ -190,8 +220,9 @@ impl RevisionFilesComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_finder(&self) {
|
fn open_finder(&self) {
|
||||||
self.queue
|
self.queue.push(InternalEvent::OpenFileFinder(
|
||||||
.push(InternalEvent::OpenFileFinder(self.files.clone()));
|
self.files.clone().unwrap_or_default(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_file(&mut self, file: &Option<PathBuf>) {
|
pub fn find_file(&mut self, file: &Option<PathBuf>) {
|
||||||
|
|
@ -221,9 +252,10 @@ impl RevisionFilesComponent {
|
||||||
fn selection_changed(&mut self) {
|
fn selection_changed(&mut self) {
|
||||||
//TODO: retrieve TreeFile from tree datastructure
|
//TODO: retrieve TreeFile from tree datastructure
|
||||||
if let Some(file) = self.selected_file_path_with_prefix() {
|
if let Some(file) = self.selected_file_path_with_prefix() {
|
||||||
|
if let Some(files) = &self.files {
|
||||||
let path = Path::new(&file);
|
let path = Path::new(&file);
|
||||||
if let Some(item) =
|
if let Some(item) =
|
||||||
self.files.iter().find(|f| f.path == path)
|
files.iter().find(|f| f.path == path)
|
||||||
{
|
{
|
||||||
if let Ok(path) = path.strip_prefix("./") {
|
if let Ok(path) = path.strip_prefix("./") {
|
||||||
return self.current_file.load_file(
|
return self.current_file.load_file(
|
||||||
|
|
@ -235,6 +267,7 @@ impl RevisionFilesComponent {
|
||||||
self.current_file.clear();
|
self.current_file.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect) {
|
fn draw_tree<B: Backend>(&self, f: &mut Frame<B>, area: Rect) {
|
||||||
let tree_height = usize::from(area.height.saturating_sub(2));
|
let tree_height = usize::from(area.height.saturating_sub(2));
|
||||||
|
|
@ -268,18 +301,30 @@ impl RevisionFilesComponent {
|
||||||
let is_tree_focused = matches!(self.focus, Focus::Tree);
|
let is_tree_focused = matches!(self.focus, Focus::Tree);
|
||||||
|
|
||||||
let title = self.title_within(tree_width);
|
let title = self.title_within(tree_width);
|
||||||
ui::draw_list_block(
|
let block = Block::default()
|
||||||
f,
|
|
||||||
area,
|
|
||||||
Block::default()
|
|
||||||
.title(Span::styled(
|
.title(Span::styled(
|
||||||
title,
|
title,
|
||||||
self.theme.title(is_tree_focused),
|
self.theme.title(is_tree_focused),
|
||||||
))
|
))
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(self.theme.block(is_tree_focused)),
|
.border_style(self.theme.block(is_tree_focused));
|
||||||
items,
|
|
||||||
|
if self.files.is_some() {
|
||||||
|
ui::draw_list_block(f, area, block, items);
|
||||||
|
} else {
|
||||||
|
ui::draw_list_block(
|
||||||
|
f,
|
||||||
|
area,
|
||||||
|
block,
|
||||||
|
vec![Span::styled(
|
||||||
|
Cow::from(strings::loading_text(
|
||||||
|
&self.key_config,
|
||||||
|
)),
|
||||||
|
self.theme.text(false, false),
|
||||||
|
)]
|
||||||
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if is_tree_focused {
|
if is_tree_focused {
|
||||||
self.scroll.draw(f, area, &self.theme);
|
self.scroll.draw(f, area, &self.theme);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ use crate::{
|
||||||
AsyncAppNotification, AsyncNotification,
|
AsyncAppNotification, AsyncNotification,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::sync::{CommitId, RepoPathRef};
|
use asyncgit::{
|
||||||
|
sync::{CommitId, RepoPathRef},
|
||||||
|
AsyncGitNotification,
|
||||||
|
};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame};
|
use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame};
|
||||||
|
|
@ -47,6 +50,7 @@ impl RevisionFilesPopup {
|
||||||
repo: RepoPathRef,
|
repo: RepoPathRef,
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
sender: &Sender<AsyncAppNotification>,
|
sender: &Sender<AsyncAppNotification>,
|
||||||
|
sender_git: Sender<AsyncGitNotification>,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
key_config: SharedKeyConfig,
|
key_config: SharedKeyConfig,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -55,6 +59,7 @@ impl RevisionFilesPopup {
|
||||||
repo,
|
repo,
|
||||||
queue,
|
queue,
|
||||||
sender,
|
sender,
|
||||||
|
sender_git,
|
||||||
theme,
|
theme,
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
),
|
),
|
||||||
|
|
@ -75,8 +80,8 @@ impl RevisionFilesPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn update(&mut self, ev: AsyncNotification) {
|
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
|
||||||
self.files.update(ev);
|
self.files.update(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ use crate::{
|
||||||
AsyncAppNotification, AsyncNotification,
|
AsyncAppNotification, AsyncNotification,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::sync::{self, RepoPathRef};
|
use asyncgit::{
|
||||||
|
sync::{self, RepoPathRef},
|
||||||
|
AsyncGitNotification,
|
||||||
|
};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
|
||||||
pub struct FilesTab {
|
pub struct FilesTab {
|
||||||
|
|
@ -25,6 +28,7 @@ impl FilesTab {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
repo: RepoPathRef,
|
repo: RepoPathRef,
|
||||||
sender: &Sender<AsyncAppNotification>,
|
sender: &Sender<AsyncAppNotification>,
|
||||||
|
sender_git: Sender<AsyncGitNotification>,
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
key_config: SharedKeyConfig,
|
key_config: SharedKeyConfig,
|
||||||
|
|
@ -35,6 +39,7 @@ impl FilesTab {
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
queue,
|
queue,
|
||||||
sender,
|
sender,
|
||||||
|
sender_git,
|
||||||
theme,
|
theme,
|
||||||
key_config,
|
key_config,
|
||||||
),
|
),
|
||||||
|
|
@ -59,10 +64,15 @@ impl FilesTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn update_async(&mut self, ev: AsyncNotification) {
|
pub fn update_async(
|
||||||
|
&mut self,
|
||||||
|
ev: AsyncNotification,
|
||||||
|
) -> Result<()> {
|
||||||
if self.is_visible() {
|
if self.is_visible() {
|
||||||
self.files.update(ev);
|
self.files.update(ev)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue