fetchTree: Lix uses tag object hash instead of commit hash when ref points to a tag #1070

Closed
opened 2025-12-08 18:30:56 +00:00 by goldstein · 5 comments
Member

Describe the bug

When fetching git inputs with fetchTree, resulting source info contains rev pointing to the tree object instead of the commit object.

Steps To Reproduce

Evaluate

(builtins.fetchTree { type = "git"; url = "https://codeberg.org/goldstein/nix-tag-fetch-demo"; ref = "refs/tags/tag"; }).rev

The result is 047e5dffbba66b5eb0c1d8db04f661997fb825af, which is the tag object hash.

Expected behavior

7e6de55d9af75b3647c91f7e939107fcb3c8f196, which is the corresponding commit hash: https://codeberg.org/goldstein/nix-tag-fetch-demo/src/tag/tag. This is what CppNix does.

nix --version output

nix (Lix, like Nix) 2.95.0-pre20251128-dev_d5d03cd
System type: x86_64-linux
Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux
Features: gc, signed-caches
System configuration file: /etc/nix/nix.conf
User configuration files: /home/goldstein/.config/nix/nix.conf:/etc/xdg/nix/nix.conf:/home/goldstein/.nix-profile/etc/xdg/nix/nix.conf:/nix/profile/etc/xdg/nix/nix.conf:/home/goldstein/.local/state/nix/profile/etc/xdg/nix/nix.conf:/etc/profiles/per-user/goldstein/etc/xdg/nix/nix.conf:/nix/var/nix/profiles/default/etc/xdg/nix/nix.conf:/run/current-system/sw/etc/xdg/nix/nix.conf
Store directory: /nix/store
State directory: /nix/var/nix
Data directory: /nix/store/vqngvg0ljwb0zikxjh7h24akfbs82f0r-lix-2.95.0-pre20251128-dev_d5d03cd/share

Additional context

This is a divergence with CppNix. It might be intentional, but if so, I didn’t find info about it.

I do think returning commit hash here would be correct: flake docs say that returned rev is “the commit hash”.

## Describe the bug When fetching git inputs with `fetchTree`, resulting source info contains `rev` pointing to the tree object instead of the commit object. ## Steps To Reproduce Evaluate ```nix (builtins.fetchTree { type = "git"; url = "https://codeberg.org/goldstein/nix-tag-fetch-demo"; ref = "refs/tags/tag"; }).rev ``` The result is `047e5dffbba66b5eb0c1d8db04f661997fb825af`, which is the tag object hash. ## Expected behavior `7e6de55d9af75b3647c91f7e939107fcb3c8f196`, which is the corresponding commit hash: https://codeberg.org/goldstein/nix-tag-fetch-demo/src/tag/tag. This is what CppNix does. ## `nix --version` output ``` nix (Lix, like Nix) 2.95.0-pre20251128-dev_d5d03cd System type: x86_64-linux Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux Features: gc, signed-caches System configuration file: /etc/nix/nix.conf User configuration files: /home/goldstein/.config/nix/nix.conf:/etc/xdg/nix/nix.conf:/home/goldstein/.nix-profile/etc/xdg/nix/nix.conf:/nix/profile/etc/xdg/nix/nix.conf:/home/goldstein/.local/state/nix/profile/etc/xdg/nix/nix.conf:/etc/profiles/per-user/goldstein/etc/xdg/nix/nix.conf:/nix/var/nix/profiles/default/etc/xdg/nix/nix.conf:/run/current-system/sw/etc/xdg/nix/nix.conf Store directory: /nix/store State directory: /nix/var/nix Data directory: /nix/store/vqngvg0ljwb0zikxjh7h24akfbs82f0r-lix-2.95.0-pre20251128-dev_d5d03cd/share ``` ## Additional context This is a divergence with CppNix. It might be intentional, but if so, I didn’t find info about it. I do think returning commit hash here would be correct: [flake docs](https://docs.lix.systems/manual/lix/stable/command-ref/new-cli/nix3-flake.html#flake-format) say that returned rev is “the commit hash”.
Owner

yes, returning the commit hash rather than the tag hash seems like the much better behavior here. if the tag gets deleted the lockfile would cease to work even if the commit is still there, which is probably bad (and we've seen multiple projects pull and recreate tags, or just destroy them outright)

yes, returning the commit hash rather than the tag hash seems like the much better behavior here. if the tag gets deleted the lockfile would cease to work even if the commit is still there, which is probably bad (and we've seen multiple projects pull and recreate tags, or just destroy them outright)
Author
Member

I think it’s this function that determines rev:

Lines 330 to 350 in 50c47c3
static std::optional<Path> resolveRefToCachePath(
Input & input,
const Path & cacheDir,
std::vector<Path> & gitRefFileCandidates,
std::function<bool(const Path&)> condition)
{
if (input.getRef()->starts_with("refs/")) {
Path fullpath = cacheDir + "/" + *input.getRef();
if (condition(fullpath)) {
return fullpath;
}
}
for (auto & candidate : gitRefFileCandidates) {
if (condition(candidate)) {
return candidate;
}
}
return std::nullopt;
}

It finds the ref file (from <ref>, refs/tags/<ref> and refs/heads/<ref>), reads it and returns the result. It avoids a shellout to git, but causes Lix to return the tag object hash. It’s not hard to parse the referenced object (I expect it to be something like “check that the first four bytes are tag , skip until NUL, parse object <hash>”), but that would require decompressing it and I’m not sure how to access zlib APIs from the Lix source code. Alternatively, we could just shellout to git rev-list -n 1 <ref>.

I’m willing to implement either approach, but I’d need guidance on which one is preferred and (for the first one) how to decompress zlib data.

I think it’s this function that determines `rev`: https://git.lix.systems/lix-project/lix/src/commit/50c47c340fe48fe98f51043ec49c5ec03ba39212/lix/libfetchers/git.cc#L330-L350 It finds the ref file (from `<ref>`, `refs/tags/<ref>` and `refs/heads/<ref>`), reads it and returns the result. It avoids a shellout to git, but causes Lix to return the tag object hash. It’s not hard to parse the referenced object (I expect it to be something like “check that the first four bytes are `tag `, skip until NUL, parse `object <hash>`”), but that would require decompressing it and I’m not sure how to access zlib APIs from the Lix source code. Alternatively, we could just shellout to `git rev-list -n 1 <ref>`. I’m willing to implement either approach, but I’d need guidance on which one is preferred and (for the first one) how to decompress zlib data.
Owner

shalling out to git is absolutely fine, we don't run this often enough to want to take the complexity penalty of optimizing this for speed :)

shalling out to git is absolutely fine, we don't run this often enough to want to take the complexity penalty of optimizing this for speed :)
Author
Member

Okay, thanks, I’ll patch it in a day or two then.

Okay, thanks, I’ll patch it in a day or two then.
Member

This issue was mentioned on Gerrit on the following CLs:

  • commit message in cl/4762 ("libfetchers: use commit hash as rev for tag refs")
<!-- GERRIT_LINKBOT: {"cls": [{"backlink": "https://gerrit.lix.systems/c/lix/+/4762", "number": 4762, "kind": "commit message"}], "cl_meta": {"4762": {"change_title": "libfetchers: use commit hash as `rev` for tag refs"}}} --> This issue was mentioned on Gerrit on the following CLs: * commit message in [cl/4762](https://gerrit.lix.systems/c/lix/+/4762) ("libfetchers: use commit hash as `rev` for tag refs")
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lix-project/lix#1070
No description provided.