Skip to content

Commit c931b70

Browse files
emo-ethjameswenzelEvalir
authored
fix(forge): make recursive forge update optional via --recursive flag (#5980)
* don't default to recursive submodule updates * update and remove ignore from recursive submodule update test * chore: update test to use new repo location --------- Co-authored-by: James Wenzel <[email protected]> Co-authored-by: Enrique Ortiz <[email protected]>
1 parent c602db6 commit c931b70

File tree

5 files changed

+69
-18
lines changed

5 files changed

+69
-18
lines changed

crates/cli/src/utils/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ https://github.com/foundry-rs/foundry/issues/new/choose"
546546
force: bool,
547547
remote: bool,
548548
no_fetch: bool,
549+
recursive: bool,
549550
paths: I,
550551
) -> Result<()>
551552
where
@@ -554,16 +555,27 @@ https://github.com/foundry-rs/foundry/issues/new/choose"
554555
{
555556
self.cmd()
556557
.stderr(self.stderr())
557-
.args(["submodule", "update", "--progress", "--init", "--recursive"])
558+
.args(["submodule", "update", "--progress", "--init"])
558559
.args(self.shallow.then_some("--depth=1"))
559560
.args(force.then_some("--force"))
560561
.args(remote.then_some("--remote"))
561562
.args(no_fetch.then_some("--no-fetch"))
563+
.args(recursive.then_some("--recursive"))
562564
.args(paths)
563565
.exec()
564566
.map(drop)
565567
}
566568

569+
pub fn submodule_foreach(self, recursive: bool, cmd: impl AsRef<OsStr>) -> Result<()> {
570+
self.cmd()
571+
.stderr(self.stderr())
572+
.args(["submodule", "foreach"])
573+
.args(recursive.then_some("--recursive"))
574+
.arg(cmd)
575+
.exec()
576+
.map(drop)
577+
}
578+
567579
pub fn submodule_init(self) -> Result<()> {
568580
self.cmd().stderr(self.stderr()).args(["submodule", "init"]).exec().map(drop)
569581
}

crates/forge/bin/cmd/init.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl InitArgs {
8484
git.submodule_init()?;
8585
} else {
8686
// if not shallow, initialize and clone submodules (without fetching latest)
87-
git.submodule_update(false, false, true, None::<PathBuf>)?;
87+
git.submodule_update(false, false, true, true, None::<PathBuf>)?;
8888
}
8989
} else {
9090
// if target is not empty

crates/forge/bin/cmd/install.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ impl DependencyInstallOpts {
126126

127127
if dependencies.is_empty() && !self.no_git {
128128
p_println!(!self.quiet => "Updating dependencies in {}", libs.display());
129-
git.submodule_update(false, false, false, Some(&libs))?;
129+
// recursively fetch all submodules (without fetching latest)
130+
git.submodule_update(false, false, false, true, Some(&libs))?;
130131
}
131132
fs::create_dir_all(&libs)?;
132133

@@ -303,8 +304,8 @@ impl Installer<'_> {
303304
trace!(?dep, url, ?path, "installing git submodule");
304305
self.git.submodule_add(true, url, path)?;
305306

306-
trace!("updating submodule recursively");
307-
self.git.submodule_update(false, false, false, Some(path))
307+
trace!("initializing submodule recursively");
308+
self.git.submodule_update(false, false, false, true, Some(path))
308309
}
309310

310311
fn git_checkout(self, dep: &Dependency, path: &Path, recurse: bool) -> Result<String> {

crates/forge/bin/cmd/update.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,29 @@ pub struct UpdateArgs {
2323
/// Override the up-to-date check.
2424
#[clap(short, long)]
2525
force: bool,
26+
27+
/// Recursively update submodules.
28+
#[clap(short, long)]
29+
recursive: bool,
2630
}
2731
impl_figment_convert_basic!(UpdateArgs);
2832

2933
impl UpdateArgs {
3034
pub fn run(self) -> Result<()> {
3135
let config = self.try_load_config_emit_warnings()?;
3236
let (root, paths) = dependencies_paths(&self.dependencies, &config)?;
33-
Git::new(&root).submodule_update(self.force, true, false, paths)
37+
// fetch the latest changes for each submodule (recursively if flag is set)
38+
let git = Git::new(&root);
39+
if self.recursive {
40+
// update submodules recursively
41+
git.submodule_update(self.force, true, false, true, paths)
42+
} else {
43+
// update root submodules
44+
git.submodule_update(self.force, true, false, false, paths)?;
45+
// initialize submodules of each submodule recursively (otherwise direct submodule
46+
// dependencies will revert to last commit)
47+
git.submodule_foreach(false, "git submodule update --init --progress --recursive ")
48+
}
3449
}
3550
}
3651

crates/forge/tests/cli/cmd.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,6 @@ forgetest!(can_install_latest_release_tag, |prj: TestProject, mut cmd: TestComma
10091009
// Tests that forge update doesn't break a working dependency by recursively updating nested
10101010
// dependencies
10111011
forgetest!(
1012-
#[ignore]
10131012
can_update_library_with_outdated_nested_dependency,
10141013
|prj: TestProject, mut cmd: TestCommand| {
10151014
cmd.git_init();
@@ -1018,39 +1017,63 @@ forgetest!(
10181017
let git_mod = prj.root().join(".git/modules/lib");
10191018
let git_mod_file = prj.root().join(".gitmodules");
10201019

1021-
let package = libs.join("issue-2264-repro");
1022-
let package_mod = git_mod.join("issue-2264-repro");
1020+
// get paths to check inside install fn
1021+
let package = libs.join("forge-5980-test");
1022+
let package_mod = git_mod.join("forge-5980-test");
10231023

10241024
let install = |cmd: &mut TestCommand| {
1025-
cmd.forge_fuse().args(["install", "foundry-rs/issue-2264-repro", "--no-commit"]);
1025+
// install main dependency
1026+
cmd.forge_fuse().args(["install", "evalir/forge-5980-test", "--no-commit"]);
10261027
cmd.assert_non_empty_stdout();
1028+
1029+
// assert pathbufs exist
10271030
assert!(package.exists());
10281031
assert!(package_mod.exists());
10291032

10301033
let submods = read_string(&git_mod_file);
1031-
assert!(submods.contains("https://github.com/foundry-rs/issue-2264-repro"));
1034+
assert!(submods.contains("https://github.com/evalir/forge-5980-test"));
10321035
};
10331036

10341037
install(&mut cmd);
1035-
cmd.forge_fuse().args(["update", "lib/issue-2264-repro"]);
1038+
// try to update the top-level dependency; there should be no update for this dependency,
1039+
// but its sub-dependency has upstream (breaking) changes; forge should not attempt to
1040+
// update the sub-dependency
1041+
cmd.forge_fuse().args(["update", "lib/forge-5980-test"]);
10361042
cmd.stdout_lossy();
10371043

1044+
// add explicit remappings for test file
1045+
let config = Config {
1046+
remappings: vec![
1047+
Remapping::from_str("forge-5980-test/=lib/forge-5980-test/src/").unwrap().into(),
1048+
// explicit remapping for sub-dependendy seems necessary for some reason
1049+
Remapping::from_str(
1050+
"forge-5980-test-dep/=lib/forge-5980-test/lib/forge-5980-test-dep/src/",
1051+
)
1052+
.unwrap()
1053+
.into(),
1054+
],
1055+
..Default::default()
1056+
};
1057+
prj.write_config(config);
1058+
1059+
// create test file that uses the top-level dependency; if the sub-dependency is updated,
1060+
// compilation will fail
10381061
prj.inner()
10391062
.add_source(
1040-
"MyTokenCopy",
1063+
"CounterCopy",
10411064
r#"
10421065
// SPDX-License-Identifier: UNLICENSED
1043-
pragma solidity ^0.6.0;
1044-
import "issue-2264-repro/MyToken.sol";
1045-
contract MyTokenCopy is MyToken {
1066+
pragma solidity ^0.8.0;
1067+
import "forge-5980-test/Counter.sol";
1068+
contract CounterCopy is Counter {
10461069
}
10471070
"#,
10481071
)
10491072
.unwrap();
10501073

1051-
cmd.forge_fuse().args(["build"]);
1074+
// build and check output
1075+
cmd.forge_fuse().arg("build");
10521076
let output = cmd.stdout_lossy();
1053-
10541077
assert!(output.contains("Compiler run successful",));
10551078
}
10561079
);

0 commit comments

Comments
 (0)