☰
watchexec-filterer-globset:Watchexec 默认事件过滤器的语义、源码与演进全解析
2026/9/29 5:27:42 网站建设 项目流程
  • 开发工具
  • CLI

【免费下载链接】watchexec

Executes commands in response to file modifications

项目地址:https://gitcode.com/gh_mirrors/wa/watchexec
点击查看免费下载

本篇文章围绕 Watchexec 生态中承担默认过滤职责的watchexec-filterer-globsetcrate 展开,结合其 CHANGELOG.md、README.md 与 源码实现,完整讲解它的定位、构造 API、事件级与目录级双重过滤语义、ignore 文件集成、CLI 参数映射以及从 v1.0.0 到 v8.0.4 的演进脉络。读完本文,你将理解 Watchexec 的--filter、--ignore、--exts等选项在底层究竟如何被解释执行,也能掌握自顶向下剪枝语义这一关键设计对文件监听性能的影响。

一、定位:Watchexec 的默认过滤实现

watchexec-filterer-globset是 Watchexec 工作区中的一个独立 crate,其 README 明确自述为:

The default filterer implementation for Watchexec.

也就是说,Watchexec CLI 当前使用的默认过滤器正是它。它是一套"仅基于路径"(path-only)的过滤器,在 crates/filterer/globset/src/lib.rs 的 crate 文档中这样描述:

本过滤器模拟watchexecv1 过滤器的行为,但由于内部实现不同,并不与其完全一致。它目前被用作 Watchexec CLI 的默认过滤器。

在 Watchexec 架构中,"过滤"由库级 trait 定义。crates/lib/src/filter.rs 中的Filterertrait 只要求实现者提供两个同步方法:

  • check_dir(&self, path) -> Result<bool, RuntimeError>:在协调文件系统事件源时被调用,用于决定一个目录是否应被纳入监听。返回false会把该目录连同其后代整体排除出事件源;文档明确要求"仅当该目录下所有事件都可以安全忽略时才拒绝它"。
  • check_event(&self, event, priority) -> Result<bool, RuntimeError>:对(几乎)每一个事件调用,返回false表示丢弃该事件。

两个方法都被要求"同步、快速、不得阻塞线程",任何昂贵的工作都应该在构造阶段完成。GlobsetFilterer正是遵循这一契约的实现:它在new()中完成所有编译与快照,运行时只做匹配。

二、核心构造:GlobsetFilterer::new的六类输入

GlobsetFilterer的结构体定义(crates/filterer/globset/src/lib.rs)包含六个成员,与构造函数一一对应:

构造参数成员类型作用
originPathBuf项目原点(project origin),所有 glob 的相对基准
filtersGitignore正向过滤 glob 集合:只有匹配的路径才能通过
ignoresGitignore反向忽略 glob 集合:匹配的路径将被拒绝
whitelistHashSet<PathBuf>精确路径白名单:即使被过滤也强制放行
ignore_filesIgnoreFilterer来自 ignore 文件(.gitignore等)的过滤规则
extensionsVec<OsString>扩展名白名单:只放行这些扩展名的文件

构造函数的完整签名(crates/filterer/globset/src/lib.rs):

pub async fn new( origin: impl AsRef<Path>, filters: impl IntoIterator<Item = (String, Option<PathBuf>)>, ignores: impl IntoIterator<Item = (String, Option<PathBuf>)>, whitelist: impl IntoIterator<Item = PathBuf>, ignore_files: impl IntoIterator<Item = IgnoreFile>, extensions: impl IntoIterator<Item = OsString>, ) -> Result<Self, Error>

几个关键的构造语义:

  • glob 的作用域:filters与ignores每一项都是(模式字符串, Option<路径>)元组。Some(path)表示该模式只在指定文件夹内生效(例如某个.gitignore所在的目录),None表示全局生效。构造时二者分别喂给GitignoreBuilder::add_line编译成两棵独立的Gitignore结构。
  • 原点规范化:origin先经dunce::canonicalize得到真实路径,再经simplify_path(即dunce::simplified(path).normalize())消解..与路径别名(crates/filterer/globset/src/lib.rs)。白名单路径同样在构造期做simplify_path,保证与事件路径比较时二者处于同一形态。
  • 空集合语义:若filters为空,则只使用ignores;若两者皆为空,过滤器对所有路径放行(对应测试 empty_filter_passes_everything)。
  • 错误处理:路径规范化失败产生Error::Canonicalize,glob 编译失败产生Error::Glob,二者都来自ignore_filescrate 的错误体系。
  • full_debug feature:Gitignore类型的Debug实现会输出大量内部信息,默认实现将其遮蔽为"ignore::gitignore::Gitignore{...}";启用 Cargo.toml 中定义的full_debugfeature 后恢复完整Debug(这正是 CHANGELOG v4.0.1 的改动内容)。

三、事件级过滤(check_event)的判定顺序

check_event是过滤器的主战场。它的执行顺序(crates/filterer/globset/src/lib.rs)如下:

  1. 路径规范化:克隆事件,把所有Tag::Path中的路径做simplify_path处理,后续所有比较都基于规范化后的路径。
  2. 白名单短路:若白名单非空,且事件携带的任一路径命中白名单,直接放行(return Ok(true)),不再执行任何后续检查。
  3. ignore 文件检查:交给内部IgnoreFilterer.check_event;被 ignore 文件命中则拒绝。该调用被expect("IgnoreFilterer never errors")包裹,因为IgnoreFilterer的实现保证不返回错误(crates/filterer/ignore/src/lib.rs)。
  4. 非路径事件放行:如果事件不携带任何路径 tag(例如键盘、信号、内部事件),直接通过。
  5. 逐路径判定:对事件中的每个路径执行paths.any(...)——只要有一个路径通过,事件即通过。每个路径依次经过:
    • globset ignore 检查:ignored_by_globs(见下节),被忽略则此路径失败;
    • 正向过滤:若filters非空,用filters.matched(path, is_dir)匹配,命中is_ignore(即被该 glob 包含)则放行;
    • 1.x 兼容重映射:在 Unix 上保留了一个 Watchexec 1.x 的兼容行为(代码注释标记为"1.x bug, TODO remove at 2.0"):当路径位于 origin 之下时,会构造一个origin + 双分隔符 + 相对路径的"重映射路径"再匹配一次,模拟 v1 的怪异行为(对应测试 extensions_and_filters_glob 中注释"Watchexec 1.x buggy behaviour"的部分);
    • 扩展名过滤:若extensions非空,目录直接失败;有扩展名则看是否命中集合中的任意一项;无扩展名的文件失败(extensions_fail_extensionless);
    • 默认放行:若以上过滤条件一个都没启用(filtered == false),路径通过。

值得注意的优先级结论(对应测试 ignores_take_precedence):忽略规则优先于正向过滤——即使某个路径同时命中filters与ignores,结果仍是拒绝。而--exts扩展名过滤则是"与"关系之外的追加条件:既有 glob 过滤又有扩展名过滤时,两者必须其一命中才通过。

四、目录级过滤(check_dir):v8.0.2 引入的源码目录剪枝

check_dir是 v8.0.2 版本的核心新特性(CHANGELOG 原文):

Add source-directory filtering from ignore files and ignore globs, with top-down ancestor semantics suitable for pruning recursive walks. Keep positive filters, extension filters, and exact-path whitelists event-only so they do not prune possible matching descendants.

它的实现(crates/filterer/globset/src/lib.rs)非常简洁,只做两件事:

  1. 先交给ignore_files.check_dir(path);
  2. 再调用ignored_by_globs(path, true)(以目录身份匹配 ignore globs)。

ignored_by_globs(crates/filterer/globset/src/lib.rs)实现的是自顶向下祖先语义:

  • 先取路径相对 origin 的祖先链(path.ancestors(),跳过自身,截断到 origin 边界),再从靠近 origin 的一侧(顶层)向叶子方向逐个用self.ignores.matched(ancestor, true)匹配;
  • 一旦某个祖先被 ignore glob 命中,立即判定整棵子树被忽略——后代的否定(negation,!前缀)无法重新打开已经被剪枝的祖先;
  • 最后才匹配路径自身。

这种语义与文件系统遍历(walk)的方向一致,因此天然适合作为递归遍历的剪枝判断:遍历器在进入目录前先询问check_dir,被拒绝的目录连同其后代直接跳过,不必逐一检查。对应的测试包括:

  • source_checks_manual_ignore_boundary_parent_and_negation:**/prunes使prunes与prunes/nested目录级失败;
  • descendant_negation_does_not_reopen_ignored_parent:parent/+!parent/child时,parent/child目录仍失败——被忽略的父目录不可被后代否定重开;
  • explicitly_unignored_parent_allows_descendant_negation:只有先对祖先本身显式否定(!parent/),其下的否定才重新生效。

同时,v8.0.2 还明确了分层原则:正向过滤(filters)、扩展名过滤(extensions)、精确路径白名单(whitelist)只作用于事件级,不参与目录级判定。原因正如 CHANGELOG 所说:若用它们去剪枝目录,可能误伤"目录内含匹配后代"的情况。测试 source_checks_do_not_use_positive_filters_or_extensions 验证了:即使filters = ["**/*.rs"]、extensions = ["rs"],目录build、src在check_dir层面仍然放行;而 exact_whitelist_is_event_only_for_source_checks 验证了白名单"事件级放行、目录级仍拒绝"的行为。

此外,ignored_by_globs对边界情况有明确约定:路径在 origin 之外时,祖先列表为空,只精确匹配路径自身——因为过滤器不知道外部路径的遍历根,不能把项目相对 glob 应用到任意文件系统祖先上。测试 out_of_origin_paths_do_not_match_external_ancestors 与 relative_origin_stops_ancestor_matching_at_project_boundary 分别覆盖了外部路径与相对 origin 的场景。

五、ignore 文件集成:构造期快照,不监视改动

GlobsetFilterer的第五个参数是ignore_files: impl IntoIterator<Item = IgnoreFile>。IgnoreFile(crates/ignore-files/src/lib.rs)是一个携带三条元数据的结构:

  • path:ignore 文件路径;
  • applies_in:该文件生效的子树,None表示全局 ignore;
  • applies_to:该文件所属的项目类型(由project_originscrate 提供的ProjectType),例如仅当检测到 Git 仓库时才加载.gitignore。

构造时,这些文件被一次性编译进IgnoreFilter(来自ignore-filescrate),再包进 IgnoreFilterer(一个极薄的新类型包装,同时实现Filterer)。IgnoreFilterer的事件检查对每个路径调用match_path_or_ancestors——这正是与 globset 一致的自顶向下祖先语义(crates/ignore-files/src/filter.rs)。

一个重要行为在文档与 doc 注释中被反复强调:ignore 文件只在构造期间读取,不会被监视后续编辑;要加载新增或修改的 ignore 文件,必须重建过滤器(crates/filterer/globset/src/lib.rs)。这符合Filterertrait 的"昂贵工作放到构造阶段"契约。

六、在 Watchexec CLI 中如何组装

CLI 侧的组装代码在 crates/cli/src/filterer.rs:WatchexecFilterer包裹一个GlobsetFilterer,并在其外再叠加--fs-events文件系统事件类型过滤与--filter-progjaq 程序过滤。六个构造参数分别来自命令行:

GlobsetFilterer::new参数对应 CLI 选项说明
origin--project-origin(或自动发现)项目原点;未指定时由 dirs::project_origin 按标记文件向上探测
filters--filter/-f、--filter-file正向 glob,作用于工作目录(workdir)
ignores--ignore/-i、--ignore-file及内置默认 ignore反向 glob
whitelist--watch/-w、--watch-non-recursive等被监听路径被显式监听的确切路径自动进入白名单,即使被 ignore 规则覆盖也会收到事件
ignore_files--no-global-ignore、--no-vcs-ignore、--no-project-ignore、--no-discover-ignore等开关控制的自动发现见下
extensions--exts/-e支持.js或js两种写法,--exts解析时自动剥离前导点(crates/cli/src/filterer.rs)

内置的默认 ignore 集合(除非--no-default-ignore)包括:.DS_Store、watchexec.*.log、*.py[co]、#*#、.#*、.*.kate-swp、.*.sw?、.*.sw?x,以及VCS_DIR_NAMES中的版本控制目录(.git、.hg、.svn、.bzr、_darcs、.fossil-settings、.pijul),每个目录同时匹配**/<目录>与**/<目录>/**两条 glob(crates/cli/src/filterer.rs)。

ignore 文件的自动发现则由ignore-filescrate 完成,覆盖 Git(.gitignore、.git/info/exclude、core.excludesFile)、Mercurial、Bazaar、Darcs、Fossil 以及 Watchexec 自身的.ignore,全局与项目级文件各有明确的查找顺序(crates/cli/src/args/filtering.rs)。--ignore-nothing是一个总开关,等价于同时禁用自动发现与默认 ignore(显式传入的--ignore仍生效)。

七、版本演进:从独立 crate 到默认过滤器(CHANGELOG 全记录)

CHANGELOG.md 记录了该 crate 的完整演进史,摘要如下:

  • v1.0.0(2022-06-23):作为独立 crate 首次发布。
  • v1.0.1(2022-09-07):依赖更新,miette 升至 5.3.0。
  • v1.1.0(2023-01-09):MSRV(最低支持 Rust 版本)提升到 1.61.0。
  • v1.2.0(2023-03-18):放弃 MSRV 策略(PR #510)。rust-version字段保留,仅用于指示 crate 自身代码所需的最低 Rust 版本估计值,但依赖可能早已前进;此后只假定并测试最新稳定版。
  • v2.0.1(2023-12-09):改为直接依赖watchexec-events,不再经过watchexec的 re-export。
  • v3.0.0(2024-01-01):引入对watchexec-filterer-ignore与ignore-files的依赖——ignore 文件支持由此接入。
  • v4.0.0(2024-04-20):升级到 watchexec 4。
  • v4.0.1(2024-04-28):默认隐藏ignorecrate 的Debug输出噪音,可用full_debugfeature 恢复。
  • v5.0.0(2024-10-13):新增 whitelist 参数——精确路径白名单能力由此引入。
  • v6.0.0(2024-10-14):升级到 watchexec 5。
  • v7.0.0(2025-02-09):无条目说明(依赖或内部调整)。
  • v8.0.0(2025-05-15):无条目说明。
  • v8.0.1(2026-08-22):无条目说明。
  • v8.0.2(2026-08-24):引入目录级过滤——从 ignore 文件与 ignore globs 增加 source-directory 过滤,采用适用于递归遍历剪枝的自顶向下祖先语义;同时明确正向过滤、扩展名过滤与精确路径白名单保持事件级,不做目录剪枝(详见本文第四、五节)。
  • v8.0.3(2026-09-03)、v8.0.4(2026-09-15):无条目说明,当前最新版本,与 Cargo.toml 中version = "8.0.4"一致。

从时间线可以看出,该 crate 与watchexec库版本号同步跳跃(4→5→8),而rust-version = "1.61.0"至今保留在 Cargo.toml 中,作为对 1.2.0 决策的延续。CHANGELOG 中头部还有一条Next (YYYY-MM-DD)占位条目,表示未发布的下一版本待记录。

八、测试与行为验证

该 crate 的集成测试集中在 crates/filterer/globset/tests/filtering.rs,测试辅助设施(tests/helpers/mod.rs)提供了file_does_pass、dir_doesnt_pass等断言方法,把路径构造、事件打包、check_event调用封装成一行式断言。值得关注的测试族:

  • glob 语法语义:精确文件名(exact_filename)、带目录的精确名(exact_filename_in_folder)、隐藏目录(exact_filename_in_hidden_folder)、*尾缀(glob_single_final_ext_star)、*/与/前缀、**/possum、possum/**、apples/**/oranges等中缀双星模式,正向与忽略两组各有完整对照(如ignore_glob_trailing_double_star与glob_trailing_double_star);
  • 目录 ignore 与剪枝:裸匹配(ignore_folder_with_bare_match)、前导斜杠、尾随斜杠(目录专属 glob 不忽略边界文件本身)、**/prunes/**与**/prunes组合(ignore_folder_correctly_with_double_and_double_double_globs)——后者说明"/prunes/不匹配目录边界,遍历仍可能进入"这一细节;
  • 白名单优先级:whitelist_overrides_ignore、whitelist_overrides_ignore_files、嵌套白名单(whitelist_overrides_ignore_files_nested),以及路径别名归一化(direct_whitelist_normalises_constructor_and_event_aliases,构造时用first/../watched、事件用second/../watched);
  • 多路径事件:multipath_allow_on_any_one_pass验证"任一路径通过即通过";
  • 非路径事件:nonpath_event_passes验证键盘与内部事件必然放行;
  • 平台特化:Windows 下两个测试(direct_whitelist_simplifies_verbatim_windows_path等)验证\\?\前缀的 verbatim 路径被dunce::simplified归一化。

附:继续深入

  • 过滤器接口定义:crates/lib/src/filter.rs
  • 事件与优先级模型:crates/events/src/event.rs
  • ignore 文件解析与祖先匹配:crates/ignore-files/src/filter.rs
  • ignore 文件自动发现:crates/ignore-files/src/discover.rs
  • CLI 组装与命令行参数:crates/cli/src/filterer.rs、crates/cli/src/args/filtering.rs
  • 依赖关系与 feature 定义:crates/filterer/globset/Cargo.toml
  • 开发工具
  • CLI

【免费下载链接】watchexec

Executes commands in response to file modifications

项目地址:https://gitcode.com/gh_mirrors/wa/watchexec
点击查看免费下载
上一篇:easy-vibe:如何在真实工作流中发现 AI 机会 —— 面向 B 端与 C 端的场景实战地图
下一篇:Oracle OpenGrok Docker容器使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询