make git status fetching async

This commit is contained in:
Stephan Dilly 2020-03-23 11:42:50 +01:00
parent d7001a88a3
commit 4fdf09fc56
10 changed files with 176 additions and 47 deletions

View File

@ -37,7 +37,7 @@ gitui
* [x] inspect diffs
* [x] commit
* [x] [input polling in thread](assets/perf_compare.jpg)
* [ ] put libgit calls in threadpool
* [x] async git API for fluid control
* [ ] show content of new unstaged files
* [ ] discard untracked files (remove)
* [ ] (un)staging selected hunks

View File

@ -1,8 +1,8 @@
use crate::{sync, Diff};
use crate::{hash, sync, AsyncNotification, Diff};
use crossbeam_channel::Sender;
use log::trace;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
hash::Hash,
sync::{Arc, Mutex},
};
@ -13,12 +13,12 @@ struct Request<R, A>(R, Option<A>);
pub struct AsyncDiff {
current: Arc<Mutex<Request<u64, Diff>>>,
sender: Sender<()>,
sender: Sender<AsyncNotification>,
}
impl AsyncDiff {
///
pub fn new(sender: Sender<()>) -> Self {
pub fn new(sender: Sender<AsyncNotification>) -> Self {
Self {
current: Arc::new(Mutex::new(Request(0, None))),
sender,
@ -31,11 +31,11 @@ impl AsyncDiff {
file_path: String,
stage: bool,
) -> Option<Diff> {
trace!("request");
let request = DiffRequest(file_path.clone(), stage);
let mut hasher = DefaultHasher::new();
request.hash(&mut hasher);
let hash = hasher.finish();
let hash = hash(&request);
{
let mut current = self.current.lock().unwrap();
@ -62,7 +62,7 @@ impl AsyncDiff {
}
if notify {
sender.send(()).unwrap();
sender.send(AsyncNotification::Diff).unwrap();
}
});

View File

@ -1,10 +1,36 @@
mod diff;
mod status;
pub mod sync;
pub use crate::{
diff::AsyncDiff,
status::AsyncStatus,
sync::{
diff::{Diff, DiffLine, DiffLineType},
status::{StatusItem, StatusItemType, StatusType},
status::{StatusItem, StatusItemType},
},
};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
time::{SystemTime, UNIX_EPOCH},
};
#[derive(Copy, Clone, Debug)]
pub enum AsyncNotification {
Status,
Diff,
}
pub fn hash<T: Hash>(v: &T) -> u64 {
let mut hasher = DefaultHasher::new();
v.hash(&mut hasher);
hasher.finish()
}
pub fn current_tick() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}

93
asyncgit/src/status.rs Normal file
View File

@ -0,0 +1,93 @@
use crate::{hash, sync, AsyncNotification, StatusItem};
use crossbeam_channel::Sender;
use log::trace;
use std::{
hash::Hash,
sync::{Arc, Mutex},
};
use sync::status::StatusType;
#[derive(Default, Hash, Clone)]
pub struct Status {
pub work_dir: Vec<StatusItem>,
pub stage: Vec<StatusItem>,
}
struct Request<R, A>(R, Option<A>);
///
pub struct AsyncStatus {
current: Arc<Mutex<Request<u64, Status>>>,
last: Arc<Mutex<Status>>,
sender: Sender<AsyncNotification>,
}
impl AsyncStatus {
///
pub fn new(sender: Sender<AsyncNotification>) -> Self {
Self {
current: Arc::new(Mutex::new(Request(0, None))),
last: Arc::new(Mutex::new(Status::default())),
sender,
}
}
///
pub fn last(&mut self) -> Status {
let last = self.last.lock().unwrap();
last.clone()
}
///
pub fn fetch(&mut self, request: u64) -> Option<Status> {
let hash_request = hash(&request);
trace!("request: {} [hash: {}]", request, hash_request);
{
let mut current = self.current.lock().unwrap();
if current.0 == hash_request {
return current.1.clone();
}
current.0 = hash_request;
current.1 = None;
}
let arc_current = Arc::clone(&self.current);
let arc_last = Arc::clone(&self.last);
let sender = self.sender.clone();
rayon_core::spawn(move || {
let res = Self::get_status();
trace!("status fetched: {}", hash(&res));
let mut notify = false;
{
let mut current = arc_current.lock().unwrap();
if current.0 == hash_request {
current.1 = Some(res.clone());
notify = true;
}
}
{
let mut last = arc_last.lock().unwrap();
*last = res;
}
if notify {
sender.send(AsyncNotification::Status).unwrap();
}
});
None
}
fn get_status() -> Status {
let work_dir =
sync::status::get_index(StatusType::WorkingDir);
let stage = sync::status::get_index(StatusType::Stage);
Status { stage, work_dir }
}
}

View File

@ -2,5 +2,4 @@ pub mod diff;
pub mod status;
pub mod utils;
pub use status::get_index;
pub use utils::{commit, index_reset, stage_add, stage_reset};

View File

@ -2,7 +2,7 @@ use crate::sync::utils;
use git2::{Status, StatusOptions, StatusShow};
use scopetime::scope_time;
#[derive(PartialEq, Copy, Clone)]
#[derive(Copy, Clone, Hash)]
pub enum StatusItemType {
New,
Modified,
@ -28,7 +28,7 @@ impl From<Status> for StatusItemType {
}
///
#[derive(Default, PartialEq, Clone)]
#[derive(Default, Clone, Hash)]
pub struct StatusItem {
pub path: String,
pub status: Option<StatusItemType>,

View File

@ -5,7 +5,9 @@ use crate::{
},
keys, strings,
};
use asyncgit::{sync, AsyncDiff, StatusType};
use asyncgit::{
current_tick, sync, AsyncDiff, AsyncNotification, AsyncStatus,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
use itertools::Itertools;
@ -43,12 +45,13 @@ pub struct App {
index_wd: IndexComponent,
diff: DiffComponent,
async_diff: AsyncDiff,
async_status: AsyncStatus,
}
// public interface
impl App {
///
pub fn new(sender: Sender<()>) -> Self {
pub fn new(sender: Sender<AsyncNotification>) -> Self {
Self {
focus: Focus::Status,
diff_target: DiffTarget::WorkingDir,
@ -56,16 +59,12 @@ impl App {
commit: CommitComponent::default(),
index_wd: IndexComponent::new(
strings::TITLE_STATUS,
StatusType::WorkingDir,
true,
),
index: IndexComponent::new(
strings::TITLE_INDEX,
StatusType::Stage,
false,
),
index: IndexComponent::new(strings::TITLE_INDEX, false),
diff: DiffComponent::default(),
async_diff: AsyncDiff::new(sender),
async_diff: AsyncDiff::new(sender.clone()),
async_status: AsyncStatus::new(sender),
}
}
@ -200,10 +199,27 @@ impl App {
///
pub fn update(&mut self) {
trace!("app::update");
trace!("update");
self.index.update();
self.index_wd.update();
self.update_diff();
self.async_status.fetch(current_tick());
}
///
pub fn update_git(&mut self, ev: AsyncNotification) {
trace!("update_git: {:?}", ev);
match ev {
AsyncNotification::Diff => self.update_diff(),
AsyncNotification::Status => self.update_status(),
}
}
///
pub fn update_status(&mut self) {
let status = self.async_status.last();
self.index.update(&status.stage);
self.index_wd.update(&status.work_dir);
self.update_diff();
}

View File

@ -1,6 +1,6 @@
use crate::components::{CommandInfo, Component};
use crate::ui;
use asyncgit::{sync, StatusItem, StatusItemType, StatusType};
use asyncgit::{hash, StatusItem, StatusItemType};
use crossterm::event::{Event, KeyCode};
use std::{borrow::Cow, cmp};
use tui::{
@ -15,7 +15,6 @@ use tui::{
pub struct IndexComponent {
title: String,
items: Vec<StatusItem>,
index_type: StatusType,
selection: Option<usize>,
focused: bool,
show_selection: bool,
@ -23,26 +22,21 @@ pub struct IndexComponent {
impl IndexComponent {
///
pub fn new(
title: &str,
index_type: StatusType,
focus: bool,
) -> Self {
pub fn new(title: &str, focus: bool) -> Self {
Self {
title: title.to_string(),
items: Vec::new(),
index_type,
selection: None,
focused: focus,
show_selection: focus,
}
}
///
pub fn update(&mut self) {
let new_status = sync::get_index(self.index_type.into());
if self.items != new_status {
self.items = new_status;
///
pub fn update(&mut self, list: &Vec<StatusItem>) {
if hash(&self.items) != hash(list) {
self.items = list.clone();
self.selection =
if self.items.len() > 0 { Some(0) } else { None };

View File

@ -33,19 +33,19 @@ fn main() -> Result<()> {
terminal.clear()?;
let (tx, rx) = unbounded();
let (tx_git, rx_git) = unbounded();
let mut app = App::new(tx);
let mut app = App::new(tx_git);
let receiver = poll::start_polling_thread();
let rx_input = poll::start_polling_thread();
app.update();
loop {
let mut events: Vec<QueueEvent> = Vec::new();
select! {
recv(receiver) -> inputs => events = inputs.unwrap(),
recv(rx) -> _ => events.push(QueueEvent::AsyncEvent),
recv(rx_input) -> inputs => events.append(&mut inputs.unwrap()),
recv(rx_git) -> ev => events.push(QueueEvent::GitEvent(ev.unwrap())),
}
{
@ -55,7 +55,7 @@ fn main() -> Result<()> {
match e {
QueueEvent::InputEvent(ev) => app.event(ev),
QueueEvent::Tick => app.update(),
QueueEvent::AsyncEvent => app.update_diff(),
QueueEvent::GitEvent(ev) => app.update_git(ev),
}
}

View File

@ -1,3 +1,4 @@
use asyncgit::AsyncNotification;
use crossbeam_channel::{unbounded, Receiver};
use crossterm::event::{self, Event};
use std::{
@ -6,10 +7,10 @@ use std::{
};
///
#[derive(Clone)]
#[derive(Clone, Copy)]
pub enum QueueEvent {
Tick,
AsyncEvent,
GitEvent(AsyncNotification),
InputEvent(Event),
}