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 <sergei.nizovtsev@eqvilent.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
Sergei Nizovtsev 2025-01-12 05:30:46 +03:00 committed by GitHub
parent 7269273458
commit 051aaa5fe5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 21 additions and 13 deletions

1
Cargo.lock generated
View file

@ -4739,6 +4739,7 @@ dependencies = [
"thiserror 2.0.11",
"toml",
"tracing",
"walkdir",
]
[[package]]

View file

@ -23,3 +23,4 @@ serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
walkdir = { workspace = true }

View file

@ -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 {