diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 9fe271fe8ce..bc38c72a847 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -668,6 +668,7 @@ struct GitSourceAccessor : SourceAccessor struct State { ref repo; + std::string gitRev; Object root; std::optional lfsFetch = std::nullopt; }; @@ -678,6 +679,7 @@ struct GitSourceAccessor : SourceAccessor : state_{ State { .repo = repo_, + .gitRev = rev.gitRev(), .root = peelToTreeOrBlob(lookupObject(*repo_, hashToOID(rev)).get()), .lfsFetch = smudgeLfs ? std::make_optional(lfs::Fetch(*repo_, hashToOID(rev))) : std::nullopt, } @@ -707,7 +709,28 @@ struct GitSourceAccessor : SourceAccessor } } - return std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get())); + // Apply git filters including CRLF conversion + git_buf filtered = GIT_BUF_INIT; + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + + git_oid oid; + if (git_oid_fromstr(&oid, state->gitRev.c_str())) + throw Error("cannot convert '%s' to a Git OID", state->gitRev.c_str()); + + opts.attr_commit_id = oid; + opts.flags = GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT; + + int error = git_blob_filter(&filtered, blob.get(), path.rel_c_str(), &opts); + if (error != 0) { + const git_error *e = git_error_last(); + std::string errorMsg = e ? e->message : "Unknown error"; + git_buf_dispose(&filtered); + throw Error("Failed to filter blob: " + errorMsg); + } + std::string result(filtered.ptr, filtered.size); + git_buf_dispose(&filtered); + + return result; } std::string readFile(const CanonPath & path) override diff --git a/tests/functional/fetchGit.sh b/tests/functional/fetchGit.sh index a41aa35c028..ba08cef7aba 100755 --- a/tests/functional/fetchGit.sh +++ b/tests/functional/fetchGit.sh @@ -303,3 +303,27 @@ git -C "$empty" config user.name "Foobar" git -C "$empty" commit --allow-empty --allow-empty-message --message "" nix eval --impure --expr "let attrs = builtins.fetchGit $empty; in assert attrs.lastModified != 0; assert attrs.rev != \"0000000000000000000000000000000000000000\"; assert attrs.revCount == 1; true" + +# Test a repo with `eol=crlf`. +repo="$TEST_ROOT/crlf" +git init "$repo" +git -C $repo config user.email "foobar@example.com" +git -C $repo config user.name "Foobar" + +echo -n -e 'foo\nbar\nbaz' > "$repo/newlines.txt" +echo -n -e 'foo\nbar\nbaz' > "$repo/test.txt" +git -C "$repo" add newlines.txt test.txt +git -C "$repo" commit -m 'Add files containing LF line endings' +echo 'test.txt eol=crlf' > "$repo/.gitattributes" +git -C "$repo" add .gitattributes +git -C "$repo" commit -m 'Add eol=crlf to gitattributes' +narhash=$(nix eval --raw --impure --expr "(builtins.fetchGit { url = \"$repo\"; ref = \"master\"; }).narHash") +[[ "$narhash" = "sha256-k7u7RAaF+OvrbtT3KCCDQA8e9uOdflUo5zSgsosoLzA=" ]] + +# Ensure that NAR hash doesn't depend on user configuration. +rm -rf $TEST_HOME/.cache/nix +export GIT_CONFIG_GLOBAL="$TEST_ROOT/gitconfig" +git config --global core.autocrlf true +new_narhash=$(nix eval --raw --impure --expr "(builtins.fetchGit { url = \"$repo\"; ref = \"master\"; }).narHash") +[[ "$new_narhash" = "$narhash" ]] +unset GIT_CONFIG_GLOBAL