diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 6bf8c5b5d1628..5a0377ee68c0c 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 { + self.cmd().args(["rev-parse", "--show-cdup"]).exec().map(|out| out.stdout.is_empty()) + } + pub fn is_clean(self) -> Result { 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 f1a2166352655..81ba2bb5bef96 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 f0774a36802ab..1be56c29405b6 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();