2.5 KiB · text History 69753e1
defmodule GitGud.Imports.Providers.GitHub do
@moduledoc """
Repository discovery against github.com or a GitHub Enterprise Server.
`base_url` is the **API** root — `https://api.github.com` for the
hosted flavour, `https://ghe.example.com/api/v3` for Enterprise.
An owner may be an organization or a user and the API paths differ, so
we try `/orgs/:owner/repos` first and fall back to `/users/:owner/repos`
on a 404.
"""
@behaviour GitGud.Imports.Provider
alias GitGud.Imports.Provider
@per_page 100
@impl true
def label, do: "GitHub"
@impl true
def default_base_url, do: "https://api.github.com"
@impl true
def list_repositories(owner, opts) do
base = Keyword.fetch!(opts, :base_url) |> String.trim_trailing("/")
token = Keyword.get(opts, :token)
case fetch_all("#{base}/orgs/#{URI.encode(owner)}/repos", token) do
{:error, :not_found} -> fetch_all("#{base}/users/#{URI.encode(owner)}/repos", token)
other -> other
end
end
defp fetch_all(url, token) do
Provider.paginate(fn page ->
request(url, token, page: page, per_page: @per_page)
end)
end
defp request(url, token, params) do
req =
Req.new(
url: url,
params: params,
headers: headers(token),
receive_timeout: 30_000,
retry: :safe_transient
)
|> Req.merge(Application.get_env(:git_gud, :imports_req_options, []))
case Req.get(req) do
{:ok, %Req.Response{status: 200, body: body}} when is_list(body) ->
{:ok, Enum.map(body, &normalize/1), length(body) == @per_page}
{:ok, %Req.Response{status: 404}} ->
{:error, :not_found}
{:ok, %Req.Response{status: status}} when status in [401, 403] ->
{:error, {:unauthorized, status}}
{:ok, %Req.Response{status: status, body: body}} ->
{:error, {:http_error, status, message(body)}}
{:error, reason} ->
{:error, {:transport, reason}}
end
end
defp headers(nil),
do: [{"accept", "application/vnd.github+json"}, {"user-agent", "gitgud-importer"}]
defp headers(token),
do: [{"authorization", "Bearer " <> token} | headers(nil)]
defp normalize(repo) do
%{
name: repo["name"],
full_name: repo["full_name"] || repo["name"],
clone_url: repo["clone_url"],
description: repo["description"],
default_branch: repo["default_branch"],
private: !!repo["private"],
fork: !!repo["fork"],
archived: !!repo["archived"]
}
end
defp message(%{"message" => m}) when is_binary(m), do: m
defp message(_), do: nil
end