fix(cli): aura new skips git init inside an existing work tree (0107 fieldtest, orthogonal)
The cargo-new mirror completed: a scaffold landing inside a tracked tree no longer nests a .git (the gitlink/submodule hazard that bit the fieldtest corpus); outside a work tree the best-effort init is unchanged. Oracle: git rev-parse --is-inside-work-tree in the target dir; any failure reads as not-in-a-work-tree so the init stays best-effort. RED-first (tdd-author handoff): the inside-a-work-tree pin observed failing on the nested .git; the outside guard pinned against over-removal. Gates: workspace 1003/0, clippy -D warnings clean. closes #204
This commit is contained in:
@@ -239,6 +239,12 @@ pub fn scaffold(spec: &ScaffoldSpec) -> Result<(), String> {
|
||||
write("blueprints/signal.json", render(SIGNAL_JSON, spec))?;
|
||||
write("CLAUDE.md", render(CLAUDE_MD, spec))?;
|
||||
// Best-effort git init: a warning, never an error (cargo-new mirror).
|
||||
// Also the cargo-new mirror: inside an existing work tree the init is
|
||||
// SKIPPED silently — a nested .git is a gitlink/submodule hazard when the
|
||||
// scaffold lands in a tracked tree (#204).
|
||||
if inside_git_work_tree(&spec.target_dir) {
|
||||
return Ok(());
|
||||
}
|
||||
match std::process::Command::new("git")
|
||||
.arg("init")
|
||||
.current_dir(&spec.target_dir)
|
||||
@@ -250,6 +256,18 @@ pub fn scaffold(spec: &ScaffoldSpec) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether `dir` already sits inside a git work tree (`git rev-parse`, the
|
||||
/// same oracle cargo-new consults). A missing git binary or any failure reads
|
||||
/// as "not in a work tree" — the init stays best-effort either way.
|
||||
fn inside_git_work_tree(dir: &std::path::Path) -> bool {
|
||||
std::process::Command::new("git")
|
||||
.args(["rev-parse", "--is-inside-work-tree"])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.map(|o| o.status.success() && String::from_utf8_lossy(&o.stdout).trim() == "true")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -141,6 +141,72 @@ fn new_namespace_override_reaches_the_built_vocabulary() {
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
/// Property: `aura new` mirrors `cargo new` — when the target directory is
|
||||
/// already inside a git work tree, the scaffolder does NOT initialize a
|
||||
/// nested repo. A nested `.git` under a tracked tree is a gitlink/submodule
|
||||
/// hazard (it bit the 0107 fieldtest corpus); the enclosing repo owns the
|
||||
/// scaffold's history.
|
||||
#[test]
|
||||
fn new_inside_a_work_tree_skips_the_nested_git() {
|
||||
let base = tmp("insidegit");
|
||||
let init = Command::new("git")
|
||||
.arg("init")
|
||||
.current_dir(&base)
|
||||
.output()
|
||||
.expect("git init the enclosing work tree");
|
||||
assert!(
|
||||
init.status.success(),
|
||||
"git init base failed: {}",
|
||||
String::from_utf8_lossy(&init.stderr)
|
||||
);
|
||||
|
||||
let out = aura(&["new", "demo-lab"], &base);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"aura new failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let proj = base.join("demo-lab");
|
||||
// The full scaffold still lands unchanged...
|
||||
for f in [
|
||||
"Cargo.toml",
|
||||
"Aura.toml",
|
||||
".gitignore",
|
||||
"src/lib.rs",
|
||||
"blueprints/signal.json",
|
||||
"CLAUDE.md",
|
||||
] {
|
||||
assert!(proj.join(f).is_file(), "missing scaffolded file {f}");
|
||||
}
|
||||
// ...but without a nested repo of its own.
|
||||
assert!(
|
||||
!proj.join(".git").exists(),
|
||||
"scaffold nested a .git inside an existing work tree (gitlink hazard)"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
/// Guard against over-removal of the git-init: OUTSIDE any work tree the
|
||||
/// scaffold still initializes its own repo. `std::env::temp_dir()` is not
|
||||
/// inside a git work tree (the e2e helpers here already rely on that), so the
|
||||
/// scaffold's parent is genuinely detached from this checkout's tree.
|
||||
#[test]
|
||||
fn new_outside_a_work_tree_initializes_git() {
|
||||
let base = tmp("outsidegit");
|
||||
let out = aura(&["new", "demo-lab"], &base);
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"aura new failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let proj = base.join("demo-lab");
|
||||
assert!(
|
||||
proj.join(".git").is_dir(),
|
||||
"scaffold outside a work tree must initialize its own repo"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_works_inside_an_unbuilt_project_tree() {
|
||||
// Scaffold once (unbuilt), then scaffold AGAIN from inside that tree:
|
||||
|
||||
Reference in New Issue
Block a user