From bac82f0afe55ae643eadec42f067647b733e8fc5 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 17 Sep 2026 10:37:54 +0200 Subject: [PATCH] new: skip the git-init question inside existing repositories --- src/new/git.rs | 35 +++++++++++++++++++++++++++++++++++ src/new/questions.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/new/git.rs b/src/new/git.rs index d87ce96..61935ce 100644 --- a/src/new/git.rs +++ b/src/new/git.rs @@ -28,6 +28,23 @@ pub fn ensure_repository(dir: &Path, init: bool) -> Result 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()); + } } diff --git a/src/new/questions.rs b/src/new/questions.rs index 6684618..8e39e6f 100644 --- a/src/new/questions.rs +++ b/src/new/questions.rs @@ -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> { } } - // 14. Git init. - cli.git = prompt::confirm("Initialize a git repository?", cli.git)?; + // 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 { + (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 { @@ -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());