1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//! `torii grep` — search tracked files for a pattern. Wrapper over
//! `git grep`, which is consistently faster than `ripgrep` on
//! tracked-only content because it indexes through the gitdir's pack
//! files. Different concern from `torii scan` (secrets pattern matching).
//!
//! Flags surfaced (others can be passed via `--`):
//! - `--ignore-case` / `-i`
//! - `--word-regexp` / `-w`
//! - `--line-number` / `-n` (on by default — git grep doesn't show
//! line numbers without this; we flip the default)
//! - `--files-with-matches` / `-l`
//! - any literal extra args after `--`
//!
//! Anything not listed is passed through `--` so power users still have
//! full git grep semantics available.
use crate::error::{Result, ToriiError};
use std::path::Path;
use std::process::Command;
#[derive(Debug, Default)]
pub struct Opts {
pub ignore_case: bool,
pub word_regexp: bool,
pub files_with_matches: bool,
/// Suppress line numbers (otherwise on by default).
pub no_line_number: bool,
/// Raw extra args appended after `--`.
pub extra: Vec<String>,
}
pub fn grep(repo_path: &Path, pattern: &str, paths: &[String], opts: &Opts) -> Result<()> {
let mut args = vec!["grep".to_string()];
if !opts.no_line_number {
args.push("-n".to_string());
}
if opts.ignore_case {
args.push("-i".to_string());
}
if opts.word_regexp {
args.push("-w".to_string());
}
if opts.files_with_matches {
args.push("-l".to_string());
}
args.push(pattern.to_string());
if !paths.is_empty() {
args.push("--".to_string());
args.extend(paths.iter().cloned());
}
args.extend(opts.extra.iter().cloned());
let status = Command::new("git")
.args(&args)
.current_dir(repo_path)
.status()
.map_err(|e| ToriiError::InvalidConfig(format!("invoke git grep: {e}")))?;
// git grep exits 1 when nothing matched — not an error from torii's
// perspective, just zero results. Only propagate >1 as a real failure.
match status.code() {
Some(0) | Some(1) => Ok(()),
Some(code) => Err(ToriiError::InvalidConfig(format!(
"git grep exited with status {code}"
))),
None => Err(ToriiError::InvalidConfig("git grep terminated by signal".into())),
}
}