From 13af418e724e141ae7dfa7957476d691eee7d0e9 Mon Sep 17 00:00:00 2001 From: evalir Date: Mon, 4 Dec 2023 19:13:41 -0400 Subject: [PATCH] fix(`forge`): pre-emptively create `lib` dir if it doesn't exist for updating submodules (#6521) * fix(forge): pre-emptively create lib dir if it doesn't exist for updating submodules * chore: only run submodule update if the dir is not empty * chore: revert to prev behavior * chore: add extra check for .gitmodules * chore: remove useless checks * fix: only update submodules if there are any * fix: only search for top git repo if its being used * relax error and add test --------- Co-authored-by: Matthias Seitz --- crates/cli/src/utils/mod.rs | 13 +++++++++++++ crates/forge/bin/cmd/install.rs | 20 +++++++++++++++++--- crates/forge/tests/cli/cmd.rs | 17 +++++++++++++++++ crates/test-utils/src/util.rs | 25 +++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 8a0e51ad9b78..425732de95e6 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -540,6 +540,19 @@ https://github.com/foundry-rs/foundry/issues/new/choose" .map(|stdout| stdout.lines().any(|line| line.starts_with('-'))) } + /// Returns true if the given path has no submodules by checking `git submodule status` + pub fn has_submodules(self, paths: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + self.cmd() + .args(["submodule", "status"]) + .args(paths) + .get_stdout_lossy() + .map(|stdout| stdout.trim().lines().next().is_some()) + } + pub fn submodule_add( self, force: bool, diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index d01fe9794062..270e8c97ce4e 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -124,10 +124,24 @@ impl DependencyInstallOpts { let libs = git.root.join(install_lib_dir); if dependencies.is_empty() && !self.no_git { - p_println!(!self.quiet => "Updating dependencies in {}", libs.display()); - // recursively fetch all submodules (without fetching latest) - git.submodule_update(false, false, false, true, Some(&libs))?; + // Use the root of the git repository to look for submodules. + let root = Git::root_of(git.root)?; + match git.has_submodules(Some(&root)) { + Ok(true) => { + p_println!(!quiet => "Updating dependencies in {}", libs.display()); + // recursively fetch all submodules (without fetching latest) + git.submodule_update(false, false, false, true, Some(&libs))?; + } + + Err(err) => { + warn!(?err, "Failed to check for submodules"); + } + _ => { + // no submodules, nothing to do + } + } } + fs::create_dir_all(&libs)?; let installer = Installer { git, no_commit }; diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index b43617078fa8..5e8283f2785a 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -895,6 +895,23 @@ forgetest!(can_install_and_remove, |prj, cmd| { remove(&mut cmd, "lib/forge-std"); }); +// test to check we can run `forge install` in an empty dir +forgetest!(can_install_empty, |prj, cmd| { + // create + cmd.git_init(); + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); + + // create initial commit + fs::write(prj.root().join("README.md"), "Initial commit").unwrap(); + + cmd.git_add().unwrap(); + cmd.git_commit("Initial commit").unwrap(); + + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); +}); + // test to check that package can be reinstalled after manually removing the directory forgetest!(can_reinstall_after_manual_remove, |prj, cmd| { cmd.git_init(); diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 6a879378acdf..9f5e541b1325 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -680,6 +680,31 @@ impl TestCommand { output } + /// Returns a new [Command] that is inside the current project dir + pub fn cmd_in_current_dir(&self, program: &str) -> Command { + let mut cmd = Command::new(program); + cmd.current_dir(self.project.root()); + cmd + } + + /// Runs `git add .` inside the project's dir + #[track_caller] + pub fn git_add(&self) -> Result<()> { + let mut cmd = self.cmd_in_current_dir("git"); + cmd.arg("add").arg("."); + let output = cmd.output()?; + self.ensure_success(&output) + } + + /// Runs `git commit .` inside the project's dir + #[track_caller] + pub fn git_commit(&self, msg: &str) -> Result<()> { + let mut cmd = self.cmd_in_current_dir("git"); + cmd.arg("commit").arg("-m").arg(msg); + let output = cmd.output()?; + self.ensure_success(&output) + } + /// Executes the command and returns the `(stdout, stderr)` of the output as lossy `String`s. /// /// Expects the command to be successful.