From 8f39c99cac71857243b612fd1ae82c509c4b7630 Mon Sep 17 00:00:00 2001 From: Dmitry Bogatov Date: Wed, 13 Dec 2023 17:00:29 -0500 Subject: [PATCH] feat: Add support for using watchman(1) instead of walking filesystem Anecodically, for project having 4k files and 7 different formatters, this change can reduce time it takes for treefmt(1) to figure out that it has nothing to do from ~350ms down to ~8m. If "WATCHMAN_SOCK" environment variable is not set and "watchman(1)" command are not available, "treefmt" transparently falls back on walking the file system. Setting the environment variable is highly recommended, since is avoids subprocess invocation. Ref: https://github.com/numtide/treefmt/issues/261 --- Cargo.lock | 378 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/command/format.rs | 30 +++- src/engine.rs | 156 ++++++++++++++++- src/eval_cache.rs | 4 + src/formatter.rs | 13 ++ 6 files changed, 571 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 226d14e2..bb9aa5bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "0.7.20" @@ -92,6 +107,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -129,6 +159,31 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] + [[package]] name = "cast" version = "0.3.0" @@ -472,6 +527,102 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures 0.1.31", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -493,6 +644,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "globset" version = "0.4.10" @@ -576,6 +733,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -619,9 +785,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linux-raw-sys" @@ -635,12 +801,28 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.5.0" @@ -656,6 +838,26 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + [[package]] name = "mockall" version = "0.11.4" @@ -708,6 +910,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -726,12 +937,47 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets", +] + [[package]] name = "path-clean" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "plotters" version = "0.3.5" @@ -848,6 +1094,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -888,6 +1143,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" version = "0.37.27" @@ -945,6 +1206,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b929ea725591083cbca8b8ea178ed6efc918eccd40b784e199ce88967104199" +dependencies = [ + "anyhow", + "byteorder", + "bytes 0.4.12", + "serde", + "thiserror", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -990,6 +1264,40 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1116,6 +1424,52 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +dependencies = [ + "backtrace", + "bytes 1.5.0", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes 1.5.0", + "futures-core", + "futures-io", + "futures-sink", + "log", + "pin-project-lite", + "slab", + "tokio", +] + [[package]] name = "toml" version = "0.5.11" @@ -1147,7 +1501,9 @@ dependencies = [ "serde_json", "sha-1", "tempfile", + "tokio", "toml", + "watchman_client", "which", ] @@ -1260,6 +1616,24 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "watchman_client" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839fea2d85719bb69089290d7970bba2131f544448db8f990ea75813c30775ca" +dependencies = [ + "anyhow", + "bytes 1.5.0", + "futures 0.3.29", + "maplit", + "serde", + "serde_bser", + "thiserror", + "tokio", + "tokio-util", + "winapi", +] + [[package]] name = "web-sys" version = "0.3.64" diff --git a/Cargo.toml b/Cargo.toml index fb6f441e..1bd51e49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ sha-1 = "0.9.2" tempfile = "3.2.0" toml = "0.5" which = "4.0.2" +watchman_client = "0.8.0" +tokio = "1.35" [dev-dependencies] criterion = "0.3" diff --git a/src/command/format.rs b/src/command/format.rs index 16b7e97d..340b4e77 100644 --- a/src/command/format.rs +++ b/src/command/format.rs @@ -1,10 +1,13 @@ use crate::engine::run_treefmt; use anyhow::anyhow; use directories::ProjectDirs; -use log::debug; +use log::{debug, warn}; use std::path::{Path, PathBuf}; +use tokio; +use watchman_client::prelude::Connector; -pub fn format_cmd( +#[tokio::main] +pub async fn format_cmd( tree_root: &Option, work_dir: &Path, config_file: &Path, @@ -49,6 +52,25 @@ pub 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) => { + // 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) + } + }; + // Finally run the main formatter logic from the engine. run_treefmt( &tree_root, @@ -62,7 +84,9 @@ pub fn format_cmd( fail_on_change, allow_missing_formatter, selected_formatters, - )?; + &client.as_ref(), + ) + .await?; Ok(()) } diff --git a/src/engine.rs b/src/engine.rs index 311f7ca0..62f96973 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! The main formatting engine logic is in this module. use crate::config::Root; -use crate::{config, eval_cache::CacheManifest, formatter::FormatterName}; +use crate::{config, eval_cache::CacheManifest, formatter::FormatterName, Deserialize}; use crate::{expand_path, formatter::Formatter, get_meta, get_path_meta, FileMeta}; use anyhow::anyhow; use ignore::{Walk, WalkBuilder}; @@ -12,6 +12,7 @@ use std::io::{self, Write}; use std::iter::Iterator; use std::path::{Path, PathBuf}; use std::{collections::BTreeMap, time::Instant}; +use watchman_client::prelude::*; /// Controls how the information is displayed at the end of a run pub enum DisplayType { @@ -21,9 +22,17 @@ pub enum DisplayType { Long, } +watchman_client::query_result_type! { + struct WatchmanResponse { + name: NameField, + mtime: MTimeField, + size: SizeField, + } +} + /// Run the treefmt #[allow(clippy::too_many_arguments)] -pub fn run_treefmt( +pub async fn run_treefmt( tree_root: &Path, work_dir: &Path, cache_dir: &Path, @@ -35,6 +44,7 @@ pub fn run_treefmt( fail_on_change: bool, allow_missing_formatter: bool, selected_formatters: &Option>, + watchman: &Option<&Client>, ) -> anyhow::Result<()> { assert!(tree_root.is_absolute()); assert!(work_dir.is_absolute()); @@ -89,11 +99,130 @@ pub fn run_treefmt( // Insert the new formatter configs cache.update_formatters(formatters.clone()); } + stats.timed_debug("update formatters"); + let mut resolved_root: Option = None; + + let matches = match watchman { + None => { + let walker = build_walker(paths, hidden); + + let matches = collect_matches_from_walker(walker, &formatters, &mut stats); + stats.timed_debug("tree walk"); + matches + } + Some(watchman) => { + let root = watchman + .resolve_root(CanonicalPath::with_canonicalized_path( + tree_root.to_path_buf(), + )) + .await + .map_err(anyhow::Error::new)?; + stats.timed_debug("resolve root"); + + let mut matches: Vec = vec![]; + + for fmt in formatters.values() { + let mut includes: Vec = vec![]; + let mut excludes: Vec = vec![]; + + // As far as "watchman" concerned, "*.c" matches only $ROOT/foo.c, but not + // $ROOT/src.foo.c + for pattern in fmt.includes_str.iter() { + let mut s = pattern.to_string(); + s.insert_str(0, "**/"); + + includes.push(Expr::Match(MatchTerm { + glob: s, + include_dot_files: hidden, + no_escape: false, + wholename: true, + })); + } + for pattern in fmt.excludes_str.iter() { + let mut s = pattern.to_string(); + s.insert_str(0, "**/"); + + excludes.push(Expr::Match(MatchTerm { + glob: s, + include_dot_files: hidden, + no_escape: false, + wholename: true, + })); + } + if excludes.len() > 0 { + matches.push(Expr::All(vec![ + Expr::Any(includes), + Expr::Not(Box::new(Expr::Any(excludes))), + ])); + } else { + matches.push(Expr::Any(includes)); + } + } + + let since = if no_cache { + ClockSpec::null() + } else { + match cache.clock.clone() { + // FIXME: Avoid .clone() + Some(since) => since, + _ => ClockSpec::null(), + } + }; - let walker = build_walker(paths, hidden); + let query = QueryRequestCommon { + expression: Some(Expr::All(vec![ + Expr::Exists, + Expr::FileType(FileType::Regular), + Expr::Since(SinceTerm::ObservedClock(since)), + Expr::Any(matches), + ])), + ..Default::default() + }; - let matches = collect_matches_from_walker(walker, &formatters, &mut stats); - stats.timed_debug("tree walk"); + let resp: QueryResult = watchman + .query(&root, query) + .await + .map_err(anyhow::Error::new)?; + resolved_root = Some(root); + let files = resp.files.unwrap_or_default(); + stats.timed_debug(&format!("watchman query (returned {} files)", files.len())); + let mut matches: BTreeMap> = + Default::default(); + + // Now the data returned by "watchman" must be massaged into the shape used by + // stat(2)-based branch of code. List of fields returned by "watchman" are files that + // (1) are changed and (2) match some glob of some formatter, but I have to figure + // myself to which formatter they belong. Also, "watchman" has no concept of exclusion + // globs, so that also must be filtered out. + // + // In addition, "watchman" returns list of files modified without respect to + // "paths" parameter, so that also must be post-processed. + for r in files.iter() { + let pathbuf = expand_path(&r.name.clone().into_inner(), tree_root); // FIXME: Get rid of clone(). + match formatters.values().find(|fmt| fmt.is_match(&pathbuf)) { + // Watchman is not aware of exclusion globs. + None => {} + Some(fmt) => { + let pathbuf = expand_path(&pathbuf, tree_root); + // File changed is somewhere under "paths". + if paths.iter().any(|path| pathbuf.starts_with(path)) { + let meta = FileMeta { + size: r.size.clone().into_inner(), + mtime: r.mtime.clone().into_inner(), + }; + matches + .entry(fmt.name.clone()) + .or_default() + .insert(pathbuf, meta); + } + } + } + } + stats.timed_debug("post-process watchman list"); + matches + } + }; + stats.timed_debug("finish walking"); // Filter out all of the paths that were already in the cache let matches = if !no_cache { @@ -153,8 +282,21 @@ pub fn run_treefmt( stats.timed_debug("format"); if !no_cache { - // Record the new matches in the cache - cache.add_results(new_matches.clone()); + match watchman { + None => cache.add_results(new_matches.clone()), + // We only need "since" value in cache. + Some(watchman) => { + cache.clock = Some( + watchman + .clock(&resolved_root.unwrap(), Default::default()) + .await?, + ); + } + } + + if !watchman.is_some() { + cache.add_results(new_matches.clone()); + }; // And write to disk cache.write(cache_dir, treefmt_toml); stats.timed_debug("write cache"); diff --git a/src/eval_cache.rs b/src/eval_cache.rs index 4ca64495..57dd9f08 100644 --- a/src/eval_cache.rs +++ b/src/eval_cache.rs @@ -12,6 +12,7 @@ use std::collections::BTreeMap; use std::fs::{create_dir_all, read_to_string, File}; use std::io::Write; use std::path::{Path, PathBuf}; +use watchman_client::pdu::ClockSpec; /// Metadata about the formatter #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] @@ -37,6 +38,8 @@ pub struct FormatterInfo { #[derive(Debug, Default, Deserialize, Serialize)] /// RootManifest pub struct CacheManifest { + /// Timestamp of last full-tree formatting. + pub clock: Option, /// Map of all the formatter infos pub formatters: BTreeMap, /// Map of all the formatted paths @@ -46,6 +49,7 @@ pub struct CacheManifest { impl Clone for CacheManifest { fn clone(&self) -> Self { Self { + clock: self.clock.clone(), formatters: self.formatters.clone(), matches: self.matches.clone(), } diff --git a/src/formatter.rs b/src/formatter.rs index 4b695106..4799b3f8 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -88,8 +88,18 @@ pub struct Formatter { pub work_dir: PathBuf, /// File or Folder that is included to be formatted pub includes: GlobSet, + /// The string representation of glob patterns that was used + /// to build `includes` member to be used to query `watchman(1). + /// + /// There is no interface to decompile `GlobSet`. + pub includes_str: Vec, /// File or Folder that is excluded to be formatted pub excludes: GlobSet, + /// The string representation of glob patterns that was used + /// to build `excludes` member to be used to query `watchman(1). + /// + /// There is no interface to decompile `GlobSet`. + pub excludes_str: Vec, } impl Formatter { @@ -191,7 +201,10 @@ impl Formatter { options: cfg.options.clone(), work_dir, includes, + // TODO: Avoid clone(). + includes_str: cfg.includes.clone(), excludes, + excludes_str: cfg.excludes.clone(), }) } }