1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-29 23:13:14 +02:00

Fix format

This commit is contained in:
Leandro Reina 2024-11-20 18:24:17 +01:00
parent 79d41062d0
commit 70ffcc83d7
2 changed files with 307 additions and 287 deletions

View file

@ -110,7 +110,6 @@ TEST_F(GitUtilsTest, sink_hardlink)
}
};
namespace lfs {
TEST_F(GitUtilsTest, parseGitRemoteUrl)
@ -196,7 +195,8 @@ TEST_F(GitUtilsTest, parseGitRemoteUrl)
EXPECT_EQ(result.path, "");
}
}
TEST_F(GitUtilsTest, gitUrlToHttp) {
TEST_F(GitUtilsTest, gitUrlToHttp)
{
{
const GitUrl url = parseGitUrl("git@github.com:user/repo.git");
EXPECT_EQ(url.toHttp(), "https://github.com/user/repo.git");
@ -219,7 +219,8 @@ TEST_F(GitUtilsTest, gitUrlToHttp) {
}
}
TEST_F(GitUtilsTest, gitUrlToSsh) {
TEST_F(GitUtilsTest, gitUrlToSsh)
{
{
const GitUrl url = parseGitUrl("https://example.com/user/repo.git");
const auto [host, path] = url.toSsh();
@ -234,9 +235,11 @@ TEST_F(GitUtilsTest, gitUrlToSsh) {
}
}
class FetchAttributeTest : public ::testing::Test {
class FetchAttributeTest : public ::testing::Test
{
protected:
void SetUp() override {
void SetUp() override
{
// test literal (non-wildcard) matches too
std::string content1 = "litfile filter=lfs diff=lfs merge=lfs -text";
auto rules1 = parseGitAttrFile(content1);
@ -251,23 +254,25 @@ protected:
Fetch fetch2;
};
TEST_F(FetchAttributeTest, ExactMatch) {
TEST_F(FetchAttributeTest, ExactMatch)
{
EXPECT_TRUE(fetch1.hasAttribute("litfile", "filter", "lfs"));
EXPECT_FALSE(fetch1.hasAttribute("other", "filter", "lfs"));
}
TEST_F(FetchAttributeTest, WildcardMatch) {
TEST_F(FetchAttributeTest, WildcardMatch)
{
EXPECT_TRUE(fetch2.hasAttribute("match.wildcard", "filter", "lfs"));
EXPECT_FALSE(fetch2.hasAttribute("nomatch.otherext", "filter", "lfs"));
EXPECT_FALSE(fetch2.hasAttribute("nomatch.wildcard.extra", "filter", "lfs"));
}
TEST_F(FetchAttributeTest, EmptyPath) {
TEST_F(FetchAttributeTest, EmptyPath)
{
EXPECT_FALSE(fetch1.hasAttribute("", "filter", "lfs"));
EXPECT_FALSE(fetch2.hasAttribute("", "filter", "lfs"));
}
} // namespace lfs
} // namespace nix

View file

@ -16,31 +16,34 @@
#include "processes.hh"
#include "url.hh"
namespace fs = std::filesystem;
namespace nix {
namespace lfs {
// see Fetch::rules
struct AttrRule {
struct AttrRule
{
std::string pattern;
std::unordered_map<std::string, std::string> attributes;
git_pathspec* pathspec = nullptr;
git_pathspec * pathspec = nullptr;
AttrRule() = default;
explicit AttrRule(std::string pat) : pattern(std::move(pat)) {
explicit AttrRule(std::string pat)
: pattern(std::move(pat))
{
initPathspec();
}
~AttrRule() {
~AttrRule()
{
if (pathspec) {
git_pathspec_free(pathspec);
}
}
AttrRule(const AttrRule& other)
AttrRule(const AttrRule & other)
: pattern(other.pattern)
, attributes(other.attributes)
, pathspec(nullptr)
@ -50,10 +53,11 @@ struct AttrRule {
}
}
void initPathspec() {
void initPathspec()
{
git_strarray patterns = {0};
const char* pattern_str = pattern.c_str();
patterns.strings = const_cast<char**>(&pattern_str);
const char * pattern_str = pattern.c_str();
patterns.strings = const_cast<char **>(&pattern_str);
patterns.count = 1;
if (git_pathspec_new(&pathspec, &patterns) != 0) {
@ -63,43 +67,44 @@ struct AttrRule {
};
// git-lfs metadata about a file
struct Md {
struct Md
{
std::string path; // fs path relative to repo root, no ./ prefix
std::string oid; // git-lfs managed object id. you give this to the lfs server
// for downloads
size_t size; // in bytes
};
struct GitUrl {
struct GitUrl
{
std::string protocol;
std::string user;
std::string host;
std::string port;
std::string path;
std::string toHttp() const {
std::string toHttp() const
{
if (protocol.empty() || host.empty()) {
return "";
}
std::string prefix = ((protocol == "ssh") ? "https" : protocol) + "://";
return prefix + host +
(port.empty() ? "" : ":" + port) + "/" + path;
return prefix + host + (port.empty() ? "" : ":" + port) + "/" + path;
}
// [host, path]
std::pair<std::string, std::string> toSsh() const {
std::pair<std::string, std::string> toSsh() const
{
if (host.empty()) {
return {"", ""};
}
std::string userPart = user.empty() ? "" : user + "@";
return {
userPart + host,
path
};
return {userPart + host, path};
}
};
struct Fetch {
struct Fetch
{
// only true after init()
bool ready = false;
@ -115,42 +120,46 @@ struct Fetch {
// paths tagged with `filter=lfs` need to be smudged by downloading from lfs server
std::vector<AttrRule> rules = {};
void init(git_repository* repo, const std::string& gitattributesContent);
bool hasAttribute(const std::string& path, const std::string& attrName, const std::string& attrValue) const;
void fetch(const git_blob* pointerBlob, const std::string& pointerFilePath, Sink& sink) const;
std::vector<nlohmann::json> fetchUrls(const std::vector<Md> &metadatas) const;
void init(git_repository * repo, const std::string & gitattributesContent);
bool hasAttribute(const std::string & path, const std::string & attrName, const std::string & attrValue) const;
void fetch(const git_blob * pointerBlob, const std::string & pointerFilePath, Sink & sink) const;
std::vector<nlohmann::json> fetchUrls(const std::vector<Md> & metadatas) const;
};
static size_t writeCallback(void *contents, size_t size, size_t nmemb,
std::string *s) {
static size_t writeCallback(void * contents, size_t size, size_t nmemb, std::string * s)
{
size_t newLength = size * nmemb;
s->append((char *)contents, newLength);
s->append((char *) contents, newLength);
return newLength;
}
struct SinkCallbackData {
Sink* sink;
struct SinkCallbackData
{
Sink * sink;
std::string_view sha256Expected;
HashSink hashSink;
SinkCallbackData(Sink* sink, std::string_view sha256)
SinkCallbackData(Sink * sink, std::string_view sha256)
: sink(sink)
, sha256Expected(sha256)
, hashSink(HashAlgorithm::SHA256)
{}
{
}
};
static size_t sinkWriteCallback(void *contents, size_t size, size_t nmemb, SinkCallbackData *data) {
static size_t sinkWriteCallback(void * contents, size_t size, size_t nmemb, SinkCallbackData * data)
{
size_t totalSize = size * nmemb;
data->hashSink({(char *)contents, totalSize});
(*data->sink)({(char *)contents, totalSize});
data->hashSink({(char *) contents, totalSize});
(*data->sink)({(char *) contents, totalSize});
return totalSize;
}
// if authHeader is "", downloadToSink assumes to auth is expected
void downloadToSink(const std::string &url, const std::string &authHeader, Sink &sink, std::string_view sha256Expected) {
CURL *curl;
void downloadToSink(
const std::string & url, const std::string & authHeader, Sink & sink, std::string_view sha256Expected)
{
CURL * curl;
CURLcode res;
curl = curl_easy_init();
@ -161,7 +170,7 @@ void downloadToSink(const std::string &url, const std::string &authHeader, Sink
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
struct curl_slist *headers = nullptr;
struct curl_slist * headers = nullptr;
if (!authHeader.empty()) {
const std::string authHeader_prepend = "Authorization: " + authHeader;
headers = curl_slist_append(headers, authHeader_prepend.c_str());
@ -177,23 +186,27 @@ void downloadToSink(const std::string &url, const std::string &authHeader, Sink
const auto sha256Actual = data.hashSink.finish().first.to_string(HashFormat::Base16, false);
if (sha256Actual != data.sha256Expected) {
throw std::runtime_error("sha256 mismatch: while fetching " + url + ": expected " + std::string(data.sha256Expected) + " but got " + sha256Actual);
throw std::runtime_error(
"sha256 mismatch: while fetching " + url + ": expected " + std::string(data.sha256Expected) + " but got "
+ sha256Actual);
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
std::string getLfsApiToken(const GitUrl& u) {
std::string getLfsApiToken(const GitUrl & u)
{
const auto [maybeUserAndHost, path] = u.toSsh();
auto [status, output] = runProgram(RunOptions {
auto [status, output] = runProgram(RunOptions{
.program = "ssh",
.args = {maybeUserAndHost, "git-lfs-authenticate", path, "download"},
});
if (output.empty())
throw std::runtime_error("git-lfs-authenticate: no output (cmd: ssh " + maybeUserAndHost + " git-lfs-authenticate " + path + " download)");
throw std::runtime_error(
"git-lfs-authenticate: no output (cmd: ssh " + maybeUserAndHost + " git-lfs-authenticate " + path
+ " download)");
nlohmann::json query_resp = nlohmann::json::parse(output);
if (!query_resp.contains("header"))
@ -206,15 +219,16 @@ std::string getLfsApiToken(const GitUrl& u) {
return res;
}
std::string getLfsEndpointUrl(git_repository *repo) {
std::string getLfsEndpointUrl(git_repository * repo)
{
int err;
git_remote* remote = NULL;
git_remote * remote = NULL;
err = git_remote_lookup(&remote, repo, "origin");
if (err < 0) {
return "";
}
const char *url_c_str = git_remote_url(remote);
const char * url_c_str = git_remote_url(remote);
if (!url_c_str) {
return "";
}
@ -222,7 +236,8 @@ std::string getLfsEndpointUrl(git_repository *repo) {
return std::string(url_c_str);
}
std::string git_attr_value_to_string(git_attr_value_t value) {
std::string git_attr_value_to_string(git_attr_value_t value)
{
switch (value) {
case GIT_ATTR_VALUE_UNSPECIFIED:
return "GIT_ATTR_VALUE_UNSPECIFIED";
@ -237,8 +252,8 @@ std::string git_attr_value_to_string(git_attr_value_t value) {
}
}
std::optional<Md> parseLfsMetadata(const std::string &content, const std::string &filename) {
std::optional<Md> parseLfsMetadata(const std::string & content, const std::string & filename)
{
// https://github.com/git-lfs/git-lfs/blob/2ef4108/docs/spec.md
//
// example git-lfs pointer file:
@ -293,19 +308,18 @@ std::optional<Md> parseLfsMetadata(const std::string &content, const std::string
return std::make_optional(Md{filename, oid, std::stoul(size)});
}
// there's already a ParseURL here https://github.com/b-camacho/nix/blob/ef6fa54e05cd4134ec41b0d64c1a16db46237f83/src/libutil/url.cc#L13
// but that does not handle git's custom scp-like syntax
GitUrl parseGitUrl(const std::string& url) {
// there's already a ParseURL here
// https://github.com/b-camacho/nix/blob/ef6fa54e05cd4134ec41b0d64c1a16db46237f83/src/libutil/url.cc#L13 but that does
// not handle git's custom scp-like syntax
GitUrl parseGitUrl(const std::string & url)
{
GitUrl result;
// regular protocols
const std::regex r_url(
R"(^(ssh|git|https?|ftps?)://(?:([^@]+)@)?([^:/]+)(?::(\d+))?/(.*))");
const std::regex r_url(R"(^(ssh|git|https?|ftps?)://(?:([^@]+)@)?([^:/]+)(?::(\d+))?/(.*))");
// "alternative scp-like syntax" https://git-scm.com/docs/git-fetch#_git_urls
const std::regex r_scp_like_url(
R"(^(?:([^@]+)@)?([^:/]+):(/?.*))");
const std::regex r_scp_like_url(R"(^(?:([^@]+)@)?([^:/]+):(/?.*))");
std::smatch matches;
if (std::regex_match(url, matches, r_url)) {
@ -314,8 +328,7 @@ GitUrl parseGitUrl(const std::string& url) {
result.host = matches[3].str();
result.port = matches[4].str();
result.path = matches[5].str();
}
else if (std::regex_match(url, matches, r_scp_like_url)) {
} else if (std::regex_match(url, matches, r_scp_like_url)) {
result.protocol = "ssh";
result.user = matches[1].str();
@ -326,7 +339,7 @@ GitUrl parseGitUrl(const std::string& url) {
return result;
}
std::vector<AttrRule> parseGitAttrFile(const std::string& content)
std::vector<AttrRule> parseGitAttrFile(const std::string & content)
{
std::vector<AttrRule> rules;
std::string content_str(content);
@ -348,14 +361,15 @@ std::vector<AttrRule> parseGitAttrFile(const std::string& content)
rule.pattern = line.substr(0, pattern_end);
git_strarray patterns = {0};
const char* pattern_str = rule.pattern.c_str();
patterns.strings = const_cast<char**>(&pattern_str);
const char * pattern_str = rule.pattern.c_str();
patterns.strings = const_cast<char **>(&pattern_str);
patterns.count = 1;
if (git_pathspec_new(&rule.pathspec, &patterns) != 0) {
auto error = git_error_last();
std::stringstream ss;
ss << "git_pathspec_new parsing '" << line << "': " << (error ? error->message : "unknown error") << std::endl;
ss << "git_pathspec_new parsing '" << line << "': " << (error ? error->message : "unknown error")
<< std::endl;
warn(ss.str());
continue;
}
@ -396,7 +410,8 @@ std::vector<AttrRule> parseGitAttrFile(const std::string& content)
return rules;
}
void Fetch::init(git_repository* repo, const std::string& gitattributesContent) {
void Fetch::init(git_repository * repo, const std::string & gitattributesContent)
{
const auto remoteUrl = lfs::getLfsEndpointUrl(repo);
this->gitUrl = parseGitUrl(remoteUrl);
@ -407,15 +422,13 @@ void Fetch::init(git_repository* repo, const std::string& gitattributesContent)
this->ready = true;
}
bool Fetch::hasAttribute(const std::string& path, const std::string& attrName, const std::string& attrValue) const
bool Fetch::hasAttribute(const std::string & path, const std::string & attrName, const std::string & attrValue) const
{
for (auto it = rules.rbegin(); it != rules.rend(); ++it) {
int match = git_pathspec_matches_path(
it->pathspec,
0, // no flags
path.c_str()
);
path.c_str());
if (match > 0) {
auto attr = it->attributes.find(attrName);
@ -428,16 +441,17 @@ bool Fetch::hasAttribute(const std::string& path, const std::string& attrName, c
return false;
}
nlohmann::json mdToPayload(const std::vector<Md> &items) {
nlohmann::json mdToPayload(const std::vector<Md> & items)
{
nlohmann::json jArray = nlohmann::json::array();
for (const auto &md : items) {
for (const auto & md : items) {
jArray.push_back({{"oid", md.oid}, {"size", md.size}});
}
return jArray;
}
std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Md> &metadatas) const {
std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Md> & metadatas) const
{
nlohmann::json oidList = mdToPayload(metadatas);
nlohmann::json data = {
{"operation", "download"},
@ -445,7 +459,7 @@ std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Md> &metadatas) c
data["objects"] = oidList;
auto dataStr = data.dump();
CURL *curl = curl_easy_init();
CURL * curl = curl_easy_init();
char curlErrBuf[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlErrBuf);
std::string responseString;
@ -455,14 +469,13 @@ std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Md> &metadatas) c
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataStr.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
struct curl_slist *headers = NULL;
struct curl_slist * headers = NULL;
if (this->token != "") {
const auto authHeader = "Authorization: " + token;
headers = curl_slist_append(headers, authHeader.c_str());
}
headers =
curl_slist_append(headers, "Content-Type: application/vnd.git-lfs+json");
headers = curl_slist_append(headers, "Content-Type: application/vnd.git-lfs+json");
headers = curl_slist_append(headers, "Accept: application/vnd.git-lfs+json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
@ -487,22 +500,21 @@ std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Md> &metadatas) c
try {
auto resp = nlohmann::json::parse(responseString);
if (resp.contains("objects")) {
objects.insert(objects.end(), resp["objects"].begin(),
resp["objects"].end());
objects.insert(objects.end(), resp["objects"].begin(), resp["objects"].end());
} else {
throw std::runtime_error("response does not contain 'objects'");
}
return objects;
} catch (const nlohmann::json::parse_error& e) {
} catch (const nlohmann::json::parse_error & e) {
std::stringstream ss;
ss << "response did not parse as json: " << responseString;
throw std::runtime_error(ss.str());
}
}
void Fetch::fetch(const git_blob* pointerBlob, const std::string& pointerFilePath, Sink& sink) const {
void Fetch::fetch(const git_blob * pointerBlob, const std::string & pointerFilePath, Sink & sink) const
{
constexpr size_t chunkSize = 128 * 1024; // 128 KiB
auto size = git_blob_rawsize(pointerBlob);
@ -510,7 +522,8 @@ void Fetch::fetch(const git_blob* pointerBlob, const std::string& pointerFilePat
debug("Skip git-lfs, pointer file too large");
warn("Encountered a file that should have been a pointer, but wasn't: %s", pointerFilePath);
for (size_t offset = 0; offset < size; offset += chunkSize) {
sink(std::string((const char *) git_blob_rawcontent(pointerBlob) + offset, std::min(chunkSize, size - offset)));
sink(std::string(
(const char *) git_blob_rawcontent(pointerBlob) + offset, std::min(chunkSize, size - offset)));
}
return;
}
@ -521,7 +534,8 @@ void Fetch::fetch(const git_blob* pointerBlob, const std::string& pointerFilePat
debug("Skip git-lfs, invalid pointer file");
warn("Encountered a file that should have been a pointer, but wasn't: %s", pointerFilePath);
for (size_t offset = 0; offset < size; offset += chunkSize) {
sink(std::string((const char *) git_blob_rawcontent(pointerBlob) + offset, std::min(chunkSize, size - offset)));
sink(std::string(
(const char *) git_blob_rawcontent(pointerBlob) + offset, std::min(chunkSize, size - offset)));
}
return;
}
@ -535,12 +549,13 @@ void Fetch::fetch(const git_blob* pointerBlob, const std::string& pointerFilePat
std::string oid = obj.at("oid");
std::string ourl = obj.at("actions").at("download").at("href");
std::string authHeader = "";
if (obj.at("actions").at("download").contains("header") && obj.at("actions").at("download").at("header").contains("Authorization")) {
if (obj.at("actions").at("download").contains("header")
&& obj.at("actions").at("download").at("header").contains("Authorization")) {
authHeader = obj["actions"]["download"]["header"]["Authorization"];
}
// oid is also the sha256
downloadToSink(ourl, authHeader, sink, oid);
} catch (const nlohmann::json::out_of_range& e) {
} catch (const nlohmann::json::out_of_range & e) {
std::stringstream ss;
ss << "bad json from /info/lfs/objects/batch: " << obj << " " << e.what();
throw std::runtime_error(ss.str());