From 051aaa5fe57de3a5e461693c4c75d5631382d1df Mon Sep 17 00:00:00 2001 From: Sergei Nizovtsev Date: Sun, 12 Jan 2025 05:30:46 +0300 Subject: [PATCH] Fix git-tag cache-key reader in case of slashes (#10467) (#10500) ## Summary The assumption that all tags are listed under a flat `.git/ref/tags` structure was wrong. Git creates a hierarchy of directories for tags containing slashes. To fix the cache key calculation, we need to recursively traverse all files under that folder instead. ## Test Plan 1. Create an `uv` project with git-tag cache-keys; 2. Add any tag with slash; 3. Run `uv sync` and see uv_cache_info error in verbose log; 4. `uv sync` doesn't trigger reinstall on next tag addition or removal; 5. With fix applied, reinstall triggers on every tag update and there are no errors in the log. Fixes #10467 --------- Co-authored-by: Sergei Nizovtsev Co-authored-by: Charlie Marsh --- Cargo.lock | 1 + crates/uv-cache-info/Cargo.toml | 1 + crates/uv-cache-info/src/git_info.rs | 32 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2ba39b1a..c6696c606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4739,6 +4739,7 @@ dependencies = [ "thiserror 2.0.11", "toml", "tracing", + "walkdir", ] [[package]] diff --git a/crates/uv-cache-info/Cargo.toml b/crates/uv-cache-info/Cargo.toml index ec17a4416..6b10bbebe 100644 --- a/crates/uv-cache-info/Cargo.toml +++ b/crates/uv-cache-info/Cargo.toml @@ -23,3 +23,4 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } toml = { workspace = true } tracing = { workspace = true } +walkdir = { workspace = true } diff --git a/crates/uv-cache-info/src/git_info.rs b/crates/uv-cache-info/src/git_info.rs index 9df098e69..ad566d13b 100644 --- a/crates/uv-cache-info/src/git_info.rs +++ b/crates/uv-cache-info/src/git_info.rs @@ -1,6 +1,9 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use tracing::warn; +use walkdir::WalkDir; + #[derive(Debug, thiserror::Error)] pub(crate) enum GitInfoError { #[error("The repository at {0} is missing a `.git` directory")] @@ -80,24 +83,27 @@ impl Tags { .find(|git_dir| git_dir.exists()) .ok_or_else(|| GitInfoError::MissingGitDir(path.to_path_buf()))?; - let git_refs_path = - git_refs(&git_dir).ok_or_else(|| GitInfoError::MissingRefs(git_dir.clone()))?; + let git_tags_path = git_refs(&git_dir) + .ok_or_else(|| GitInfoError::MissingRefs(git_dir.clone()))? + .join("tags"); let mut tags = BTreeMap::new(); // Map each tag to its commit. - let read_dir = match fs_err::read_dir(git_refs_path.join("tags")) { - Ok(read_dir) => read_dir, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Ok(Self(tags)); - } - Err(err) => return Err(err.into()), - }; - for entry in read_dir { - let entry = entry?; + for entry in WalkDir::new(&git_tags_path).contents_first(true) { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + warn!("Failed to read Git tags: {err}"); + continue; + } + }; let path = entry.path(); - if let Some(tag) = path.file_name().and_then(|name| name.to_str()) { - let commit = fs_err::read_to_string(&path)?.trim().to_string(); + if !entry.file_type().is_file() { + continue; + } + if let Ok(Some(tag)) = path.strip_prefix(&git_tags_path).map(|name| name.to_str()) { + let commit = fs_err::read_to_string(path)?.trim().to_string(); // The commit should be 40 hexadecimal characters. if commit.len() != 40 {