nixpkgs-plugin-update: key cached downloads by repo and ref

Namespace cache entries by repository and fetch ref instead of keying them only
by commit text.

Once tagged fetches are preserved, different repositories can legitimately share
the same tag name. Keying the cache by the bare ref lets unrelated plugins
collide and reuse the wrong prefetched hash. Use repo URI plus tag/commit so
cache hits stay repository-specific.
This commit is contained in:
Austin Horstman
2026-04-19 10:04:57 -05:00
parent 9615c1e0b6
commit 10573a3579
@@ -840,9 +840,7 @@ class Editor:
current_plugins = self.get_current_plugins(config, self.nixpkgs)
current_plugin_specs = self.load_plugin_spec(config, input_file)
cache: Cache = Cache(
[plugin for _description, plugin in current_plugins], self.cache_file
)
cache: Cache = Cache(current_plugins, self.cache_file)
current_plugin_map = {
plugin.normalized_name: plugin for _description, plugin in current_plugins
}
@@ -1107,7 +1105,7 @@ def prefetch_plugin(
latest_tag,
)
cached_plugin = cache[commit] if cache else None
cached_plugin = cache[target_cache_key(p.repo.uri, commit, source_tag)] if cache else None
if cached_plugin is not None:
log.debug(f"Cache hit for {p.name}!")
cached_plugin.name = p.name
@@ -1200,13 +1198,27 @@ def get_cache_path(cache_file_name: str) -> Path | None:
return Path(xdg_cache, cache_file_name)
def plugin_cache_key(repo_uri: str, plugin: "Plugin") -> str:
ref = f"{GIT_TAGS_PREFIX}{plugin.tag}" if plugin.tag is not None else plugin.commit
return f"{repo_uri}@{ref}"
def target_cache_key(repo_uri: str, commit: str, source_tag: str | None) -> str:
ref = f"{GIT_TAGS_PREFIX}{source_tag}" if source_tag is not None else commit
return f"{repo_uri}@{ref}"
class Cache:
def __init__(self, initial_plugins: list[Plugin], cache_file_name: str) -> None:
def __init__(
self,
initial_plugins: list[tuple[PluginDesc, Plugin]],
cache_file_name: str,
) -> None:
self.cache_file = get_cache_path(cache_file_name)
downloads = {}
for plugin in initial_plugins:
downloads[plugin.commit] = plugin
for plugin_desc, plugin in initial_plugins:
downloads[plugin_cache_key(plugin_desc.repo.uri, plugin)] = plugin
downloads.update(self.load())
self.downloads = downloads
@@ -1217,7 +1229,7 @@ class Cache:
downloads: dict[str, Plugin] = {}
with open(self.cache_file) as f:
data = json.load(f)
for attr in data.values():
for cache_key, attr in data.items():
p = Plugin(
attr["name"],
attr["commit"],
@@ -1227,7 +1239,7 @@ class Cache:
last_tag=attr.get("last_tag"),
tag=attr.get("tag"),
)
downloads[attr["commit"]] = p
downloads[cache_key] = p
return downloads
def store(self) -> None:
@@ -1237,8 +1249,8 @@ class Cache:
os.makedirs(self.cache_file.parent, exist_ok=True)
with open(self.cache_file, "w+") as f:
data = {}
for name, attr in self.downloads.items():
data[name] = attr.as_json()
for cache_key, attr in self.downloads.items():
data[cache_key] = attr.as_json()
json.dump(data, f, indent=4, sort_keys=True)
def __getitem__(self, key: str) -> Plugin | None:
@@ -1258,7 +1270,7 @@ def prefetch(
if current_plugins is not None:
current_plugin = current_plugins.get(pluginDesc.name.replace(".", "-"))
plugin, redirect = prefetch_plugin(pluginDesc, cache, current_plugin)
cache[plugin.commit] = plugin
cache[target_cache_key(pluginDesc.repo.uri, plugin.commit, plugin.tag)] = plugin
return (pluginDesc, plugin, redirect)
except Exception as e:
return (pluginDesc, e, None)