# GitHub.jl

[![Coverage Status](https://coveralls.io/repos/JuliaWeb/GitHub.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/JuliaWeb/GitHub.jl?branch=master)

GitHub.jl provides a Julia interface to the [GitHub API v3](https://developer.github.com/v3/). Using GitHub.jl, you can do things like:

- query for basic repository, organization, and user information
- programmatically take user-level actions (e.g. starring a repository, commenting on an issue, etc.)
- set up listeners that can detect and respond to repository events
- create and retrieve commit statuses (i.e. report CI pending/failure/success statuses to GitHub)

Here's a table of contents for this rather lengthy README:

[1. Response Types](#response-types)

[2. REST Methods](#rest-methods)

[3. Authentication](#authentication)

[4. Pagination](#pagination)

[5. Handling Webhook Events](#handling-webhook-events)

[6. GitHub Enterprise](#github-enterprise)

## Response Types

GitHub's JSON responses are parsed and returned to the caller as types of the form `G<:GitHub.GitHubType`. Here's some useful information about these types:

- All fields are `Union{Nothing, T}`.
- Field names generally match the corresponding field in GitHub's JSON representation (the exception is `"type"`, which has the corresponding field name `typ` to avoid the obvious language conflict).
- `GitHubType`s can be passed as arguments to API methods in place of (and in combination with) regular identifying properties. For example, `create_status(repo, commit)` could be called as:

   - `create_status(::GitHub.Repo, ::GitHub.Commit)`
   - `create_status(::GitHub.Repo, ::AbstractString)` where the second argument is the SHA
   - `create_status(::AbstractString, ::GitHub.Commit)` where the first argument is the full qualified repo name
   - `create_status(::AbstractString, ::AbstractString)` where the first argument is the repo name, and the second is the SHA

Here's a table that matches up the provided `GitHubType`s with their corresponding API documentation, as well as alternative identifying values:

| type              | alternative identifying property                                  | link(s) to documentation                                                                                                                                                                                      |
|-------------------|-------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Owner`           | login, e.g. `"octocat"`                                           | [organizations](https://developer.github.com/v3/orgs/), [users](https://developer.github.com/v3/users/)                                                                                                       |
| `Repo`            | full_name, e.g. `"JuliaWeb/GitHub.jl"`                            | [repositories](https://developer.github.com/v3/repos/)                                                                                                                                                        |
| `Commit`          | sha, e.g. `"d069993b320c57b2ba27336406f6ec3a9ae39375"`            | [repository commits](https://developer.github.com/v3/repos/commits/)                                                                                                                                          |
| `GitCommit`       | sha, e.g. `"d069993b320c57b2ba27336406f6ec3a9ae39375"`            | [raw git commits](https://developer.github.com/v3/git/commits/)                                                                                                                                               |
| `Branch`          | name, e.g. `master`                                               | [repository branches](https://developer.github.com/v3/repos/#get-branch)                                                                                                                                      |
| `Content`         | path, e.g. `"src/owners/owners.jl"`                               | [repository contents](https://developer.github.com/v3/repos/contents/)                                                                                                                                        |
| `Comment`         | id, e.g. `162224613`                                              | [commit comments](https://developer.github.com/v3/repos/comments/), [issue comments](https://developer.github.com/v3/issues/comments/), [PR review comments](https://developer.github.com/v3/pulls/comments/) |
| `Label`           | name, e.g. `bug`                                                  | [issue labels](https://docs.github.com/en/rest/reference/issues#labels)                                                                                                                                       |
| `Status`          | id, e.g. `366961773`                                              | [commit statuses](https://developer.github.com/v3/repos/statuses/)                                                                                                                                            |
| `PullRequest`     | number, e.g. `44`                                                 | [pull requests](https://developer.github.com/v3/pulls/)                                                                                                                                                       |
| `PullRequestFile` | filename, e.g. `file1.txt`                                        | [pull request files](https://docs.github.com/en/rest/reference/pulls#list-pull-requests-files)                                                                                                                |
| `Issue`           | number, e.g. `31`                                                 | [issues](https://developer.github.com/v3/issues/)                                                                                                                                                             |
| `Team`            | id, e.g. `1`                                                      | [teams](https://developer.github.com/v3/orgs/teams)                                                                                                                                                           |
| `Gist`            | id, e.g. `0bace7cc774df4b3a4b0ee9aaa271ef6`                       | [gists](https://developer.github.com/v3/gists)                                                                                                                                                                |
| `Review`          | id, e.g. `1`                                                      | [reviews](https://developer.github.com/v3/pulls/reviews/)                                                                                                                                                     |
| `Blob`            | sha, e.g. `"95c8d1aa2a7b1e6d672e15b67e0df4abbe57dcbe"`            | [raw git blobs](https://developer.github.com/v3/git/blobs/)                                                                                                                                                   |
| `Tree`            | sha, e.g. `"78e524d5e979e326a7c144ce195bf94ca9b04fa0"`            | [raw git trees](https://developer.github.com/v3/git/trees/)                                                                                                                                                   |
| `Tag`             | tag name, e.g. `v1.0`                                             | [git tags](https://docs.github.com/en/rest/reference/git#tags)                                                                                                                                                |
| `References`      | reference name, e.g. `heads/master` (note: omits leading `refs/`) | [references](https://developer.github.com/v3/git/refs/)                                                                                                                                                       |
| `Secrets`         | secret name, e.g. `TAGBOT_SECRET`                                 | [secrets](https://developer.github.com/v3/actions/secrets/)                                                                                                                                                   |
| `DeployKeys`      | id, e.g., 12345                                                   | [deploy keys](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#deploy-keys)                                                                                                               |


You can inspect which fields are available for a type `G<:GitHubType` by calling `fieldnames(G)`.

## REST Methods

GitHub.jl implements a bunch of methods that make REST requests to GitHub's API. The below sections list these methods (note that a return type of `Tuple{Vector{T}, Dict}` means the result is [paginated](#pagination)).

These methods all accept keyword arguments which control how the request is made to GitHub:

- `max_retries::Int=5`: how many retries to attempt in requesting the resources. Retries are only made for idempotent requests ("GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE") and delays respect GitHub [rate limit headers](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#checking-the-status-of-your-rate-limit).
- `verbose::Bool=true`: whether or not to log retries as Info level logs
- `max_sleep_seconds::Real=60*20`: if GitHub.jl intends to sleep for longer than `max_sleep_seconds` before retrying, e.g. due to rate limit headers from GitHub, throws an `RetryDelayException` instead. 

#### Users and Organizations

| method                          | return type                  | documentation                                                                                                                                                                                               |
|---------------------------------|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `whoami()`                      | `Owner`                      | get currently authenticated user as a [user](https://developer.github.com/v3/users/#get-the-authenticated-user)                                                                                             |
| `owner(owner[, isorg = false])` | `Owner`                      | get `owner` as a [user](https://developer.github.com/v3/users/#get-a-single-user) or [organization](https://developer.github.com/v3/orgs/#get-an-organization)                                              |
| `orgs(owner)`                   | `Tuple{Vector{Owner}, Dict}` | [get the `owner`'s organizations](https://developer.github.com/v3/orgs/#list-user-organizations)                                                                                                            |
| `followers(owner)`              | `Tuple{Vector{Owner}, Dict}` | [get the `owner`'s followers](https://developer.github.com/v3/users/followers/#list-followers-of-a-user)                                                                                                    |
| `following(owner)`              | `Tuple{Vector{Owner}, Dict}` | [get the users followed by `owner`](https://developer.github.com/v3/users/followers/#list-users-followed-by-another-user)                                                                                   |
| `repos(owner[, isorg = false])` | `Tuple{Vector{Repo}, Dict}`  | [get the `owner`'s repositories](https://developer.github.com/v3/repos/#list-user-repositories)/[get an organization's repositories](https://developer.github.com/v3/repos/#list-organization-repositories) |
| `teams(owner)`                  | `Tuple{Vector{Team}, Dict}`  | [get the `organizations`'s teams repositories](https://developer.github.com/v3/orgs/teams/#list-teams)                                                                                                      |
| `sshkeys(owner)`                | `Tuple{Vector{Dict}, Dict}`  | [get the `owner`'s public ssh keys](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user)                                                                                                |
| `gpgkeys(owner)`                | `Tuple{Vector{Dict}, Dict}`  | [get the `owner`'s public gpg keys](https://developer.github.com/v3/users/gpg_keys/#list-gpg-keys-for-a-user)                                                                                               |


#### Teams
| method               | return type                  | documentation                                                                                            |
|----------------------|------------------------------|----------------------------------------------------------------------------------------------------------|
| `members(team)`      | `Tuple{Vector{Owner}, Dict}` | get team members as [users](https://developer.github.com/v3/orgs/teams/#get-team-membership)             |
| `repos(owner, team)` | `Tuple{Vector{Repo}, Dict}`  | get team repositories as [users](https://docs.github.com/en/rest/reference/teams#list-team-repositories) |


#### Repositories

| method                                                   | return type                    | documentation                                                                                                                                   |
|----------------------------------------------------------|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `repo(repo)`                                             | `Repo`                         | [get `repo`](https://developer.github.com/v3/repos/#get)                                                                                        |
| `create_repo(owner, name)`                               | `Repo`                         | [create a repository of the given `name` in the given  `owner`'s account](https://developer.github.com/v3/repos/#create)                        |
| `create_fork(repo)`                                      | `Repo`                         | [create a fork of `repo`](https://developer.github.com/v3/repos/forks/#create-a-fork)                                                           |
| `forks(repo)`                                            | `Tuple{Vector{Repo}, Dict}`    | [get `repo`'s forks](https://developer.github.com/v3/repos/forks/#list-forks)                                                                   |
| `contributors(repo)`                                     | `Dict`                         | [get `repo`'s contributors](https://developer.github.com/v3/repos/#list-contributors)                                                           |
| `collaborators(repo)`                                    | `Tuple{Vector{Owner}, Dict}`   | [get `repo`'s collaborators](https://developer.github.com/v3/repos/collaborators/#list)                                                         |
| `iscollaborator(repo, user)`                             | `Bool`                         | [check if `user` is a collaborator on `repo`](https://developer.github.com/v3/repos/collaborators/#get)                                         |
| `add_collaborator(repo, user)`                           | `HTTP.Response`                | [add `user` as a collaborator to `repo`](https://developer.github.com/v3/repos/collaborators/#add-collaborator)                                 |
| `remove_collaborator(repo, user)`                        | `HTTP.Response`                | [remove `user` as a collaborator from `repo`](https://developer.github.com/v3/repos/collaborators/#remove-collaborator)                         |
| `collaborator_permission(repo, user)`                    | `HTTP.Response`                | [get the `repo` permission of a collaborator](https://developer.github.com/v3/repos/collaborators/#get-repository-permissions-for-a-user)       |
| `stats(repo, stat[, attempts = 3])`                      | `HTTP.Response`                | [get information on `stat` (e.g. "contributors", "code_frequency", "commit_activity", etc.)](https://developer.github.com/v3/repos/statistics/) |
| `topics(repo)`                                           | `Vector{String}`               | [get the list of topics of a repository.)](https://docs.github.com/en/rest/repos/repos#get-all-repository-topics)                               |
| `set_topics(repo, topics)`                               | `Vector{String}`               | [set the list of topics of a repository.)](https://docs.github.com/en/rest/repos/repos#replace-all-repository-topics)                           |
| `commit(repo, sha)`                                      | `Commit`                       | [get the commit specified by `sha`](https://developer.github.com/v3/repos/commits/#get-a-single-commit)                                         |
| `commits(repo)`                                          | `Tuple{Vector{Commit}, Dict}`  | [get `repo`'s commits](https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository)                                             |
| `commits(repo, pr)`                                      | `Tuple{Vector{Commit}, Dict}`  | [get `pr`'s commits for `repo`](https://docs.github.com/en/rest/reference/pulls#list-commits-on-a-pull-request)                                 |
| `compare(repo, base, head)`                              | `Comparison`                   | [compare `repo`'s commits](https://docs.github.com/en/rest/commits/commits#compare-two-commits)                                                 |
| `branch(repo, branch)`                                   | `Branch`                       | [get the branch specified by `branch`](https://developer.github.com/v3/repos/#get-branch)                                                       |
| `branches(repo)`                                         | `Tuple{Vector{Branch}, Dict}`  | [get `repo`'s branches](https://developer.github.com/v3/repos/#list-branches)                                                                   |
| `file(repo, path)`                                       | `Content`                      | [get the file specified by `path`](https://developer.github.com/v3/repos/contents/#get-contents)                                                |
| `directory(repo, path)`                                  | `Tuple{Vector{Content}, Dict}` | [get the contents of the directory specified by `path`](https://developer.github.com/v3/repos/contents/#get-contents)                           |
| `create_file(repo, path)`                                | `Dict`                         | [create a file at `path` in `repo`](https://developer.github.com/v3/repos/contents/#create-a-file)                                              |
| `update_file(repo, path)`                                | `Dict`                         | [update a file at `path` in `repo`](https://developer.github.com/v3/repos/contents/#update-a-file)                                              |
| `delete_file(repo, path)`                                | `Dict`                         | [delete a file at `path` in `repo`](https://developer.github.com/v3/repos/contents/#delete-a-file)                                              |
| `permalink(content::Content, commit)`                    | `URIs.URI`                     | [get a permalink for `content` at the SHA specified by `commit`](https://help.github.com/articles/getting-permanent-links-to-files/)            |
| `readme(repo)`                                           | `Content`                      | [get `repo`'s README](https://developer.github.com/v3/repos/contents/#get-the-readme)                                                           |
| `create_status(repo, sha)`                               | `Status`                       | [create a status for the commit specified by `sha`](https://developer.github.com/v3/repos/statuses/#create-a-status)                            |
| `statuses(repo, ref)`                                    | `Tuple{Vector{Status}, Dict}`  | [get the statuses posted to `ref`](https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref)                            |
| `status(repo, ref)`                                      | `Status`                       | [get the combined status for `ref`](https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref)                 |
| `create_webhook(owner, repo)`                            | `Webhook`                      | [create a webhook for `repo`](https://developer.github.com/v3/repos/hooks/#create-a-hook)                                                       |
| `secrets(repo; auth)`                                    | `Tuple{Vector{Secret}, Dict}`  | [get names of all secrets for `repo`](https://developer.github.com/v3/actions/secrets/#list-repository-secrets)                                 |
| `secret(repo, name; auth)`                               | `Secret`                       | [get status of secret in `repo`](https://developer.github.com/v3/actions/secrets/#get-a-repository-secret)                                      |
| `create_secret(repo, name; value, auth)`                 | `nothing`                      | [create a secret for `repo`](https://developer.github.com/v3/actions/secrets/#create-or-update-a-repository-secret)                             |
| `delete_secret(repo, name; auth)`                        | `nothing`                      | [delete a secret for `repo`](https://developer.github.com/v3/actions/secrets/#delete-a-repository-secret)                                       |
| `deploykeys(repo; auth)`                                 | `Tuple{Vector{DeployKey}, Dict}`| [get all deploy keys for `repo`](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#list-deploy-keys)                        |
| `deploykey(repo, key; auth)`                             | `DeployKey`                    | [get the deploy `key` in `repo`](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-a-deploy-key)                         |
| `create_deploykey(repo; params=..., auth)`               | `nothing`                      | [create a deploy key for `repo`](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#create-a-deploy-key)                      |
| `delete_deploykey(repo, key; auth)`                      | `nothing`                      | [delete a deploy key for `repo`](https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#delete-a-deploy-key)                      |
| `releases(repo, key; auth)`                      | `nothing`                      | [get the releases for `repo`](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases)                      |

#### Pull Requests and Issues

| method                          | return type                             | documentation                                                                                                     |
|---------------------------------|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `pull_request(repo, pr)`        | `PullRequest`                           | [get the pull request specified by `pr`](https://developer.github.com/v3/pulls/#get-a-single-pull-request)        |
| `pull_requests(repo)`           | `Tuple{Vector{PullRequest}, Dict}`      | [get `repo`'s pull requests](https://developer.github.com/v3/pulls/#list-pull-requests)                           |
| `pull_request_files(repo, pr)`  | `Tuple{Vector{PullRequestFiles}, Dict}` | [get this `repo`'s `pr`'s file changes](https://docs.github.com/en/rest/reference/pulls#list-pull-requests-files) |
| `create_pull_request(repo)`     | `PullRequest`                           | [create pull request in `repo`](https://developer.github.com/v3/pulls/#create-a-pull-request)                     |
| `update_pull_request(repo, pr)` | `PullRequest`                           | [update the given `pr` in `repo`](https://developer.github.com/v3/pulls/#update-a-pull-request)                   |
| `close_pull_request(repo, pr)`  | `PullRequest`                           | [close the given `pr` in `repo`](https://developer.github.com/v3/pulls/#update-a-pull-request)                    |
| `issue(repo, issue)`            | `Issue`                                 | [get the issue specified by `issue`](https://developer.github.com/v3/issues/#get-a-single-issue)                  |
| `issues(repo)`                  | `Tuple{Vector{Issue}, Dict}`            | [get `repo`'s issues](https://developer.github.com/v3/issues/#list-issues-for-a-repository)                       |
| `create_issue(repo)`            | `Issue`                                 | [create an issue in `repo`](https://developer.github.com/v3/issues/#create-an-issue)                              |
| `edit_issue(repo, issue)`       | `Issue`                                 | [edit `issue` in `repo`](https://developer.github.com/v3/issues/#edit-an-issue)                                   |
| `reviews(repo, pr)`             | `Tuple{Vector{PullRequest}, Dict}`      | [get a `pr`'s reviews](https://developer.github.com/v3/pulls/reviews/#list-reviews-on-a-pull-request)             |
| `dismiss_review(repo, review)`  | `HTTP.Response`                         | [dismiss `review` in `repo`](https://developer.github.com/v3/pulls/reviews/#dismiss-a-pull-request-review)        |

#### Comments

| method                                   | return type                    | documentation                                                                                                                                                        |
|------------------------------------------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `comment(repo, comment, :issue)`         | `Comment`                      | [get an issue `comment` from `repo`](https://developer.github.com/v3/issues/comments/#get-a-single-comment)                                                          |
| `comment(repo, comment, :pr)`            | `Comment`                      | [get a PR `comment` from `repo`](https://developer.github.com/v3/issues/comments/#get-a-single-comment)                                                              |
| `comment(repo, comment, :review)`        | `Comment`                      | [get an review `comment` from `repo`](https://developer.github.com/v3/pulls/comments/#get-a-single-comment)                                                          |
| `comment(repo, comment, :commit)`        | `Comment`                      | [get a commit `comment` from `repo`](https://developer.github.com/v3/repos/comments/#get-a-single-commit-comment)                                                    |
| `comments(repo, issue, :issue)`          | `Tuple{Vector{Comment}, Dict}` | [get the comments on `issue` in `repo`](https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue)                                                  |
| `comments(repo, pr, :pr)`                | `Tuple{Vector{Comment}, Dict}` | [get the comments on `pr` in `repo`](https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue)                                                     |
| `comments(repo, pr, :review)`            | `Tuple{Vector{Comment}, Dict}` | [get the review comments on `pr` in `repo`](https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request)                                         |
| `comments(repo, commit, :commit)`        | `Tuple{Vector{Comment}, Dict}` | [get the comments on `commit` in `repo`](https://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit)                                          |
| `create_comment(repo, issue, :issue)`    | `Comment`                      | [create a comment on `issue` in `repo`](https://developer.github.com/v3/issues/comments/#create-a-comment)                                                           |
| `create_comment(repo, pr, :pr)`          | `Comment`                      | [create a comment on `pr` in `repo`](https://developer.github.com/v3/issues/comments/#create-a-comment)                                                              |
| `create_comment(repo, pr, :review)`      | `Comment`                      | [create a review comment on `pr` in `repo`](https://developer.github.com/v3/pulls/comments/#create-a-comment)                                                        |
| `create_comment(repo, commit, :commit)`  | `Comment`                      | [create a comment on `commit` in `repo`](https://developer.github.com/v3/repos/comments/#create-a-commit-comment)                                                    |
| `edit_comment(repo, comment, :issue)`    | `Comment`                      | [edit the issue `comment` in `repo`](https://developer.github.com/v3/issues/comments/#edit-a-comment)                                                                |
| `edit_comment(repo, comment, :pr)`       | `Comment`                      | [edit the PR `comment` in `repo`](https://developer.github.com/v3/issues/comments/#edit-a-comment)                                                                   |
| `edit_comment(repo, comment, :review)`   | `Comment`                      | [edit the review `comment` in `repo`](https://developer.github.com/v3/pulls/comments/#edit-a-comment)                                                                |
| `edit_comment(repo, comment, :commit)`   | `Comment`                      | [edit the commit `comment` in `repo`](https://developer.github.com/v3/repos/comments/#update-a-commit-comment)                                                       |
| `delete_comment(repo, comment, :issue)`  | `HTTP.Response`                | [delete the issue `comment` from `repo`](https://developer.github.com/v3/issues/comments/#delete-a-comment)                                                          |
| `delete_comment(repo, comment, :pr)`     | `HTTP.Response`                | [delete the PR `comment` from `repo`](https://developer.github.com/v3/issues/comments/#delete-a-comment)                                                             |
| `delete_comment(repo, comment, :review)` | `HTTP.Response`                | [delete the review `comment` from `repo`](https://developer.github.com/v3/pulls/comments/#delete-a-comment)                                                          |
| `delete_comment(repo, comment, :commit)` | `HTTP.Response`                | [delete the commit`comment` from `repo`](https://developer.github.com/v3/repos/comments/#delete-a-commit-comment)                                                    |
| `delete_comment(repo, comment, :commit)` | `HTTP.Response`                | [delete the commit`comment` from `repo`](https://developer.github.com/v3/repos/comments/#delete-a-commit-comment)                                                    |
| `reply_to(repo, review, comment, body)`  | `HTTP.Response`                | [reply to the `comment` (of `review` in `repo`) creating a new comment with the specified `body`](https://developer.github.com/v3/pulls/comments/#alternative-input) |

#### Labels


| method                                   | return type                    | documentation                                                                                                                                                        |
|------------------------------------------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `labels(repo,issue)`                     | `Vector{Label}`                | [list labels from `issue`](https://docs.github.com/en/rest/reference/issues#list-labels-for-an-issue)                                                                |
| `add_labels(repo, issue, labels)`        | `Vector{Label}`                | [add labels to an `issue`](https://docs.github.com/en/rest/reference/issues#add-labels-to-an-issue)                                                                  |
| `set_labels(repo, issue, labels)`        | `Vector{Label}`                | [set the labels for an `issue`](https://docs.github.com/en/rest/reference/issues#set-labels-for-an-issue)                                                            |
| `remove_all_labels(repo, issue)`         | `HTTP.Response`                | [remove all labels from an `issue`](https://docs.github.com/en/rest/reference/issues#remove-all-labels-from-an-issue)                                                |
| `remove_label(repo, issue, label)`       | `HTTP.Response`                | [remove a label from an `issue`](https://docs.github.com/en/rest/reference/issues#remove-a-label-from-an-issue)                                                      |

#### Social Activity

| method             | return type                  | documentation                                                                                                            |
|--------------------|------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| `star(repo)`       | `HTTP.Response`              | [star `repo`](https://developer.github.com/v3/activity/starring/#star-a-repository)                                      |
| `unstar(repo)`     | `HTTP.Response`              | [unstar `repo`](https://developer.github.com/v3/activity/starring/#unstar-a-repository)                                  |
| `stargazers(repo)` | `Tuple{Vector{Owner}, Dict}` | [get `repo`'s stargazers](https://developer.github.com/v3/activity/starring/#list-stargazers)                            |
| `starred(user)`    | `Tuple{Vector{Repo}, Dict}`  | [get repositories starred by `user`](https://developer.github.com/v3/activity/starring/#list-repositories-being-starred) |
| `watchers(repo)`   | `Tuple{Vector{Owner}, Dict}` | [get `repo`'s watchers](https://developer.github.com/v3/activity/watching/#list-watchers)                                |
| `watched(user)`    | `Tuple{Vector{Repo}, Dict}`  | [get repositories watched by `user`](https://developer.github.com/v3/activity/watching/#list-repositories-being-watched) |
| `watch(repo)`      | `HTTP.Response`              | [watch `repo`](https://developer.github.com/v3/activity/watching/#set-a-repository-subscription)                         |
| `unwatch(repo)`    | `HTTP.Response`              | [unwatch `repo`](https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription)                    |

#### Gists

| method                   | return type                 | documentation                                                                                                             |
|--------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------|
| `gist(id)`               | `Gist`                      | [get the gist specified by `id`](https://developer.github.com/v3/gists/#get-a-single-gist)                                |
| `gist(id, revision)`     | `Gist`                      | [get the gist specified by `id` and `revision`](https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist) |
| `gists()`                | `Tuple{Vector{Gist}, Dict}` | [get all public gists](https://developer.github.com/v3/gists/#list-all-public-gists)                                      |
| `gists(owner)`           | `Tuple{Vector{Gist}, Dict}` | [get all gists for `owner`](https://developer.github.com/v3/gists/#list-a-users-gists)                                    |
| `create_gist()`          | `Gist`                      | [create a gist](https://developer.github.com/v3/gists/#create-a-gist)                                                     |
| `edit_gist(gist)`        | `Gist`                      | [edit a gist](https://developer.github.com/v3/gists/#edit-a-gist)                                                         |
| `delete_gist(gist)`      | `HTTP.Response`             | [delete a gist](https://developer.github.com/v3/gists/#delete-a-gist)                                                     |
| `create_gist_fork(gist)` | `Gist`                      | [fork a gist](https://developer.github.com/v3/gists/#fork-a-gist)                                                         |
| `gist_forks(gist)`       | `Tuple{Vector{Gist}, Dict}` | [list the forks of a gist](https://developer.github.com/v3/gists/#list-gist-forks)                                        |
| `star_gist(gist)`        | `HTTP.Response`             | [star `gist`](https://developer.github.com/v3/gists/#star-a-gist)                                                         |
| `starred_gists()`        | `Tuple{Vector{Gist}, Dict}` | [get the starred `gist`s](https://developer.github.com/v3/gists/#list-starred-gists)                                      |
| `unstar_gist(gist)`      | `HTTP.Response`             | [unstar `gist`](https://developer.github.com/v3/gists/#unstar-a-gist)                                                     |

#### Git Data

| method                   | return type         | documentation                                                                                          |
|--------------------------|---------------------|--------------------------------------------------------------------------------------------------------|
| `blob(repo, sha)`        | `Blob`              | [Look up a blob in the `repo` by its SHA](https://developer.github.com/v3/git/blobs/#get-a-blob)       |
| `create_blob(repo)`      | `Blob`              | [Create a blob in the `repo`](https://developer.github.com/v3/git/blobs/#create-a-blob)                |
| `gitcommit(repo, sha)`   | `GitCommit`         | [Look up a commit in the `repo` by its SHA](https://developer.github.com/v3/git/commits/#get-a-commit) |
| `create_gitcommit(repo)` | `GitCommit`         | [Create a commit in the `repo`](https://developer.github.com/v3/git/commits/#create-a-commit)          |
| `tree(repo, sha)`        | `Tree`              | [Look up a tree in the `repo` by its SHA](https://developer.github.com/v3/git/trees/#get-a-tree)       |
| `create_tree(repo)`      | `Tree`              | [Create a tree in the `repo`](https://developer.github.com/v3/git/trees/create-a-tree)                 |
| `tag(repo, sha)`         | `Tag`               | [Look up a tag in the `repo` by its name](https://docs.github.com/en/rest/reference/git#get-a-tag)     |
| `create_tag(repo)`       | `Tag`               | [Create a tag in the `repo`](https://docs.github.com/en/rest/reference/git#create-a-tag-object)        |
| `reference(repo, name)`  | `Reference`         | [Look up a ref in the `repo` by its name](https://developer.github.com/v3/git/refs/#get-a-reference)   |
| `references(repo)`       | `Vector{Reference}` | [Get all `refs ` of the repo](https://developer.github.com/v3/git/refs/#get-all-references)            |
| `create_reference(repo)` | `Reference`         | [Create a reference in the `repo`](https://developer.github.com/v3/git/refs/#create-a-reference)       |
| `update_reference(repo)` | `Reference`         | [Update a reference in the `repo`](https://developer.github.com/v3/git/refs/#create-a-reference)       |
| `delete_reference(repo)` | `GitCommit`         | [Delete a the `repo`](https://developer.github.com/v3/git/refs/#delete-a-reference)                    |
| `tag(repo)`              | `Reference`         | [Update a reference in the `repo`](https://developer.github.com/v3/git/refs/#create-a-reference)       |
| `delete_reference(repo)` | `GitCommit`         | [Delete a the `repo`](https://developer.github.com/v3/git/refs/#delete-a-reference)                    |

#### GitHub Apps
| method                   | return type                 | documentation                                                                                                                                  |
|--------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
| `app(id)`                | `App`                       | [get the GitHub app with the specified `id`](https://developer.github.com/v3/apps/#get-a-single-github-app)                                    |
| `app(slug)`              | `App`                       | [get the GitHub app with the specified `slug`](https://developer.github.com/v3/apps/#get-a-single-github-app)                                  |
| `app(;auth=auth)`        | `App`                       | [get the GitHub app authenticated by the corresponding `auth`](https://developer.github.com/v3/apps/#get-the-authenticated-github-app)         |
| `installations(auth)`    | `Vector{Installation}`      | [get the installations for the GitHub app authenticated by the corresponding `auth`](https://developer.github.com/v3/apps/#find-installations) |
| `repos(i::Installation)` | `Tuple{Vector{Repo}, Dict}` | [get the active repositories for this installation](https://developer.github.com/v3/apps/#find-installations)                                  |

#### GitHub Check Runs
| method                                        | return type | documentation                                                                                               |
|-----------------------------------------------|-------------|-------------------------------------------------------------------------------------------------------------|
| `create_check_run(repo; params=...)`          | `CheckRun`  | [Create a new check run](https://developer.github.com/v3/checks/runs/#create-a-check-run)                   |
| `update_check_run(repo, id::Int; params=...)` | `CheckRun`  | [Update the check run with the given `id`](https://developer.github.com/v3/checks/runs/#update-a-check-run) |


#### Licenses
| method                            | return type       | documentation                                                                                                                                 |
| --------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `licenses(; params=...)`          | `Vector{License}` | [Get all commonly used licenses](https://docs.github.com/en/free-pro-team@latest/rest/reference/licenses#get-all-commonly-used-licenses)      |
| `license(license_id; params=...)` | `License`         | [Get a license](https://docs.github.com/en/free-pro-team@latest/rest/reference/licenses#get-a-license)                                        |
| `repo_license(repo; params=...)`  | `Content`         | [Get the license for a respository](https://docs.github.com/en/free-pro-team@latest/rest/reference/licenses#get-the-license-for-a-repository) |

#### Miscellaneous

| method                | return type | documentation                                                                                                |
|-----------------------|-------------|--------------------------------------------------------------------------------------------------------------|
| `rate_limit()`        | `Dict`      | [get your rate limit status](https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status) |
| `authenticate(token)` | `OAuth2`    | [validate `token` and return an authentication object](https://developer.github.com/v3/#authentication)      |

#### Keyword Arguments

All REST methods accept the following keyword arguments:

| keyword        | type                   | default value            | description                                                                                   |
|----------------|------------------------|--------------------------|-----------------------------------------------------------------------------------------------|
| `auth`         | `GitHub.Authorization` | `GitHub.AnonymousAuth()` | The request's authorization                                                                   |
| `params`       | `Dict`                 | `Dict()`                 | The request's query parameters                                                                |
| `headers`      | `Dict`                 | `Dict()`                 | The request's headers. Note that these headers will be mutated by GitHub.jl request methods.  |
| `handle_error` | `Bool`                 | `true`                   | If `true`, a Julia error will be thrown in the event that GitHub's response reports an error. |
| `page_limit`   | `Real`                 | `Inf`                    | The number of pages to return (only applies to paginated results, obviously)                  |

## Authentication

To authenticate your requests to GitHub, you'll need to generate an appropriate [access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). Then, you can do stuff like the following (this example assumes that you set an environmental variable `GITHUB_AUTH` containing the access token):

```julia
import GitHub
myauth = GitHub.authenticate(ENV["GITHUB_AUTH"]) # don't hardcode your access tokens!
GitHub.star("JuliaWeb/GitHub.jl"; auth = myauth)  # star the GitHub.jl repo as the user identified by myauth
```

As you can see, you can propagate the identity/permissions of the `myauth` token to GitHub.jl's methods by passing `auth = myauth` as a keyword argument.

Note that if authentication is not provided, they'll be subject to the restrictions GitHub imposes on unauthenticated requests (such as [stricter rate limiting](https://developer.github.com/v3/#rate-limiting))

### Authenticating as a GitHub app

GitHub apps (formerly called integrations) have [their own authentication format](https://developer.github.com/apps/building-integrations/setting-up-and-registering-github-apps/about-authentication-options-for-github-apps/)
based on [JSON Web Tokens](https://jwt.io/). When creating a GitHub app, you will be
prompted to download your app's private key. You can use this private key to
authenticate as a Github App using the `JWTAuth` type:
```
appauth = JWTAuth(1234, "privkey.pem") # Replace with your app id/privkey file
```

The following shows a complete example that opens an issue on every repository
on which your application gets installed:

```
listener = GitHub.EventListener() do event
    # On installation, open an issue on every repository we got installed in
    if event.kind == "installation"
        # Authenticate as the application
        appauth = GitHub.JWTAuth(1234, "privkey.pem")
        # Now, get permissions for this particular installation
        installation = Installation(event.payload["installation"])
        auth = create_access_token(installation, appauth)
        for repo in event.payload["repositories"]
            create_issue(GitHub.Repo(repo), auth=auth,
                params = Dict(
                    :title => "Hello World",
                    :body => "Thank you for installing me - I needed that"
            ))
        end
    end
    return HTTP.Response(200)
end
GitHub.run(listener, host=IPv4(0,0,0,0), port=8888)
```

## Pagination

GitHub will often [paginate](https://developer.github.com/v3/#pagination) results for requests that return multiple items. On the GitHub.jl side of things, it's pretty easy to see which methods return paginated results by referring to the [REST Methods documentation](#rest-methods); if a method returns a `Tuple{Vector{T}, Dict}`, that means its results are paginated.

Paginated methods return both the response values, and some pagination metadata. You can use the `per_page`/`page` query parameters and the `page_limit` keyword argument to configure result pagination.

For example, let's request a couple pages of GitHub.jl's PRs, and configure our result pagination to see how it works:

```julia
# show all PRs (both open and closed), and give me 3 items per page starting at page 2
julia> myparams = Dict("state" => "all", "per_page" => 3, "page" => 2);

julia> prs, page_data = pull_requests("JuliaWeb/GitHub.jl"; params = myparams, page_limit = 2);

julia> prs # 3 items per page * 2 page limit == 6 items, as expected
6-element Array{GitHub.PullRequest,1}:
 GitHub.PullRequest(44)
 GitHub.PullRequest(43)
 GitHub.PullRequest(42)
 GitHub.PullRequest(41)
 GitHub.PullRequest(39)
 GitHub.PullRequest(38)

julia> page_data
Dict{String,String} with 4 entries:
  "prev"  => "https://api.github.com/repositories/16635105/pulls?page=2&per_page=3&state=all"
  "next"  => "https://api.github.com/repositories/16635105/pulls?page=4&per_page=3&state=all"
  "first" => "https://api.github.com/repositories/16635105/pulls?page=1&per_page=3&state=all"
  "last"  => "https://api.github.com/repositories/16635105/pulls?page=7&per_page=3&state=all"
```

In the above, `prs` contains the results from page 2 and 3. We know this because we specified page 2 as our starting page (`"page" => 2`), and limited the response to 2 pages max (`page_limit = 2`). In addition, we know that exactly 2 pages were actually retrieved, since there are 6 items and we said each page should only contain 3 items (`"per_page" => 3`).

The values provided by `page_data` are the same values that are included in the [Link header](https://developer.github.com/v3/#link-header) of the last requested item. You can continue paginating by starting a new paginated request at one of these links using the `start_page` keyword argument:

```julia
# Continue paging, starting with `page_data["next"]`.
# Note that the `params` kwarg can't be used here because
# the link passed to `start_page` has its own parameters
julia> prs2, page_data2 = pull_requests("JuliaWeb/GitHub.jl"; page_limit = 2, start_page = page_data["next"]);

julia> prs2
6-element Array{GitHub.PullRequest,1}:
 GitHub.PullRequest(37)
 GitHub.PullRequest(34)
 GitHub.PullRequest(32)
 GitHub.PullRequest(30)
 GitHub.PullRequest(24)
 GitHub.PullRequest(22)

julia> page_data2
Dict{String,String} with 4 entries:
  "prev"  => "https://api.github.com/repositories/16635105/pulls?page=4&per_page=3&state=all"
  "next"  => "https://api.github.com/repositories/16635105/pulls?page=6&per_page=3&state=all"
  "first" => "https://api.github.com/repositories/16635105/pulls?page=1&per_page=3&state=all"
  "last"  => "https://api.github.com/repositories/16635105/pulls?page=7&per_page=3&state=all"
```

## Handling Webhook Events

GitHub.jl comes with configurable `EventListener` and `CommentListener` types that can be used as basic servers for parsing and responding to events delivered by [GitHub's repository Webhooks](https://developer.github.com/webhooks/).

#### `EventListener`

When an `EventListener` receives an event, it performs some basic validation and wraps the event payload (and some other data) in [a `WebhookEvent` type](https://github.com/JuliaWeb/GitHub.jl/blob/master/src/activity/events/events.jl). This `WebhookEvent` instance, along with the provided `Authorization`, is then fed to the server's handler function, which the user defines to determine the server's response behavior. The handler function is expected to return an `HTTP.Response` that is then sent back to GitHub.

The `EventListener` constructor takes the following keyword arguments:

- `auth`: GitHub authorization (usually with repo-level permissions).
- `secret`: A string used to verify the event source. If the event is from a GitHub Webhook, it's the Webhook's secret. If a secret is not provided, the server won't validate the secret signature of incoming requests.
- `repos`: A vector of `Repo`s (or fully qualified repository names) listing all acceptable repositories. All repositories are whitelisted by default.
- `events`: A vector of [event names](https://developer.github.com/webhooks/#events) listing all acceptable events (e.g. ["commit_comment", "pull_request"]). All events are whitelisted by default.
- `forwards`: A vector of `URIs.URI`s (or URI strings) to which any incoming requests should be forwarded (after being validated by the listener)

Here's an example that demonstrates how to construct and run an `EventListener` that does benchmarking on every commit and PR:

```julia
import GitHub
import URIs
# EventListener settings
myauth = GitHub.authenticate(ENV["GITHUB_AUTH"])
mysecret = ENV["MY_SECRET"]
myevents = ["pull_request", "push"]
myrepos = [GitHub.Repo("owner1/repo1"), "owner2/repo2"] # can be Repos or repo names
myforwards = [URIs.URI("http://myforward1.com"), "http://myforward2.com"] # can be URIs.URIs or URI strings

# Set up Status parameters
pending_params = Dict(
    "state" => "pending",
    "context" => "Benchmarker",
    "description" => "Running benchmarks..."
)

success_params = Dict(
    "state" => "success",
    "context" => "Benchmarker",
    "description" => "Benchmarks complete!"
)

error_params(err) = Dict(
    "state" => "error",
    "context" => "Benchmarker",
    "description" => "Error: $err"
)

# We can use Julia's `do` notation to set up the listener's handler function
listener = GitHub.EventListener(auth = myauth,
                                secret = mysecret,
                                repos = myrepos,
                                events = myevents,
                                forwards = myforwards) do event
    kind, payload, repo = event.kind, event.payload, event.repository

    if kind == "pull_request" && payload["action"] == "closed"
        return HTTP.Response(200)
    end

    if event.kind == "push"
        sha = event.payload["after"]
    elseif event.kind == "pull_request"
        sha = event.payload["pull_request"]["head"]["sha"]
    end

    GitHub.create_status(repo, sha; auth = myauth, params = pending_params)

    try
        # run_and_log_benchmarks isn't actually a defined function, but you get the point
        run_and_log_benchmarks(event, "\$(sha)-benchmarks.csv")
    catch err
        GitHub.create_status(repo, sha; auth = myauth, params = error_params(err))
        return HTTP.Response(500)
    end

    GitHub.create_status(repo, sha; auth = myauth, params = success_params)

    return HTTP.Response(200)
end

# Start the listener on localhost at port 8000
GitHub.run(listener, IPv4(127,0,0,1), 8000)
```

#### `CommentListener`

A `CommentListener` is a special kind of `EventListener` that allows users to pass data to the listener's handler function via commenting. This is useful for triggering events on repositories that require configuration settings.

A `CommentListener` automatically filters out all non-comment events, and then checks the body of each comment event against a trigger `Regex` supplied by the user. If a match is found in the comment, then the `CommentListener` calls its handler function, passing it the event and the corresponding `RegexMatch`.

The `CommentListener` constructor takes the following keyword arguments:

- `auth`: same as `EventListener`
- `secret`: same as `EventListener`
- `repos`: same as `EventListener`
- `forwards`: same as `EventListener`
- `check_collab`: If `true`, only acknowledge comments made by repository collaborators. Note that, if `check_collab` is `true`, `auth` must have the appropriate permissions to query the comment's repository for the collaborator status of the commenter. `check_collab` is `true` by default.
- `use_access_token`: If `check_collab` is set to `true` and `auth` is using JWT authentication for GitHub Apps, then set this to `true`.

For example, let's set up a silly `CommentListener` that responds to the commenter with a greeting. To give a demonstration of the desired behavior, if a collaborator makes a comment like:

```
Man, I really would like to be greeted today.

`sayhello("Bob", "outgoing")`
```

We want the `CommentLister` to reply:

```
Hello, Bob, you look very outgoing today!
```

Here's the code that will make this happen:

```julia
import GitHub

# CommentListener settings
trigger = r"`sayhello\(.*?\)`"
myauth = GitHub.authenticate(ENV["GITHUB_AUTH"])
mysecret = ENV["MY_SECRET"]

# We can use Julia's `do` notation to set up the listener's handler function.
# Note that, in our example case, `phrase` will be "`sayhello(\"Bob\", \"outgoing\")`"
listener = GitHub.CommentListener(trigger; auth = myauth, secret = mysecret) do event, phrase
    # In our example case, this code sets name to "Bob" and adjective to "outgoing"
    name, adjective = matchall(r"\".*?\"", phrase)
    comment_params = Dict("body" => "Hello, $name, you look very $adjective today!")

    # Parse the original comment event for all the necessary reply info
    comment = GitHub.Comment(event.payload["comment"])

    if event.kind == "issue_comment"
        comment_kind = :issue
        reply_to = event.payload["issue"]["number"]
    elseif event.kind == "commit_comment"
        comment_kind = :commit
        reply_to = comment.commit_id
    elseif event.kind == "pull_request_review_comment"
        comment_kind = :review
        reply_to = event.payload["pull_request"]["number"]
        # load required query params for review comment creation
        comment_params["commit_id"] = comment.commit_id
        comment_params["path"] = comment.path
        comment_params["position"] = comment.position
    end

    # send the comment creation request to GitHub
    GitHub.create_comment(event.repository, reply_to, comment_kind; auth = myauth, params = comment_params)

    return HTTP.Response(200)
end

# Start the listener on localhost at port 8000
GitHub.run(listener, IPv4(127,0,0,1), 8000)
```

## GitHub Enterprise

This library work with github.com, and also with self-hosted github, a.k.a. GitHub Enterprise.

To use it with self-hosted github, you need to create `GitHubWebAPI` structure and pass it to functions when needed.
Following example shows obtaining repository info `private/Package.jl` on github instance with API `https://git.company.com/api/v3`.

```julia
import GitHub
import URIs

api = GitHub.GitHubWebAPI(URIs.URI("https://git.company.com/api/v3"))
myauth = GitHub.authenticate(api, ENV["GITHUB_AUTH"])
myrepo = GitHub.repo(api, "private/Package.jl", auth=myauth)
```

## SSH keys

You can generate public-private key pairs with `GitHub.genkeys`. Here's an example adding a deploy key and secret, in this case to deploy documentation via GitHub Actions:

```julia
pubkey, privkey = GitHub.genkeys()
create_deploykey(repo; auth, params=Dict("key"=>pubkey, "title"=>"Documenter", "read_only"=>false))
create_secret(repo, "DOCUMENTER_KEY"; auth, value=privkey)
```

`privkey` is sent in encrypted form to GitHub. Do **not** share `privkey` with others or post it publicly;
doing so breaches the security of your repository.
You can [read more](https://www.ssh.com/ssh/public-key-authentication) about the meaning of SSH keys and their security implications.
