lib/operations/git.ex

defmodule GitHub.Git do
  @moduledoc """
  Provides API endpoints related to git
  """

  @default_client GitHub.Client

  @doc """
  Create a blob

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/blobs#create-a-blob)

  """
  @spec create_blob(String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.ShortBlob.t()} | {:error, GitHub.Error.t()}
  def create_blob(owner, repo, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, body: body],
      call: {GitHub.Git, :create_blob},
      url: "/repos/#{owner}/#{repo}/git/blobs",
      body: body,
      method: :post,
      request: [{"application/json", :map}],
      response: [
        {201, {GitHub.ShortBlob, :t}},
        {403, {GitHub.BasicError, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Create a commit

  Creates a new Git [commit object](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects).

  **Signature verification object**

  The response will include a `verification` object that describes the result of verifying the commit's signature. The following fields are included in the `verification` object:

  | Name | Type | Description |
  | ---- | ---- | ----------- |
  | `verified` | `boolean` | Indicates whether GitHub considers the signature in this commit to be verified. |
  | `reason` | `string` | The reason for verified value. Possible values and their meanings are enumerated in the table below. |
  | `signature` | `string` | The signature that was extracted from the commit. |
  | `payload` | `string` | The value that was signed. |

  These are the possible values for `reason` in the `verification` object:

  | Value | Description |
  | ----- | ----------- |
  | `expired_key` | The key that made the signature is expired. |
  | `not_signing_key` | The "signing" flag is not among the usage flags in the GPG key that made the signature. |
  | `gpgverify_error` | There was an error communicating with the signature verification service. |
  | `gpgverify_unavailable` | The signature verification service is currently unavailable. |
  | `unsigned` | The object does not include a signature. |
  | `unknown_signature_type` | A non-PGP signature was found in the commit. |
  | `no_user` | No user was associated with the `committer` email address in the commit. |
  | `unverified_email` | The `committer` email address in the commit was associated with a user, but the email address is not verified on their account. |
  | `bad_email` | The `committer` email address in the commit is not included in the identities of the PGP key that made the signature. |
  | `unknown_key` | The key that made the signature has not been registered with any user's account. |
  | `malformed_signature` | There was an error parsing the signature. |
  | `invalid` | The signature could not be cryptographically verified using the key whose key-id was found in the signature. |
  | `valid` | None of the above errors applied, so the signature is considered to be verified. |

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/commits#create-a-commit)

  """
  @spec create_commit(String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.Git.Commit.t()} | {:error, GitHub.Error.t()}
  def create_commit(owner, repo, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, body: body],
      call: {GitHub.Git, :create_commit},
      url: "/repos/#{owner}/#{repo}/git/commits",
      body: body,
      method: :post,
      request: [{"application/json", :map}],
      response: [
        {201, {GitHub.Git.Commit, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Create a reference

  Creates a reference for your repository. You are unable to create new references for empty repositories, even if the commit SHA-1 hash used exists. Empty repositories are repositories without branches.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/refs#create-a-reference)

  """
  @spec create_ref(String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.Git.Ref.t()} | {:error, GitHub.Error.t()}
  def create_ref(owner, repo, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, body: body],
      call: {GitHub.Git, :create_ref},
      url: "/repos/#{owner}/#{repo}/git/refs",
      body: body,
      method: :post,
      request: [{"application/json", :map}],
      response: [
        {201, {GitHub.Git.Ref, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Create a tag object

  Note that creating a tag object does not create the reference that makes a tag in Git. If you want to create an annotated tag in Git, you have to do this call to create the tag object, and then [create](https://docs.github.com/rest/git/refs#create-a-reference) the `refs/tags/[tag]` reference. If you want to create a lightweight tag, you only have to [create](https://docs.github.com/rest/git/refs#create-a-reference) the tag reference - this call would be unnecessary.

  **Signature verification object**

  The response will include a `verification` object that describes the result of verifying the commit's signature. The following fields are included in the `verification` object:

  | Name | Type | Description |
  | ---- | ---- | ----------- |
  | `verified` | `boolean` | Indicates whether GitHub considers the signature in this commit to be verified. |
  | `reason` | `string` | The reason for verified value. Possible values and their meanings are enumerated in table below. |
  | `signature` | `string` | The signature that was extracted from the commit. |
  | `payload` | `string` | The value that was signed. |

  These are the possible values for `reason` in the `verification` object:

  | Value | Description |
  | ----- | ----------- |
  | `expired_key` | The key that made the signature is expired. |
  | `not_signing_key` | The "signing" flag is not among the usage flags in the GPG key that made the signature. |
  | `gpgverify_error` | There was an error communicating with the signature verification service. |
  | `gpgverify_unavailable` | The signature verification service is currently unavailable. |
  | `unsigned` | The object does not include a signature. |
  | `unknown_signature_type` | A non-PGP signature was found in the commit. |
  | `no_user` | No user was associated with the `committer` email address in the commit. |
  | `unverified_email` | The `committer` email address in the commit was associated with a user, but the email address is not verified on their account. |
  | `bad_email` | The `committer` email address in the commit is not included in the identities of the PGP key that made the signature. |
  | `unknown_key` | The key that made the signature has not been registered with any user's account. |
  | `malformed_signature` | There was an error parsing the signature. |
  | `invalid` | The signature could not be cryptographically verified using the key whose key-id was found in the signature. |
  | `valid` | None of the above errors applied, so the signature is considered to be verified. |

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/tags#create-a-tag-object)

  """
  @spec create_tag(String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.Git.Tag.t()} | {:error, GitHub.Error.t()}
  def create_tag(owner, repo, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, body: body],
      call: {GitHub.Git, :create_tag},
      url: "/repos/#{owner}/#{repo}/git/tags",
      body: body,
      method: :post,
      request: [{"application/json", :map}],
      response: [
        {201, {GitHub.Git.Tag, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Create a tree

  The tree creation API accepts nested entries. If you specify both a tree and a nested path modifying that tree, this endpoint will overwrite the contents of the tree with the new path contents, and create a new tree structure.

  If you use this endpoint to add, delete, or modify the file contents in a tree, you will need to commit the tree and then update a branch to point to the commit. For more information see "[Create a commit](https://docs.github.com/rest/git/commits#create-a-commit)" and "[Update a reference](https://docs.github.com/rest/git/refs#update-a-reference)."

  Returns an error if you try to delete a file that does not exist.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/trees#create-a-tree)

  """
  @spec create_tree(String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.Git.Tree.t()} | {:error, GitHub.Error.t()}
  def create_tree(owner, repo, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, body: body],
      call: {GitHub.Git, :create_tree},
      url: "/repos/#{owner}/#{repo}/git/trees",
      body: body,
      method: :post,
      request: [{"application/json", :map}],
      response: [
        {201, {GitHub.Git.Tree, :t}},
        {403, {GitHub.BasicError, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Delete a reference

  Deletes the provided reference.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/refs#delete-a-reference)

  """
  @spec delete_ref(String.t(), String.t(), String.t(), keyword) ::
          :ok | {:error, GitHub.Error.t()}
  def delete_ref(owner, repo, ref, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, ref: ref],
      call: {GitHub.Git, :delete_ref},
      url: "/repos/#{owner}/#{repo}/git/refs/#{ref}",
      method: :delete,
      response: [
        {204, :null},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Get a blob

  The `content` in the response will always be Base64 encoded.

  This endpoint supports the following custom media types. For more information, see "[Media types](https://docs.github.com/rest/using-the-rest-api/getting-started-with-the-rest-api#media-types)."

  - **`application/vnd.github.raw+json`**: Returns the raw blob data.
  - **`application/vnd.github+json`**: Returns a JSON representation of the blob with `content` as a base64 encoded string. This is the default if no media type is specified.

  **Note** This endpoint supports blobs up to 100 megabytes in size.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/blobs#get-a-blob)

  """
  @spec get_blob(String.t(), String.t(), String.t(), keyword) ::
          {:ok, GitHub.Blob.t()} | {:error, GitHub.Error.t()}
  def get_blob(owner, repo, file_sha, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, file_sha: file_sha],
      call: {GitHub.Git, :get_blob},
      url: "/repos/#{owner}/#{repo}/git/blobs/#{file_sha}",
      method: :get,
      response: [
        {200, {GitHub.Blob, :t}},
        {403, {GitHub.BasicError, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Get a commit object

  Gets a Git [commit object](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects).

  To get the contents of a commit, see "[Get a commit](https://docs.github.com/rest/commits/commits#get-a-commit)."

  **Signature verification object**

  The response will include a `verification` object that describes the result of verifying the commit's signature. The following fields are included in the `verification` object:

  | Name | Type | Description |
  | ---- | ---- | ----------- |
  | `verified` | `boolean` | Indicates whether GitHub considers the signature in this commit to be verified. |
  | `reason` | `string` | The reason for verified value. Possible values and their meanings are enumerated in the table below. |
  | `signature` | `string` | The signature that was extracted from the commit. |
  | `payload` | `string` | The value that was signed. |

  These are the possible values for `reason` in the `verification` object:

  | Value | Description |
  | ----- | ----------- |
  | `expired_key` | The key that made the signature is expired. |
  | `not_signing_key` | The "signing" flag is not among the usage flags in the GPG key that made the signature. |
  | `gpgverify_error` | There was an error communicating with the signature verification service. |
  | `gpgverify_unavailable` | The signature verification service is currently unavailable. |
  | `unsigned` | The object does not include a signature. |
  | `unknown_signature_type` | A non-PGP signature was found in the commit. |
  | `no_user` | No user was associated with the `committer` email address in the commit. |
  | `unverified_email` | The `committer` email address in the commit was associated with a user, but the email address is not verified on their account. |
  | `bad_email` | The `committer` email address in the commit is not included in the identities of the PGP key that made the signature. |
  | `unknown_key` | The key that made the signature has not been registered with any user's account. |
  | `malformed_signature` | There was an error parsing the signature. |
  | `invalid` | The signature could not be cryptographically verified using the key whose key-id was found in the signature. |
  | `valid` | None of the above errors applied, so the signature is considered to be verified. |

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/commits#get-a-commit-object)

  """
  @spec get_commit(String.t(), String.t(), String.t(), keyword) ::
          {:ok, GitHub.Git.Commit.t()} | {:error, GitHub.Error.t()}
  def get_commit(owner, repo, commit_sha, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, commit_sha: commit_sha],
      call: {GitHub.Git, :get_commit},
      url: "/repos/#{owner}/#{repo}/git/commits/#{commit_sha}",
      method: :get,
      response: [
        {200, {GitHub.Git.Commit, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Get a reference

  Returns a single reference from your Git database. The `:ref` in the URL must be formatted as `heads/<branch name>` for branches and `tags/<tag name>` for tags. If the `:ref` doesn't match an existing ref, a `404` is returned.

  **Note:** You need to explicitly [request a pull request](https://docs.github.com/rest/pulls/pulls#get-a-pull-request) to trigger a test merge commit, which checks the mergeability of pull requests. For more information, see "[Checking mergeability of pull requests](https://docs.github.com/rest/guides/getting-started-with-the-git-database-api#checking-mergeability-of-pull-requests)".

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/refs#get-a-reference)

  """
  @spec get_ref(String.t(), String.t(), String.t(), keyword) ::
          {:ok, GitHub.Git.Ref.t()} | {:error, GitHub.Error.t()}
  def get_ref(owner, repo, ref, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, ref: ref],
      call: {GitHub.Git, :get_ref},
      url: "/repos/#{owner}/#{repo}/git/ref/#{ref}",
      method: :get,
      response: [
        {200, {GitHub.Git.Ref, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Get a tag

  **Signature verification object**

  The response will include a `verification` object that describes the result of verifying the commit's signature. The following fields are included in the `verification` object:

  | Name | Type | Description |
  | ---- | ---- | ----------- |
  | `verified` | `boolean` | Indicates whether GitHub considers the signature in this commit to be verified. |
  | `reason` | `string` | The reason for verified value. Possible values and their meanings are enumerated in table below. |
  | `signature` | `string` | The signature that was extracted from the commit. |
  | `payload` | `string` | The value that was signed. |

  These are the possible values for `reason` in the `verification` object:

  | Value | Description |
  | ----- | ----------- |
  | `expired_key` | The key that made the signature is expired. |
  | `not_signing_key` | The "signing" flag is not among the usage flags in the GPG key that made the signature. |
  | `gpgverify_error` | There was an error communicating with the signature verification service. |
  | `gpgverify_unavailable` | The signature verification service is currently unavailable. |
  | `unsigned` | The object does not include a signature. |
  | `unknown_signature_type` | A non-PGP signature was found in the commit. |
  | `no_user` | No user was associated with the `committer` email address in the commit. |
  | `unverified_email` | The `committer` email address in the commit was associated with a user, but the email address is not verified on their account. |
  | `bad_email` | The `committer` email address in the commit is not included in the identities of the PGP key that made the signature. |
  | `unknown_key` | The key that made the signature has not been registered with any user's account. |
  | `malformed_signature` | There was an error parsing the signature. |
  | `invalid` | The signature could not be cryptographically verified using the key whose key-id was found in the signature. |
  | `valid` | None of the above errors applied, so the signature is considered to be verified. |

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/tags#get-a-tag)

  """
  @spec get_tag(String.t(), String.t(), String.t(), keyword) ::
          {:ok, GitHub.Git.Tag.t()} | {:error, GitHub.Error.t()}
  def get_tag(owner, repo, tag_sha, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, tag_sha: tag_sha],
      call: {GitHub.Git, :get_tag},
      url: "/repos/#{owner}/#{repo}/git/tags/#{tag_sha}",
      method: :get,
      response: [
        {200, {GitHub.Git.Tag, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  Get a tree

  Returns a single tree using the SHA1 value or ref name for that tree.

  If `truncated` is `true` in the response then the number of items in the `tree` array exceeded our maximum limit. If you need to fetch more items, use the non-recursive method of fetching trees, and fetch one sub-tree at a time.


  **Note**: The limit for the `tree` array is 100,000 entries with a maximum size of 7 MB when using the `recursive` parameter.

  ## Options

    * `recursive`: Setting this parameter to any value returns the objects or subtrees referenced by the tree specified in `:tree_sha`. For example, setting `recursive` to any of the following will enable returning objects or subtrees: `0`, `1`, `"true"`, and `"false"`. Omit this parameter to prevent recursively returning objects or subtrees.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/trees#get-a-tree)

  """
  @spec get_tree(String.t(), String.t(), String.t(), keyword) ::
          {:ok, GitHub.Git.Tree.t()} | {:error, GitHub.Error.t()}
  def get_tree(owner, repo, tree_sha, opts \\ []) do
    client = opts[:client] || @default_client
    query = Keyword.take(opts, [:recursive])

    client.request(%{
      args: [owner: owner, repo: repo, tree_sha: tree_sha],
      call: {GitHub.Git, :get_tree},
      url: "/repos/#{owner}/#{repo}/git/trees/#{tree_sha}",
      method: :get,
      query: query,
      response: [
        {200, {GitHub.Git.Tree, :t}},
        {404, {GitHub.BasicError, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end

  @doc """
  List matching references

  Returns an array of references from your Git database that match the supplied name. The `:ref` in the URL must be formatted as `heads/<branch name>` for branches and `tags/<tag name>` for tags. If the `:ref` doesn't exist in the repository, but existing refs start with `:ref`, they will be returned as an array.

  When you use this endpoint without providing a `:ref`, it will return an array of all the references from your Git database, including notes and stashes if they exist on the server. Anything in the namespace is returned, not just `heads` and `tags`.

  **Note:** You need to explicitly [request a pull request](https://docs.github.com/rest/pulls/pulls#get-a-pull-request) to trigger a test merge commit, which checks the mergeability of pull requests. For more information, see "[Checking mergeability of pull requests](https://docs.github.com/rest/guides/getting-started-with-the-git-database-api#checking-mergeability-of-pull-requests)".

  If you request matching references for a branch named `feature` but the branch `feature` doesn't exist, the response can still include other matching head refs that start with the word `feature`, such as `featureA` and `featureB`.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/refs#list-matching-references)

  """
  @spec list_matching_refs(String.t(), String.t(), String.t(), keyword) ::
          {:ok, [GitHub.Git.Ref.t()]} | {:error, GitHub.Error.t()}
  def list_matching_refs(owner, repo, ref, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, ref: ref],
      call: {GitHub.Git, :list_matching_refs},
      url: "/repos/#{owner}/#{repo}/git/matching-refs/#{ref}",
      method: :get,
      response: [{200, [{GitHub.Git.Ref, :t}]}, {409, {GitHub.BasicError, :t}}],
      opts: opts
    })
  end

  @doc """
  Update a reference

  Updates the provided reference to point to a new SHA. For more information, see "[Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References)" in the Git documentation.

  ## Resources

    * [API method documentation](https://docs.github.com/rest/git/refs#update-a-reference)

  """
  @spec update_ref(String.t(), String.t(), String.t(), map, keyword) ::
          {:ok, GitHub.Git.Ref.t()} | {:error, GitHub.Error.t()}
  def update_ref(owner, repo, ref, body, opts \\ []) do
    client = opts[:client] || @default_client

    client.request(%{
      args: [owner: owner, repo: repo, ref: ref, body: body],
      call: {GitHub.Git, :update_ref},
      url: "/repos/#{owner}/#{repo}/git/refs/#{ref}",
      body: body,
      method: :patch,
      request: [{"application/json", :map}],
      response: [
        {200, {GitHub.Git.Ref, :t}},
        {409, {GitHub.BasicError, :t}},
        {422, {GitHub.ValidationError, :t}}
      ],
      opts: opts
    })
  end
end