Reuse the result of which git (#8224)

## Summary

Cache the path to git executable in a `LazyLock` and reuse it throughout
the process. This might reduce some costs on finding the git executable.
This commit is contained in:
Jo 2024-10-16 01:50:43 +08:00 committed by GitHub
parent 3d27b484ea
commit 0b5cc9595a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 27 additions and 15 deletions

1
Cargo.lock generated
View file

@ -4680,6 +4680,7 @@ dependencies = [
"uv-cache-key",
"uv-fs",
"uv-static",
"which",
]
[[package]]

View file

@ -32,3 +32,4 @@ thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
which = { workspace = true }

View file

@ -4,6 +4,7 @@
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::str::{self, FromStr};
use std::sync::LazyLock;
use crate::sha::GitOid;
use crate::GitSha;
@ -11,6 +12,7 @@ use anyhow::{anyhow, Context, Result};
use cargo_util::{paths, ProcessBuilder};
use reqwest::StatusCode;
use reqwest_middleware::ClientWithMiddleware;
use tracing::debug;
use url::Url;
use uv_fs::Simplified;
@ -20,6 +22,9 @@ use uv_static::EnvVars;
/// checkout is ready to go. See [`GitCheckout::reset`] for why we need this.
const CHECKOUT_READY_LOCK: &str = ".ok";
/// A global cache of the result of `which git`.
pub static GIT: LazyLock<Result<PathBuf, which::Error>> = LazyLock::new(|| which::which("git"));
/// A reference to commit or commit-ish.
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
@ -158,7 +163,7 @@ impl GitRepository {
/// Opens an existing Git repository at `path`.
pub(crate) fn open(path: &Path) -> Result<GitRepository> {
// Make sure there is a Git repository at the specified path.
ProcessBuilder::new("git")
ProcessBuilder::new(GIT.as_ref()?)
.arg("rev-parse")
.cwd(path)
.exec_with_output()?;
@ -177,7 +182,7 @@ impl GitRepository {
// opts.external_template(false);
// Initialize the repository.
ProcessBuilder::new("git")
ProcessBuilder::new(GIT.as_ref()?)
.arg("init")
.cwd(path)
.exec_with_output()?;
@ -189,7 +194,7 @@ impl GitRepository {
/// Parses the object ID of the given `refname`.
fn rev_parse(&self, refname: &str) -> Result<GitOid> {
let result = ProcessBuilder::new("git")
let result = ProcessBuilder::new(GIT.as_ref()?)
.arg("rev-parse")
.arg(refname)
.cwd(&self.path)
@ -295,7 +300,7 @@ impl GitDatabase {
/// Get a short OID for a `revision`, usually 7 chars or more if ambiguous.
pub(crate) fn to_short_id(&self, revision: GitOid) -> Result<String> {
let output = ProcessBuilder::new("git")
let output = ProcessBuilder::new(GIT.as_ref()?)
.arg("rev-parse")
.arg("--short")
.arg(revision.as_str())
@ -372,7 +377,7 @@ impl GitCheckout {
// Perform a local clone of the repository, which will attempt to use
// hardlinks to set up the repository. This should speed up the clone operation
// quite a bit if it works.
ProcessBuilder::new("git")
ProcessBuilder::new(GIT.as_ref()?)
.arg("clone")
.arg("--local")
// Make sure to pass the local file path and not a file://... url. If given a url,
@ -418,7 +423,7 @@ impl GitCheckout {
debug!("reset {} to {}", self.repo.path.display(), self.revision);
// Perform the hard reset.
ProcessBuilder::new("git")
ProcessBuilder::new(GIT.as_ref()?)
.arg("reset")
.arg("--hard")
.arg(self.revision.as_str())
@ -426,7 +431,7 @@ impl GitCheckout {
.exec_with_output()?;
// Update submodules (`git submodule update --recursive`).
ProcessBuilder::new("git")
ProcessBuilder::new(GIT.as_ref()?)
.arg("submodule")
.arg("update")
.arg("--recursive")
@ -592,7 +597,7 @@ fn fetch_with_cli(
refspecs: &[String],
tags: bool,
) -> Result<()> {
let mut cmd = ProcessBuilder::new("git");
let mut cmd = ProcessBuilder::new(GIT.as_ref()?);
cmd.arg("fetch");
if tags {
cmd.arg("--tags");

View file

@ -1,7 +1,7 @@
use url::Url;
pub use crate::credentials::{store_credentials_from_url, GIT_STORE};
pub use crate::git::GitReference;
pub use crate::git::{GitReference, GIT};
pub use crate::resolver::{
GitResolver, GitResolverError, RepositoryReference, ResolvedRepositoryReference,
};

View file

@ -11,6 +11,7 @@ use uv_cli::AuthorFrom;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::{VersionControlError, VersionControlSystem};
use uv_fs::{Simplified, CWD};
use uv_git::GIT;
use uv_pep440::Version;
use uv_pep508::PackageName;
use uv_python::{
@ -896,14 +897,14 @@ fn get_author_info(path: &Path, author_from: AuthorFrom) -> Option<Author> {
/// Fetch the default author from git configuration.
fn get_author_from_git(path: &Path) -> Result<Author> {
let Ok(git) = which::which("git") else {
let Ok(git) = GIT.as_ref() else {
anyhow::bail!("`git` not found in PATH")
};
let mut name = None;
let mut email = None;
let output = Command::new(&git)
let output = Command::new(git)
.arg("config")
.arg("--get")
.arg("user.name")
@ -915,7 +916,7 @@ fn get_author_from_git(path: &Path) -> Result<Author> {
name = Some(String::from_utf8_lossy(&output.stdout).trim().to_string());
}
let output = Command::new(&git)
let output = Command::new(git)
.arg("config")
.arg("--get")
.arg("user.email")

View file

@ -11397,6 +11397,9 @@ fn git_source_refs() -> Result<()> {
fn git_source_missing_tag() -> Result<()> {
let context = TestContext::new("3.12");
let mut filters = context.filters();
filters.push(("`.*/git fetch (.*)`", "`git fetch $1`"));
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
@ -11410,7 +11413,7 @@ fn git_source_missing_tag() -> Result<()> {
uv-public-pypackage = { git = "https://github.com/astral-test/uv-public-pypackage", tag = "missing" }
"#})?;
uv_snapshot!(context.filters(), context.pip_compile()
uv_snapshot!(filters, context.pip_compile()
.arg("pyproject.toml"), @r###"
success: false
exit_code: 2

View file

@ -1594,7 +1594,7 @@ fn install_git_public_https_missing_branch_or_tag() {
let mut filters = context.filters();
// Windows does not style the command the same as Unix, so we must omit it from the snapshot
filters.push(("`git fetch .*`", "`git fetch [...]`"));
filters.push(("`.*/git(.exe)? fetch .*`", "`git fetch [...]`"));
filters.push(("exit status", "exit code"));
uv_snapshot!(filters, context.pip_install()
@ -1624,7 +1624,7 @@ fn install_git_public_https_missing_commit() {
let mut filters = context.filters();
// Windows does not style the command the same as Unix, so we must omit it from the snapshot
filters.push(("`git fetch .*`", "`git fetch [...]`"));
filters.push(("`.*/git(.exe)? fetch .*`", "`git fetch [...]`"));
filters.push(("exit status", "exit code"));
// There are flakes on Windows where this irrelevant error is appended
@ -1842,6 +1842,7 @@ fn install_git_private_https_pat_not_authorized() {
let mut filters = context.filters();
filters.insert(0, (token, "***"));
filters.push(("`.*/git fetch (.*)`", "`git fetch $1`"));
// We provide a username otherwise (since the token is invalid), the git cli will prompt for a password
// and hang the test