Skip to content

Commit 87c17de

Browse files
committed
fix(grep): cap indexed search for max-count
1 parent 5d6cb63 commit 87c17de

1 file changed

Lines changed: 95 additions & 5 deletions

File tree

crates/bashkit/src/builtins/grep.rs

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,9 @@ async fn try_indexed_search(
987987
{
988988
return None;
989989
}
990+
if opts.max_count == Some(0) {
991+
return Some(Vec::new());
992+
}
990993

991994
let sc = fs.as_search_capable()?;
992995
let mut seen_paths = std::collections::HashSet::new();
@@ -1038,7 +1041,7 @@ async fn try_indexed_search(
10381041
} else {
10391042
None
10401043
},
1041-
max_results: None,
1044+
max_results: opts.max_count,
10421045
};
10431046

10441047
let results = provider.search(&query).ok()?;
@@ -1085,7 +1088,7 @@ mod tests {
10851088
};
10861089
use std::collections::HashMap;
10871090
use std::path::{Path, PathBuf};
1088-
use std::sync::Arc;
1091+
use std::sync::{Arc, Mutex};
10891092

10901093
async fn run_grep(args: &[&str], stdin: Option<&str>) -> Result<ExecResult> {
10911094
let grep = Grep;
@@ -1116,6 +1119,7 @@ mod tests {
11161119
struct IndexedTestFs {
11171120
inner: InMemoryFs,
11181121
matches: Vec<SearchMatch>,
1122+
query_max_results: Option<Arc<Mutex<Vec<Option<usize>>>>>,
11191123
}
11201124

11211125
#[async_trait::async_trait]
@@ -1169,12 +1173,24 @@ mod tests {
11691173

11701174
struct IndexedProvider {
11711175
matches: Vec<SearchMatch>,
1176+
query_max_results: Option<Arc<Mutex<Vec<Option<usize>>>>>,
11721177
}
11731178

11741179
impl SearchProvider for IndexedProvider {
1175-
fn search(&self, _query: &SearchQuery) -> Result<SearchResults> {
1180+
fn search(&self, query: &SearchQuery) -> Result<SearchResults> {
1181+
if let Some(query_max_results) = &self.query_max_results {
1182+
query_max_results
1183+
.lock()
1184+
.expect("query max results lock should not be poisoned")
1185+
.push(query.max_results);
1186+
}
1187+
let matches = if let Some(max_results) = query.max_results {
1188+
self.matches.iter().take(max_results).cloned().collect()
1189+
} else {
1190+
self.matches.clone()
1191+
};
11761192
Ok(SearchResults {
1177-
matches: self.matches.clone(),
1193+
matches,
11781194
truncated: false,
11791195
})
11801196
}
@@ -1193,6 +1209,7 @@ mod tests {
11931209
fn search_provider(&self, _path: &Path) -> Option<Box<dyn SearchProvider>> {
11941210
Some(Box::new(IndexedProvider {
11951211
matches: self.matches.clone(),
1212+
query_max_results: self.query_max_results.clone(),
11961213
}))
11971214
}
11981215
}
@@ -1203,7 +1220,11 @@ mod tests {
12031220
args: &[&str],
12041221
) -> Result<ExecResult> {
12051222
let grep = Grep;
1206-
let fs: Arc<dyn FileSystem> = Arc::new(IndexedTestFs { inner, matches });
1223+
let fs: Arc<dyn FileSystem> = Arc::new(IndexedTestFs {
1224+
inner,
1225+
matches,
1226+
query_max_results: None,
1227+
});
12071228
let mut vars = HashMap::new();
12081229
let mut cwd = PathBuf::from("/");
12091230
let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
@@ -1571,6 +1592,7 @@ mod tests {
15711592
line_number: 1,
15721593
line_content: "secret".to_string(),
15731594
}],
1595+
query_max_results: None,
15741596
});
15751597

15761598
let mut vars = HashMap::new();
@@ -1619,6 +1641,7 @@ mod tests {
16191641
let fs: Arc<dyn FileSystem> = Arc::new(IndexedTestFs {
16201642
inner,
16211643
matches: Vec::new(),
1644+
query_max_results: None,
16221645
});
16231646

16241647
let mut vars = HashMap::new();
@@ -1678,6 +1701,7 @@ mod tests {
16781701
line_content: "hidden SECRET".to_string(),
16791702
},
16801703
],
1704+
query_max_results: None,
16811705
});
16821706

16831707
let mut vars = HashMap::new();
@@ -1754,6 +1778,72 @@ mod tests {
17541778
assert!(result.stdout.contains("/b/second.txt:needle in b"));
17551779
}
17561780

1781+
#[tokio::test]
1782+
async fn test_grep_recursive_indexed_search_passes_max_count_to_provider() {
1783+
let inner = InMemoryFs::new();
1784+
inner.mkdir(Path::new("/dir"), true).await.unwrap();
1785+
inner
1786+
.write_file(Path::new("/dir/first.txt"), b"needle first\n")
1787+
.await
1788+
.unwrap();
1789+
inner
1790+
.write_file(Path::new("/dir/second.txt"), b"needle second\n")
1791+
.await
1792+
.unwrap();
1793+
let query_max_results = Arc::new(Mutex::new(Vec::new()));
1794+
let fs: Arc<dyn FileSystem> = Arc::new(IndexedTestFs {
1795+
inner,
1796+
matches: vec![
1797+
SearchMatch {
1798+
path: PathBuf::from("/dir/first.txt"),
1799+
line_number: 1,
1800+
line_content: "needle first".to_string(),
1801+
},
1802+
SearchMatch {
1803+
path: PathBuf::from("/dir/second.txt"),
1804+
line_number: 1,
1805+
line_content: "needle second".to_string(),
1806+
},
1807+
],
1808+
query_max_results: Some(query_max_results.clone()),
1809+
});
1810+
1811+
let grep = Grep;
1812+
let mut vars = HashMap::new();
1813+
let mut cwd = PathBuf::from("/");
1814+
let args: Vec<String> = ["-r", "-m1", "needle", "/dir"]
1815+
.iter()
1816+
.map(|s| s.to_string())
1817+
.collect();
1818+
let ctx = Context {
1819+
args: &args,
1820+
env: &HashMap::new(),
1821+
variables: &mut vars,
1822+
cwd: &mut cwd,
1823+
fs,
1824+
stdin: None,
1825+
#[cfg(feature = "http_client")]
1826+
http_client: None,
1827+
#[cfg(feature = "git")]
1828+
git_client: None,
1829+
#[cfg(feature = "ssh")]
1830+
ssh_client: None,
1831+
shell: None,
1832+
};
1833+
1834+
let result = grep.execute(ctx).await.unwrap();
1835+
1836+
assert_eq!(result.exit_code, 0);
1837+
assert_eq!(result.stdout, "/dir/first.txt:needle first\n");
1838+
assert_eq!(
1839+
query_max_results
1840+
.lock()
1841+
.expect("query max results lock should not be poisoned")
1842+
.as_slice(),
1843+
&[Some(1)]
1844+
);
1845+
}
1846+
17571847
#[tokio::test]
17581848
async fn test_grep_recursive_indexed_search_respects_exclude_dir() {
17591849
let inner = InMemoryFs::new();

0 commit comments

Comments
 (0)