ripgrep ignore crate 深度解析:构建尊重 .gitignore 的高速递归目录迭代器
ignore 是 ripgrep 项目中承担“文件筛选”职责的核心 crate:它提供一个尊重 glob、文件类型与 .gitignore 规则的快速递归目录迭代器,同时向下暴露可独立使用的 gitignore 与文件类型匹配器。读完本文,你将掌握在 Rust 项目中集成目录遍历(Walk/WalkBuilder)、配置各类忽略规则、理解 ripgrep 默认排除优先级,以及使用 WalkParallel 并行遍历与 IncrementalIgnore 增量匹配的完整方法。
ignore crate 的定位与模块构成
根据 crates/ignore/README.md 的官方描述,ignore crate 提供两类能力:
- 快速递归目录迭代器——遍历时自动应用 globs、文件类型、
.gitignore等过滤器; - 低层匹配器——对需要更细粒度控制的场景,直接暴露 gitignore 匹配器与文件类型匹配器。
从源码结构看,这些能力分别落在 crates/ignore/src/ 下的各模块中:
| 模块 | 职责 |
|---|---|
| walk.rs | Walk、WalkBuilder、WalkParallel、DirEntry 等迭代器核心 |
| gitignore.rs | Gitignore/GitignoreBuilder,解析并匹配 gitignore 语法 |
| types.rs | 文件类型定义与匹配(如 rust、c 等类型) |
| overrides.rs | glob 覆盖匹配器(白名单/黑名单) |
| dir.rs | 将 override、ignore 文件、类型匹配器组合成的 Ignore 复合匹配器 |
| incremental.rs | IncrementalIgnore 按路径做缓存式匹配,无需全量遍历 |
| default_types.rs | 内置文件类型定义表 |
crate 的公开导出集中在 crates/ignore/src/lib.rs:Walk、WalkBuilder、WalkParallel、WalkState、ParallelVisitor 等从 walk 模块导出,gitignore、overrides、types 作为公开模块导出,incremental 中的 IncrementalIgnore、IncrementalMatch 也直接重导出到根命名空间。该 crate 采用 MIT 或 UNLICENSE 双许可。
安装与依赖声明
在 Cargo.toml 中加入依赖即可使用(README 给出的版本号为 0.4):
[dependencies]
ignore = "0.4"
本仓库内该 crate 自身的清单见 crates/ignore/Cargo.toml。需要说明的是,版本能力以当前仓库实际代码为准;若使用 crates.io 上的发布版本,行为以对应 tag 的源码为界。
基础用法:Walk 递归迭代器
README 中的第一个示例演示了最基本的使用方式——递归遍历当前目录,并根据 .ignore、.gitignore 等文件中的 glob 自动过滤条目:
use ignore::Walk;
for result in Walk::new("./") {
// Each item yielded by the iterator is either a directory entry or an
// error, so either print the path or the error.
match result {
Ok(entry) => println!("{}", entry.path().display()),
Err(err) => println!("ERROR: {}", err),
}
}
从 crates/ignore/src/walk.rs 可以看到,Walk::new 实际上就是 WalkBuilder::new(path).build() 的简写,因此它天然携带全部默认设置(尊重 .gitignore、跳过隐藏文件等)。迭代器产出的 Result 中:
Ok(DirEntry)是目录条目。DirEntry(walk.rs)除path()外还提供depth()、file_name()、file_type()、metadata()、path_is_symlink()、is_stdin(),以及一个error()方法——后者报告“处理该条目时”的软性错误,例如某个目录下忽略文件解析失败;Err(Error)是遍历本身的错误(如 I/O 失败、符号链接环路),见后文错误模型一节。
多目录遍历时,文档建议对同一个 builder 调用 add 而不是创建多个 Walk,因为这样可以跨迭代复用资源(见 WalkBuilder::add 的注释,walk.rs)。
进阶配置:WalkBuilder 及其主要选项
README 的进阶示例展示了如何关闭默认启用的隐藏文件过滤:
use ignore::WalkBuilder;
for result in WalkBuilder::new("./").hidden(false).build() {
println!("{:?}", result);
}
WalkBuilder 的完整选项较多,下面结合 crates/ignore/src/walk.rs 中各方法的文档注释,整理一份可直接参考的选项表:
| 方法 | 默认值 | 作用 |
|---|---|---|
hidden(yes) |
true |
是否忽略以 . 开头的隐藏文件/目录 |
parents(yes) |
true |
是否读取每个路径父目录中的 .gitignore |
ignore(yes) |
true |
是否读取 .ignore 文件(语法同 gitignore,ripgrep、Ag 等搜索工具支持) |
git_ignore(yes) |
true |
是否读取 .gitignore 文件 |
git_exclude(yes) |
true |
是否读取 .git/info/exclude |
git_global(yes) |
true |
是否读取全局 gitignore(core.excludesFile,缺省时 $XDG_CONFIG_HOME/git/ignore,再缺省 $HOME/.config/git/ignore) |
standard_filters(yes) |
true |
以一组开关形式同时切换上表前 6 项;调用后仍可对单项覆盖 |
require_git(yes) |
— | 是否要求存在 git 仓库才应用 git 相关规则;设为 false 时即使在 git 根目录之上也会读取父级 .gitignore(与 git 行为不同) |
ignore_case_insensitive(yes) |
false |
忽略大小写解析忽略文件 |
max_depth(d) / min_depth(d) |
None |
限制递归深度区间;builder 会自动纠正两者冲突 |
max_filesize(n) |
None |
超过该字节数的文件被跳过(只作用于文件) |
follow_links(yes) |
false |
是否跟随符号链接 |
same_file_system(yes) |
false |
不跨文件系统边界;目前仅支持 Unix 与 Windows |
threads(n) |
0(自动启发式选择) |
仅对 build_parallel 生效的线程数 |
sort_by_file_name(cmp) / sort_by_file_path(cmp) |
不排序 | 对单线程迭代器按名称/路径排序;并行迭代器不使用排序器 |
skip_stdout(yes) |
false |
跳过与 stdout 指向同一文件的条目,防止 grep -r foo ./ > results 这类自读自写死循环 |
filter_entry(pred) |
无 | 自定义谓词:返回 false 的条目被跳过,且不再深入其目录 |
add_ignore(path) |
— | 显式追加一个全局忽略文件(优先级低于其他所有来源) |
add_custom_ignore_filename(name) |
— | 追加自定义忽略文件名,优先级高于其他所有忽略文件 |
overrides(ov) / types(t) |
无 | 注入 glob 覆盖匹配器 / 文件类型匹配器 |
current_dir(cwd) |
自动探测 | 指定匹配全局 gitignore 所用的工作目录 |
一个典型的组合配置示例(在尊重默认规则的基础上,追加 .rgignore 并限制深度):
use ignore::WalkBuilder;
let walker = WalkBuilder::new("./")
.add_custom_ignore_filename(".rgignore")
.max_depth(Some(4))
.build();
for result in walker {
// ...
}
值得注意的是 build_matchers(walk.rs):它构建一套“只做路径匹配、不做递归遍历”的 IncrementalIgnore 匹配器,应用与遍历相同的按路径过滤配置(glob 覆盖、.ignore/.gitignore、全局 gitignore、自定义忽略文件名、文件类型选择、隐藏文件、深度与大小限制),但不应用需要目录条目状态的特性(如 filter_entry)。这在 ripgrep 中用于对少量变更文件做增量判定。
排除规则与优先级:一个条目如何被决定跳过
WalkBuilder 的文档(crates/ignore/src/walk.rs)明确规定了默认配置下每个路径经过的七步判定顺序:
- glob 覆盖(overrides)优先检查。若命中覆盖 glob,则匹配立即停止;仅当命中的是“忽略 glob”(以
!开头的覆盖 glob)时路径才被跳过,否则视为白名单。 - 检查忽略文件。来源包括
.ignore、.gitignore、.git/info/exclude、全局 gitignore 以及显式添加的忽略文件。文件类别间的优先级为:.ignore>.gitignore>.git/info/exclude> 全局 gitignore > 显式添加的忽略文件。类别优先级不受目录层级影响——任意层级的.ignore都压过任意层级的.gitignore;而在同一类别内部,嵌套更深的文件优先级更高。 - 若上一步得到“忽略”结果,匹配停止、路径被跳过;若得到“白名单”(反斜杠否定规则,如
!foo)结果,则匹配继续,后续匹配器仍可能推翻它。 - 对非目录路径运行文件类型匹配器;忽略结果跳过,白名单结果继续。
- 若路径尚未被白名单化且是隐藏文件,则跳过。
- 对非目录路径比较文件大小,超过
max_filesize则跳过。 - 走到这一步的路径被迭代器产出。
这套顺序解释了实际使用中常见的两个疑问:为什么 .gitignore 里的规则会被同目录的 .ignore 压过,以及为什么“深层目录的更具体规则”总是赢。单文件层面的软错误(某个 glob 解析失败但不影响其余规则)通过 DirEntry::error 报告,而不是中断遍历——Walk::new 的迭代结果中 Ok(entry) 条目可能同时携带 entry.error()。
低层匹配器:gitignore 与文件类型
gitignore 模块
crates/ignore/src/gitignore.rs 的模块文档明确指出:该模块从零实现了 gitignore man page 描述的规范,不会 shell 调用 git 命令行工具——这意味着无 git 依赖、可在任何平台运行。
核心类型:
Glob(gitignore.rs):表示 gitignore 文件中的单条 glob,记录来源文件(from())、原始字符串(original())、编译后真正用于转换的正则形式(actual())、是否白名单(is_whitelist())、是否仅匹配目录(is_only_dir())。Gitignore:同一目录内一个或多个 gitignore 文件的组合匹配器。Gitignore::new(path)永远返回有效匹配器——即使文件部分有效(如某一条 glob 非法而其余合法),I/O 错误也被吞掉;需要细粒度错误控制时应使用GitignoreBuilder。GitignoreBuilder:可多次add不同来源(文件、字符串、reader),编译成GlobSet后一次性匹配;构建错误会以带行号/路径标签的形式返回。
types 与 overrides 模块
types模块管理文件类型定义(如rust、c、html),内置定义来自 default_types.rs,用户可通过自定义定义扩展或覆盖;overrides模块实现“覆盖 glob”:不带前缀的 glob 视为白名单(强制包含),以!开头视为忽略(强制排除),在七步判定中占据最高优先级。
IncrementalIgnore:不做遍历的增量匹配
crates/ignore/src/incremental.rs 中的 IncrementalIgnore 通过 WalkBuilder::build_matchers 构建。它按目录粒度缓存已编译的忽略匹配器:首次查询某路径时读取根目录及其父链上的忽略文件,之后复用缓存。文档同时给出明确警告:该接口“每条路径的匹配开销更大”,不适合用来驱动整棵树遍历,其设计目标是避免“仅检测到少量文件增删时重新遍历整棵目录树”。其文档示例与 ripgrep 的 .rgignore 用法完全一致:
use ignore::WalkBuilder;
let mut builder = WalkBuilder::new(".");
builder.add_custom_ignore_filename(".rgignore");
let mut matchers = builder.build_matchers();
let matcher = &mut matchers[0];
if matcher.matched("src/generated.rs", false).is_ignore() {
println!("ignored");
}
并行遍历:WalkParallel
对大规模目录树,build_parallel 返回的 WalkParallel 不是 Iterator,而是必须通过 run(closure) 驱动;闭包对每个条目返回 WalkState::Continue 或 WalkState::Abort。仓库自带示例 crates/ignore/examples/walk.rs 展示了并行(6 线程)、串行 ignore 遍历与普通 walkdir 三者的对照写法,其中并行部分的关键代码:
let walker = WalkBuilder::new(path).threads(6).build_parallel();
walker.run(|| {
let tx = tx.clone();
Box::new(move |result| {
use ignore::WalkState::*;
tx.send(DirEntry::Y(result.unwrap())).unwrap();
Continue
})
});
线程数由 threads(n) 控制,默认 0 表示由启发式自动选择;该选项只对并行迭代器生效,串行 Walk 忽略它(见 walk.rs 的注释)。
错误模型:软错误与硬错误
Error 枚举(crates/ignore/src/lib.rs)按上下文对错误打标签,便于定位:
Partial(Vec<Error>):部分成功的“软”错误集合,例如忽略文件中某条 glob 非法但其余生效;WithLineNumber { line, err }/WithPath { path, err }/WithDepth { depth, err }:分别附带行号、文件路径、递归深度;Loop { ancestor, child }:跟随符号链接时检测到目录环;Io(std::io::Error):读取忽略文件等 I/O 错误;Glob { glob, err }:glob 解析失败,glob字段保留用户原始书写形式;UnrecognizedFileType(String)/InvalidDefinition:文件类型未定义或自定义定义无法解析。
配套辅助方法包括 is_partial()、is_io()、io_error() / into_io_error()、depth(),帮助调用方区分“可降级处理”与“必须终止”的错误。这也是迭代器 API 将“条目级软错误”与“遍历级硬错误”分离的原因:软错误随 DirEntry 携带,硬错误直接作为 Err 产出。
测试与验证
ignore crate 的测试可用作行为验证参考,位于 crates/ignore/tests/:
- gitignore_matched_path_or_any_parents_tests.rs:验证“匹配路径或其任一父目录”的 gitignore 语义(配套的 .gitignore 数据文件 gitignore_matched_path_or_any_parents_tests.gitignore 内含多种规则);
- gitignore_skip_bom.rs:验证带 BOM 头的 gitignore 文件(数据文件 gitignore_skip_bom.gitignore)能被正确跳过 BOM 后解析。
在 ripgrep 主程序层面,crates/core/haystack.rs 等模块基于本 crate 的迭代器构建搜索入口;本文所述优先级与过滤顺序即 ripgrep 默认排除文件行为的底层依据。
小结
ignore crate 把“尊重 gitignore 的目录遍历”做成了一个可组合的公共 API:Walk 一行代码起步,WalkBuilder 提供隐藏文件、全局 gitignore、深度/大小限制、排序、过滤谓词等精细开关,WalkParallel 提供并行能力,gitignore/types/overrides 模块与 IncrementalIgnore 则覆盖无需遍历的匹配场景。理解其七步判定顺序与五类忽略文件的优先级(.ignore > .gitignore > .git/info/exclude > 全局 > 显式),是正确使用这个 crate、并正确解释 ripgrep 为何跳过(或不跳过)某个文件的关键。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00