use std::fs;
use std::path::Path;
use std::process::Command;
use rag_rat_core::language::Language;
use rag_rat_core::{Config, IndexDatabase};
mod common;
use common::{git, git_commit, unique_dir};
#[test]
fn worktree_overlay_ignores_inherited_git_dir_env() {
let base = unique_dir("wtenv");
let main = base.join("main");
fs::create_dir_all(main.join("src")).unwrap();
fs::write(main.join("src/keep.rs"), "pub fn keep_fn() {}\n").unwrap();
fs::write(main.join("src/reinf.rs"), "pub fn classify_seg() {}\n").unwrap();
fs::write(main.join(".gitignore"), "/wt/\n").unwrap();
fs::write(
main.join("rag-rat.toml"),
"[index]\nroot = \".\"\ndatabase = \".rag-rat/index.sqlite\"\n\n[target_bindings]\nrust = \
[\"src\"]\n",
)
.unwrap();
git(&main, &["init", "-q"]);
git(&main, &["config", "user.email", "t@e.com"]);
git(&main, &["config", "user.name", "t"]);
git(&main, &["add", "."]);
git_commit(&main, &["-q", "-m", "C1 has reinf"]);
let wt = main.join("wt");
git(&main, &["worktree", "add", "-q", "-b", "feat", wt.to_str().unwrap()]);
fs::remove_file(main.join("src/reinf.rs")).unwrap();
git(&main, &["add", "."]);
git_commit(&main, &["-q", "-m", "C2 removed reinf"]);
let config_path = main.join("rag-rat.toml");
let config = Config::load(&config_path).unwrap();
IndexDatabase::rebuild(&config).unwrap();
let git_dir = main.join(".git/worktrees/feat");
let binary = env!("CARGO_BIN_EXE_rag-rat");
let out = Command::new(binary)
.arg("--config")
.arg(&config_path)
.args(["index", "--worktree"])
.arg(&wt)
.env("RAG_RAT_NO_WATCH", "1")
.env("GIT_DIR", &git_dir)
.env("GIT_WORK_TREE", &wt)
.output()
.unwrap();
assert!(
out.status.success(),
"index --worktree failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let mut db = IndexDatabase::open(&config.database).unwrap();
db.use_worktree_scope(&main, Some(&wt)).unwrap();
let hits = db.symbols("classify_seg", Some(Language::Rust), 10).unwrap();
assert!(
!hits.is_empty(),
"an inherited worktree GIT_DIR hijacked resolution and pruned the overlay (reinf.rs \
missing)"
);
let _ = fs::remove_dir_all(&base);
}
#[test]
fn default_config_discovery_reaches_the_main_worktree_from_a_linked_checkout() {
let base = unique_dir("wtdisc");
let main = base.join("main");
fs::create_dir_all(main.join("src")).unwrap();
fs::write(main.join("src/lib.rs"), "pub fn discovery_anchor() {}\n").unwrap();
git(&main, &["init", "-q"]);
git(&main, &["config", "user.email", "t@e.com"]);
git(&main, &["config", "user.name", "t"]);
git(&main, &["add", "."]);
git_commit(&main, &["-q", "-m", "seed"]);
let linked = base.join("wt");
git(&main, &["worktree", "add", "--detach", "-q", linked.to_str().unwrap()]);
let run = |dir: &Path, args: &[&str]| {
Command::new(env!("CARGO_BIN_EXE_rag-rat"))
.current_dir(dir)
.args(args)
.env("RAG_RAT_HOOK_DISABLE", "1")
.env("RAG_RAT_NO_WATCH", "1")
.env("RAG_RAT_DATA_DIR", base.join("data"))
.env("RAG_RAT_MODEL_CACHE", base.join("cache"))
.output()
.unwrap()
};
let out = run(&linked, &["gc"]);
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr);
let main_toml = main.canonicalize().unwrap().join("rag-rat.toml");
assert!(
stderr.contains(&main_toml.display().to_string()),
"the config-less hint names MAIN's config path from a linked checkout: {stderr}"
);
fs::write(
main.join("rag-rat.toml"),
"[index]\nroot = \".\"\ndatabase = \".rag-rat/index.sqlite\"\n\n[target_bindings]\nrust = \
[\"src\"]\n",
)
.unwrap();
assert!(!linked.join("rag-rat.toml").exists(), "the linked checkout has no branch config");
let out = run(&linked, &["index", "--full"]);
assert!(
out.status.success(),
"index from the linked checkout resolves main's config: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
main.join(".rag-rat/index.sqlite").exists(),
"the index landed at MAIN's configured database"
);
assert!(!linked.join(".rag-rat").exists(), "nothing resolved against the linked checkout");
let out = run(&linked, &["memory", "list"]);
assert!(
out.status.success(),
"a read command from the linked checkout works too: {}",
String::from_utf8_lossy(&out.stderr)
);
let _ = fs::remove_dir_all(&base);
}