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: [github.com/dwyl/**gogs/issues**](https://github.com/dwyl/gogs/issues) """ import Gogs.Helpers require Logger @mock Application.compile_env(:gogs, :mock) Logger.debug("Gogs > config :gogs, mock: #{to_string(@mock)}") @git (@mock && Gogs.GitMock) || Git @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 """ `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 api endpoint: #{url}") params = %{ name: repo_name, private: private, description: repo_name, readme: repo_name } Gogs.Http.post(url, params) 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}") Gogs.Http.delete(url) end @doc """ `remote_read_file/4` reads a file from the remote repo. Accepts 4 arguments: `org_name`, `repo_name`, `file_name` and `branch_name`. The 4th argument is *optional* and defaults to `"master"` (the default branch for a repo hosted on `Gogs`). Makes a `GET` request to the remote `Gogs` instance as defined by the environment variable `GOGS_URL`. Returns `{:ok, %HTTPoison.Response{ body: response_body}}` Uses REST API Endpoint: ```sh GET /repos/:username/:reponame/raw/:branchname/:path ``` Ref: https://github.com/gogs/docs-api/blob/master/Repositories/Contents.md#get-contents """ @spec remote_read_raw(String.t(), String.t(), String.t(), String.t()) :: {:ok, map} | {:error, any} def remote_read_raw(org_name, repo_name, file_name, branch_name \\ "master") do url = api_base_url() <> "repos/#{org_name}/#{repo_name}/raw/#{branch_name}/#{file_name}" Logger.debug("Gogs.remote_read_raw: #{url}") Gogs.Http.get_raw(url) end @doc """ `remote_render_markdown_html/4` uses `Gog` built-in Markdown processor to render a Markdown Doc e.g. the `README.md` so you can easily use the HTML. Accepts 4 arguments: `org_name`, `repo_name`, `file_name` and `branch_name`. The 4th argument is *optional* and defaults to `"master"` (the default branch for a repo hosted on `Gogs`). Makes a `GET` request to the remote `Gogs` instance as defined by the environment variable `GOGS_URL`. Returns `{:ok, %HTTPoison.Response{ body: response_body}}` Uses REST API Endpoint: ```sh POST /markdown/raw ``` Ref: https://github.com/dwyl/gogs/issues/23 """ @spec remote_render_markdown_html(String.t(), String.t(), String.t(), String.t()) :: {:ok, map} | {:error, any} def remote_render_markdown_html(org_name, repo_name, file_name, branch_name \\ "master") do # First retrieve the Raw Markdown Text we want to render: {:ok, %HTTPoison.Response{body: raw_markdown}} = Gogs.remote_read_raw(org_name, repo_name, file_name, branch_name) Logger.debug("raw_markdown: #{raw_markdown}") url = api_base_url() <> "markdown/raw" Logger.info("remote_render_markdown_html/4 #{url}") # temp_context = "https://github.com/gogs/gogs" # Ask Gogs to redner the Raw Markdown to HTML: Gogs.Http.post_raw_html(url, raw_markdown) # I agree, this is clunky ... We wont use it for latency-sensitive apps. # But it could be useful for a quick/easy Static Site / Blog App. 💭 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()) :: String.t() def clone(git_repo_url) do org_name = get_org_name_from_url(git_repo_url) repo_name = get_repo_name_from_url(git_repo_url) local_path = local_repo_path(org_name, repo_name) Logger.info("git clone #{git_repo_url} #{local_path}") case inject_git().clone([git_repo_url, local_path]) do {:ok, %Git.Repository{path: path}} -> # Logger.info("Cloned repo: #{git_repo_url} to: #{path}") path {:error, git_err} -> Logger.error("Gogs.clone/1 tried to clone #{git_repo_url}, got: #{git_err.message}") local_path end end @doc """ `local_branch_create/3` 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(org_name, repo_name, branch_name \\ "draft") do case Git.checkout(local_git_repo(org_name, repo_name), ["-b", branch_name]) do {:ok, res} -> {:ok, res} {:error, git_err} -> Logger.error( "Git.checkout error: #{git_err.message}, #{repo_name} (should not thow error)" ) {:error, git_err.message} end end @doc """ `local_file_read/3` reads the raw text from the `file_name`, params: `org_name`, `repo_name` & `file_name` """ @spec local_file_read(String.t(), String.t(), String.t()) :: {:ok, String.t()} | {:error, any()} def local_file_read(org_name, repo_name, file_name) do file_path = Path.join([local_repo_path(org_name, repo_name), file_name]) File.read(file_path) 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(), String.t()) :: :ok | {:error, any} def local_file_write_text(org_name, repo_name, file_name, text) do file_path = Path.join([local_repo_path(org_name, repo_name), file_name]) Logger.info("attempting to write to #{file_path}") File.touch!(file_path) File.write(file_path, text) end @doc """ `commit/3` 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(), String.t(), map) :: {:ok, any} | {:error, any} def commit(org_name, repo_name, params) do repo = local_git_repo(org_name, 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/2` pushes the `org_name/repo_name` (current active branch) to the remote repository URL. Mocked during test/CI. """ @spec push(String.t(), String.t()) :: {:ok, any} | {:error, any} def push(org_name, repo_name) do # Get the current git branch: git_repo = local_git_repo(org_name, 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