Files
Kigi-CLI/crates/codegen/kigi-tools/src/gitignore.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

113 lines
4.0 KiB
Rust

//! Shared gitignore matching utility.
//!
//! Single source of truth for checking whether a path is ignored by
//! `.gitignore` rules. Used by both the initial AGENTS.md discovery
//! (`kigi-agent::prompt::ignore`) and the runtime tracker
//! (`AgentsMdTracker`).
use ignore::gitignore::Gitignore;
use std::path::Path;
/// Strips the `git_root` prefix before matching — gitignore patterns are
/// repo-relative, so `/repo/build/out.o` becomes `build/out.o` when
/// `git_root` is `/repo`.
pub fn is_ignored(gitignore: &Gitignore, path: &Path, git_root: Option<&Path>) -> bool {
let check_path = match git_root {
Some(root) => match path.strip_prefix(root) {
Ok(relative) => relative,
// Outside the repo (e.g. ~/.kigi/Agents.md) → not ignored.
Err(_) => return false,
},
None => {
// Absolute path + no git root → can't strip to repo-relative;
// the `ignore` crate panics on absolute paths not under root.
if path.is_absolute() {
return false;
}
path
}
};
// matched_path_or_any_parents checks parent dirs too, so
// `build/AGENTS.md` correctly matches a `build/` pattern.
gitignore
.matched_path_or_any_parents(check_path, check_path.is_dir())
.is_ignore()
}
#[cfg(test)]
mod tests {
use super::*;
use ignore::gitignore::GitignoreBuilder;
fn build_gitignore(root: &Path, patterns: &[&str]) -> Gitignore {
let mut builder = GitignoreBuilder::new(root);
for pattern in patterns {
builder.add_line(None, pattern).unwrap();
}
builder.build().unwrap()
}
#[test]
fn is_ignored_matches_gitignored_paths() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Canonicalize to handle macOS /tmp → /private/tmp
let root = &dunce::canonicalize(root).unwrap();
let gi = build_gitignore(root, &["build/"]);
assert!(is_ignored(&gi, &root.join("build/out.o"), Some(root)));
assert!(is_ignored(&gi, &root.join("build/sub/file.rs"), Some(root)));
}
#[test]
fn is_ignored_does_not_match_non_gitignored_paths() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let root = &dunce::canonicalize(root).unwrap();
let gi = build_gitignore(root, &["build/"]);
assert!(!is_ignored(&gi, &root.join("src/main.rs"), Some(root)));
assert!(!is_ignored(&gi, &root.join("AGENTS.md"), Some(root)));
}
#[test]
fn is_ignored_strips_git_root_prefix() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let root = &dunce::canonicalize(root).unwrap();
let gi = build_gitignore(root, &["build/"]);
assert!(is_ignored(&gi, &root.join("build/out.o"), Some(root)));
assert!(is_ignored(
&gi,
&std::path::PathBuf::from("build/out.o"),
None
));
}
#[test]
fn is_ignored_returns_false_for_path_outside_git_root() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let root = &dunce::canonicalize(root).unwrap();
let gi = build_gitignore(root, &["build/", "*.md"]);
let outside_path = std::path::PathBuf::from("/some/other/path/Agents.md");
assert!(!is_ignored(&gi, &outside_path, Some(root)));
}
/// Regression: running outside a git repo panicked with
/// "path is expected to be under the root" (ignore crate assert).
#[test]
fn regression_no_panic_on_absolute_path_without_git_root() {
let gi = build_gitignore(Path::new("."), &["node_modules/", "*.log"]);
let abs_path = Path::new("/Users/someone/home/AGENTS.md");
// Proves the raw crate panics with these inputs.
assert!(
std::panic::catch_unwind(|| {
gi.matched_path_or_any_parents(abs_path, false);
})
.is_err()
);
assert!(!is_ignored(&gi, abs_path, None));
}
}