diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0401b8734..2a9021ced 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New features
+* Support background filesystem monitoring via watchman triggers enabled with
+ the `core.watchman.register_trigger = true` config.
+
* Show paths to config files when configuration errors occur
* `jj fix` now supports configuring the default revset for `-s` using the
diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs
index d8b83e0b0..72af109d3 100644
--- a/cli/src/cli_util.rs
+++ b/cli/src/cli_util.rs
@@ -1222,7 +1222,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
let progress = crate::progress::snapshot_progress(ui);
let new_tree_id = locked_ws.locked_wc().snapshot(SnapshotOptions {
base_ignores,
- fsmonitor_kind: self.settings.fsmonitor_kind()?,
+ fsmonitor_settings: self.settings.fsmonitor_settings()?,
progress: progress.as_ref().map(|x| x as _),
max_new_file_size: self.settings.max_new_file_size()?,
})?;
diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs
index 79401c0cb..f2489820a 100644
--- a/cli/src/commands/debug.rs
+++ b/cli/src/commands/debug.rs
@@ -19,6 +19,7 @@ use std::io::Write as _;
use clap::Subcommand;
use jj_lib::backend::TreeId;
use jj_lib::default_index::{AsCompositeIndex as _, DefaultIndexStore, DefaultReadonlyIndex};
+use jj_lib::fsmonitor::{FsmonitorSettings, WatchmanConfig};
use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::merged_tree::MergedTree;
use jj_lib::object_id::ObjectId;
@@ -374,11 +375,11 @@ fn cmd_debug_watchman(
match subcommand {
DebugWatchmanSubcommand::Status => {
// TODO(ilyagr): It would be nice to add colors here
- match command.settings().fsmonitor_kind()? {
- jj_lib::fsmonitor::FsmonitorKind::Watchman => {
+ match command.settings().fsmonitor_settings()? {
+ FsmonitorSettings::Watchman { .. } => {
writeln!(ui.stdout(), "Watchman is enabled via `core.fsmonitor`.")?
}
- jj_lib::fsmonitor::FsmonitorKind::None => writeln!(
+ FsmonitorSettings::None => writeln!(
ui.stdout(),
"Watchman is disabled. Set `core.fsmonitor=\"watchman\"` to \
enable.\nAttempting to contact the `watchman` CLI regardless..."
@@ -391,7 +392,7 @@ fn cmd_debug_watchman(
}
};
let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?;
- let _ = wc.query_watchman()?;
+ let _ = wc.query_watchman(&WatchmanConfig::default())?;
writeln!(
ui.stdout(),
"The watchman server seems to be installed and working correctly."
@@ -399,12 +400,12 @@ fn cmd_debug_watchman(
}
DebugWatchmanSubcommand::QueryClock => {
let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?;
- let (clock, _changed_files) = wc.query_watchman()?;
+ let (clock, _changed_files) = wc.query_watchman(&WatchmanConfig::default())?;
writeln!(ui.stdout(), "Clock: {clock:?}")?;
}
DebugWatchmanSubcommand::QueryChangedFiles => {
let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?;
- let (_clock, changed_files) = wc.query_watchman()?;
+ let (_clock, changed_files) = wc.query_watchman(&WatchmanConfig::default())?;
writeln!(ui.stdout(), "Changed files: {changed_files:?}")?;
}
DebugWatchmanSubcommand::ResetClock => {
diff --git a/cli/src/commands/untrack.rs b/cli/src/commands/untrack.rs
index 6fbee3f97..449724468 100644
--- a/cli/src/commands/untrack.rs
+++ b/cli/src/commands/untrack.rs
@@ -69,7 +69,7 @@ pub(crate) fn cmd_untrack(
// untracked because they're not ignored.
let wc_tree_id = locked_ws.locked_wc().snapshot(SnapshotOptions {
base_ignores,
- fsmonitor_kind: command.settings().fsmonitor_kind()?,
+ fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
max_new_file_size: command.settings().max_new_file_size()?,
})?;
diff --git a/cli/src/config-schema.json b/cli/src/config-schema.json
index 25934f95d..5fb08e129 100644
--- a/cli/src/config-schema.json
+++ b/cli/src/config-schema.json
@@ -168,6 +168,16 @@
"type": "string",
"enum": ["none", "watchman"],
"description": "Whether to use an external filesystem monitor, useful for large repos"
+ },
+ "watchman": {
+ "type": "object",
+ "properties": {
+ "register_snapshot_trigger": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether to use triggers to monitor for changes in the background."
+ }
+ }
}
}
},
diff --git a/cli/src/merge_tools/diff_working_copies.rs b/cli/src/merge_tools/diff_working_copies.rs
index 63a4f60c4..d87d4c1cc 100644
--- a/cli/src/merge_tools/diff_working_copies.rs
+++ b/cli/src/merge_tools/diff_working_copies.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
use futures::StreamExt;
use jj_lib::backend::MergedTreeId;
-use jj_lib::fsmonitor::FsmonitorKind;
+use jj_lib::fsmonitor::FsmonitorSettings;
use jj_lib::gitignore::GitIgnoreFile;
use jj_lib::local_working_copy::{TreeState, TreeStateError};
use jj_lib::matchers::Matcher;
@@ -279,7 +279,7 @@ diff editing in mind and be a little inaccurate.
.unwrap_or(diff_wc.right_tree_state);
output_tree_state.snapshot(SnapshotOptions {
base_ignores,
- fsmonitor_kind: FsmonitorKind::None,
+ fsmonitor_settings: FsmonitorSettings::None,
progress: None,
max_new_file_size: u64::MAX,
})?;
diff --git a/docs/config.md b/docs/config.md
index 35ebf4e4f..d75a5de94 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -800,6 +800,10 @@ To configure the Watchman filesystem monitor, set
`core.fsmonitor = "watchman"`. Ensure that you have [installed the Watchman
executable on your system](https://facebook.github.io/watchman/docs/install).
+You can configure `jj` to use watchman triggers to automatically create
+snapshots on filestem changes by setting
+`core.watchman.register_snapshot_trigger = true`.
+
You can check whether Watchman is enabled and whether it is installed correctly
using `jj debug watchman status`.
diff --git a/lib/src/fsmonitor.rs b/lib/src/fsmonitor.rs
index 21351340e..7626682bf 100644
--- a/lib/src/fsmonitor.rs
+++ b/lib/src/fsmonitor.rs
@@ -21,13 +21,23 @@
#![warn(missing_docs)]
use std::path::PathBuf;
-use std::str::FromStr;
+
+use config::{Config, ConfigError};
+
+use crate::settings::ConfigResultExt;
+
+/// Config for Watchman filesystem monitor ().
+#[derive(Default, Eq, PartialEq, Clone, Debug)]
+pub struct WatchmanConfig {
+ /// Whether to use triggers to monitor for changes in the background.
+ register_trigger: bool,
+}
/// The recognized kinds of filesystem monitors.
#[derive(Eq, PartialEq, Clone, Debug)]
-pub enum FsmonitorKind {
+pub enum FsmonitorSettings {
/// The Watchman filesystem monitor ().
- Watchman,
+ Watchman(WatchmanConfig),
/// Only used in tests.
Test {
@@ -44,20 +54,27 @@ pub enum FsmonitorKind {
None,
}
-impl FromStr for FsmonitorKind {
- type Err = config::ConfigError;
-
- fn from_str(s: &str) -> Result {
- match s {
- "watchman" => Ok(Self::Watchman),
- "test" => Err(config::ConfigError::Message(
- "cannot use test fsmonitor in real repository".to_string(),
- )),
- "none" => Ok(Self::None),
- other => Err(config::ConfigError::Message(format!(
- "unknown fsmonitor kind: {}",
- other
- ))),
+impl FsmonitorSettings {
+ /// Creates an `FsmonitorSettings` from a `config`.
+ pub fn from_config(config: &Config) -> Result {
+ match config.get_string("core.fsmonitor") {
+ Ok(s) => match s.as_str() {
+ "watchman" => Ok(Self::Watchman(WatchmanConfig {
+ register_trigger: config
+ .get_bool("core.watchman.register_snapshot_trigger")
+ .optional()?
+ .unwrap_or_default(),
+ })),
+ "test" => Err(ConfigError::Message(
+ "cannot use test fsmonitor in real repository".to_string(),
+ )),
+ "none" => Ok(Self::None),
+ other => Err(ConfigError::Message(format!(
+ "unknown fsmonitor kind: {other}",
+ ))),
+ },
+ Err(ConfigError::NotFound(_)) => Ok(Self::None),
+ Err(err) => Err(err),
}
}
}
@@ -77,6 +94,10 @@ pub mod watchman {
Clock as InnerClock, ClockSpec, NameOnly, QueryRequestCommon, QueryResult,
};
+ use crate::fsmonitor_watchman_extensions::{
+ list_triggers, register_trigger, remove_trigger, TriggerRequest,
+ };
+
/// Represents an instance in time from the perspective of the filesystem
/// monitor.
///
@@ -139,6 +160,9 @@ pub mod watchman {
#[error("Failed to query Watchman")]
WatchmanQueryError(#[source] watchman_client::Error),
+
+ #[error("Failed to register Watchman trigger")]
+ WatchmanTriggerError(#[source] watchman_client::Error),
}
/// Handle to the underlying Watchman instance.
@@ -153,7 +177,10 @@ pub mod watchman {
/// copy to build up its in-memory representation of the
/// filesystem, which may take some time.
#[instrument]
- pub async fn init(working_copy_path: &Path) -> Result {
+ pub async fn init(
+ working_copy_path: &Path,
+ config: &super::WatchmanConfig,
+ ) -> Result {
info!("Initializing Watchman filesystem monitor...");
let connector = watchman_client::Connector::new();
let client = connector
@@ -166,10 +193,20 @@ pub mod watchman {
.resolve_root(working_copy_root)
.await
.map_err(Error::ResolveRootError)?;
- Ok(Fsmonitor {
+
+ let monitor = Fsmonitor {
client,
resolved_root,
- })
+ };
+
+ // Registering the trigger causes an unconditional evaluation of the query, so
+ // test if it is already registered first.
+ if !config.register_trigger {
+ monitor.unregister_trigger().await?;
+ } else if !monitor.is_trigger_registered().await? {
+ monitor.register_trigger().await?;
+ }
+ Ok(monitor)
}
/// Query for changed files since the previous point in time.
@@ -184,24 +221,6 @@ pub mod watchman {
) -> Result<(Clock, Option>), Error> {
// TODO: might be better to specify query options by caller, but we
// shouldn't expose the underlying watchman API too much.
- let exclude_dirs = [Path::new(".git"), Path::new(".jj")];
- let excludes = itertools::chain(
- // the directories themselves
- [expr::Expr::Name(expr::NameTerm {
- paths: exclude_dirs.iter().map(|&name| name.to_owned()).collect(),
- wholename: true,
- })],
- // and all files under the directories
- exclude_dirs.iter().map(|&name| {
- expr::Expr::DirName(expr::DirNameTerm {
- path: name.to_owned(),
- depth: None,
- })
- }),
- )
- .collect();
- let expression = expr::Expr::Not(Box::new(expr::Expr::Any(excludes)));
-
info!("Querying Watchman for changed files...");
let QueryResult {
version: _,
@@ -219,7 +238,7 @@ pub mod watchman {
&self.resolved_root,
QueryRequestCommon {
since: previous_clock.map(|Clock(clock)| clock),
- expression: Some(expression),
+ expression: Some(self.build_exclude_expr()),
..Default::default()
},
)
@@ -242,5 +261,73 @@ pub mod watchman {
Ok((clock, Some(paths)))
}
}
+
+ /// Return whether or not a trigger has been registered already.
+ #[instrument(skip(self))]
+ async fn is_trigger_registered(&self) -> Result {
+ info!("Checking for an existing Watchman trigger...");
+ Ok(list_triggers(&self.client, &self.resolved_root)
+ .await
+ .map_err(Error::WatchmanTriggerError)?
+ .triggers
+ .iter()
+ .any(|t| t.name == "jj-background-monitor"))
+ }
+
+ /// Register trigger for changed files.
+ #[instrument(skip(self))]
+ async fn register_trigger(&self) -> Result<(), Error> {
+ info!("Registering Watchman trigger...");
+ register_trigger(
+ &self.client,
+ &self.resolved_root,
+ TriggerRequest {
+ name: "jj-background-monitor".to_string(),
+ command: vec![
+ "jj".to_string(),
+ "files".to_string(),
+ "-r".to_string(),
+ "root()".to_string(),
+ ],
+ expression: Some(self.build_exclude_expr()),
+ ..Default::default()
+ },
+ )
+ .await
+ .map_err(Error::WatchmanTriggerError)?;
+ Ok(())
+ }
+
+ /// Register trigger for changed files.
+ #[instrument(skip(self))]
+ async fn unregister_trigger(&self) -> Result<(), Error> {
+ info!("Unregistering Watchman trigger...");
+ remove_trigger(&self.client, &self.resolved_root, "jj-background-monitor")
+ .await
+ .map_err(Error::WatchmanTriggerError)?;
+ Ok(())
+ }
+
+ /// Build an exclude expr for `working_copy_path`.
+ fn build_exclude_expr(&self) -> expr::Expr {
+ // TODO: consider parsing `.gitignore`.
+ let exclude_dirs = [Path::new(".git"), Path::new(".jj")];
+ let excludes = itertools::chain(
+ // the directories themselves
+ [expr::Expr::Name(expr::NameTerm {
+ paths: exclude_dirs.iter().map(|&name| name.to_owned()).collect(),
+ wholename: true,
+ })],
+ // and all files under the directories
+ exclude_dirs.iter().map(|&name| {
+ expr::Expr::DirName(expr::DirNameTerm {
+ path: name.to_owned(),
+ depth: None,
+ })
+ }),
+ )
+ .collect();
+ expr::Expr::Not(Box::new(expr::Expr::Any(excludes)))
+ }
}
}
diff --git a/lib/src/fsmonitor_watchman_extensions.rs b/lib/src/fsmonitor_watchman_extensions.rs
new file mode 100644
index 000000000..6183c40ca
--- /dev/null
+++ b/lib/src/fsmonitor_watchman_extensions.rs
@@ -0,0 +1,186 @@
+// Copyright 2024 The Jujutsu Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// TODO: remove this file after watchman adopts and releases it.
+// https://github.com/facebook/watchman/pull/1221
+#![allow(missing_docs)]
+
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize, Serializer};
+use watchman_client::expr::Expr;
+use watchman_client::{Client, Error, ResolvedRoot};
+
+/// Registers a trigger.
+pub async fn register_trigger(
+ client: &Client,
+ root: &ResolvedRoot,
+ request: TriggerRequest,
+) -> Result {
+ let response: TriggerResponse = client
+ .generic_request(TriggerCommand(
+ "trigger",
+ root.project_root().to_path_buf(),
+ request,
+ ))
+ .await?;
+ Ok(response)
+}
+
+/// Removes a registered trigger.
+pub async fn remove_trigger(
+ client: &Client,
+ root: &ResolvedRoot,
+ name: &str,
+) -> Result {
+ let response: TriggerDelResponse = client
+ .generic_request(TriggerDelCommand(
+ "trigger-del",
+ root.project_root().to_path_buf(),
+ name.into(),
+ ))
+ .await?;
+ Ok(response)
+}
+
+/// Lists registered triggers.
+pub async fn list_triggers(
+ client: &Client,
+ root: &ResolvedRoot,
+) -> Result {
+ let response: TriggerListResponse = client
+ .generic_request(TriggerListCommand(
+ "trigger-list",
+ root.project_root().to_path_buf(),
+ ))
+ .await?;
+ Ok(response)
+}
+
+/// The `trigger` command request.
+///
+/// The fields are explained in detail here:
+///
+#[derive(Deserialize, Serialize, Default, Clone, Debug)]
+pub struct TriggerRequest {
+ /// Defines the name of the trigger.
+ pub name: String,
+
+ /// Specifies the command to invoke.
+ pub command: Vec,
+
+ /// It true, matching files (up to system limits) will be added to the
+ /// command's execution args.
+ #[serde(default, skip_serializing_if = "is_false")]
+ pub append_files: bool,
+
+ /// Specifies the expression used to filter candidate matches.
+ #[serde(skip_serializing_if = "Option::is_none", skip_deserializing)]
+ pub expression: Option,
+
+ /// Configure the way `stdin` is configured for the executed trigger.
+ #[serde(
+ default,
+ skip_serializing_if = "TriggerStdinConfig::is_devnull",
+ serialize_with = "TriggerStdinConfig::serialize",
+ skip_deserializing
+ )]
+ pub stdin: TriggerStdinConfig,
+
+ /// Specifies a file to write the output stream to. Prefix with `>` to
+ /// overwrite and `>>` to append.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub stdout: Option,
+
+ /// Specifies a file to write the error stream to. Prefix with `>` to
+ /// overwrite and `>>` to append.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub stderr: Option,
+
+ /// Specifies a limit on the number of files reported on stdin when stdin is
+ /// set to hold the set of matched files.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub max_files_stdin: Option,
+
+ /// Specifies the working directory that will be set prior to spawning the
+ /// process. The default is to set the working directory to the watched
+ /// root. The value of this property is a string that will be interpreted
+ /// relative to the watched root.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub chdir: Option,
+}
+
+#[derive(Clone, Debug)]
+pub enum TriggerStdinConfig {
+ DevNull,
+ FieldNames(Vec),
+ NamePerLine,
+}
+
+impl Default for TriggerStdinConfig {
+ fn default() -> Self {
+ Self::DevNull
+ }
+}
+
+impl TriggerStdinConfig {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ match self {
+ Self::DevNull => serializer.serialize_str("/dev/null"),
+ Self::FieldNames(names) => serializer.collect_seq(names.iter()),
+ Self::NamePerLine => serializer.serialize_str("NAME_PER_LINE"),
+ }
+ }
+
+ fn is_devnull(&self) -> bool {
+ matches!(self, Self::DevNull)
+ }
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub struct TriggerCommand(pub &'static str, pub PathBuf, pub TriggerRequest);
+
+#[derive(Deserialize, Debug)]
+pub struct TriggerResponse {
+ pub version: String,
+ pub disposition: String,
+ pub triggerid: String,
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub struct TriggerDelCommand(pub &'static str, pub PathBuf, pub String);
+
+#[derive(Deserialize, Debug)]
+pub struct TriggerDelResponse {
+ pub version: String,
+ pub deleted: bool,
+ pub trigger: String,
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub struct TriggerListCommand(pub &'static str, pub PathBuf);
+
+#[derive(Deserialize, Debug)]
+pub struct TriggerListResponse {
+ pub version: String,
+ pub triggers: Vec,
+}
+
+#[allow(clippy::trivially_copy_pass_by_ref)]
+fn is_false(v: &bool) -> bool {
+ !*v
+}
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index 149956ad2..f5ac9f18d 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -44,6 +44,8 @@ pub mod fileset;
mod fileset_parser;
pub mod fmt_util;
pub mod fsmonitor;
+#[cfg(feature = "watchman")]
+pub mod fsmonitor_watchman_extensions;
#[cfg(feature = "git")]
pub mod git;
#[cfg(feature = "git")]
diff --git a/lib/src/local_working_copy.rs b/lib/src/local_working_copy.rs
index d45338f20..e4c417e6c 100644
--- a/lib/src/local_working_copy.rs
+++ b/lib/src/local_working_copy.rs
@@ -49,7 +49,7 @@ use crate::conflicts::{self, materialize_tree_value, MaterializedTreeValue};
use crate::file_util::{check_symlink_support, try_symlink};
#[cfg(feature = "watchman")]
use crate::fsmonitor::watchman;
-use crate::fsmonitor::FsmonitorKind;
+use crate::fsmonitor::{FsmonitorSettings, WatchmanConfig};
use crate::gitignore::GitIgnoreFile;
use crate::lock::FileLock;
use crate::matchers::{
@@ -726,8 +726,9 @@ impl TreeState {
#[instrument(skip(self))]
pub async fn query_watchman(
&self,
+ config: &WatchmanConfig,
) -> Result<(watchman::Clock, Option>), TreeStateError> {
- let fsmonitor = watchman::Fsmonitor::init(&self.working_copy_path)
+ let fsmonitor = watchman::Fsmonitor::init(&self.working_copy_path, config)
.await
.map_err(|err| TreeStateError::Fsmonitor(Box::new(err)))?;
let previous_clock = self.watchman_clock.clone().map(watchman::Clock::from);
@@ -744,19 +745,19 @@ impl TreeState {
pub fn snapshot(&mut self, options: SnapshotOptions) -> Result {
let SnapshotOptions {
base_ignores,
- fsmonitor_kind,
+ fsmonitor_settings,
progress,
max_new_file_size,
} = options;
let sparse_matcher = self.sparse_matcher();
- let fsmonitor_clock_needs_save = fsmonitor_kind != FsmonitorKind::None;
+ let fsmonitor_clock_needs_save = fsmonitor_settings != FsmonitorSettings::None;
let mut is_dirty = fsmonitor_clock_needs_save;
let FsmonitorMatcher {
matcher: fsmonitor_matcher,
watchman_clock,
- } = self.make_fsmonitor_matcher(fsmonitor_kind)?;
+ } = self.make_fsmonitor_matcher(fsmonitor_settings)?;
let fsmonitor_matcher = match fsmonitor_matcher.as_ref() {
None => &EverythingMatcher,
Some(fsmonitor_matcher) => fsmonitor_matcher.as_ref(),
@@ -1024,13 +1025,13 @@ impl TreeState {
#[instrument(skip_all)]
fn make_fsmonitor_matcher(
&self,
- fsmonitor_kind: FsmonitorKind,
+ fsmonitor_settings: FsmonitorSettings,
) -> Result {
- let (watchman_clock, changed_files) = match fsmonitor_kind {
- FsmonitorKind::None => (None, None),
- FsmonitorKind::Test { changed_files } => (None, Some(changed_files)),
+ let (watchman_clock, changed_files) = match fsmonitor_settings {
+ FsmonitorSettings::None => (None, None),
+ FsmonitorSettings::Test { changed_files } => (None, Some(changed_files)),
#[cfg(feature = "watchman")]
- FsmonitorKind::Watchman => match self.query_watchman() {
+ FsmonitorSettings::Watchman(config) => match self.query_watchman(&config) {
Ok((watchman_clock, changed_files)) => (Some(watchman_clock.into()), changed_files),
Err(err) => {
tracing::warn!(?err, "Failed to query filesystem monitor");
@@ -1038,7 +1039,7 @@ impl TreeState {
}
},
#[cfg(not(feature = "watchman"))]
- FsmonitorKind::Watchman => {
+ FsmonitorSettings::Watchman(_) => {
return Err(SnapshotError::Other {
message: "Failed to query the filesystem monitor".to_string(),
err: "Cannot query Watchman because jj was not compiled with the `watchman` \
@@ -1686,9 +1687,10 @@ impl LocalWorkingCopy {
#[cfg(feature = "watchman")]
pub fn query_watchman(
&self,
+ config: &WatchmanConfig,
) -> Result<(watchman::Clock, Option>), WorkingCopyStateError> {
self.tree_state()?
- .query_watchman()
+ .query_watchman(config)
.map_err(|err| WorkingCopyStateError {
message: "Failed to query watchman".to_string(),
err: err.into(),
diff --git a/lib/src/settings.rs b/lib/src/settings.rs
index 8497ced2a..c0a17ef3b 100644
--- a/lib/src/settings.rs
+++ b/lib/src/settings.rs
@@ -23,7 +23,7 @@ use rand_chacha::ChaCha20Rng;
use crate::backend::{ChangeId, Commit, Signature, Timestamp};
use crate::fmt_util::binary_prefix;
-use crate::fsmonitor::FsmonitorKind;
+use crate::fsmonitor::FsmonitorSettings;
use crate::signing::SignBehavior;
#[derive(Debug, Clone)]
@@ -164,12 +164,8 @@ impl UserSettings {
self.config.get_string("user.email").unwrap_or_default()
}
- pub fn fsmonitor_kind(&self) -> Result {
- match self.config.get_string("core.fsmonitor") {
- Ok(fsmonitor_kind) => Ok(fsmonitor_kind.parse()?),
- Err(config::ConfigError::NotFound(_)) => Ok(FsmonitorKind::None),
- Err(err) => Err(err),
- }
+ pub fn fsmonitor_settings(&self) -> Result {
+ FsmonitorSettings::from_config(&self.config)
}
// Must not be changed to avoid git pushing older commits with no set email
diff --git a/lib/src/working_copy.rs b/lib/src/working_copy.rs
index 5760487e5..5d414719e 100644
--- a/lib/src/working_copy.rs
+++ b/lib/src/working_copy.rs
@@ -24,7 +24,7 @@ use thiserror::Error;
use crate::backend::{BackendError, MergedTreeId};
use crate::commit::Commit;
-use crate::fsmonitor::FsmonitorKind;
+use crate::fsmonitor::FsmonitorSettings;
use crate::gitignore::{GitIgnoreError, GitIgnoreFile};
use crate::op_store::{OperationId, WorkspaceId};
use crate::repo_path::{RepoPath, RepoPathBuf};
@@ -189,7 +189,7 @@ pub struct SnapshotOptions<'a> {
/// The fsmonitor (e.g. Watchman) to use, if any.
// TODO: Should we make this a field on `LocalWorkingCopy` instead since it's quite specific to
// that implementation?
- pub fsmonitor_kind: FsmonitorKind,
+ pub fsmonitor_settings: FsmonitorSettings,
/// A callback for the UI to display progress.
pub progress: Option<&'a SnapshotProgress<'a>>,
/// The size of the largest file that should be allowed to become tracked
@@ -205,7 +205,7 @@ impl SnapshotOptions<'_> {
pub fn empty_for_test() -> Self {
SnapshotOptions {
base_ignores: GitIgnoreFile::empty(),
- fsmonitor_kind: FsmonitorKind::None,
+ fsmonitor_settings: FsmonitorSettings::None,
progress: None,
max_new_file_size: u64::MAX,
}
diff --git a/lib/tests/test_local_working_copy.rs b/lib/tests/test_local_working_copy.rs
index 82e100407..361aa7aed 100644
--- a/lib/tests/test_local_working_copy.rs
+++ b/lib/tests/test_local_working_copy.rs
@@ -23,7 +23,7 @@ use indoc::indoc;
use itertools::Itertools;
use jj_lib::backend::{MergedTreeId, TreeId, TreeValue};
use jj_lib::file_util::{check_symlink_support, try_symlink};
-use jj_lib::fsmonitor::FsmonitorKind;
+use jj_lib::fsmonitor::FsmonitorSettings;
use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::merge::{Merge, MergedTreeValue};
use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder};
@@ -1183,7 +1183,7 @@ fn test_fsmonitor() {
locked_ws
.locked_wc()
.snapshot(SnapshotOptions {
- fsmonitor_kind: FsmonitorKind::Test {
+ fsmonitor_settings: FsmonitorSettings::Test {
changed_files: fs_paths,
},
..SnapshotOptions::empty_for_test()