Skip to content

Commit 370aff5

Browse files
authored
Default to tick-based updates (#1657)
* Default to tick-based updates This commit reintroduces code that was previously removed in favor of a notify-based update trigger. It turned out that notify-based updates can cause issues in larger repositories, so tick-based updates seemed like a safer default. #1444 #1310 * Add FAQ entry for --watcher * Remove --poll
1 parent 836e03c commit 370aff5

File tree

5 files changed

+63
-52
lines changed

5 files changed

+63
-52
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4949
* commit hooks report "command not found" on Windows with wsl2 installed ([#1528](https://github.com/extrawurst/gitui/issues/1528))
5050
* crashes on entering submodules ([#1510](https://github.com/extrawurst/gitui/issues/1510))
5151
* fix race issue: revlog messages sometimes appear empty ([#1473](https://github.com/extrawurst/gitui/issues/1473))
52+
* default to tick-based updates [[@cruessler](https://github.com/cruessler)] ([#1444](https://github.com/extrawurst/gitui/issues/1444))
5253

5354
### Changed
5455
* minimum supported rust version bumped to 1.64 (thank you `clap`)

FAQ.md

+5
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ Note that in some cases adding the line `ssh-add -K ~/.ssh/id_ed25519`(or whatev
1818

1919
If you want to use `vi`-style keys or customize your key bindings in any other fassion see the specific docs on that: [key config](./KEY_CONFIG.md)
2020

21+
## 3. <a name="watcher"></a> Watching for changes <small><sup>[Top ▲](#table-of-contents)</sup></small>
22+
23+
By default, `gitui` polls for changes in the working directory every 5 seconds. If you supply `--watcher` as an argument, it uses a `notify`-based approach instead. This is usually faster and was for some time the default update strategy. It turned out, however, that `notify`-based updates can cause issues on some platforms, so tick-based updates seemed like a safer default.
24+
25+
See #1444 for details.

src/args.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::{
1515
pub struct CliArgs {
1616
pub theme: PathBuf,
1717
pub repo_path: RepoPath,
18-
pub poll_watcher: bool,
18+
pub notify_watcher: bool,
1919
}
2020

2121
pub fn process_cmdline() -> Result<CliArgs> {
@@ -54,13 +54,13 @@ pub fn process_cmdline() -> Result<CliArgs> {
5454
get_app_config_path()?.join("theme.ron")
5555
};
5656

57-
let arg_poll: bool =
58-
*arg_matches.get_one("poll").unwrap_or(&false);
57+
let notify_watcher: bool =
58+
*arg_matches.get_one("watcher").unwrap_or(&false);
5959

6060
Ok(CliArgs {
6161
theme,
62-
poll_watcher: arg_poll,
6362
repo_path,
63+
notify_watcher,
6464
})
6565
}
6666

@@ -96,9 +96,9 @@ fn app() -> ClapApp {
9696
.num_args(0),
9797
)
9898
.arg(
99-
Arg::new("poll")
100-
.help("Poll folder for changes instead of using file system events. This can be useful if you run into issues with maximum # of file descriptors")
101-
.long("polling")
99+
Arg::new("watcher")
100+
.help("Use notify-based file system watcher instead of tick-based update. This is more performant, but can cause issues on some platforms. See https://github.com/extrawurst/gitui/blob/master/FAQ.md#watcher for details.")
101+
.long("watcher")
102102
.action(clap::ArgAction::SetTrue),
103103
)
104104
.arg(

src/main.rs

+37-11
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use asyncgit::{
5656
AsyncGitNotification,
5757
};
5858
use backtrace::Backtrace;
59-
use crossbeam_channel::{tick, unbounded, Receiver, Select};
59+
use crossbeam_channel::{never, tick, unbounded, Receiver, Select};
6060
use crossterm::{
6161
terminal::{
6262
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
@@ -83,11 +83,13 @@ use std::{
8383
use ui::style::Theme;
8484
use watcher::RepoWatcher;
8585

86+
static TICK_INTERVAL: Duration = Duration::from_secs(5);
8687
static SPINNER_INTERVAL: Duration = Duration::from_millis(80);
8788

8889
///
8990
#[derive(Clone)]
9091
pub enum QueueEvent {
92+
Tick,
9193
Notify,
9294
SpinnerUpdate,
9395
AsyncEvent(AsyncNotification),
@@ -114,6 +116,12 @@ pub enum AsyncNotification {
114116
Git(AsyncGitNotification),
115117
}
116118

119+
#[derive(Clone, Copy, PartialEq)]
120+
enum Updater {
121+
Ticker,
122+
NotifyWatcher,
123+
}
124+
117125
fn main() -> Result<()> {
118126
let app_start = Instant::now();
119127

@@ -145,14 +153,20 @@ fn main() -> Result<()> {
145153
let mut repo_path = cliargs.repo_path;
146154
let input = Input::new();
147155

156+
let updater = if cliargs.notify_watcher {
157+
Updater::NotifyWatcher
158+
} else {
159+
Updater::Ticker
160+
};
161+
148162
loop {
149163
let quit_state = run_app(
150164
app_start,
151165
repo_path.clone(),
152166
theme,
153167
key_config.clone(),
154168
&input,
155-
cliargs.poll_watcher,
169+
updater,
156170
&mut terminal,
157171
)?;
158172

@@ -173,18 +187,24 @@ fn run_app(
173187
theme: Theme,
174188
key_config: KeyConfig,
175189
input: &Input,
176-
poll_watcher: bool,
190+
updater: Updater,
177191
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
178192
) -> Result<QuitState, anyhow::Error> {
179193
let (tx_git, rx_git) = unbounded();
180194
let (tx_app, rx_app) = unbounded();
181195

182196
let rx_input = input.receiver();
183-
let watcher = RepoWatcher::new(
184-
repo_work_dir(&repo)?.as_str(),
185-
poll_watcher,
186-
);
187-
let rx_watcher = watcher.receiver();
197+
198+
let (rx_ticker, rx_watcher) = match updater {
199+
Updater::NotifyWatcher => {
200+
let repo_watcher =
201+
RepoWatcher::new(repo_work_dir(&repo)?.as_str());
202+
203+
(never(), repo_watcher.receiver())
204+
}
205+
Updater::Ticker => (tick(TICK_INTERVAL), never()),
206+
};
207+
188208
let spinner_ticker = tick(SPINNER_INTERVAL);
189209

190210
let mut app = App::new(
@@ -210,6 +230,7 @@ fn run_app(
210230
&rx_input,
211231
&rx_git,
212232
&rx_app,
233+
&rx_ticker,
213234
&rx_watcher,
214235
&spinner_ticker,
215236
)?
@@ -235,7 +256,9 @@ fn run_app(
235256
}
236257
app.event(ev)?;
237258
}
238-
QueueEvent::Notify => app.update()?,
259+
QueueEvent::Tick | QueueEvent::Notify => {
260+
app.update()?;
261+
}
239262
QueueEvent::AsyncEvent(ev) => {
240263
if !matches!(
241264
ev,
@@ -309,6 +332,7 @@ fn select_event(
309332
rx_input: &Receiver<InputEvent>,
310333
rx_git: &Receiver<AsyncGitNotification>,
311334
rx_app: &Receiver<AsyncAppNotification>,
335+
rx_ticker: &Receiver<Instant>,
312336
rx_notify: &Receiver<()>,
313337
rx_spinner: &Receiver<Instant>,
314338
) -> Result<QueueEvent> {
@@ -317,6 +341,7 @@ fn select_event(
317341
sel.recv(rx_input);
318342
sel.recv(rx_git);
319343
sel.recv(rx_app);
344+
sel.recv(rx_ticker);
320345
sel.recv(rx_notify);
321346
sel.recv(rx_spinner);
322347

@@ -331,8 +356,9 @@ fn select_event(
331356
2 => oper.recv(rx_app).map(|e| {
332357
QueueEvent::AsyncEvent(AsyncNotification::App(e))
333358
}),
334-
3 => oper.recv(rx_notify).map(|_| QueueEvent::Notify),
335-
4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate),
359+
3 => oper.recv(rx_ticker).map(|_| QueueEvent::Notify),
360+
4 => oper.recv(rx_notify).map(|_| QueueEvent::Notify),
361+
5 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate),
336362
_ => bail!("unknown select source"),
337363
}?;
338364

src/watcher.rs

+13-34
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
use anyhow::Result;
22
use crossbeam_channel::{unbounded, Sender};
3-
use notify::{
4-
Config, Error, PollWatcher, RecommendedWatcher, RecursiveMode,
5-
Watcher,
6-
};
7-
use notify_debouncer_mini::{
8-
new_debouncer, new_debouncer_opt, DebouncedEvent,
9-
};
3+
use notify::{Error, RecommendedWatcher, RecursiveMode, Watcher};
4+
use notify_debouncer_mini::{new_debouncer, DebouncedEvent};
105
use scopetime::scope_time;
116
use std::{path::Path, thread, time::Duration};
127

@@ -15,9 +10,9 @@ pub struct RepoWatcher {
1510
}
1611

1712
impl RepoWatcher {
18-
pub fn new(workdir: &str, poll: bool) -> Self {
13+
pub fn new(workdir: &str) -> Self {
1914
log::trace!(
20-
"poll watcher: {poll} recommended: {:?}",
15+
"recommended watcher: {:?}",
2116
RecommendedWatcher::kind()
2217
);
2318

@@ -27,7 +22,7 @@ impl RepoWatcher {
2722

2823
thread::spawn(move || {
2924
let timeout = Duration::from_secs(2);
30-
create_watcher(poll, timeout, tx, &workdir);
25+
create_watcher(timeout, tx, &workdir);
3126
});
3227

3328
let (out_tx, out_rx) = unbounded();
@@ -72,7 +67,6 @@ impl RepoWatcher {
7267
}
7368

7469
fn create_watcher(
75-
poll: bool,
7670
timeout: Duration,
7771
tx: std::sync::mpsc::Sender<
7872
Result<Vec<DebouncedEvent>, Vec<Error>>,
@@ -81,27 +75,12 @@ fn create_watcher(
8175
) {
8276
scope_time!("create_watcher");
8377

84-
if poll {
85-
let config = Config::default()
86-
.with_poll_interval(Duration::from_secs(2));
87-
let mut bouncer = new_debouncer_opt::<_, PollWatcher>(
88-
timeout, None, tx, config,
89-
)
90-
.expect("Watch create error");
91-
bouncer
92-
.watcher()
93-
.watch(Path::new(&workdir), RecursiveMode::Recursive)
94-
.expect("Watch error");
95-
96-
std::mem::forget(bouncer);
97-
} else {
98-
let mut bouncer = new_debouncer(timeout, None, tx)
99-
.expect("Watch create error");
100-
bouncer
101-
.watcher()
102-
.watch(Path::new(&workdir), RecursiveMode::Recursive)
103-
.expect("Watch error");
104-
105-
std::mem::forget(bouncer);
106-
};
78+
let mut bouncer =
79+
new_debouncer(timeout, None, tx).expect("Watch create error");
80+
bouncer
81+
.watcher()
82+
.watch(Path::new(&workdir), RecursiveMode::Recursive)
83+
.expect("Watch error");
84+
85+
std::mem::forget(bouncer);
10786
}

0 commit comments

Comments
 (0)