Skip to content

Commit

Permalink
fix(forge):forge init --template correctly handles submodules (#5911)
Browse files Browse the repository at this point in the history
* fix: forge init template submodules

* appease clippy

* update test

* add modules check to regular template test

---------

Co-authored-by: James Wenzel <jameswenzel@users.noreply.github.com>
  • Loading branch information
emo-eth and jameswenzel authored Sep 27, 2023
1 parent 80df71f commit dfa3e4c
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 22 deletions.
54 changes: 51 additions & 3 deletions crates/cli/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,22 @@ impl<'a> Git<'a> {
.map(drop)
}

pub fn fetch(
shallow: bool,
remote: impl AsRef<OsStr>,
branch: Option<impl AsRef<OsStr>>,
) -> Result<()> {
Self::cmd_no_root()
.stderr(Stdio::inherit())
.arg("fetch")
.args(shallow.then_some("--no-tags"))
.args(shallow.then_some("--depth=1"))
.arg(remote)
.args(branch)
.exec()
.map(drop)
}

#[inline]
pub fn root(self, root: &Path) -> Git<'_> {
Git { root, ..self }
Expand Down Expand Up @@ -418,6 +434,23 @@ impl<'a> Git<'a> {
self.cmd().arg("add").args(paths).exec().map(drop)
}

pub fn reset(self, hard: bool, tree: impl AsRef<OsStr>) -> Result<()> {
self.cmd().arg("reset").args(hard.then_some("--hard")).arg(tree).exec().map(drop)
}

pub fn commit_tree(
self,
tree: impl AsRef<OsStr>,
msg: Option<impl AsRef<OsStr>>,
) -> Result<String> {
self.cmd()
.arg("commit-tree")
.arg(tree)
.args(msg.as_ref().is_some().then_some("-m"))
.args(msg)
.get_stdout_lossy()
}

pub fn rm<I, S>(self, force: bool, paths: I) -> Result<()>
where
I: IntoIterator<Item = S>,
Expand Down Expand Up @@ -484,8 +517,12 @@ https://github.com/foundry-rs/foundry/issues/new/choose"
}
}

pub fn commit_hash(self, short: bool) -> Result<String> {
self.cmd().arg("rev-parse").args(short.then_some("--short")).arg("HEAD").get_stdout_lossy()
pub fn commit_hash(self, short: bool, revision: &str) -> Result<String> {
self.cmd()
.arg("rev-parse")
.args(short.then_some("--short"))
.arg(revision)
.get_stdout_lossy()
}

pub fn tag(self) -> Result<String> {
Expand Down Expand Up @@ -521,7 +558,13 @@ https://github.com/foundry-rs/foundry/issues/new/choose"
.map(drop)
}

pub fn submodule_update<I, S>(self, force: bool, remote: bool, paths: I) -> Result<()>
pub fn submodule_update<I, S>(
self,
force: bool,
remote: bool,
no_fetch: bool,
paths: I,
) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
Expand All @@ -532,11 +575,16 @@ https://github.com/foundry-rs/foundry/issues/new/choose"
.args(self.shallow.then_some("--depth=1"))
.args(force.then_some("--force"))
.args(remote.then_some("--remote"))
.args(no_fetch.then_some("--no-fetch"))
.args(paths)
.exec()
.map(drop)
}

pub fn submodule_init(self) -> Result<()> {
self.cmd().stderr(self.stderr()).args(["submodule", "init"]).exec().map(drop)
}

pub fn cmd(self) -> Command {
let mut cmd = Self::cmd_no_root();
cmd.current_dir(self.root);
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl DocArgs {
}
}

let commit = foundry_cli::utils::Git::new(&root).commit_hash(false).ok();
let commit = foundry_cli::utils::Git::new(&root).commit_hash(false, "HEAD").ok();

let mut builder = DocBuilder::new(root.clone(), config.project_paths().sources)
.with_should_build(self.build)
Expand Down
39 changes: 24 additions & 15 deletions crates/forge/bin/cmd/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,39 @@ impl InitArgs {
let root = dunce::canonicalize(root)?;
let git = Git::new(&root).quiet(quiet).shallow(shallow);

// if a template is provided, then this command clones the template repo, removes the .git
// folder, and initializes a new git repo—-this ensures there is no history from the
// template and the template is not set as a remote.
// if a template is provided, then this command initializes a git repo,
// fetches the template repo, and resets the git history to the head of the fetched
// repo with no other history
if let Some(template) = template {
let template = if template.contains("://") {
template
} else {
"https://github.com/".to_string() + &template
};
p_println!(!quiet => "Initializing {} from {}...", root.display(), template);
// initialize the git repository
git.init()?;

if let Some(branch) = branch {
Git::clone_with_branch(shallow, &template, branch, Some(&root))?;
// fetch the template - always fetch shallow for templates since git history will be
// collapsed. gitmodules will be initialized after the template is fetched
Git::fetch(true, &template, branch)?;
// reset git history to the head of the template
// first get the commit hash that was fetched
let commit_hash = git.commit_hash(true, "FETCH_HEAD")?;
// format a commit message for the new repo
let commit_msg = format!("chore: init from {template} at {commit_hash}");
// get the hash of the FETCH_HEAD with the new commit message
let new_commit_hash = git.commit_tree("FETCH_HEAD^{tree}", Some(commit_msg))?;
// reset head of this repo to be the head of the template repo
git.reset(true, new_commit_hash)?;

// if shallow, just initialize submodules
if shallow {
git.submodule_init()?;
} else {
Git::clone(shallow, &template, Some(&root))?;
// if not shallow, initialize and clone submodules (without fetching latest)
git.submodule_update(false, false, true, None::<PathBuf>)?;
}
// Modify the git history.
let commit_hash = git.commit_hash(true)?;
std::fs::remove_dir_all(root.join(".git"))?;

git.init()?;
git.add(Some("--all"))?;
let commit_msg = format!("chore: init from {template} at {commit_hash}");
git.commit(&commit_msg)?;
} else {
// if target is not empty
if root.read_dir().map_or(false, |mut i| i.next().is_some()) {
Expand Down Expand Up @@ -157,7 +166,7 @@ impl InitArgs {

/// Returns the commit hash of the project if it exists
pub fn get_commit_hash(root: &Path) -> Option<String> {
Git::new(root).commit_hash(true).ok()
Git::new(root).commit_hash(true, "HEAD").ok()
}

/// Initialises `root` as a git repository, if it isn't one already.
Expand Down
4 changes: 2 additions & 2 deletions crates/forge/bin/cmd/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl DependencyInstallOpts {

if dependencies.is_empty() && !self.no_git {
p_println!(!self.quiet => "Updating dependencies in {}", libs.display());
git.submodule_update(false, false, Some(&libs))?;
git.submodule_update(false, false, false, Some(&libs))?;
}
fs::create_dir_all(&libs)?;

Expand Down Expand Up @@ -304,7 +304,7 @@ impl Installer<'_> {
self.git.submodule_add(true, url, path)?;

trace!("updating submodule recursively");
self.git.submodule_update(false, false, Some(path))
self.git.submodule_update(false, false, false, Some(path))
}

fn git_checkout(self, dep: &Dependency, path: &Path, recurse: bool) -> Result<String> {
Expand Down
2 changes: 1 addition & 1 deletion crates/forge/bin/cmd/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl UpdateArgs {
pub fn run(self) -> Result<()> {
let config = self.try_load_config_emit_warnings()?;
let (root, paths) = dependencies_paths(&self.dependencies, &config)?;
Git::new(&root).submodule_update(self.force, true, paths)
Git::new(&root).submodule_update(self.force, true, false, paths)
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/forge/tests/cli/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ forgetest!(can_init_template, |prj: TestProject, mut cmd: TestCommand| {
assert!(prj.root().join(".git").exists());
assert!(prj.root().join("foundry.toml").exists());
assert!(prj.root().join("lib/forge-std").exists());
// assert that gitmodules were correctly initialized
assert!(prj.root().join(".git/modules").exists());
assert!(prj.root().join("src").exists());
assert!(prj.root().join("test").exists());
});
Expand All @@ -391,6 +393,8 @@ forgetest!(can_init_template_with_branch, |prj: TestProject, mut cmd: TestComman
assert!(prj.root().join(".git").exists());
assert!(prj.root().join(".dapprc").exists());
assert!(prj.root().join("lib/ds-test").exists());
// assert that gitmodules were correctly initialized
assert!(prj.root().join(".git/modules").exists());
assert!(prj.root().join("src").exists());
assert!(prj.root().join("scripts").exists());
});
Expand Down

0 comments on commit dfa3e4c

Please sign in to comment.