diff options
Diffstat (limited to 'src/libfetchers/github.cc')
-rw-r--r-- | src/libfetchers/github.cc | 182 |
1 files changed, 137 insertions, 45 deletions
diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index c01917dcc..e59c83be4 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -8,21 +8,18 @@ namespace nix::fetchers { -std::regex ownerRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript); -std::regex repoRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript); - -struct GitHubInput : Input +struct GitArchiveInput : Input { std::string owner; std::string repo; std::optional<std::string> ref; std::optional<Hash> rev; - std::string type() const override { return "github"; } + virtual std::shared_ptr<GitArchiveInput> _clone() const = 0; bool operator ==(const Input & other) const override { - auto other2 = dynamic_cast<const GitHubInput *>(&other); + auto other2 = dynamic_cast<const GitArchiveInput *>(&other); return other2 && owner == other2->owner @@ -47,7 +44,7 @@ struct GitHubInput : Input if (ref) path += "/" + *ref; if (rev) path += "/" + rev->to_string(Base16, false); return ParsedURL { - .scheme = "github", + .scheme = type(), .path = path, }; } @@ -64,30 +61,18 @@ struct GitHubInput : Input return attrs; } - void clone(const Path & destDir) const override - { - std::shared_ptr<const Input> input = inputFromURL(fmt("git+ssh://git@github.com/%s/%s.git", owner, repo)); - input = input->applyOverrides(ref.value_or("master"), rev); - input->clone(destDir); - } + virtual Hash getRevFromRef(nix::ref<Store> store, std::string_view ref) const = 0; + + virtual std::string getDownloadUrl() const = 0; std::pair<Tree, std::shared_ptr<const Input>> fetchTreeInternal(nix::ref<Store> store) const override { auto rev = this->rev; auto ref = this->ref.value_or("master"); - if (!rev) { - auto url = fmt("https://api.github.com/repos/%s/%s/commits/%s", - owner, repo, ref); - auto json = nlohmann::json::parse( - readFile( - store->toRealPath( - downloadFile(store, url, "source", false).storePath))); - rev = Hash(json["sha"], htSHA1); - debug("HEAD revision for '%s' is %s", url, rev->gitRev()); - } + if (!rev) rev = getRevFromRef(store, ref); - auto input = std::make_shared<GitHubInput>(*this); + auto input = _clone(); input->ref = {}; input->rev = *rev; @@ -109,15 +94,8 @@ struct GitHubInput : Input }; } - // FIXME: use regular /archive URLs instead? api.github.com - // might have stricter rate limits. + auto url = input->getDownloadUrl(); - auto url = fmt("https://api.github.com/repos/%s/%s/tarball/%s", - owner, repo, rev->to_string(Base16, false)); - - std::string accessToken = settings.githubAccessToken.get(); - if (accessToken != "") - url += "?access_token=" + accessToken; auto tree = downloadTarball(store, url, "source", true); @@ -140,7 +118,7 @@ struct GitHubInput : Input { if (!ref && !rev) return shared_from_this(); - auto res = std::make_shared<GitHubInput>(*this); + auto res = _clone(); if (ref) res->ref = ref; if (rev) res->rev = rev; @@ -149,14 +127,21 @@ struct GitHubInput : Input } }; -struct GitHubInputScheme : InputScheme +struct GitArchiveInputScheme : InputScheme { + std::string type; + + GitArchiveInputScheme(std::string && type) : type(type) + { } + + virtual std::unique_ptr<GitArchiveInput> create() = 0; + std::unique_ptr<Input> inputFromURL(const ParsedURL & url) override { - if (url.scheme != "github") return nullptr; + if (url.scheme != type) return nullptr; auto path = tokenizeString<std::vector<std::string>>(url.path, "/"); - auto input = std::make_unique<GitHubInput>(); + auto input = create(); if (path.size() == 2) { } else if (path.size() == 3) { @@ -165,27 +150,27 @@ struct GitHubInputScheme : InputScheme else if (std::regex_match(path[2], refRegex)) input->ref = path[2]; else - throw BadURL("in GitHub URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[2]); + throw BadURL("in URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[2]); } else - throw BadURL("GitHub URL '%s' is invalid", url.url); + throw BadURL("URL '%s' is invalid", url.url); for (auto &[name, value] : url.query) { if (name == "rev") { if (input->rev) - throw BadURL("GitHub URL '%s' contains multiple commit hashes", url.url); + throw BadURL("URL '%s' contains multiple commit hashes", url.url); input->rev = Hash(value, htSHA1); } else if (name == "ref") { if (!std::regex_match(value, refRegex)) - throw BadURL("GitHub URL '%s' contains an invalid branch/tag name", url.url); + throw BadURL("URL '%s' contains an invalid branch/tag name", url.url); if (input->ref) - throw BadURL("GitHub URL '%s' contains multiple branch/tag names", url.url); + throw BadURL("URL '%s' contains multiple branch/tag names", url.url); input->ref = value; } } if (input->ref && input->rev) - throw BadURL("GitHub URL '%s' contains both a commit hash and a branch/tag name", url.url); + throw BadURL("URL '%s' contains both a commit hash and a branch/tag name", url.url); input->owner = path[0]; input->repo = path[1]; @@ -195,13 +180,13 @@ struct GitHubInputScheme : InputScheme std::unique_ptr<Input> inputFromAttrs(const Attrs & attrs) override { - if (maybeGetStrAttr(attrs, "type") != "github") return {}; + if (maybeGetStrAttr(attrs, "type") != type) return {}; for (auto & [name, value] : attrs) if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev") - throw Error("unsupported GitHub input attribute '%s'", name); + throw Error("unsupported input attribute '%s'", name); - auto input = std::make_unique<GitHubInput>(); + auto input = create(); input->owner = getStrAttr(attrs, "owner"); input->repo = getStrAttr(attrs, "repo"); input->ref = maybeGetStrAttr(attrs, "ref"); @@ -211,6 +196,113 @@ struct GitHubInputScheme : InputScheme } }; +struct GitHubInput : GitArchiveInput +{ + std::string type() const override { return "github"; } + + std::shared_ptr<GitArchiveInput> _clone() const override + { return std::make_shared<GitHubInput>(*this); } + + Hash getRevFromRef(nix::ref<Store> store, std::string_view ref) const override + { + auto url = fmt("https://api.github.com/repos/%s/%s/commits/%s", + owner, repo, ref); + auto json = nlohmann::json::parse( + readFile( + store->toRealPath( + downloadFile(store, url, "source", false).storePath))); + auto rev = Hash(json["sha"], htSHA1); + debug("HEAD revision for '%s' is %s", url, rev.gitRev()); + return rev; + } + + std::string getDownloadUrl() const override + { + // FIXME: use regular /archive URLs instead? api.github.com + // might have stricter rate limits. + + auto url = fmt("https://api.github.com/repos/%s/%s/tarball/%s", + owner, repo, rev->to_string(Base16, false)); + + std::string accessToken = settings.githubAccessToken.get(); + if (accessToken != "") + url += "?access_token=" + accessToken; + + return url; + } + + void clone(const Path & destDir) const override + { + std::shared_ptr<const Input> input = inputFromURL(fmt("git+ssh://git@github.com/%s/%s.git", owner, repo)); + input = input->applyOverrides(ref.value_or("master"), rev); + input->clone(destDir); + } +}; + +struct GitHubInputScheme : GitArchiveInputScheme +{ + GitHubInputScheme() : GitArchiveInputScheme("github") { } + + std::unique_ptr<GitArchiveInput> create() override + { + return std::make_unique<GitHubInput>(); + } +}; + +struct GitLabInput : GitArchiveInput +{ + std::string type() const override { return "gitlab"; } + + std::shared_ptr<GitArchiveInput> _clone() const override + { return std::make_shared<GitLabInput>(*this); } + + Hash getRevFromRef(nix::ref<Store> store, std::string_view ref) const override + { + auto url = fmt("https://gitlab.com/api/v4/projects/%s%%2F%s/repository/branches/%s", + owner, repo, ref); + auto json = nlohmann::json::parse( + readFile( + store->toRealPath( + downloadFile(store, url, "source", false).storePath))); + auto rev = Hash(json["commit"]["id"], htSHA1); + debug("HEAD revision for '%s' is %s", url, rev.gitRev()); + return rev; + } + + std::string getDownloadUrl() const override + { + // FIXME: This endpoint has a rate limit threshold of 5 requests per minute. + + auto url = fmt("https://gitlab.com/api/v4/projects/%s%%2F%s/repository/archive.tar.gz?sha=%s", + owner, repo, rev->to_string(Base16, false)); + + /* # FIXME: add privat token auth (`curl --header "PRIVATE-TOKEN: <your_access_token>"`) + std::string accessToken = settings.githubAccessToken.get(); + if (accessToken != "") + url += "?access_token=" + accessToken;*/ + + return url; + } + + void clone(const Path & destDir) const override + { + std::shared_ptr<const Input> input = inputFromURL(fmt("git+ssh://git@gitlab.com/%s/%s.git", owner, repo)); + input = input->applyOverrides(ref.value_or("master"), rev); + input->clone(destDir); + } +}; + +struct GitLabInputScheme : GitArchiveInputScheme +{ + GitLabInputScheme() : GitArchiveInputScheme("gitlab") { } + + std::unique_ptr<GitArchiveInput> create() override + { + return std::make_unique<GitLabInput>(); + } +}; + static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); }); +static auto r2 = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); }); } |