Skip to content

Commit 04b1b1e

Browse files
committed
Backward compatibility hack for Git inputs that depend on Git filters
Before Nix 2.20, we used git, which applies Git filters (in particular doing end-of-line conversion based on .gitattributes). In 2.20, we switched to libgit2 and stopped applying filters, which is probably better for reproducibility. However, that breaks existing lock files / fetchTree calls for Git inputs that use those filters, since it invalidates the NAR hash. So as a backward compatibility hack, we now check the NAR hash computed over the Git tree without filtering applied. If there is a hash mismatch, we try again *with* filtering. If that succeeds, we print a warning and return the filtered tree.
1 parent 0bc3370 commit 04b1b1e

File tree

2 files changed

+64
-7
lines changed

2 files changed

+64
-7
lines changed

src/libfetchers/git.cc

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "nix/util/json-utils.hh"
1717
#include "nix/util/archive.hh"
1818
#include "nix/util/mounted-source-accessor.hh"
19+
#include "nix/fetchers/fetch-to-store.hh"
1920

2021
#include <regex>
2122
#include <string.h>
@@ -637,6 +638,12 @@ struct GitInputScheme : InputScheme
637638
return shallow || input.getRevCount().has_value();
638639
}
639640

641+
std::string makeFingerprint(const Input & input, const Hash & rev) const
642+
{
643+
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "")
644+
+ (getLfsAttr(input) ? ";l" : "");
645+
}
646+
640647
std::pair<ref<SourceAccessor>, Input>
641648
getAccessorFromCommit(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
642649
{
@@ -783,6 +790,33 @@ struct GitInputScheme : InputScheme
783790
bool smudgeLfs = getLfsAttr(input);
784791
auto accessor = repo->getAccessor(rev, exportIgnore, "«" + input.to_string(true) + "»", smudgeLfs);
785792

793+
/* Backward compatibility hack for locks produced by Nix < 2.20 that depend on Git filters. Nix >= 2.20 doesn't
794+
* apply Git filters, so we may get a NAR hash mismatch. If that happens, try again with filters enabled. */
795+
if (auto expectedNarHash = input.getNarHash()) {
796+
if (accessor->pathExists(CanonPath(".gitattributes"))) {
797+
accessor->fingerprint = makeFingerprint(input, rev);
798+
auto narHashNoFilters =
799+
fetchToStore2(settings, *store, {accessor}, FetchMode::DryRun, input.getName()).second;
800+
if (expectedNarHash != narHashNoFilters) {
801+
auto accessor2 =
802+
repo->getAccessor(rev, exportIgnore, "«" + input.to_string(true) + "»", smudgeLfs, true);
803+
accessor2->fingerprint = makeFingerprint(input, rev) + ";f";
804+
auto narHashFilters =
805+
fetchToStore2(settings, *store, {accessor2}, FetchMode::DryRun, input.getName()).second;
806+
if (expectedNarHash == narHashFilters) {
807+
warn(
808+
"Git input '%s' specifies a NAR hash '%s' that is only correct if Git filters are applied.\n"
809+
"This is pre-Nix 2.20 behavior; in Nix 2.20 and later, Git filters are not applied.\n"
810+
"Please update the NAR hash to '%s'.",
811+
input.to_string(),
812+
expectedNarHash->to_string(HashFormat::SRI, true),
813+
narHashNoFilters.to_string(HashFormat::SRI, true));
814+
accessor = accessor2;
815+
}
816+
}
817+
}
818+
}
819+
786820
/* If the repo has submodules, fetch them and return a mounted
787821
input accessor consisting of the accessor for the top-level
788822
repo and the accessors for the submodules. */
@@ -942,13 +976,8 @@ struct GitInputScheme : InputScheme
942976

943977
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
944978
{
945-
auto makeFingerprint = [&](const Hash & rev) {
946-
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "")
947-
+ (getLfsAttr(input) ? ";l" : "");
948-
};
949-
950979
if (auto rev = input.getRev())
951-
return makeFingerprint(*rev);
980+
return makeFingerprint(input, *rev);
952981
else {
953982
auto repoInfo = getRepoInfo(input);
954983
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.submodules.empty()) {
@@ -964,7 +993,7 @@ struct GitInputScheme : InputScheme
964993
writeString("deleted:", hashSink);
965994
writeString(file.abs(), hashSink);
966995
}
967-
return makeFingerprint(repoInfo.workdirInfo.headRev.value_or(nullRev))
996+
return makeFingerprint(input, repoInfo.workdirInfo.headRev.value_or(nullRev))
968997
+ ";d=" + hashSink.finish().hash.to_string(HashFormat::Base16, false);
969998
}
970999
return std::nullopt;

tests/functional/fetchGit.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,31 @@ git -C "$empty" config user.name "Foobar"
310310
git -C "$empty" commit --allow-empty --allow-empty-message --message ""
311311

312312
nix eval --impure --expr "let attrs = builtins.fetchGit $empty; in assert attrs.lastModified != 0; assert attrs.rev != \"0000000000000000000000000000000000000000\"; assert attrs.revCount == 1; true"
313+
314+
# Test backward compatibility hack for Nix < 2.20 locks / fetchTree calls that expect Git filters to be applied.
315+
eol="$TEST_ROOT/git-eol"
316+
mkdir -p "$eol"
317+
git init "$eol"
318+
git -C "$eol" config user.email "[email protected]"
319+
git -C "$eol" config user.name "Foobar"
320+
printf "Hello\nWorld\n" > "$eol/crlf"
321+
git -C "$eol" add crlf
322+
git -C "$eol" commit -a -m Initial
323+
printf "crlf text eol=crlf\n" > "$eol/.gitattributes"
324+
git -C "$eol" add .gitattributes
325+
git -C "$eol" commit -a -m 'Apply gitattributes'
326+
327+
rev="$(git -C "$eol" rev-parse HEAD)"
328+
329+
export _NIX_TEST_BARF_ON_UNCACHEABLE=1
330+
331+
expectStderr 0 nix eval --expr \
332+
"assert builtins.readFile \"\${builtins.fetchTree { type = \"git\"; url = \"file://$eol\"; rev = \"$rev\"; narHash = \"sha256-kxXTCbD8wCYT9qNDKzl9bNKO8++pKY2QwAhqSqENP5k=\"; }}/crlf\" == \"Hello\r\nWorld\r\n\"; true" \
333+
| grepQuiet "Please update the NAR hash to 'sha256-LDLvcwdcwCxnuPTxSQ6gLAyopB20lD0bOQoQB3i2hsA='"
334+
335+
nix eval --expr \
336+
"assert builtins.readFile \"\${builtins.fetchTree { type = \"git\"; url = \"file://$eol\"; rev = \"$rev\"; narHash = \"sha256-LDLvcwdcwCxnuPTxSQ6gLAyopB20lD0bOQoQB3i2hsA=\"; }}/crlf\" == \"Hello\nWorld\n\"; true"
337+
338+
expectStderr 102 nix eval --expr \
339+
"assert builtins.readFile \"\${builtins.fetchTree { type = \"git\"; url = \"file://$eol\"; rev = \"$rev\"; narHash = \"sha256-DLDvcwdcwCxnuPTxSQ6gLAyopB20lD0bOQoQB3i2hsA=\"; }}/crlf\" == \"Hello\nWorld\n\"; true" \
340+
| grepQuiet "NAR hash mismatch"

0 commit comments

Comments
 (0)