2.2 KiB · text History b868907
defmodule GitGud.Workflows.Ref do
  @moduledoc """
  What a pushed ref is, in the terms workflows are written in.

  A ref name is the only thing separating "push to master" from "release
  v1.2.3", and three places need to agree about it: dispatch, which decides
  whether a workflow's `branches:`/`tags:` filter claims the push; the runner
  context, which carries `github.ref_name` and `github.ref_type` into a job;
  and anything later that wants to say "this run was a tag".

  They agreed by coincidence until they didn't. `github.ref_type` was simply
  absent from the context, so a release workflow's `if` read it as empty, took
  its "not a tag" branch on a tag push, published nothing, and reported
  success — which is worse than failing, because nothing asks to be looked at.
  """

  @doc """
  The short name: `refs/heads/main` -> `main`, `refs/tags/v1.0` -> `v1.0`.

  Anything else passes through unchanged. A caller is better served by a
  surprising value it can see than by a blank one it cannot.

      iex> GitGud.Workflows.Ref.name("refs/tags/v1.0")
      "v1.0"
  """
  @spec name(String.t() | nil) :: String.t()
  def name("refs/heads/" <> name), do: name
  def name("refs/tags/" <> name), do: name
  def name(ref) when is_binary(ref), do: ref
  def name(_), do: ""

  @doc """
  `"branch"`, `"tag"`, or `""` for a ref that is neither.

  These are GitHub's spellings, because `github.ref_type` is compared against
  them in workflows people copy from its documentation.

      iex> GitGud.Workflows.Ref.type("refs/tags/v1.0")
      "tag"
  """
  @spec type(String.t() | nil) :: String.t()
  def type("refs/heads/" <> _), do: "branch"
  def type("refs/tags/" <> _), do: "tag"
  def type(_), do: ""

  @doc """
  The filter context for `GitGud.Workflows.Yaml.matches?/3`.

  `%{branch: name}` or `%{tag: name}`, and `%{}` for a ref that is neither —
  which filters on nothing rather than guessing.

      iex> GitGud.Workflows.Ref.context("refs/heads/main")
      %{branch: "main"}
  """
  @spec context(String.t() | nil) :: %{
          optional(:branch) => String.t(),
          optional(:tag) => String.t()
        }
  def context("refs/heads/" <> branch), do: %{branch: branch}
  def context("refs/tags/" <> tag), do: %{tag: tag}
  def context(_), do: %{}
end