feat(cli): scaffolds make the initial commit — provenance resolves from run one
aura new and aura nodes new follow their git init with a best-effort initial commit (inline committer identity, warning-never-fatal, skipped whenever the init itself was skipped per #204), so a fresh project's first run stamps a real commit instead of an empty provenance block. closes #243
This commit is contained in:
@@ -283,7 +283,9 @@ pub fn scaffold_project(spec: &ProjectScaffoldSpec) -> Result<(), String> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() {
|
match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() {
|
||||||
Ok(o) if o.status.success() => {}
|
Ok(o) if o.status.success() => {
|
||||||
|
commit_all(&spec.target_dir, "aura new scaffold");
|
||||||
|
}
|
||||||
_ => eprintln!("aura: warning: git init failed (project created without a repo)"),
|
_ => eprintln!("aura: warning: git init failed (project created without a repo)"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -322,7 +324,9 @@ pub fn scaffold_node_crate(spec: &ScaffoldSpec) -> Result<(), String> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() {
|
match std::process::Command::new("git").arg("init").current_dir(&spec.target_dir).output() {
|
||||||
Ok(o) if o.status.success() => {}
|
Ok(o) if o.status.success() => {
|
||||||
|
commit_all(&spec.target_dir, "aura nodes new scaffold");
|
||||||
|
}
|
||||||
_ => eprintln!("aura: warning: git init failed (node crate created without a repo)"),
|
_ => eprintln!("aura: warning: git init failed (node crate created without a repo)"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -344,6 +348,41 @@ pub fn append_nodes_pointer(project_root: &Path, pointer: &str) -> Result<(), St
|
|||||||
.map_err(|e| format!("writing `{}`: {e}", path.display()))
|
.map_err(|e| format!("writing `{}`: {e}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stage everything and make the initial commit in a freshly-`git init`ed
|
||||||
|
/// scaffold, so `git rev-parse HEAD` resolves from the very first run
|
||||||
|
/// (otherwise HEAD stays unborn and provenance stamping sees no commit). A
|
||||||
|
/// committer identity is passed inline via `-c` so this works on a box with
|
||||||
|
/// no global git config; best-effort — a failure is a warning, never fatal,
|
||||||
|
/// and never runs when `git init` itself was skipped or failed.
|
||||||
|
fn commit_all(dir: &std::path::Path, message: &str) {
|
||||||
|
let add = std::process::Command::new("git")
|
||||||
|
.args(["add", "-A"])
|
||||||
|
.current_dir(dir)
|
||||||
|
.output();
|
||||||
|
if !matches!(add, Ok(o) if o.status.success()) {
|
||||||
|
eprintln!("aura: warning: git add failed (scaffold created without an initial commit)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let commit = std::process::Command::new("git")
|
||||||
|
.args([
|
||||||
|
"-c",
|
||||||
|
"user.email=aura@localhost",
|
||||||
|
"-c",
|
||||||
|
"user.name=aura",
|
||||||
|
"commit",
|
||||||
|
"-q",
|
||||||
|
"-m",
|
||||||
|
message,
|
||||||
|
])
|
||||||
|
.current_dir(dir)
|
||||||
|
.output();
|
||||||
|
if !matches!(commit, Ok(o) if o.status.success()) {
|
||||||
|
eprintln!(
|
||||||
|
"aura: warning: git commit failed (scaffold created without an initial commit)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether `dir` already sits inside a git work tree (`git rev-parse`, the
|
/// 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
|
/// 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.
|
/// as "not in a work tree" — the init stays best-effort either way.
|
||||||
|
|||||||
@@ -37,6 +37,34 @@ fn nodes_new_scaffolds_a_sibling_crate_and_attaches_it() {
|
|||||||
assert!(toml.contains("../lab-nodes"), "{toml}");
|
assert!(toml.contains("../lab-nodes"), "{toml}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Property (#243): a freshly scaffolded node crate stamps resolvable
|
||||||
|
/// provenance too — `aura nodes new` follows the same `git init` with an
|
||||||
|
/// initial commit as `aura new` (`scaffold_node_crate`, `scaffold.rs`), so
|
||||||
|
/// `git rev-parse HEAD` resolves from the crate's very first run rather than
|
||||||
|
/// leaving HEAD unborn. This half of #243's property runs and is asserted
|
||||||
|
/// separately from `new_outside_a_work_tree_leaves_a_resolvable_head`
|
||||||
|
/// (`project_new.rs`), which covers the `aura new` project half only.
|
||||||
|
#[test]
|
||||||
|
fn nodes_new_leaves_a_resolvable_head() {
|
||||||
|
let cwd = temp_cwd("resolvable-head");
|
||||||
|
assert!(aura(&["new", "lab"], &cwd).status.success());
|
||||||
|
let proj = cwd.join("lab");
|
||||||
|
let engine = env!("CARGO_MANIFEST_DIR").to_string() + "/../..";
|
||||||
|
let out = aura(&["nodes", "new", "lab-nodes", "--engine-path", &engine], &proj);
|
||||||
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
let crate_dir = cwd.join("lab-nodes");
|
||||||
|
let head = Command::new("git")
|
||||||
|
.args(["rev-parse", "HEAD"])
|
||||||
|
.current_dir(&crate_dir)
|
||||||
|
.output()
|
||||||
|
.expect("run git rev-parse HEAD");
|
||||||
|
assert!(
|
||||||
|
head.status.success(),
|
||||||
|
"fresh node-crate scaffold has no HEAD (`git rev-parse HEAD` failed): {}",
|
||||||
|
String::from_utf8_lossy(&head.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Property (#241): `--namespace` sets the vocabulary prefix independently
|
/// Property (#241): `--namespace` sets the vocabulary prefix independently
|
||||||
/// of the crate's own (directory) name — a crate can be named anything, but
|
/// of the crate's own (directory) name — a crate can be named anything, but
|
||||||
/// the blueprints that reference its nodes address them under the namespace
|
/// the blueprints that reference its nodes address them under the namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user