From 4f7dba21e24e3b0f4106f449e1884cdc708afe05 Mon Sep 17 00:00:00 2001
From: 0xvv <public_double_v@protonmail.com>
Date: Fri, 17 Jan 2025 00:38:38 +0100
Subject: [PATCH] fix(forge): init a git repo at root during init unless
 explicitely stated

---
 crates/cli/src/utils/mod.rs   |  4 ++++
 crates/forge/bin/cmd/init.rs  | 22 ++++++++++++++++-----
 crates/forge/tests/cli/cmd.rs | 37 +++++++++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+), 5 deletions(-)

diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs
index 6bf8c5b5d162..5a0377ee68c0 100644
--- a/crates/cli/src/utils/mod.rs
+++ b/crates/cli/src/utils/mod.rs
@@ -440,6 +440,10 @@ impl<'a> Git<'a> {
         self.cmd().args(["rev-parse", "--is-inside-work-tree"]).status().map(|s| s.success())
     }
 
+    pub fn is_repo_root(self) -> Result<bool> {
+        self.cmd().args(["rev-parse", "--show-cdup"]).exec().map(|out| out.stdout.is_empty())
+    }
+
     pub fn is_clean(self) -> Result<bool> {
         self.cmd().args(["status", "--porcelain"]).exec().map(|out| out.stdout.is_empty())
     }
diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs
index f1a216635265..81ba2bb5bef9 100644
--- a/crates/forge/bin/cmd/init.rs
+++ b/crates/forge/bin/cmd/init.rs
@@ -37,13 +37,18 @@ pub struct InitArgs {
     #[arg(long, conflicts_with = "template")]
     pub vscode: bool,
 
+    /// Do not initialize a new git repository. Only valid if the target is in a git repository.
+    /// If not in a git repository, a repo will still be initialized.
+    #[arg(long, conflicts_with = "template")]
+    pub use_parent_git: bool,
+
     #[command(flatten)]
     pub install: DependencyInstallOpts,
 }
 
 impl InitArgs {
     pub fn run(self) -> Result<()> {
-        let Self { root, template, branch, install, offline, force, vscode } = self;
+        let Self { root, template, branch, install, offline, force, vscode, use_parent_git } = self;
         let DependencyInstallOpts { shallow, no_git, no_commit } = install;
 
         // create the root dir if it does not exist
@@ -138,7 +143,7 @@ impl InitArgs {
 
             // set up the repo
             if !no_git {
-                init_git_repo(git, no_commit)?;
+                init_git_repo(git, no_commit, use_parent_git)?;
             }
 
             // install forge-std
@@ -163,15 +168,22 @@ impl InitArgs {
     }
 }
 
-/// Initialises `root` as a git repository, if it isn't one already.
+/// Initialises `root` as a git repository, if it isn't one already, unless 'use_parent_git' is
+/// true.
 ///
 /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already.
 ///
-/// Commits everything in `root` if `no_commit` is false.
-fn init_git_repo(git: Git<'_>, no_commit: bool) -> Result<()> {
+/// Commits everything if `no_commit` is false.
+fn init_git_repo(git: Git<'_>, no_commit: bool, use_parent_git: bool) -> Result<()> {
     // git init
+    // if not in a git repo, initialize one
     if !git.is_in_repo()? {
         git.init()?;
+    } else {
+        // if target is not the repo root init a new one unless `use_parent_git` is true
+        if !git.is_repo_root()? && !use_parent_git {
+            git.init()?;
+        }
     }
 
     // .gitignore
diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs
index f0774a36802a..1be56c29405b 100644
--- a/crates/forge/tests/cli/cmd.rs
+++ b/crates/forge/tests/cli/cmd.rs
@@ -501,6 +501,43 @@ Warning: Target directory is not empty, but `--force` was specified
     assert_eq!(gitignore, "not foundry .gitignore");
 });
 
+// `forge init --use-parent-git` works on already initialized git repository
+forgetest!(can_init_using_parent_repo, |prj, cmd| {
+    let root = prj.root();
+
+    // initialize new git repo
+    let status = Command::new("git")
+        .arg("init")
+        .current_dir(root)
+        .stdout(Stdio::null())
+        .stderr(Stdio::null())
+        .status()
+        .expect("could not run git init");
+    assert!(status.success());
+    assert!(root.join(".git").exists());
+
+    prj.create_file("README.md", "non-empty dir");
+    prj.create_file(".gitignore", "not foundry .gitignore");
+
+    let folder = "foundry-folder";
+    cmd.arg("init").arg(folder).arg("--force").arg("--use-parent-git").assert_success().stdout_eq(
+        str![[r#"
+Initializing [..]...
+Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None)
+    Installed forge-std[..]
+    Initialized forge project
+
+"#]],
+    );
+
+    assert!(root.join(folder).join("lib/forge-std").exists());
+
+    // not overwritten
+    let gitignore = root.join(".gitignore");
+    let gitignore = fs::read_to_string(gitignore).unwrap();
+    assert_eq!(gitignore, "not foundry .gitignore");
+});
+
 // Checks that remappings.txt and .vscode/settings.json is generated
 forgetest!(can_init_vscode, |prj, cmd| {
     prj.wipe();