Skip to content

Commit 4d5cd6e

Browse files
committed
Add --show-root flag to print resolved project root
1 parent 4e145a7 commit 4d5cd6e

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

src/cli.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub enum ColorChoice {
1515
)]
1616
pub struct Args {
1717
/// The search query (natural language or code snippet).
18-
#[arg(required_unless_present_any = ["reindex", "stats", "clear_cache", "index_only", "type_list", "interactive", "serve"])]
18+
#[arg(required_unless_present_any = ["reindex", "stats", "clear_cache", "index_only", "type_list", "interactive", "serve", "show_root"])]
1919
pub query: Option<String>,
2020

2121
/// Paths to search (files or directories). Defaults to current directory.
@@ -124,6 +124,10 @@ pub struct Args {
124124
#[arg(long, default_value_t = 1000)]
125125
pub index_warn_threshold: usize,
126126

127+
/// Print the resolved project root and exit.
128+
#[arg(long)]
129+
pub show_root: bool,
130+
127131
/// Start HTTP server mode.
128132
#[arg(long)]
129133
pub serve: bool,

src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ fn run() -> Result<bool> {
148148

149149
// Find project root and compute path relationships
150150
let project_root = find_project_root(&index_root);
151+
152+
// Handle --show-root (no model or index needed)
153+
if args.show_root {
154+
let canon = project_root
155+
.canonicalize()
156+
.unwrap_or_else(|_| project_root.clone());
157+
println!("{}", canon.display());
158+
return Ok(true);
159+
}
151160
let project_root_canon = project_root
152161
.canonicalize()
153162
.unwrap_or_else(|_| project_root.clone());

tests/integration.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,57 @@ fn test_incremental_indexing() {
142142
let new_hash = index.get_file_hash("a.rs").unwrap();
143143
assert_eq!(new_hash, Some("hash_a_v2".to_string()));
144144
}
145+
146+
// --- CLI flag tests using the binary ---
147+
148+
#[test]
149+
fn test_show_root_at_git_root() {
150+
let dir = tempfile::TempDir::new().unwrap();
151+
std::fs::create_dir(dir.path().join(".git")).unwrap();
152+
153+
let output = std::process::Command::new(env!("CARGO_BIN_EXE_vecgrep"))
154+
.arg("--show-root")
155+
.current_dir(dir.path())
156+
.output()
157+
.unwrap();
158+
159+
assert!(output.status.success());
160+
let stdout = String::from_utf8(output.stdout).unwrap();
161+
let expected = dir.path().canonicalize().unwrap();
162+
assert_eq!(stdout.trim(), expected.display().to_string());
163+
}
164+
165+
#[test]
166+
fn test_show_root_from_subdirectory() {
167+
let dir = tempfile::TempDir::new().unwrap();
168+
std::fs::create_dir(dir.path().join(".git")).unwrap();
169+
std::fs::create_dir_all(dir.path().join("src/deep")).unwrap();
170+
171+
let output = std::process::Command::new(env!("CARGO_BIN_EXE_vecgrep"))
172+
.arg("--show-root")
173+
.current_dir(dir.path().join("src/deep"))
174+
.output()
175+
.unwrap();
176+
177+
assert!(output.status.success());
178+
let stdout = String::from_utf8(output.stdout).unwrap();
179+
let expected = dir.path().canonicalize().unwrap();
180+
assert_eq!(stdout.trim(), expected.display().to_string());
181+
}
182+
183+
#[test]
184+
fn test_show_root_no_marker_falls_back_to_cwd() {
185+
let dir = tempfile::TempDir::new().unwrap();
186+
// No .git, .hg, .jj, or .vecgrep — falls back to cwd
187+
188+
let output = std::process::Command::new(env!("CARGO_BIN_EXE_vecgrep"))
189+
.arg("--show-root")
190+
.current_dir(dir.path())
191+
.output()
192+
.unwrap();
193+
194+
assert!(output.status.success());
195+
let stdout = String::from_utf8(output.stdout).unwrap();
196+
let expected = dir.path().canonicalize().unwrap();
197+
assert_eq!(stdout.trim(), expected.display().to_string());
198+
}

0 commit comments

Comments
 (0)