diff --git a/src/command/format.rs b/src/command/format.rs index 340b4e77..ea943b7e 100644 --- a/src/command/format.rs +++ b/src/command/format.rs @@ -1,3 +1,4 @@ +use crate::command::FSScan; use crate::engine::run_treefmt; use anyhow::anyhow; use directories::ProjectDirs; @@ -18,6 +19,7 @@ pub async fn format_cmd( fail_on_change: bool, allow_missing_formatter: bool, selected_formatters: &Option>, + fs_scan: &FSScan, ) -> anyhow::Result<()> { let proj_dirs = match ProjectDirs::from("com", "NumTide", "treefmt") { Some(x) => x, @@ -52,23 +54,21 @@ pub async fn format_cmd( paths ); - let client = match Connector::new().connect().await { - Err(e) => { - warn!( - "watchman is not available (err = {:?}), falling back on stat(2)", - e - ); - None - } - Ok(c) => { + let client = match fs_scan { + FSScan::Stat => None, + FSScan::Watchman => { // This is important. Subprocess wastes ~20ms. if !std::env::var("WATCHMAN_SOCK").is_ok() { warn!( "Environment variable `WATCHMAN_SOCK' is not set, falling back on subprocess" ); }; - Some(c) + match Connector::new().connect().await { + Err(e) => return Err(anyhow!("watchman is not available (err = {:?})", e)), + Ok(c) => Some(c), + } } + FSScan::Auto => Connector::new().connect().await.ok(), }; // Finally run the main formatter logic from the engine. diff --git a/src/command/mod.rs b/src/command/mod.rs index 24eafdee..9c512430 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -19,6 +19,43 @@ use std::{ path::{Path, PathBuf}, }; +/// Enumeration specifying filesystem scan method for modified files. +#[derive(Debug, Clone)] +pub enum FSScan { + /// Recursive walking the filesystem using getdents(2) and statx(2) system calls. + /// + /// Scales linearly with total count of files in the repository. + Stat, + + /// Query list of modified files from external watchman(1) process. + /// + /// Watchman uses inotify(2), so this method scales linearly with count of files in + /// the repository that were actually modified. + /// + /// It is user responsibility to get watchman(1) process up and running and preferable + /// set "WATCHMAN_SOCK" environment variable. One way to set that environment variable + /// is to put following snippet into ~/.bashrc: + /// + /// export WATCHMAN_SOCK=$(watchman get-sockname | jq .sockname -r) + Watchman, + + /// Try to use watchman(1), and silently fall back on stat should it fail. + Auto, +} + +impl std::str::FromStr for FSScan { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "stat" => Ok(Self::Stat), + "watchman" => Ok(Self::Watchman), + "auto" => Ok(Self::Auto), + _ => Err(format!("Unknown file-system scan method: {s}")), + } + } +} + /// ✨ format all your language! #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -84,6 +121,10 @@ pub struct Cli { /// Select formatters name to apply. Defaults to all formatters. #[arg(short, long)] pub formatters: Option>, + + /// Select filesystem scan method (stat, watchman, auto). + #[arg(long, default_value = "auto")] + pub fs_scan: FSScan, } fn current_dir() -> anyhow::Result { @@ -173,6 +214,7 @@ pub fn run_cli(cli: &Cli) -> anyhow::Result<()> { cli.fail_on_change, cli.allow_missing_formatter, &cli.formatters, + &cli.fs_scan, )? } }