mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
Async tag fetching (#195)
This commit is contained in:
parent
a84ae0950c
commit
315cf615e0
13 changed files with 171 additions and 26 deletions
|
|
@ -6,11 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- async fetching tags to improve reactivity in giant repos ([#170](https://github.com/extrawurst/gitui/issues/170))
|
||||
|
||||
### Fixed
|
||||
- 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))
|
||||
- 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))
|
||||
- new tags were not picked up in revlog view ([#190](https://github.com/extrawurst/gitui/issues/190))
|
||||
|
||||
## [0.8.1] - 2020-07-07
|
||||
|
||||
|
|
|
|||
|
|
@ -122,11 +122,13 @@ impl AsyncDiff {
|
|||
|
||||
arc_pending.fetch_sub(1, Ordering::Relaxed);
|
||||
|
||||
if notify {
|
||||
sender
|
||||
.send(AsyncNotification::Diff)
|
||||
.expect("error sending diff");
|
||||
}
|
||||
sender
|
||||
.send(if notify {
|
||||
AsyncNotification::Diff
|
||||
} else {
|
||||
AsyncNotification::FinishUnchanged
|
||||
})
|
||||
.expect("error sending diff");
|
||||
});
|
||||
|
||||
Ok(None)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ mod error;
|
|||
mod revlog;
|
||||
mod status;
|
||||
pub mod sync;
|
||||
mod tags;
|
||||
|
||||
pub use crate::{
|
||||
commit_files::AsyncCommitFiles,
|
||||
|
|
@ -23,6 +24,7 @@ pub use crate::{
|
|||
diff::{DiffLine, DiffLineType, FileDiff},
|
||||
status::{StatusItem, StatusItemType},
|
||||
},
|
||||
tags::AsyncTags,
|
||||
};
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
|
|
@ -32,6 +34,8 @@ use std::{
|
|||
/// this type is used to communicate events back through the channel
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum AsyncNotification {
|
||||
/// this indicates that no new state was fetched but that a async process finished
|
||||
FinishUnchanged,
|
||||
///
|
||||
Status,
|
||||
///
|
||||
|
|
@ -40,6 +44,8 @@ pub enum AsyncNotification {
|
|||
Log,
|
||||
///
|
||||
CommitFiles,
|
||||
///
|
||||
Tags,
|
||||
}
|
||||
|
||||
/// current working director `./`
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ impl AsyncLog {
|
|||
)
|
||||
.expect("failed to fetch");
|
||||
arc_pending.store(false, Ordering::Relaxed);
|
||||
|
||||
Self::notify(&sender);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use git2::{Commit, Error, Oid};
|
|||
use scopetime::scope_time;
|
||||
|
||||
/// identifies a single commit
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct CommitId(Oid);
|
||||
|
||||
impl CommitId {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ mod tags;
|
|||
pub mod utils;
|
||||
|
||||
pub(crate) use branch::get_branch_name;
|
||||
|
||||
pub use commit::{amend, commit};
|
||||
pub use commit_details::{get_commit_details, CommitDetails};
|
||||
pub use commit_files::get_commit_files;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use super::{utils::repo, CommitId};
|
||||
use crate::error::Result;
|
||||
use scopetime::scope_time;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// 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
|
||||
pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
||||
|
|
|
|||
127
asyncgit/src/tags.rs
Normal file
127
asyncgit/src/tags.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ impl DetailsComponent {
|
|||
pub fn set_commit(
|
||||
&mut self,
|
||||
id: Option<CommitId>,
|
||||
tags: &Tags,
|
||||
tags: Option<&Tags>,
|
||||
) -> Result<()> {
|
||||
self.tags.clear();
|
||||
|
||||
|
|
@ -51,8 +51,10 @@ impl DetailsComponent {
|
|||
};
|
||||
|
||||
if let Some(id) = id {
|
||||
if let Some(tags) = tags.get(&id) {
|
||||
self.tags.extend(tags.clone());
|
||||
if let Some(tags) = tags {
|
||||
if let Some(tags) = tags.get(&id) {
|
||||
self.tags.extend(tags.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl CommitDetailsComponent {
|
|||
pub fn set_commit(
|
||||
&mut self,
|
||||
id: Option<CommitId>,
|
||||
tags: &Tags,
|
||||
tags: Option<&Tags>,
|
||||
) -> Result<()> {
|
||||
self.details.set_commit(id, tags)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -92,14 +92,8 @@ impl CommitList {
|
|||
self.tags.as_ref()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn has_tags(&self) -> bool {
|
||||
self.tags.is_some()
|
||||
}
|
||||
|
||||
///
|
||||
pub fn clear(&mut self) {
|
||||
self.tags = None;
|
||||
self.items.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use crate::{
|
|||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{CommitId, Tags},
|
||||
AsyncDiff, AsyncNotification, DiffParams, DiffType,
|
||||
sync::CommitId, AsyncDiff, AsyncNotification, DiffParams,
|
||||
DiffType,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
|
|
@ -225,7 +225,7 @@ impl InspectCommitComponent {
|
|||
}
|
||||
|
||||
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()?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ use anyhow::Result;
|
|||
use asyncgit::{
|
||||
cached,
|
||||
sync::{self, CommitId},
|
||||
AsyncLog, AsyncNotification, FetchStatus, CWD,
|
||||
AsyncLog, AsyncNotification, AsyncTags, FetchStatus, CWD,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use std::time::Duration;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
|
|
@ -30,6 +31,7 @@ pub struct Revlog {
|
|||
commit_details: CommitDetailsComponent,
|
||||
list: CommitList,
|
||||
git_log: AsyncLog,
|
||||
git_tags: AsyncTags,
|
||||
queue: Queue,
|
||||
visible: bool,
|
||||
branch_name: cached::BranchName,
|
||||
|
|
@ -51,6 +53,7 @@ impl Revlog {
|
|||
),
|
||||
list: CommitList::new(strings::LOG_TITLE, theme),
|
||||
git_log: AsyncLog::new(sender),
|
||||
git_tags: AsyncTags::new(sender),
|
||||
visible: false,
|
||||
branch_name: cached::BranchName::new(CWD),
|
||||
}
|
||||
|
|
@ -59,6 +62,7 @@ impl Revlog {
|
|||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.git_log.is_pending()
|
||||
|| self.git_tags.is_pending()
|
||||
|| self.commit_details.any_work_pending()
|
||||
}
|
||||
|
||||
|
|
@ -78,9 +82,7 @@ impl Revlog {
|
|||
self.fetch_commits()?;
|
||||
}
|
||||
|
||||
if !self.list.has_tags() || log_changed {
|
||||
self.list.set_tags(sync::get_tags(CWD)?);
|
||||
}
|
||||
self.git_tags.request(Duration::from_secs(3), false)?;
|
||||
|
||||
self.list.set_branch(
|
||||
self.branch_name.lookup().map(Some).unwrap_or(None),
|
||||
|
|
@ -89,7 +91,7 @@ impl Revlog {
|
|||
if self.commit_details.is_visible() {
|
||||
self.commit_details.set_commit(
|
||||
self.selected_commit(),
|
||||
self.list.tags().expect("tags"),
|
||||
self.list.tags(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +108,12 @@ impl Revlog {
|
|||
match ev {
|
||||
AsyncNotification::CommitFiles
|
||||
| AsyncNotification::Log => self.update()?,
|
||||
AsyncNotification::Tags => {
|
||||
if let Some(tags) = self.git_tags.last()? {
|
||||
self.list.set_tags(tags);
|
||||
self.update()?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue