summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-02-03 13:31:31 +0100
committerCanop <cano.petrole@gmail.com>2020-02-03 13:31:31 +0100
commitf21ff81ae93329584250c690acb4360afb834a65 (patch)
tree82fc2231b3f0e810212213318d14f4b69a85e546
parentacc1486032aaad94fc852f3001e6b13bbeecfe11 (diff)
cached background computation of git status
-rw-r--r--CHANGELOG.md4
-rw-r--r--Cargo.lock2
-rw-r--r--README.md6
-rw-r--r--img/20200203-git.pngbin0 -> 37222 bytes
-rw-r--r--src/app.rs9
-rw-r--r--src/browser_states.rs43
-rw-r--r--src/git.rs26
-rw-r--r--src/git_status_computer.rs114
-rw-r--r--src/lib.rs1
-rw-r--r--src/task_sync.rs29
-rw-r--r--website/docs/index.md6
11 files changed, 153 insertions, 87 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f69eb9..a46942e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+### master
+`:show_git_file_info` compute git repo statistics and file statuses.
+Statistics are computed in background and cached.
+
<a name="v0.12.2"></a>
### v0.12.2 - 2020-01-29
- fix Ctrl-J being interpreted as Enter (fix #177)
diff --git a/Cargo.lock b/Cargo.lock
index 180cecb..4d7083e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,7 +93,7 @@ dependencies = [
[[package]]
name = "broot"
-version = "0.13.0"
+version = "0.12.2"
dependencies = [
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/README.md b/README.md
index 6aef3a9..f013e0c 100644
--- a/README.md
+++ b/README.md
@@ -116,5 +116,11 @@ And you keep all broot tools, like filtering or the ability to delete or open fi
Sizes are computed in the background, you don't have to wait for them when you navigate.
+### check git statuses:
+
+![size](img/20200203-git.png)
+
+Use `:gf` to display the statuses of files (what are the new ones, the modified ones, etc.), the current branch name and the change statistics.
+
## Further Reading
See **[Broot's web site](https://dystroy.org/broot)** for instructions regarding installation and usage.
diff --git a/img/20200203-git.png b/img/20200203-git.png
new file mode 100644
index 0000000..c8ebff3
--- /dev/null
+++ b/img/20200203-git.png
Binary files differ
diff --git a/src/app.rs b/src/app.rs
index 573c893..14565c5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -18,6 +18,7 @@ use {
errors::ProgramError,
external::Launchable,
file_sizes,
+ git_status_computer,
io::WriteCleanup,
screens::Screen,
skin::Skin,
@@ -95,6 +96,7 @@ impl App {
Ok(())
}
+
/// apply a command, and returns a command, which may be the same (modified or not)
/// or a new one.
/// This normally mutates self
@@ -122,7 +124,7 @@ impl App {
}
AppStateCmdResult::RefreshState { clear_cache } => {
if clear_cache {
- file_sizes::clear_cache();
+ clear_caches();
}
cmd = self.mut_state().refresh(screen, con);
}
@@ -262,3 +264,8 @@ impl Drop for App {
debug!("we left the screen");
}
}
+
+fn clear_caches() {
+ file_sizes::clear_cache();
+ git_status_computer::clear_cache();
+}
diff --git a/src/browser_states.rs b/src/browser_states.rs
index 05033a1..00414ec 100644
--- a/src/browser_states.rs
+++ b/src/browser_states.rs
@@ -400,7 +400,6 @@ impl AppState for BrowserState {
/// do some work, totally or partially, if there's some to do.
/// Stop as soon as the dam asks for interruption
fn do_pending_task(&mut self, screen: &mut Screen, dam: &mut Dam) {
- debug!("entering do_pending_task");
if self.pending_pattern.is_some() {
let pattern_str = self.pending_pattern.to_string();
let mut options = self.tree.options.clone();
@@ -428,47 +427,9 @@ impl AppState for BrowserState {
}
} else if self.displayed_tree().is_missing_git_status_computation() {
- let root_path = self.displayed_tree().root().to_path_buf();
- let git_status = dam.try_compute(||
- git_status_computer::compute_tree_status(root_path)
- );
- debug!("computation result: {:?}", &git_status);
+ let root_path = self.displayed_tree().root();
+ let git_status = git_status_computer::get_tree_status(root_path, dam);
self.displayed_tree_mut().git_status = git_status;
- debug!(
- "self.displayed_tree().git_status = {:?}",
- &self.displayed_tree().git_status,
- );
-
- debug!(
- "AFTERCOMPUT self.displayed_tree().is_missing_git_status_computation() = {}",
- self.displayed_tree().is_missing_git_status_computation(),
- );
-
-
- //if self.options.show_git_file_info {
- // let root_path = &self.blines[self.root_id].path;
- // if let Ok(git_repo) = Repository::discover(root_path) {
- // tree.git_status = time!(
- // Debug,
- // "TreeGitStatus::from",
- // TreeGitStatus::from(&git_repo),
- // );
- // let repo_root_path = git_repo.path().parent().unwrap();
- // for mut line in tree.lines.iter_mut() {
- // if let Some(relative_path) = pathdiff::diff_paths(&line.path, &repo_root_path) {
- // line.git_status = time!(
- // Debug,
- // "LineGitStatus",
- // &relative_path,
- // LineGitStatus::from(&git_repo, &relative_path),
- // );
- // };
- // }
- // }
- //}
-
-
-
} else {
self.displayed_tree_mut().fetch_some_missing_dir_size(dam);
}
diff --git a/src/git.rs b/src/git.rs
new file mode 100644
index 0000000..3b0e35e
--- /dev/null
+++ b/src/git.rs
@@ -0,0 +1,26 @@
+
+use {
+ std::{
+ path::{
+ Path,
+ PathBuf,
+ },
+ },
+};
+
+/// return the closest parent (or self) containing a
+/// .git file
+pub fn closest_repo_dir(mut path: &Path) -> Option<PathBuf> {
+ loop {
+ let c = path.join(".git");
+ if c.exists() {
+ return Some(path.to_path_buf());
+ }
+ path = match path.parent() {
+ Some(path) => path,
+ None => {
+ return None;
+ }
+ };
+ }
+}
diff --git a/src/git_status_computer.rs b/src/git_status_computer.rs
index 4511f3e..daa3e28 100644
--- a/src/git_status_computer.rs
+++ b/src/git_status_computer.rs
@@ -1,56 +1,29 @@
use {
crate::{
+ git,
git_status::*,
task_sync::{
Dam,
ComputationResult,
+ Computation,
},
},
git2::{
Repository,
},
- //rayon::{
- // ThreadPool,
- // ThreadPoolBuilder,
- //},
- crossbeam::{channel::bounded, sync::WaitGroup},
+ crossbeam::channel::bounded,
std::{
- collections::HashSet,
- fs,
- os::unix::fs::MetadataExt,
+ collections::HashMap,
path::{Path, PathBuf},
- sync::{
- atomic::{AtomicIsize, AtomicU64, Ordering},
- Arc, Mutex,
- },
- thread,
- time::Duration,
+ sync::Mutex,
},
};
-//struct Computer {
-//
-//}
-//impl Computer {
-// pub fn new() -> Self {
-//
-// }
-//}
-
-
-pub fn compute_tree_status(root_path: PathBuf) -> ComputationResult<TreeGitStatus> {
- match Repository::discover(root_path) {
+fn compute_tree_status(root_path: &Path) -> ComputationResult<TreeGitStatus> {
+ match Repository::open(root_path) {
Ok(git_repo) => {
- debug!("repo opened");
- for _ in 0..20 {
- time!(
- Debug,
- "compute_tree_status",
- TreeGitStatus::from(&git_repo),
- );
- }
let tree_git_status = time!(
Debug,
"compute_tree_status",
@@ -67,3 +40,76 @@ pub fn compute_tree_status(root_path: PathBuf) -> ComputationResult<TreeGitStatu
}
}
}
+
+lazy_static! {
+ // the key is the path of the repository
+ static ref TS_CACHE_MX: Mutex<HashMap<PathBuf, Computation<TreeGitStatus>>> =
+ Mutex::new(HashMap::new());
+}
+/// try to get the result of the computation of the tree git status.
+/// This may be immediate if a previous computation was finished.
+/// This may wait for the result of a new computation or of a previously
+/// launched one.
+/// In any case:
+/// - this function returns as soon as the dam asks for it (ie when there's an event)
+/// - computations are never dropped unless the program ends: they continue in background
+/// and the result may be available for following queries
+pub fn get_tree_status(root_path: &Path, dam: &mut Dam) -> ComputationResult<TreeGitStatus> {
+ match git::closest_repo_dir(root_path) {
+ None => ComputationResult::None,
+ Some(repo_path) => {
+ let comp = TS_CACHE_MX.lock().unwrap().get(&repo_path).map(|c| (*c).clone());
+ match comp {
+ Some(Computation::Finished(comp_res)) => {
+ // already computed
+ comp_res.clone()
+ }
+ Some(Computation::InProgress(comp_receiver)) => {
+ // computation in progress
+ // We do a select! to wait for either the dam
+ // or the receiver
+ debug!("start select on in progress computation");
+ dam.select(comp_receiver.clone())
+ }
+ None => {
+ // not yet started. We launch the computation and store
+ // the receiver immediately.
+ // We use the dam to return from this function when
+ // needed (while letting the underlying thread finish
+ // the job)
+ //
+ // note: must also update the TS_CACHE entry at end
+ let (s, r) = bounded(1);
+ TS_CACHE_MX.lock().unwrap().insert(
+ repo_path.clone(),
+ Computation::InProgress(r),
+ );
+ dam.try_compute(move||{
+ debug!("start computation");
+ let comp_res = compute_tree_status(&repo_path);
+ debug!("comp finished - try inserting");
+ TS_CACHE_MX.lock().unwrap().insert(
+ repo_path.clone(),
+ Computation::Finished(comp_res.clone()),
+ );
+ debug!("result stored in cache, now sending to receiver");
+ if let Err(e) = s.send(comp_res.clone()) {
+ debug!("error while sending comp result: {:?}", e);
+ }
+ debug!("result sent to receiver - now returning");
+ comp_res
+ })
+ }
+ }
+ }
+ }
+}
+
+/// clear the finished or in progress computation.
+/// Limit: we may receive in cache the result of a computation
+/// which started before the clear (if this is a problem we could
+/// store a cleaning counter alongside the cache to prevent insertions)
+pub fn clear_cache() {
+ let mut ts_cache = TS_CACHE_MX.lock().unwrap();
+ ts_cache.clear();
+}
diff --git a/src/lib.rs b/src/lib.rs
index 89c8c20..294f9b0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,6 +28,7 @@ pub mod external;
pub mod file_sizes;
pub mod flat_tree;
pub mod fuzzy_patterns;
+pub mod git;
pub mod git_ignore;
pub mod git_status;
pub mod git_status_computer;
diff --git a/src/task_sync.rs b/src/task_sync.rs
index db3474f..48f4f8b 100644
--- a/src/task_sync.rs
+++ b/src/task_sync.rs
@@ -5,7 +5,6 @@ use {
bounded,
Receiver,
},
- lazy_static::lazy_static,
std::{
thread,
},
@@ -13,12 +12,12 @@ use {
};
#[derive(Debug, Clone)]
-pub enum ComputationResult<T> {
+pub enum ComputationResult<V> {
NotComputed, // not computed but will probably be
- Done(T),
+ Done(V),
None, // nothing to compute, cancelled, failed, etc.
}
-impl<T> ComputationResult<T> {
+impl<V> ComputationResult<V> {
pub fn is_done(&self) -> bool {
match &self {
Self::Done(_) => true,
@@ -77,10 +76,10 @@ impl Dam {
/// launch the computation on a new thread and return
/// when it finishes or when a new event appears on
/// the channel
- pub fn try_compute<T: Send + 'static, F: Send + 'static + FnOnce() -> ComputationResult<T>>(
+ pub fn try_compute<V: Send + 'static, F: Send + 'static + FnOnce() -> ComputationResult<V>>(
&mut self,
f: F,
- ) -> ComputationResult<T> {
+ ) -> ComputationResult<V> {
let (comp_sender, comp_receiver) = bounded(1);
thread::spawn(move|| {
let comp_res = time!(Debug, "comp in dam", f());
@@ -91,10 +90,10 @@ impl Dam {
self.select(comp_receiver)
}
- fn select<T>(
+ pub fn select<V>(
&mut self,
- computation_channel: Receiver<ComputationResult<T>>,
- ) -> ComputationResult<T> {
+ comp_receiver: Receiver<ComputationResult<V>>,
+ ) -> ComputationResult<V> {
if self.in_dam.is_some() {
// should probably not happen
debug!("There's already an event in dam");
@@ -109,7 +108,7 @@ impl Dam {
self.in_dam = event.ok();
ComputationResult::None
}
- recv(computation_channel) -> comp_res => {
+ recv(comp_receiver) -> comp_res => {
// computation finished
debug!("computation passes dam");
comp_res.unwrap_or(ComputationResult::None)
@@ -160,3 +159,13 @@ impl DamObserver {
}
}
+
+/// wraps either a computation in progress, or a finished
+/// one (even a failed or useless one).
+/// This can be stored in a map to avoid starting computations
+/// more than once.
+#[derive(Debug, Clone)]
+pub enum Computation<V> {
+ InProgress(Receiver<ComputationResult<V>>),
+ Finished(ComputationResult<V>),
+}
diff --git a/website/docs/index.md b/website/docs/index.md
index 28f241f..f6a4951 100644
--- a/website/docs/index.md
+++ b/website/docs/index.md
@@ -84,6 +84,12 @@ And you keep all broot tools, like filtering or the ability to delete or open fi
Sizes are computed in the background, you don't have to wait for them when you navigate.
+### check git statuses:
+
+![size](img/20200203-git.png)
+
+Use `:gf` to display the statuses of files (what are the new ones, the modified ones, etc.), the current branch name and the change statistics.
+
# More...
See the complete [Documentation](documentation/usage.md) and [how to install](documentation/installation.md).