new: skip the git-init question inside existing repositories

This commit is contained in:
2026-09-17 10:37:54 +02:00
parent 05e7c55d32
commit bac82f0afe
2 changed files with 74 additions and 2 deletions
+35
View File
@@ -28,6 +28,23 @@ pub fn ensure_repository(dir: &Path, init: bool) -> Result<bool, Box<dyn std::er
}
}
/// Whether `dir` sits inside a git work tree (its own or a parent's), in the
/// spirit of `git rev-parse --is-inside-work-tree` (like
/// [`crate::new::origin::GitOrigin::detect`]). Fail-soft: when `git` cannot
/// be run or the probe fails, `dir` counts as outside — the caller keeps the
/// behavior it would have without the probe, and the scaffold-time
/// repository discovery in [`ensure_repository`] has the final word anyway.
pub fn inside_work_tree(dir: &Path) -> bool {
let Ok(output) = std::process::Command::new("git")
.args(["rev-parse", "--is-inside-work-tree"])
.current_dir(dir)
.output()
else {
return false;
};
output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == "true"
}
#[cfg(test)]
mod tests {
use super::*;
@@ -61,4 +78,22 @@ mod tests {
assert!(!ensure_repository(&sub, true).unwrap());
assert!(!sub.join(".git").exists());
}
/// The probe answers like `git rev-parse --is-inside-work-tree`:
/// outside any repository it says no, inside one (at any depth, as a
/// skeleton mode scaffold would probe through its parent directory) it
/// says yes without touching the tree.
#[test]
fn inside_work_tree_follows_the_parent_repository() {
let dir = tempdir().unwrap();
assert!(!inside_work_tree(dir.path()));
git2::Repository::init(dir.path()).unwrap();
assert!(inside_work_tree(dir.path()));
let sub = dir.path().join("sub");
std::fs::create_dir(&sub).unwrap();
assert!(inside_work_tree(&sub));
assert!(!sub.join(".git").exists());
}
}
+38 -1
View File
@@ -23,6 +23,7 @@ use std::path::PathBuf;
use indicatif::MultiProgress;
use crate::new::detect::{self, Detection};
use crate::new::git;
use crate::new::options::{self, NewCli, NewOptions, SourceDir, SourceFormat, TemplateId};
use crate::new::origin::GitOrigin;
use crate::new::templates::{self, ProbeResult, ScaffoldOutcome};
@@ -467,8 +468,21 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
}
}
// 14. Git init.
// 14. Git init — asked only when a git init would actually happen
// (outside any repository, without `--no-git`). Inside an existing
// git work tree there is nothing to initialize: the question is
// skipped, the run assumes No and the scaffold logs its usual skip
// (the .gitignore files are written regardless). The probe looks at
// the packaged directory — or, for a skeleton, at the directory it
// would be created in.
let probe_dir = cli.source.clone().unwrap_or_else(|| cwd.clone());
let inside_repo = cli.git && git::inside_work_tree(&probe_dir);
if git_init_answer(!cli.git, inside_repo).is_some() {
// `--no-git` already declined, or there is nothing to initialize.
cli.git = false;
} else {
cli.git = prompt::confirm("Initialize a git repository?", cli.git)?;
}
// Resolve through the same pipeline as the non-interactive path: one
// source of truth for defaults and validation.
@@ -826,6 +840,16 @@ fn orig_origin_choices(origin: Option<&GitOrigin>) -> Vec<(String, &'static str)
choices
}
/// The git-init answer when the question must not be asked: `Some(false)`
/// both for `--no-git` (already declined) and inside an existing git work
/// tree (nothing to initialize — the scaffold logs the skip and writes the
/// `.gitignore` files anyway). `None` asks the wizard question, i.e.
/// whenever a git init would actually happen (fresh skeleton or packaged
/// directory outside any repository).
fn git_init_answer(no_git: bool, inside_repo: bool) -> Option<bool> {
(no_git || inside_repo).then_some(false)
}
/// A `debian/watch` template for GitHub/GitLab-hosted projects; `None` when
/// the homepage is not one of those hosts (the wizard skips the question).
pub fn watch_template(homepage: Option<&str>) -> Option<String> {
@@ -1250,6 +1274,19 @@ mod tests {
assert!(!summary_text(&opts(Tid::Go), Some("1.98.0")).contains("rust-toolchain"));
}
/// The git-init question is only asked when a git init would actually
/// happen: inside an existing work tree it is skipped with the assumed
/// No (with or without `--no-git`), and `--no-git` has already answered
/// it on its own.
#[test]
fn git_init_question_decision() {
assert_eq!(git_init_answer(false, true), Some(false));
assert_eq!(git_init_answer(true, true), Some(false));
assert_eq!(git_init_answer(true, false), Some(false));
// Outside any repository without --no-git: the wizard asks.
assert_eq!(git_init_answer(false, false), None);
}
#[test]
fn answer_validators() {
assert!(validate_revision_answer("1").is_ok());