Repository navigation
fix: honor git core.excludesFile when indexing (#874) #878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||||
| use std::path::Path; | ||||||||
| use std::path::{Path, PathBuf}; | ||||||||
|
|
||||||||
| /// Directories excluded when walking a non-git root. Entries are `cfg`-gated | ||||||||
| /// so a single iteration covers standard + platform-specific overrides. | ||||||||
|
|
@@ -64,6 +64,41 @@ pub(crate) const IGNORED_DIRS: &[&str] = &[ | |||||||
| "AppData/Roaming", | ||||||||
| ]; | ||||||||
|
|
||||||||
| /// Resolves git's `core.excludesFile` through the repo's own config chain, so | ||||||||
| /// repo-local, global and system settings all win in git's precedence order. | ||||||||
| pub(crate) fn git_excludes_file(base_path: &Path) -> Option<PathBuf> { | ||||||||
| let config = git2::Repository::discover(base_path) | ||||||||
| .and_then(|repo| repo.config()) | ||||||||
| .or_else(|_| git2::Config::open_default()) | ||||||||
| .ok()?; | ||||||||
|
|
||||||||
| // `get_path` expands a leading `~` for us; a configured-but-absent file is | ||||||||
| // legal in git and simply matches nothing. | ||||||||
| let path = config.get_path("core.excludesFile").ok()?; | ||||||||
| path.is_file().then_some(path) | ||||||||
| } | ||||||||
|
|
||||||||
| /// `core.excludesFile` contents as gitignore patterns, comments stripped. | ||||||||
| #[cfg(feature = "zlob")] | ||||||||
| pub(crate) fn git_excludes_patterns(base_path: &Path) -> Vec<String> { | ||||||||
| let Some(path) = git_excludes_file(base_path) else { | ||||||||
| return Vec::new(); | ||||||||
| }; | ||||||||
|
|
||||||||
| match std::fs::read_to_string(&path) { | ||||||||
| Ok(contents) => contents | ||||||||
| .lines() | ||||||||
|
Comment on lines
+69
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '60,110p' crates/fff-core/src/ignore.rs
sed -n '25,60p' crates/fff-core/src/walk/ripgrep.rs
sed -n '30,65p' crates/fff-core/src/walk/zlob.rs
rg -n 'core\.excludesFile|excludesFile|git_excludes_file' cratesRepository: dmtrKovalenko/fff Length of output: 6411 🌐 Web query:
💡 Result: <source_evidence> Citations:
🌐 Web query:
💡 Result: <source_evidence> Citations:
🏁 Script executed: sed -n '120,205p' crates/fff-core/src/walk/mod.rs
rg -n 'fn (walk|run)|walk::|WalkBuilder|base_path' crates/fff-core/src crates/fff-cli/src 2>/dev/null | head -120
rg -n 'git2' Cargo.toml Cargo.lock crates/*/Cargo.tomlRepository: dmtrKovalenko/fff Length of output: 15758 Resolve relative 🤖 Prompt for AI Agents |
||||||||
| .map(str::trim) | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Passing |
||||||||
| .filter(|line| !line.is_empty() && !line.starts_with('#')) | ||||||||
|
Comment on lines
+91
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '60,115p' crates/fff-core/src/ignore.rs
sed -n '1,100p' crates/fff-core/src/walk/zlob.rs
rg -n 'extra_ignore|is_ignored' crates/fff-core/srcRepository: dmtrKovalenko/fff Length of output: 8993 🏁 Script executed: set -e
printf '%s\n' '--- zlob references ---'
rg -n --hidden --glob '!target/**' 'zlob|extra_ignore' Cargo.toml Cargo.lock crates 2>/dev/null | head -120
printf '%s\n' '--- candidate dependency sources ---'
find . /root/.cargo/registry/src /usr/local/cargo/registry/src -path '*zlob*' -maxdepth 8 -type f 2>/dev/null | head -80Repository: dmtrKovalenko/fff Length of output: 11453 🌐 Web query:
💡 Result: <source_evidence> Citations:
🏁 Script executed: set -e
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL --retry 2 https://api.github.com/repos/dmtrKovalenko/zlob/git/trees/v1.6.5?recursive=1 > "$tmp/tree.json"
python3 - "$tmp/tree.json" <<'PY'
import json, sys
data=json.load(open(sys.argv[1]))
for item in data.get("tree", []):
p=item.get("path", "")
if "walk" in p.lower() or "ignore" in p.lower() or p.endswith((".zig", ".rs")):
print(p)
PYRepository: dmtrKovalenko/fff Length of output: 1979 🏁 Script executed: set -e
base=https://raw.githubusercontent.com/dmtrKovalenko/zlob/v1.6.5
for f in rust/src/walk.rs src/gitignore.zig src/walker/ignore_rules.zig rust/tests/git_compat.rs rust/tests/git_compat_known_gaps.rs; do
echo "--- $f ---"
curl -fsSL "$base/$f" | grep -n -C 5 -E 'extra_ignore|ignore|trailing|space|backslash|unescape|parse' | head -180 || true
doneRepository: dmtrKovalenko/fff Length of output: 28137 🏁 Script executed: set -e
base=https://raw.githubusercontent.com/dmtrKovalenko/zlob/v1.6.5
for spec in \
'src/gitignore.zig:466:540' \
'src/gitignore.zig:540:620' \
'src/walker/ignore_rules.zig:145:195' \
'src/walker/walker.zig:1:220'; do
file=${spec%%:*}; rest=${spec#*:}; start=${rest%%:*}; end=${rest##*:}
echo "--- $file:$start-$end ---"
curl -fsSL "$base/$file" | sed -n "${start},${end}p"
doneRepository: dmtrKovalenko/fff Length of output: 17060 Preserve raw ignore patterns.
Proposed fix- .map(str::trim)
- .filter(|line| !line.is_empty() && !line.starts_with('#'))
+ .filter(|line| !line.trim().is_empty() && !line.starts_with('#'))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
| .map(str::to_owned) | ||||||||
| .collect(), | ||||||||
| Err(e) => { | ||||||||
| tracing::warn!(?e, ?path, "core.excludesFile unreadable; not applied"); | ||||||||
| Vec::new() | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| #[cfg(all(not(feature = "zlob"), feature = "ripgrep"))] | ||||||||
| pub(crate) fn non_git_repo_overrides(base_path: &Path) -> Option<ignore::overrides::Override> { | ||||||||
| use ignore::overrides::OverrideBuilder; | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,17 @@ pub(crate) fn walk_collect_files( | |
| walk_builder.overrides(overrides); | ||
| } | ||
|
|
||
| // `git_global` above only consults the user-level gitconfig; resolve | ||
| // core.excludesFile through the repo's config chain instead (#874). | ||
| if is_git_repo && let Some(excludes) = crate::ignore::git_excludes_file(base_path) { | ||
| // Roots the matcher at the walk root, which is where git anchors | ||
| // core.excludesFile patterns. Must precede `add_ignore`. | ||
| walk_builder.current_dir(base_path); | ||
| if let Some(e) = walk_builder.add_ignore(&excludes) { | ||
| tracing::warn!(?e, ?excludes, "core.excludesFile not fully applied"); | ||
| } | ||
|
Comment on lines
+37
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When a repository-local |
||
| } | ||
|
|
||
| let walker = walk_builder.build_parallel(); | ||
|
|
||
| // Single lock for both collections: every entry is either a file or a | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,9 +36,21 @@ pub(crate) fn walk_collect_files( | |
| // Bulk-fetch the only metadata FileItem needs; zlob never stats more. | ||
| .metadata(WalkMetadata::SIZE | WalkMetadata::MTIME); | ||
|
|
||
| if !is_git_repo | ||
| && !IGNORED_DIRS.is_empty() | ||
| && let Err(e) = builder.extra_ignore(IGNORED_DIRS) | ||
| // zlob reads nested .gitignore/.ignore itself but never git's | ||
| // core.excludesFile, so layer that in as root-level extra rules (#874). | ||
| let global_excludes = if is_git_repo { | ||
| crate::ignore::git_excludes_patterns(base_path) | ||
| } else { | ||
| Vec::new() | ||
| }; | ||
| let extra_ignore: Vec<&str> = if is_git_repo { | ||
| global_excludes.iter().map(String::as_str).collect() | ||
| } else { | ||
| IGNORED_DIRS.to_vec() | ||
| }; | ||
|
|
||
| if !extra_ignore.is_empty() | ||
| && let Err(e) = builder.extra_ignore(&extra_ignore) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
zlob installs |
||
| { | ||
| // Interior NUL in one of the extra_ignore patterns would fail | ||
| // here — treat as if no extras were supplied rather than | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
///comments document crate-private functions, and several new comments exceed two lines. The repository guide explicitly prohibits doc comments on private functions and comments longer than two lines. Convert or remove the comments here and atignore.rs:81, shorten the block atripgrep.rs:35, and apply the same cleanup to the changed comment atzlob.rs:38. This repository requirement must be satisfied before merging.Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!