mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-27 00:14:52 +03:00
make git status fetching async
This commit is contained in:
parent
d7001a88a3
commit
4fdf09fc56
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
93
asyncgit/src/status.rs
Normal 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 }
|
||||
}
|
||||
}
|
@ -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};
|
||||
|
@ -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>,
|
||||
|
40
src/app.rs
40
src/app.rs
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
|
12
src/main.rs
12
src/main.rs
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user