Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dependencies): support pinning of tags / revs when using .gitmodules with foundry.lock #9522

Open
wants to merge 64 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3bcf1b9
parse submodule status output
yash-atreya Dec 6, 2024
a3a01ec
feat(`forge`): save submodules info on install
yash-atreya Dec 9, 2024
cd075f2
re-checkout to tag/rev after forge update
yash-atreya Dec 9, 2024
917cc13
clippy
yash-atreya Dec 9, 2024
e7fb768
fmt
yash-atreya Dec 9, 2024
8e4b543
fix
yash-atreya Dec 9, 2024
53b362d
fix
yash-atreya Dec 10, 2024
f666e8c
test
yash-atreya Dec 10, 2024
0ea584d
fix
yash-atreya Dec 10, 2024
cf88ef8
override using forge update
yash-atreya Dec 10, 2024
2a55c67
nit
yash-atreya Dec 10, 2024
19194c0
nit
yash-atreya Dec 10, 2024
7f6d026
fix: update only untagged deps
yash-atreya Dec 11, 2024
ed3ccfe
allow overrides
yash-atreya Dec 11, 2024
f893917
clippy
yash-atreya Dec 11, 2024
dd072fd
remove + rename to foundry.lock
yash-atreya Dec 11, 2024
ed14a48
nit
yash-atreya Dec 11, 2024
90d92bb
fix: sync foundry.lock on install
yash-atreya Dec 11, 2024
5640bab
sync foundry lock using forge install
yash-atreya Dec 11, 2024
7d9ce8d
fix: read_and_sync_foundry_lock
yash-atreya Dec 17, 2024
92454f8
fix
yash-atreya Dec 17, 2024
46ce492
fix test
yash-atreya Dec 17, 2024
9ae88d6
fix
yash-atreya Dec 17, 2024
fdb1b32
fix
yash-atreya Dec 17, 2024
00a887c
fix
yash-atreya Dec 17, 2024
ac922fa
Merge branch 'master' into yash/fix-forge-update
yash-atreya Jan 6, 2025
07ce536
Merge branch 'master' into yash/fix-forge-update
yash-atreya Jan 10, 2025
53eed1f
Merge branch 'master' into yash/fix-forge-update
grandizzy Jan 14, 2025
3a7e7eb
Do not run can_sync_foundry_lock test on win (fails on master branch …
grandizzy Jan 14, 2025
b7954c3
Merge branch 'master' into yash/fix-forge-update
yash-atreya Jan 29, 2025
8276b42
feat: introduce `LockFile` type, use it in forge install and forge re…
yash-atreya Jan 29, 2025
2f82d39
fix: account for clean lib/ dir while syncing lockfile
yash-atreya Jan 29, 2025
c4a13fd
fix: integrate lockfile into update
yash-atreya Jan 29, 2025
1859e4d
clippy
yash-atreya Jan 29, 2025
97fba00
fix
yash-atreya Jan 29, 2025
090da39
fix
yash-atreya Jan 29, 2025
7787895
feat(`forge`): introduces a `Lockfile` type (#9781)
yash-atreya Jan 29, 2025
ddf425d
clean up forge update
yash-atreya Jan 29, 2025
1cd019a
Merge branch 'yash/lockfile' into yash/fix-forge-update
yash-atreya Jan 29, 2025
5fb8861
nits
yash-atreya Jan 29, 2025
6fc5850
nit
yash-atreya Jan 30, 2025
a55709c
fix: update branch rev in lockfile and print updates
yash-atreya Jan 30, 2025
c42c915
fix
yash-atreya Jan 30, 2025
9b93c00
clippy
yash-atreya Jan 30, 2025
94b5238
nit
yash-atreya Jan 30, 2025
29d846a
fix
yash-atreya Jan 30, 2025
f25c0da
assert foundry lock in tests
yash-atreya Jan 30, 2025
883d048
nit
yash-atreya Jan 30, 2025
94edb91
refac ExtTester and test uni v4 foundry lock sync
yash-atreya Jan 30, 2025
cb7e434
oz sync test
yash-atreya Jan 30, 2025
8fc72e1
fix: run sync after submodule update on install
yash-atreya Jan 30, 2025
4f91ecc
fix: tag_for_commit should return earliest tag that contains commit +…
yash-atreya Jan 30, 2025
50e4a4a
Merge branch 'master' into yash/fix-forge-update
yash-atreya Jan 30, 2025
4c87a7a
fix: write lockfile after git succeeds
yash-atreya Feb 5, 2025
44e334f
Merge branch 'master' into yash/fix-forge-update
yash-atreya Feb 5, 2025
d8a1e53
feat: account for deps pinned to a branch in .gitmodules while syncing
yash-atreya Feb 10, 2025
311d4bc
fix: SUBMODULE_BRANCH_REGEX
yash-atreya Feb 10, 2025
dc22515
fix: properly parse paths from .gitmodules
yash-atreya Feb 11, 2025
068ae3c
nit
yash-atreya Feb 11, 2025
3ecfcfa
Merge branch 'master' into yash/fix-forge-update
yash-atreya Feb 24, 2025
69ffe4c
clippy
yash-atreya Feb 24, 2025
16604d9
fix tests
yash-atreya Feb 24, 2025
905c5a0
fix test
yash-atreya Feb 24, 2025
e1b1a05
Merge branch 'master' into yash/fix-forge-update
zerosnacks Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: update branch rev in lockfile and print updates
yash-atreya committed Jan 30, 2025
commit a55709c84bd72ad78936beb3337eaf1825ca9551
4 changes: 3 additions & 1 deletion crates/forge/bin/cmd/install.rs
Original file line number Diff line number Diff line change
@@ -175,7 +175,9 @@ impl DependencyInstallOpts {
if let Some(tag_or_branch) = &installed_tag {
// First, check if this tag has a branch
dep_id = Some(DepIdentifier::resolve_type(&git, &path, tag_or_branch)?);
if git.has_branch(tag_or_branch, &path)? {
if git.has_branch(tag_or_branch, &path)? &&
dep_id.as_ref().is_some_and(|id| id.is_branch())
{
// always work with relative paths when directly modifying submodules
git.cmd()
.args(["submodule", "set-branch", "-b", tag_or_branch])
69 changes: 66 additions & 3 deletions crates/forge/bin/cmd/update.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use alloy_primitives::map::HashMap;
use clap::{Parser, ValueHint};
use eyre::{Context, Result};
use forge::{DepIdentifier, Lockfile};
use forge::{DepIdentifier, DepMap, Lockfile};
use foundry_cli::{
opts::Dependency,
utils::{Git, LoadConfig},
};
use foundry_config::{impl_figment_convert_basic, Config};
use std::path::PathBuf;
use yansi::Paint;

/// CLI arguments for `forge update`.
#[derive(Clone, Debug, Parser)]
@@ -46,7 +47,7 @@ impl UpdateArgs {
let prev_len = foundry_lock.len();

// update the submodules' tags if any overrides are present

let mut prev_dep_ids: DepMap = HashMap::default();
if dep_overrides.is_empty() {
// running `forge update`, update all deps
foundry_lock.iter_mut().for_each(|(_path, dep_id)| {
@@ -61,7 +62,8 @@ impl UpdateArgs {
.strip_prefix(&root)
.wrap_err("Dependency path is not relative to the repository root")?;
if let Ok(dep_id) = DepIdentifier::resolve_type(&git, dep_path, override_tag) {
foundry_lock.override_dep(rel_path, dep_id)?
let prev = foundry_lock.override_dep(rel_path, dep_id)?;
prev_dep_ids.insert(rel_path.to_owned(), prev);
} else {
sh_warn!(
"Could not r#override submodule at {} with tag {}, try using forge install",
@@ -91,6 +93,52 @@ impl UpdateArgs {
}
}

// FIX: Branches should get updated to their latest commit on `forge update`.
// i.e if previously submodule was tracking branch `main` at rev `1234567` and now the
// remote `main` branch is at `7654321`, then submodule should also be updated to `7654321`.
// This tracking is automatically handled by git, but we need to update the lockfile entry
// to reflect the latest commit.
if dep_overrides.is_empty() {
let branch_overrides = foundry_lock
.iter_mut()
.filter_map(|(path, dep_id)| {
if dep_id.is_branch() && dep_id.overridden() {
return Some((path, dep_id))
}
None
})
.collect::<Vec<_>>();

for (path, dep_id) in branch_overrides {
let (curr_rev, curr_branch) = git.current_rev_branch(&root.join(path))?;
let name = dep_id.name();
// This can occur when the submodule is manually checked out to a different branch.
if curr_branch != name {
let warn_msg = format!(
r#"Lockfile sync warning
Lockfile is tracking branch {name} for submodule at {path:?}, but the submodule is currently on {curr_branch}.
Checking out branch {name} for submodule at {path:?}."#,
);
let _ = sh_warn!("{}", warn_msg);
git.checkout_at(name, &root.join(path)).wrap_err(format!(
"Could not checkout branch {name} for submodule at {}",
path.display()
))?;
}

// Update the lockfile entry to reflect the latest commit
let prev = std::mem::replace(
dep_id,
DepIdentifier::Branch {
name: name.to_string(),
rev: curr_rev,
r#override: true,
},
);
prev_dep_ids.insert(path.to_owned(), prev);
}
}

// checkout the submodules at the correct tags
for (path, dep_id) in foundry_lock.iter() {
git.checkout_at(dep_id.checkout_id(), &root.join(path))?;
@@ -102,6 +150,17 @@ impl UpdateArgs {
foundry_lock.write()?;
}

// Print updates from => to
for (path, prev) in prev_dep_ids {
let curr = foundry_lock.get(&path).unwrap();
sh_println!(
"Updated dep at '{}', (from: {prev}, to: {curr})",
path.display().green(),
prev = prev,
curr = curr.yellow()
)?;
}

Ok(())
}

@@ -129,6 +188,10 @@ pub fn dependencies_paths(
let git_root = Git::root_of(&config.root)?;
let libs = config.install_lib_dir();

if deps.is_empty() {
return Ok((git_root, Vec::new(), HashMap::default()));
}

let mut paths = Vec::with_capacity(deps.len());
let mut overrides = HashMap::with_capacity_and_hasher(deps.len(), Default::default());
for dep in deps {
34 changes: 28 additions & 6 deletions crates/forge/src/lockfile.rs
Original file line number Diff line number Diff line change
@@ -172,19 +172,25 @@ impl<'a> Lockfile<'a> {

/// Override a dependency in the lockfile.
///
/// Returns the overridden/previous [`DepIdentifier`].
/// This is used in `forge update` to decide whether a dep's tag/branch/rev should be updated.
///
/// Throws an error if the dependency is not found in the lockfile.
pub fn override_dep(&mut self, dep: &Path, mut new_dep_id: DepIdentifier) -> Result<()> {
self.deps
pub fn override_dep(
&mut self,
dep: &Path,
mut new_dep_id: DepIdentifier,
) -> Result<DepIdentifier> {
let prev = self
.deps
.get_mut(dep)
.map(|d| {
new_dep_id.mark_overide();
*d = new_dep_id;
std::mem::replace(d, new_dep_id)
})
.ok_or_eyre(format!("Dependency not found in lockfile: {}", dep.display()))?;

Ok(())
Ok(prev)
}

/// Returns the num of dependencies in the lockfile.
@@ -280,6 +286,17 @@ impl DepIdentifier {
}
}

/// Get the name of the dependency.
///
/// In case of a Rev, this will return the commit hash.
pub fn name(&self) -> &str {
match self {
Self::Branch { name, .. } => name,
Self::Tag { name, .. } => name,
Self::Rev { rev, .. } => rev,
}
}

/// Get the name/rev to checkout at.
pub fn checkout_id(&self) -> &str {
match self {
@@ -289,7 +306,7 @@ impl DepIdentifier {
}
}

/// Marks as dependency as overridden.
/// Marks as dependency as overriden.
pub fn mark_overide(&mut self) {
match self {
Self::Branch { r#override, .. } => *r#override = true,
@@ -298,14 +315,19 @@ impl DepIdentifier {
}
}

/// Returns whether the dependency has been overridden.
/// Returns whether the dependency has been overriden.
pub fn overridden(&self) -> bool {
match self {
Self::Branch { r#override, .. } => *r#override,
Self::Tag { r#override, .. } => *r#override,
Self::Rev { r#override, .. } => *r#override,
}
}

/// Returns whether the dependency is a branch.
pub fn is_branch(&self) -> bool {
matches!(self, Self::Branch { .. })
}
}

impl std::fmt::Display for DepIdentifier {
55 changes: 52 additions & 3 deletions crates/forge/tests/cli/install.rs
Original file line number Diff line number Diff line change
@@ -190,7 +190,7 @@ forgetest!(can_install_latest_release_tag, |prj, cmd| {
assert!(current >= version);
});

forgetest!(can_update_and_retain_tag_revs, |_prj, cmd| {
forgetest!(can_update_and_retain_tag_revs, |prj, cmd| {
cmd.git_init();

// Installs oz at release tag
@@ -203,17 +203,66 @@ forgetest!(can_update_and_retain_tag_revs, |_prj, cmd| {

let out = cmd.git_submodule_status();
let status = String::from_utf8_lossy(&out.stdout);
let mut lockfile_init = Lockfile::new(prj.root());

lockfile_init.read().unwrap();

let deps = lockfile_init.iter().map(|(path, dep_id)| (path, dep_id)).collect::<Vec<_>>();
assert_eq!(deps.len(), 2);
assert_eq!(
deps[0],
(
&PathBuf::from("lib/openzeppelin-contracts"),
&DepIdentifier::Tag {
name: "v5.1.0".to_string(),
rev: "69c8def5f222ff96f2b5beff05dfba996368aa79".to_string(),
r#override: false
}
)
);

assert_eq!(
deps[1],
(
&PathBuf::from("lib/solady"),
&DepIdentifier::Rev { rev: "513f581".to_string(), r#override: false }
)
);

let submodules_init: Submodules = status.parse().unwrap();

cmd.forge_fuse().arg("update").assert_success();

let out = cmd.git_submodule_status();
let status = String::from_utf8_lossy(&out.stdout);

let submodules_update: Submodules = status.parse().unwrap();

assert_eq!(submodules_init, submodules_update);

let mut lockfile_update = Lockfile::new(prj.root());

lockfile_update.read().unwrap();

let deps = lockfile_update.iter().map(|(path, dep_id)| (path, dep_id)).collect::<Vec<_>>();

assert_eq!(deps.len(), 2);
assert_eq!(
deps[1],
(
&PathBuf::from("lib/openzeppelin-contracts"),
&DepIdentifier::Tag {
name: "v5.1.0".to_string(),
rev: "69c8def5f222ff96f2b5beff05dfba996368aa79".to_string(),
r#override: false
}
)
);
assert_eq!(
deps[0],
(
&PathBuf::from("lib/solady"),
&DepIdentifier::Rev { rev: "513f581".to_string(), r#override: false }
)
);
});

forgetest!(can_override_tag_in_update, |_prj, cmd| {