Async tag fetching (#195)

This commit is contained in:
Stephan Dilly 2020-07-12 13:21:34 +02:00 committed by GitHub
parent a84ae0950c
commit 315cf615e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 171 additions and 26 deletions

View file

@ -6,11 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- async fetching tags to improve reactivity in giant repos ([#170](https://github.com/extrawurst/gitui/issues/170))
### Fixed ### Fixed
- removed unmaintained dependency `spin` ([#172](https://github.com/extrawurst/gitui/issues/172)) - removed unmaintained dependency `spin` ([#172](https://github.com/extrawurst/gitui/issues/172))
- opening relative paths in external editor may fail in subpaths ([#184](https://github.com/extrawurst/gitui/issues/184)) - opening relative paths in external editor may fail in subpaths ([#184](https://github.com/extrawurst/gitui/issues/184))
- crashes in revlog with utf8 commit messages ([#188](https://github.com/extrawurst/gitui/issues/188)) - crashes in revlog with utf8 commit messages ([#188](https://github.com/extrawurst/gitui/issues/188))
- `add_to_ignore` failed on files without a newline at EOF ([#191](https://github.com/extrawurst/gitui/issues/191)) - `add_to_ignore` failed on files without a newline at EOF ([#191](https://github.com/extrawurst/gitui/issues/191))
- new tags were not picked up in revlog view ([#190](https://github.com/extrawurst/gitui/issues/190))
## [0.8.1] - 2020-07-07 ## [0.8.1] - 2020-07-07

View file

@ -122,11 +122,13 @@ impl AsyncDiff {
arc_pending.fetch_sub(1, Ordering::Relaxed); arc_pending.fetch_sub(1, Ordering::Relaxed);
if notify { sender
sender .send(if notify {
.send(AsyncNotification::Diff) AsyncNotification::Diff
.expect("error sending diff"); } else {
} AsyncNotification::FinishUnchanged
})
.expect("error sending diff");
}); });
Ok(None) Ok(None)

View file

@ -13,6 +13,7 @@ mod error;
mod revlog; mod revlog;
mod status; mod status;
pub mod sync; pub mod sync;
mod tags;
pub use crate::{ pub use crate::{
commit_files::AsyncCommitFiles, commit_files::AsyncCommitFiles,
@ -23,6 +24,7 @@ pub use crate::{
diff::{DiffLine, DiffLineType, FileDiff}, diff::{DiffLine, DiffLineType, FileDiff},
status::{StatusItem, StatusItemType}, status::{StatusItem, StatusItemType},
}, },
tags::AsyncTags,
}; };
use std::{ use std::{
collections::hash_map::DefaultHasher, collections::hash_map::DefaultHasher,
@ -32,6 +34,8 @@ use std::{
/// this type is used to communicate events back through the channel /// this type is used to communicate events back through the channel
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum AsyncNotification { pub enum AsyncNotification {
/// this indicates that no new state was fetched but that a async process finished
FinishUnchanged,
/// ///
Status, Status,
/// ///
@ -40,6 +44,8 @@ pub enum AsyncNotification {
Log, Log,
/// ///
CommitFiles, CommitFiles,
///
Tags,
} }
/// current working director `./` /// current working director `./`

View file

@ -128,6 +128,7 @@ impl AsyncLog {
) )
.expect("failed to fetch"); .expect("failed to fetch");
arc_pending.store(false, Ordering::Relaxed); arc_pending.store(false, Ordering::Relaxed);
Self::notify(&sender); Self::notify(&sender);
}); });

View file

@ -4,7 +4,7 @@ use git2::{Commit, Error, Oid};
use scopetime::scope_time; use scopetime::scope_time;
/// identifies a single commit /// identifies a single commit
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct CommitId(Oid); pub struct CommitId(Oid);
impl CommitId { impl CommitId {

View file

@ -17,6 +17,7 @@ mod tags;
pub mod utils; pub mod utils;
pub(crate) use branch::get_branch_name; pub(crate) use branch::get_branch_name;
pub use commit::{amend, commit}; pub use commit::{amend, commit};
pub use commit_details::{get_commit_details, CommitDetails}; pub use commit_details::{get_commit_details, CommitDetails};
pub use commit_files::get_commit_files; pub use commit_files::get_commit_files;

View file

@ -1,10 +1,10 @@
use super::{utils::repo, CommitId}; use super::{utils::repo, CommitId};
use crate::error::Result; use crate::error::Result;
use scopetime::scope_time; use scopetime::scope_time;
use std::collections::HashMap; use std::collections::BTreeMap;
/// hashmap of tag target commit hash to tag names /// hashmap of tag target commit hash to tag names
pub type Tags = HashMap<CommitId, Vec<String>>; pub type Tags = BTreeMap<CommitId, Vec<String>>;
/// returns `Tags` type filled with all tags found in repo /// returns `Tags` type filled with all tags found in repo
pub fn get_tags(repo_path: &str) -> Result<Tags> { pub fn get_tags(repo_path: &str) -> Result<Tags> {

127
asyncgit/src/tags.rs Normal file
View file

@ -0,0 +1,127 @@
use crate::{
error::Result,
hash,
sync::{self},
AsyncNotification, CWD,
};
use crossbeam_channel::Sender;
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
time::{Duration, Instant},
};
use sync::Tags;
///
#[derive(Default, Clone)]
struct TagsResult {
hash: u64,
tags: Tags,
}
///
pub struct AsyncTags {
last: Arc<Mutex<Option<(Instant, TagsResult)>>>,
sender: Sender<AsyncNotification>,
pending: Arc<AtomicUsize>,
}
impl AsyncTags {
///
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
Self {
last: Arc::new(Mutex::new(None)),
sender: sender.clone(),
pending: Arc::new(AtomicUsize::new(0)),
}
}
/// last fetched result
pub fn last(&mut self) -> Result<Option<Tags>> {
let last = self.last.lock()?;
Ok(last.clone().map(|last| last.1.tags))
}
///
pub fn is_pending(&self) -> bool {
self.pending.load(Ordering::Relaxed) > 0
}
fn is_outdated(&self, dur: Duration) -> Result<bool> {
let last = self.last.lock()?;
Ok(last
.as_ref()
.map(|(last_time, _)| last_time.elapsed() > dur)
.unwrap_or(true))
}
///
pub fn request(
&mut self,
dur: Duration,
force: bool,
) -> Result<()> {
log::trace!("request");
if !force && (self.is_pending() || !self.is_outdated(dur)?) {
return Ok(());
}
let arc_last = Arc::clone(&self.last);
let sender = self.sender.clone();
let arc_pending = Arc::clone(&self.pending);
rayon_core::spawn(move || {
arc_pending.fetch_add(1, Ordering::Relaxed);
let notify = AsyncTags::getter(arc_last)
.expect("error getting tags");
arc_pending.fetch_sub(1, Ordering::Relaxed);
sender
.send(if notify {
AsyncNotification::Tags
} else {
AsyncNotification::FinishUnchanged
})
.expect("error sending notify");
});
Ok(())
}
fn getter(
arc_last: Arc<Mutex<Option<(Instant, TagsResult)>>>,
) -> Result<bool> {
let tags = sync::get_tags(CWD)?;
let hash = hash(&tags);
if Self::last_hash(arc_last.clone())
.map(|last| last == hash)
.unwrap_or_default()
{
return Ok(false);
}
{
let mut last = arc_last.lock()?;
let now = Instant::now();
*last = Some((now, TagsResult { tags, hash }));
}
Ok(true)
}
fn last_hash(
last: Arc<Mutex<Option<(Instant, TagsResult)>>>,
) -> Option<u64> {
last.lock()
.ok()
.and_then(|last| last.as_ref().map(|(_, last)| last.hash))
}
}

View file

@ -40,7 +40,7 @@ impl DetailsComponent {
pub fn set_commit( pub fn set_commit(
&mut self, &mut self,
id: Option<CommitId>, id: Option<CommitId>,
tags: &Tags, tags: Option<&Tags>,
) -> Result<()> { ) -> Result<()> {
self.tags.clear(); self.tags.clear();
@ -51,8 +51,10 @@ impl DetailsComponent {
}; };
if let Some(id) = id { if let Some(id) = id {
if let Some(tags) = tags.get(&id) { if let Some(tags) = tags {
self.tags.extend(tags.clone()); if let Some(tags) = tags.get(&id) {
self.tags.extend(tags.clone());
}
} }
} }

View file

@ -68,7 +68,7 @@ impl CommitDetailsComponent {
pub fn set_commit( pub fn set_commit(
&mut self, &mut self,
id: Option<CommitId>, id: Option<CommitId>,
tags: &Tags, tags: Option<&Tags>,
) -> Result<()> { ) -> Result<()> {
self.details.set_commit(id, tags)?; self.details.set_commit(id, tags)?;

View file

@ -92,14 +92,8 @@ impl CommitList {
self.tags.as_ref() self.tags.as_ref()
} }
///
pub fn has_tags(&self) -> bool {
self.tags.is_some()
}
/// ///
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.tags = None;
self.items.clear(); self.items.clear();
} }

View file

@ -9,8 +9,8 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use asyncgit::{ use asyncgit::{
sync::{CommitId, Tags}, sync::CommitId, AsyncDiff, AsyncNotification, DiffParams,
AsyncDiff, AsyncNotification, DiffParams, DiffType, DiffType,
}; };
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use crossterm::event::Event; use crossterm::event::Event;
@ -225,7 +225,7 @@ impl InspectCommitComponent {
} }
fn update(&mut self) -> Result<()> { fn update(&mut self) -> Result<()> {
self.details.set_commit(self.commit_id, &Tags::new())?; self.details.set_commit(self.commit_id, None)?;
self.update_diff()?; self.update_diff()?;
Ok(()) Ok(())

View file

@ -13,10 +13,11 @@ use anyhow::Result;
use asyncgit::{ use asyncgit::{
cached, cached,
sync::{self, CommitId}, sync::{self, CommitId},
AsyncLog, AsyncNotification, FetchStatus, CWD, AsyncLog, AsyncNotification, AsyncTags, FetchStatus, CWD,
}; };
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use crossterm::event::Event; use crossterm::event::Event;
use std::time::Duration;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -30,6 +31,7 @@ pub struct Revlog {
commit_details: CommitDetailsComponent, commit_details: CommitDetailsComponent,
list: CommitList, list: CommitList,
git_log: AsyncLog, git_log: AsyncLog,
git_tags: AsyncTags,
queue: Queue, queue: Queue,
visible: bool, visible: bool,
branch_name: cached::BranchName, branch_name: cached::BranchName,
@ -51,6 +53,7 @@ impl Revlog {
), ),
list: CommitList::new(strings::LOG_TITLE, theme), list: CommitList::new(strings::LOG_TITLE, theme),
git_log: AsyncLog::new(sender), git_log: AsyncLog::new(sender),
git_tags: AsyncTags::new(sender),
visible: false, visible: false,
branch_name: cached::BranchName::new(CWD), branch_name: cached::BranchName::new(CWD),
} }
@ -59,6 +62,7 @@ impl Revlog {
/// ///
pub fn any_work_pending(&self) -> bool { pub fn any_work_pending(&self) -> bool {
self.git_log.is_pending() self.git_log.is_pending()
|| self.git_tags.is_pending()
|| self.commit_details.any_work_pending() || self.commit_details.any_work_pending()
} }
@ -78,9 +82,7 @@ impl Revlog {
self.fetch_commits()?; self.fetch_commits()?;
} }
if !self.list.has_tags() || log_changed { self.git_tags.request(Duration::from_secs(3), false)?;
self.list.set_tags(sync::get_tags(CWD)?);
}
self.list.set_branch( self.list.set_branch(
self.branch_name.lookup().map(Some).unwrap_or(None), self.branch_name.lookup().map(Some).unwrap_or(None),
@ -89,7 +91,7 @@ impl Revlog {
if self.commit_details.is_visible() { if self.commit_details.is_visible() {
self.commit_details.set_commit( self.commit_details.set_commit(
self.selected_commit(), self.selected_commit(),
self.list.tags().expect("tags"), self.list.tags(),
)?; )?;
} }
} }
@ -106,6 +108,12 @@ impl Revlog {
match ev { match ev {
AsyncNotification::CommitFiles AsyncNotification::CommitFiles
| AsyncNotification::Log => self.update()?, | AsyncNotification::Log => self.update()?,
AsyncNotification::Tags => {
if let Some(tags) = self.git_tags.last()? {
self.list.set_tags(tags);
self.update()?;
}
}
_ => (), _ => (),
} }
} }