diff --git a/third_party/move/tools/move-package/src/resolution/git.rs b/third_party/move/tools/move-package/src/resolution/git.rs new file mode 100644 index 0000000000000..fbd34a6c985b8 --- /dev/null +++ b/third_party/move/tools/move-package/src/resolution/git.rs @@ -0,0 +1,236 @@ +// Copyright (c) Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::source_package::parsed_manifest::PackageName; +use anyhow::bail; +use std::process::{Command, ExitStatus, Stdio}; + +pub(crate) fn fetch_new_dependency( + git_url: &str, + git_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result<()> { + deep_clone_repo(git_url, git_path, dep_name)?; + checkout_rev(git_path, git_rev, dep_name)?; + Ok(()) +} + +pub(crate) fn fetch_new_dependency_shallow( + git_url: &str, + git_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result<()> { + shallow_clone_repo(git_url, git_path, dep_name)?; + + shallow_fetch_latest_origin_rev(git_path, git_rev, dep_name)?; + switch_to_fetched_rev(git_path, git_rev, dep_name)?; + + Ok(()) +} + +pub(crate) fn update_dependency( + git_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result<()> { + let status = fetch_latest_origin_rev(git_path, dep_name)?; + if !status.success() { + return Err(anyhow::anyhow!( + "Failed to fetch to latest Git state for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", + dep_name, + status + )); + } + let status = Command::new("git") + .args([ + "-C", + git_path, + "reset", + "--hard", + &format!("origin/{}", git_rev) + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map_err(|_| { + anyhow::anyhow!( + "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps", + git_rev, + dep_name + ) + })?; + if !status.success() { + return Err(anyhow::anyhow!( + "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", + git_rev, + dep_name, + status + )); + } + Ok(()) +} + +pub(crate) fn update_dependency_shallow( + git_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result<()> { + // If the current folder exists, do a fetch and reset to ensure that the branch + // is up to date + // NOTE: this means that you must run the package system with a working network connection + let status = shallow_fetch_latest_origin_rev(git_path, git_rev, dep_name)?; + if !status.success() { + return Err(anyhow::anyhow!( + "Failed to fetch to latest Git state for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", + dep_name, + status + )); + } + let status = switch_to_fetched_rev(git_path, git_rev, dep_name)?; + if !status.success() { + return Err(anyhow::anyhow!( + "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", + git_rev, + dep_name, + status + )); + } + Ok(()) +} + +// old version of initial git clone +fn deep_clone_repo(url: &str, repo_path: &str, dep_name: PackageName) -> anyhow::Result<()> { + Command::new("git") + .args(["clone", url, repo_path]) + .output() + .map_err(|_| { + anyhow::anyhow!("Failed to clone Git repository for package '{}'", dep_name) + })?; + Ok(()) +} + +fn shallow_clone_repo(url: &str, repo_path: &str, package_name: PackageName) -> anyhow::Result<()> { + Command::new("git") + .args(["clone", "--depth", "1", url, repo_path]) + .output() + .map_err(|_| { + anyhow::anyhow!( + "Failed to clone Git repository for package '{}'", + package_name + ) + })?; + Ok(()) +} + +// old version of initial git checkout +fn checkout_rev(repo_path: &str, git_rev: &str, dep_name: PackageName) -> anyhow::Result<()> { + Command::new("git") + .args(["-C", repo_path, "checkout", git_rev]) + .output() + .map_err(|_| { + anyhow::anyhow!( + "Failed to checkout Git reference '{}' for package '{}'", + git_rev, + dep_name + ) + })?; + Ok(()) +} + +// old version of git fetch +fn fetch_latest_origin_rev(repo_path: &str, dep_name: PackageName) -> anyhow::Result { + let mut cmd = Command::new("git"); + cmd.args(["-C", repo_path, "fetch", "origin"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + cmd + .status() + .map_err(|_| { + anyhow::anyhow!( + "Failed to fetch latest Git state for package '{}', to skip set --skip-fetch-latest-git-deps", + dep_name + ) + }) +} + +fn switch_to_fetched_rev( + repo_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result { + let mut cmd = Command::new("git"); + cmd.args(["reset", "--hard", "FETCH_HEAD"]) + .current_dir(repo_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + let output = cmd + .output() + .map_err(|_| { + anyhow::anyhow!( + "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps", + git_rev, + dep_name + ) + })?; + Ok(output.status) +} + +fn shallow_fetch_latest_origin_rev( + repo_path: &str, + git_rev: &str, + dep_name: PackageName, +) -> anyhow::Result { + let status = Command::new("git") + .args(["fetch", "--depth", "1", "origin", git_rev]) + .current_dir(repo_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map_err(|_| { + anyhow::anyhow!( + "Failed to checkout Git reference '{}' for package '{}'", + git_rev, + dep_name + ) + })?; + Ok(status) +} + +pub(crate) fn confirm_git_available() -> anyhow::Result<()> { + match Command::new("git").arg("--version").output() { + Ok(_) => Ok(()), + Err(e) => { + if let std::io::ErrorKind::NotFound = e.kind() { + bail!( + "git was not found, confirm you have git installed and it is on your PATH. \ + Alternatively, skip with --skip-fetch-latest-git-deps" + ); + } else { + bail!( + "Unexpected error occurred when checking for presence of `git`: {:#}", + e + ); + } + }, + } +} + +pub(crate) fn get_existing_rev(repo_path: &str, git_rev: &str) -> anyhow::Result { + let output = Command::new("git") + .args(["rev-parse", "--verify", git_rev]) + .current_dir(repo_path) + .output()?; + let stdout = String::from_utf8(output.stdout)?; + Ok(stdout.trim().to_string()) +} + +pub(crate) fn get_existing_tag(repo_path: &str, git_rev: &str) -> anyhow::Result { + let output = Command::new("git") + .args(["tag", "--list", git_rev]) + .current_dir(repo_path) + .output()?; + let stdout = String::from_utf8(output.stdout)?; + Ok(stdout.trim().to_string()) +} diff --git a/third_party/move/tools/move-package/src/resolution/mod.rs b/third_party/move/tools/move-package/src/resolution/mod.rs index a21506c6ee232..bcb8833fb4895 100644 --- a/third_party/move/tools/move-package/src/resolution/mod.rs +++ b/third_party/move/tools/move-package/src/resolution/mod.rs @@ -3,4 +3,5 @@ // SPDX-License-Identifier: Apache-2.0 mod digest; +pub(crate) mod git; pub mod resolution_graph; diff --git a/third_party/move/tools/move-package/src/resolution/resolution_graph.rs b/third_party/move/tools/move-package/src/resolution/resolution_graph.rs index 5a1a5b2dd68c5..45a8959f828e8 100644 --- a/third_party/move/tools/move-package/src/resolution/resolution_graph.rs +++ b/third_party/move/tools/move-package/src/resolution/resolution_graph.rs @@ -4,7 +4,7 @@ use crate::{ package_hooks, - resolution::digest::compute_digest, + resolution::{digest::compute_digest, git}, source_package::{ layout::SourcePackageLayout, manifest_parser::{parse_move_manifest_string, parse_source_manifest}, @@ -31,7 +31,6 @@ use std::{ fs, io::Write, path::{Path, PathBuf}, - process::{Command, Stdio}, rc::Rc, }; @@ -581,6 +580,8 @@ impl ResolvingGraph { let git_rev = git_info.git_rev.as_str(); let git_path = &git_info.download_to.display().to_string(); + let is_optimized = std::env::var("APTOS_GIT_OPTIMIZED").is_ok(); + // If there is no cached dependency, download it if !git_info.download_to.exists() { writeln!( @@ -590,56 +591,34 @@ impl ResolvingGraph { git_url, )?; - // Confirm git is available. - confirm_git_available()?; + git::confirm_git_available()?; // If the cached folder does not exist, download and clone accordingly - Command::new("git") - .args(["clone", git_url, git_path]) - .output() - .map_err(|_| { - anyhow::anyhow!("Failed to clone Git repository for package '{}'", dep_name) - })?; - Command::new("git") - .args(["-C", git_path, "checkout", git_rev]) - .output() - .map_err(|_| { - anyhow::anyhow!( - "Failed to checkout Git reference '{}' for package '{}'", - git_rev, - dep_name - ) - })?; + if is_optimized { + git::fetch_new_dependency_shallow(git_url, git_path, git_rev, dep_name)?; + } else { + git::fetch_new_dependency(git_url, git_path, git_rev, dep_name)?; + } } else if !skip_fetch_latest_git_deps { // Confirm git is available. - confirm_git_available()?; + git::confirm_git_available()?; // Update the git dependency // Check first that it isn't a git rev (if it doesn't work, just continue with the fetch) - if let Ok(rev) = Command::new("git") - .args(["-C", git_path, "rev-parse", "--verify", git_rev]) - .output() - { - if let Ok(parsable_version) = String::from_utf8(rev.stdout) { - // If it's exactly the same, then it's a git rev - if parsable_version.trim().starts_with(git_rev) { - return Ok(()); - } + if let Ok(rev) = git::get_existing_rev(git_path, git_rev) { + // If it's exactly the same, then it's a git rev + if rev.starts_with(git_rev) { + return Ok(()); } } - let tag = Command::new("git") - .args(["-C", git_path, "tag", "--list", git_rev]) - .output(); - - if let Ok(tag) = tag { - if let Ok(parsable_version) = String::from_utf8(tag.stdout) { - // If it's exactly the same, then it's a git tag, for now tags won't be updated - // Tags don't easily update locally and you can't use reset --hard to cleanup - // any extra files - if parsable_version.trim().starts_with(git_rev) { - return Ok(()); - } + let output = git::get_existing_tag(git_path, git_rev); + if let Ok(output) = output { + // If it's exactly the same, then it's a git tag, for now tags won't be updated + // Tags don't easily update locally and you can't use reset --hard to cleanup + // any extra files + if output.starts_with(git_rev) { + return Ok(()); } } @@ -652,55 +631,10 @@ impl ResolvingGraph { // If the current folder exists, do a fetch and reset to ensure that the branch // is up to date // NOTE: this means that you must run the package system with a working network connection - let status = Command::new("git") - .args([ - "-C", - git_path, - "fetch", - "origin", - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map_err(|_| { - anyhow::anyhow!( - "Failed to fetch latest Git state for package '{}', to skip set --skip-fetch-latest-git-deps", - dep_name - ) - })?; - - if !status.success() { - return Err(anyhow::anyhow!( - "Failed to fetch to latest Git state for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", - dep_name, - status - )); - } - let status = Command::new("git") - .args([ - "-C", - git_path, - "reset", - "--hard", - &format!("origin/{}", git_rev) - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map_err(|_| { - anyhow::anyhow!( - "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps", - git_rev, - dep_name - ) - })?; - if !status.success() { - return Err(anyhow::anyhow!( - "Failed to reset to latest Git state '{}' for package '{}', to skip set --skip-fetch-latest-git-deps | Exit status: {}", - git_rev, - dep_name, - status - )); + if is_optimized { + git::update_dependency_shallow(git_path, git_rev, dep_name)?; + } else { + git::update_dependency(git_path, git_rev, dep_name)?; } } } @@ -960,22 +894,3 @@ impl ResolvedPackage { } } } - -fn confirm_git_available() -> Result<()> { - match Command::new("git").arg("--version").output() { - Ok(_) => Ok(()), - Err(e) => { - if let std::io::ErrorKind::NotFound = e.kind() { - bail!( - "git was not found, confirm you have git installed and it is on your PATH. \ - Alternatively, skip with --skip-fetch-latest-git-deps" - ); - } else { - bail!( - "Unexpected error occurred when checking for presence of `git`: {:#}", - e - ); - } - }, - } -}