Skip to content

Commit 42e0d86

Browse files
domenkozarclaude
andcommitted
github: use libgit2 transport for ref resolution
Use libgit2's high level remote API (git_remote_create_detached, git_remote_connect, git_remote_ls) instead of manually downloading and parsing the smart HTTP pkt-line format. This delegates protocol handling to libgit2 while keeping auth via custom HTTP headers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8c45341 commit 42e0d86

4 files changed

Lines changed: 97 additions & 30 deletions

File tree

src/libfetchers/git-utils.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,4 +1541,39 @@ bool isLegalRefName(const std::string & refName)
15411541
return false;
15421542
}
15431543

1544+
Hash resolveRemoteRef(const std::string & url, const std::string & ref, const Headers & headers)
1545+
{
1546+
initLibGit2();
1547+
1548+
Remote remote;
1549+
if (git_remote_create_detached(Setter(remote), url.c_str()))
1550+
throw GitError("creating detached remote for '%s'", url);
1551+
1552+
// Convert Headers (vector<pair<string,string>>) to git_strarray
1553+
std::vector<std::string> headerStrs;
1554+
for (auto & [name, value] : headers)
1555+
headerStrs.push_back(name + ": " + value);
1556+
std::vector<char *> headerPtrs;
1557+
for (auto & s : headerStrs)
1558+
headerPtrs.push_back(s.data());
1559+
git_strarray customHeaders = {headerPtrs.data(), headerPtrs.size()};
1560+
1561+
if (git_remote_connect(remote.get(), GIT_DIRECTION_FETCH, nullptr, nullptr, &customHeaders))
1562+
throw GitError("connecting to remote '%s'", url);
1563+
1564+
const git_remote_head ** refs;
1565+
size_t refCount;
1566+
if (git_remote_ls(&refs, &refCount, remote.get()))
1567+
throw GitError("listing remote refs for '%s'", url);
1568+
1569+
std::regex refRegex(ref == "HEAD" ? "HEAD" : fmt("refs/(heads|tags)/%s", ref));
1570+
1571+
for (size_t i = 0; i < refCount; i++) {
1572+
if (std::regex_match(refs[i]->name, refRegex))
1573+
return toHash(refs[i]->oid);
1574+
}
1575+
1576+
throw Error("could not find ref '%s' in remote '%s'", ref, url);
1577+
}
1578+
15441579
} // namespace nix

src/libfetchers/github.cc

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -414,22 +414,11 @@ struct GitHubInputScheme : GitArchiveInputScheme
414414
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
415415
{
416416
auto host = getHost(input);
417-
auto url = fmt(
418-
host == "github.com" ? "https://api.%s/repos/%s/%s/commits/%s" : "https://%s/api/v3/repos/%s/%s/commits/%s",
419-
host,
420-
getOwner(input),
421-
getRepo(input),
422-
*input.getRef());
423-
417+
auto url = fmt("https://%s/%s/%s.git", host, getOwner(input), getRepo(input));
424418
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
425419

426-
auto downloadResult = downloadFile(store, settings, url, "source", headers);
427-
auto json = nlohmann::json::parse(
428-
store.requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
429-
430-
return RefInfo{
431-
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
432-
.treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
420+
auto rev = resolveRemoteRef(url, *input.getRef(), headers);
421+
return RefInfo{.rev = rev};
433422
}
434423

435424
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override

src/libfetchers/include/nix/fetchers/git-utils.hh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "nix/fetchers/filtering-source-accessor.hh"
44
#include "nix/util/fs-sink.hh"
5+
#include "nix/util/types.hh"
56

67
namespace nix {
78

@@ -178,4 +179,11 @@ struct Setter
178179
*/
179180
bool isLegalRefName(const std::string & refName);
180181

182+
/**
183+
* Resolve a ref on a remote repository to a commit hash using
184+
* libgit2's smart HTTP transport. The ref can be "HEAD" or a branch/tag
185+
* name (matched against refs/heads and refs/tags).
186+
*/
187+
Hash resolveRemoteRef(const std::string & url, const std::string & ref, const Headers & headers);
188+
181189
} // namespace nix

tests/nixos/github-flakes.nix

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,50 @@ let
5757

5858
private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d";
5959

60-
private-flake-api = pkgs.runCommand "private-flake" { } ''
61-
mkdir -p $out/{commits,tarball}
62-
63-
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
64-
echo '{"sha": "${private-flake-rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
60+
private-flake-git-refs = pkgs.runCommand "private-flake-git-refs" { } ''
61+
mkdir -p $out/info
62+
pkt_line() {
63+
local content="$1"
64+
local len=$(( ''${#content} + 4 ))
65+
printf '%04x%s' "$len" "$content"
66+
}
67+
{
68+
pkt_line "# service=git-upload-pack
69+
"
70+
printf '0000'
71+
pkt_line "${private-flake-rev} HEAD
72+
"
73+
pkt_line "${private-flake-rev} refs/heads/master
74+
"
75+
printf '0000'
76+
} > $out/info/refs
77+
'';
6578

66-
# Setup tarball download via API
79+
private-flake-tarball = pkgs.runCommand "private-flake-tarball" { } ''
80+
mkdir -p $out/tarball
6781
dir=private-flake
6882
mkdir $dir
6983
echo '{ outputs = {...}: {}; }' > $dir/flake.nix
7084
tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference
7185
'';
7286

73-
nixpkgs-api = pkgs.runCommand "nixpkgs-flake" { } ''
74-
mkdir -p $out/commits
75-
76-
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
77-
echo '{"sha": "${nixpkgs.rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
87+
nixpkgs-git-refs = pkgs.runCommand "nixpkgs-git-refs" { } ''
88+
mkdir -p $out/info
89+
pkt_line() {
90+
local content="$1"
91+
local len=$(( ''${#content} + 4 ))
92+
printf '%04x%s' "$len" "$content"
93+
}
94+
{
95+
pkt_line "# service=git-upload-pack
96+
"
97+
printf '0000'
98+
pkt_line "${nixpkgs.rev} HEAD
99+
"
100+
pkt_line "${nixpkgs.rev} refs/heads/master
101+
"
102+
printf '0000'
103+
} > $out/info/refs
78104
'';
79105

80106
archive = pkgs.runCommand "nixpkgs-flake" { } ''
@@ -123,25 +149,34 @@ in
123149
sslServerKey = "${cert}/server.key";
124150
sslServerCert = "${cert}/server.crt";
125151
servedDirs = [
126-
{
127-
urlPath = "/repos/NixOS/nixpkgs";
128-
dir = nixpkgs-api;
129-
}
130152
{
131153
urlPath = "/repos/fancy-enterprise/private-flake";
132-
dir = private-flake-api;
154+
dir = private-flake-tarball;
133155
}
134156
];
135157
};
136158
services.httpd.virtualHosts."github.com" = {
137159
forceSSL = true;
138160
sslServerKey = "${cert}/server.key";
139161
sslServerCert = "${cert}/server.crt";
162+
extraConfig = ''
163+
<LocationMatch "\.git/info/refs$">
164+
ForceType application/x-git-upload-pack-advertisement
165+
</LocationMatch>
166+
'';
140167
servedDirs = [
141168
{
142169
urlPath = "/NixOS/nixpkgs";
143170
dir = archive;
144171
}
172+
{
173+
urlPath = "/NixOS/nixpkgs.git";
174+
dir = nixpkgs-git-refs;
175+
}
176+
{
177+
urlPath = "/fancy-enterprise/private-flake.git";
178+
dir = private-flake-git-refs;
179+
}
145180
];
146181
};
147182
};

0 commit comments

Comments
 (0)