defmodule Gogs do @moduledoc """ Documentation for the main `Gogs` functions. This package is an `Elixir` interface to our `Gogs` Server. It contains all functions we need to create repositories, clone, add data to files, commit, push and diff. Some of these functions use `Git` and others use the `REST API`. We would _obviously_ prefer if everything was one or the other, but sadly, some things cannot be done via `Git` or `REST` so we have adopted a "hybrid" approach. If anything is unclear, please open an issue: https://github.com/dwyl/gogs/issues """ import GogsHelpers require Logger @access_token Envar.get("GOGS_ACCESS_TOKEN") @api_base_url GogsHelpers.api_base_url() @mock Application.compile_env(:gogs, :mock) @git (@mock && Gogs.GitMock) || Git @httpoison (@mock && Gogs.HTTPoisonMock) || HTTPoison @doc """ `inject_git/0` injects a `Git` TestDouble in Tests & CI so we don't have duplicate mocks in the downstream app. """ def inject_git, do: @git @doc """ `inject_poison/0` injects a TestDouble of HTTPoison in Test. see: github.com/dwyl/elixir-auth-google/issues/35 """ def inject_poison, do: @httpoison @doc """ `post/2` accepts two arguments: `url` and `params`. Makes an `HTTP POST` request to the specified `url` passing in the `params` as the request body. Auth Headers and Content-Type are implicit. """ @spec post(String.t(), map) :: {:ok, map} | {:error, any} def post(url, params \\ %{}) do # IO.inspect(url, label: url) body = Jason.encode!(params) headers = [ {"Accept", "application/json"}, {"Authorization", "token #{@access_token}"}, {"Content-Type", "application/json"} ] inject_poison().post(url, body, headers) |> parse_body_response() end @doc """ `remote_repo_create/3` accepts 3 arguments: `org_name`, `repo_name` & `private`. It creates a repo on the remote `Gogs` instance as defined by the environment variable `GOGS_URL`. For convenience it assumes that you only have _one_ `Gogs` instance. If you have more or different requirements, please share! """ @spec remote_repo_create(String.t(), String.t(), boolean) :: {:ok, map} | {:error, any} def remote_repo_create(org_name, repo_name, private \\ false) do url = @api_base_url <> "org/#{org_name}/repos" Logger.info("remote_repo_create: #{url}") params = %{ name: repo_name, private: private, description: repo_name, readme: repo_name } post(url, params) end @doc """ `delete/1` accepts a single argument `url`; the `url` for the repository to be deleted. """ @spec delete(String.t()) :: {:ok, map} | {:error, any} def delete(url) do inject_poison().delete(url <> "?token=#{@access_token}") |> parse_body_response() end @doc """ `remote_repo_delete/2` accepts two arguments: `org_name` and `repo_name`. It deletes the repo on the remote `Gogs` instance as defined by the environment variable `GOGS_URL`. """ @spec remote_repo_delete(String.t(), String.t()) :: {:ok, map} | {:error, any} def remote_repo_delete(org_name, repo_name) do url = @api_base_url <> "repos/#{org_name}/#{repo_name}" Logger.info("remote_repo_delete: #{url}") delete(url) end @doc """ `clone/1` clones a remote git repository based on `git_repo_url` returns the path of the _local_ copy of the repository. """ @spec clone(String.t()) :: {:ok, any} | {:error, any} def clone(git_repo_url) do # IO.inspect("git clone #{git_repo_url}") case inject_git().clone(git_repo_url) do {:ok, %Git.Repository{path: path}} -> # Logger.info("Cloned repo: #{git_repo_url} to: #{path}") path {:error, %Git.Error{message: message}} -> Logger.error("ERROR: Tried to clone #{git_repo_url}, got: #{message}") get_repo_name_from_url(git_repo_url) |> local_repo_path() end end @doc """ `local_branch_create/2` creates a branch with the specified name or defaults to "draft". """ @spec local_branch_create(String.t(), String.t()) :: {:ok, map} | {:error, any} def local_branch_create(repo_name, branch_name \\ "draft") do case Git.checkout(local_git_repo(repo_name), ["-b", branch_name]) do {:ok, res} -> {:ok, res} {:error, %Git.Error{message: message}} -> Logger.error("Git.checkout error: #{message}") {:ok, message} end end @doc """ `local_file_write_text/3` writes the desired `text`, to the `file_name` in the `repo_name`. Touches the file in case it doesn't already exist. """ @spec local_file_write_text(String.t(), String.t(), String.t()) :: :ok | {:error, any} def local_file_write_text(repo_name, file_name, text) do file_path = Path.join([local_repo_path(repo_name), file_name]) Logger.info("attempting to write to #{file_path}") File.touch!(file_path) File.write(file_path, text) end @doc """ `commit/2` commits the latest changes on the local branch. Accepts the `repo_name` and a `Map` of `params`: `params.message`: the commit message you want in the log `params.full_name`: the name of the person making the commit `params.email`: email address of the person committing. """ @spec commit(String.t(), map) :: {:ok, any} | {:error, any} def commit(repo_name, params) do repo = %Git.Repository{path: local_repo_path(repo_name)} # Add all files in the repo {:ok, _output} = Git.add(repo, ["."]) # Commit with message {:ok, _output} = Git.commit(repo, [ "-m", params.message, ~s(--author="#{params.full_name} <#{params.email}>") ]) end @doc """ `push/1` pushes the `repo_name` (current active branch) to the remote repository URL. Mocked during test/CI. """ @spec push(String.t()) :: {:ok, any} | {:error, any} def push(repo_name) do # Get the current git branch: git_repo = %Git.Repository{path: local_repo_path(repo_name)} {:ok, branch} = Git.branch(git_repo, ~w(--show-current)) # Remove trailing whitespace as Git chokes on it: branch = String.trim(branch) # Push the current branch: inject_git().push(git_repo, ["-u", "origin", branch]) end end