From 677508bf77fd74a69456ca86e4352eb89f0f7bf6 Mon Sep 17 00:00:00 2001
From: extrawurst <mail@rusticorn.com>
Date: Thu, 1 Sep 2022 19:55:45 +0200
Subject: [PATCH 1/2] poc

---
 Cargo.lock     | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml     |  2 ++
 deny.toml      |  4 ++-
 src/app.rs     |  2 +-
 src/main.rs    | 25 ++++++++------
 src/watcher.rs | 70 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 182 insertions(+), 12 deletions(-)
 create mode 100644 src/watcher.rs

diff --git a/Cargo.lock b/Cargo.lock
index 213f8cfab1..5955d9ca4a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -455,6 +455,18 @@ dependencies = [
  "instant",
 ]
 
+[[package]]
+name = "filetime"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys",
+]
+
 [[package]]
 name = "filetreelist"
 version = "0.5.0"
@@ -502,6 +514,15 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "futures"
 version = "0.3.24"
@@ -674,6 +695,8 @@ dependencies = [
  "itertools",
  "lazy_static",
  "log",
+ "notify",
+ "notify-debouncer-mini",
  "pprof",
  "pretty_assertions",
  "rayon-core",
@@ -764,6 +787,26 @@ dependencies = [
  "str_stack",
 ]
 
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -816,6 +859,26 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "kqueue"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -955,6 +1018,34 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
 
+[[package]]
+name = "notify"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "winapi",
+]
+
+[[package]]
+name = "notify-debouncer-mini"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c538ea1dd436b41e751922510cfbcaea2def87ed6ed94aa1edc15dc31b4c179"
+dependencies = [
+ "crossbeam-channel",
+ "notify",
+]
+
 [[package]]
 name = "num-format"
 version = "0.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 512f90e80d..17a20c955f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,8 @@ gh-emoji = { version = "1.0", optional = true }
 itertools = "0.10"
 lazy_static = "1.4"
 log = "0.4"
+notify = "5.0"
+notify-debouncer-mini = "0.2" 
 rayon-core = "1.9"
 ron = "0.8"
 scopeguard = "1.1"
diff --git a/deny.toml b/deny.toml
index e67f4800b0..8f51f26968 100644
--- a/deny.toml
+++ b/deny.toml
@@ -4,7 +4,9 @@ allow = [
     "MIT",
     "Apache-2.0",
     "BSD-2-Clause",
-    "BSD-3-Clause"
+    "BSD-3-Clause",
+    "CC0-1.0",
+    "ISC"
 ]
 copyleft = "warn"
 allow-osi-fsf-free = "neither"
diff --git a/src/app.rs b/src/app.rs
index e3cc7dc2bb..a2d7b07ae0 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -110,7 +110,7 @@ impl App {
 		theme: Theme,
 		key_config: KeyConfig,
 	) -> Self {
-		log::trace!("open repo at: {:?}", repo);
+		log::trace!("open repo at: {:?}", &repo);
 
 		let queue = Queue::new();
 		let theme = Rc::new(theme);
diff --git a/src/main.rs b/src/main.rs
index b9395be470..fd70f2338d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,11 +36,15 @@ mod strings;
 mod tabs;
 mod ui;
 mod version;
+mod watcher;
 
 use crate::{app::App, args::process_cmdline};
 use anyhow::{bail, Result};
 use app::QuitState;
-use asyncgit::{sync::RepoPath, AsyncGitNotification};
+use asyncgit::{
+	sync::{utils::repo_work_dir, RepoPath},
+	AsyncGitNotification,
+};
 use backtrace::Backtrace;
 use crossbeam_channel::{tick, unbounded, Receiver, Select};
 use crossterm::{
@@ -67,14 +71,14 @@ use tui::{
 	Terminal,
 };
 use ui::style::Theme;
+use watcher::RepoWatcher;
 
-static TICK_INTERVAL: Duration = Duration::from_secs(5);
 static SPINNER_INTERVAL: Duration = Duration::from_millis(80);
 
 ///
 #[derive(Clone)]
 pub enum QueueEvent {
-	Tick,
+	Notify,
 	SpinnerUpdate,
 	AsyncEvent(AsyncNotification),
 	InputEvent(InputEvent),
@@ -161,7 +165,8 @@ fn run_app(
 	let (tx_app, rx_app) = unbounded();
 
 	let rx_input = input.receiver();
-	let ticker = tick(TICK_INTERVAL);
+	let watcher = RepoWatcher::new(repo_work_dir(&repo)?.as_str())?;
+	let rx_watcher = watcher.receiver();
 	let spinner_ticker = tick(SPINNER_INTERVAL);
 
 	let mut app = App::new(
@@ -179,13 +184,13 @@ fn run_app(
 	loop {
 		let event = if first_update {
 			first_update = false;
-			QueueEvent::Tick
+			QueueEvent::Notify
 		} else {
 			select_event(
 				&rx_input,
 				&rx_git,
 				&rx_app,
-				&ticker,
+				&rx_watcher,
 				&spinner_ticker,
 			)?
 		};
@@ -208,7 +213,7 @@ fn run_app(
 					}
 					app.event(ev)?;
 				}
-				QueueEvent::Tick => app.update()?,
+				QueueEvent::Notify => app.update()?,
 				QueueEvent::AsyncEvent(ev) => {
 					if !matches!(
 						ev,
@@ -282,7 +287,7 @@ fn select_event(
 	rx_input: &Receiver<InputEvent>,
 	rx_git: &Receiver<AsyncGitNotification>,
 	rx_app: &Receiver<AsyncAppNotification>,
-	rx_ticker: &Receiver<Instant>,
+	rx_notify: &Receiver<()>,
 	rx_spinner: &Receiver<Instant>,
 ) -> Result<QueueEvent> {
 	let mut sel = Select::new();
@@ -290,7 +295,7 @@ fn select_event(
 	sel.recv(rx_input);
 	sel.recv(rx_git);
 	sel.recv(rx_app);
-	sel.recv(rx_ticker);
+	sel.recv(rx_notify);
 	sel.recv(rx_spinner);
 
 	let oper = sel.select();
@@ -304,7 +309,7 @@ fn select_event(
 		2 => oper.recv(rx_app).map(|e| {
 			QueueEvent::AsyncEvent(AsyncNotification::App(e))
 		}),
-		3 => oper.recv(rx_ticker).map(|_| QueueEvent::Tick),
+		3 => oper.recv(rx_notify).map(|_| QueueEvent::Notify),
 		4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate),
 		_ => bail!("unknown select source"),
 	}?;
diff --git a/src/watcher.rs b/src/watcher.rs
new file mode 100644
index 0000000000..71f542187b
--- /dev/null
+++ b/src/watcher.rs
@@ -0,0 +1,70 @@
+use anyhow::Result;
+use crossbeam_channel::{unbounded, Sender};
+use notify::{Error, FsEventWatcher, RecursiveMode};
+use notify_debouncer_mini::{
+	new_debouncer, DebouncedEvent, Debouncer,
+};
+use std::{
+	path::Path, sync::mpsc::RecvError, thread, time::Duration,
+};
+
+pub struct RepoWatcher {
+	receiver: crossbeam_channel::Receiver<()>,
+	#[allow(dead_code)]
+	debouncer: Debouncer<FsEventWatcher>,
+}
+
+impl RepoWatcher {
+	pub fn new(workdir: &str) -> Result<Self> {
+		let (tx, rx) = std::sync::mpsc::channel();
+
+		let mut debouncer =
+			new_debouncer(Duration::from_secs(2), None, tx)?;
+
+		let watcher = debouncer.watcher();
+		watcher
+			.watch(Path::new(workdir), RecursiveMode::Recursive)?;
+
+		let (out_tx, out_rx) = unbounded();
+
+		thread::spawn(move || {
+			if let Err(e) = Self::forwarder(&rx, &out_tx) {
+				//maybe we need to restart the forwarder now?
+				log::error!("notify receive error: {}", e);
+			}
+		});
+
+		Ok(Self {
+			debouncer,
+			receiver: out_rx,
+		})
+	}
+
+	///
+	pub fn receiver(&self) -> crossbeam_channel::Receiver<()> {
+		self.receiver.clone()
+	}
+
+	fn forwarder(
+		receiver: &std::sync::mpsc::Receiver<
+			Result<Vec<DebouncedEvent>, Vec<Error>>,
+		>,
+		sender: &Sender<()>,
+	) -> Result<(), RecvError> {
+		loop {
+			let ev = receiver.recv()?;
+
+			if let Ok(ev) = ev {
+				log::debug!("notify events: {}", ev.len());
+
+				for (idx, ev) in ev.iter().enumerate() {
+					log::debug!("notify [{}]: {:?}", idx, ev);
+				}
+
+				if !ev.is_empty() {
+					sender.send(()).expect("send error");
+				}
+			}
+		}
+	}
+}

From 9f695cd2d435acf198fabc01d04a29ef61062e0b Mon Sep 17 00:00:00 2001
From: extrawurst <mail@rusticorn.com>
Date: Thu, 1 Sep 2022 23:52:04 +0200
Subject: [PATCH 2/2] fix ci

---
 src/watcher.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/watcher.rs b/src/watcher.rs
index 71f542187b..a50b653478 100644
--- a/src/watcher.rs
+++ b/src/watcher.rs
@@ -1,6 +1,6 @@
 use anyhow::Result;
 use crossbeam_channel::{unbounded, Sender};
-use notify::{Error, FsEventWatcher, RecursiveMode};
+use notify::{Error, RecommendedWatcher, RecursiveMode};
 use notify_debouncer_mini::{
 	new_debouncer, DebouncedEvent, Debouncer,
 };
@@ -11,7 +11,7 @@ use std::{
 pub struct RepoWatcher {
 	receiver: crossbeam_channel::Receiver<()>,
 	#[allow(dead_code)]
-	debouncer: Debouncer<FsEventWatcher>,
+	debouncer: Debouncer<RecommendedWatcher>,
 }
 
 impl RepoWatcher {
@@ -21,8 +21,8 @@ impl RepoWatcher {
 		let mut debouncer =
 			new_debouncer(Duration::from_secs(2), None, tx)?;
 
-		let watcher = debouncer.watcher();
-		watcher
+		debouncer
+			.watcher()
 			.watch(Path::new(workdir), RecursiveMode::Recursive)?;
 
 		let (out_tx, out_rx) = unbounded();