mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28: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]
|
## [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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 `./`
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
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(
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
|
|
|
||||||
|
|
@ -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()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue