Skip to content

[pull] main from github:main #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 23, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 111 additions & 60 deletions content/admin/policies/creating-a-pre-receive-hook-script.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Creating a pre-receive hook script
intro: Use pre-receive hook scripts to create requirements for accepting or rejecting a push based on the contents.
miniTocMaxHeadingLevel: 4
redirect_from:
- /enterprise/admin/developer-workflow/creating-a-pre-receive-hook-script
- /enterprise/admin/policies/creating-a-pre-receive-hook-script
Expand All @@ -13,59 +14,109 @@ topics:
You can see examples of pre-receive hooks for {% data variables.product.prodname_ghe_server %} in the [`github/platform-samples` repository](https://github.com/github/platform-samples/tree/master/pre-receive-hooks).

### Writing a pre-receive hook script
A pre-receive hook script executes in a pre-receive hook environment on the {% data variables.product.prodname_ghe_server %} appliance. When you create a pre-receive hook script, consider the available input, output, exit-status and environment variables.
A pre-receive hook script executes in a pre-receive hook environment on {% data variables.product.product_location %}. When you create a pre-receive hook script, consider the available input, output, exit status, and environment variables.

#### Input (stdin)
After a push occurs and before any refs are updated on the remote repository, the `git-receive-pack` process invokes the pre-receive hook script with the standard input of one line per ref to be updated:
#### Input (`stdin`)
After a push occurs and before any refs are updated for the remote repository, the `git-receive-pack` process on {% data variables.product.product_location %} invokes the pre-receive hook script. Standard input for the script, `stdin`, is a string containing a line for each ref to update. Each line contains the old object name for the ref, the new object name for the ref, and the full name of the ref.

`<old-value> SP <new-value> SP <ref-name> LF`
```
<old-value> SP <new-value> SP <ref-name> LF
```

This string represents these arguments:
This string represents the following arguments.

| Argument | Description |
| :------------- | :------------- |
| `<old-value>` | Old object name stored in the `ref`.<br> When you *create* a new `ref`, this equals 40 zeroes. |
| `<new-value>` | New object name to be stored in the `ref`.<br> When you *delete* a `ref`, this equals 40 zeroes. |
| `<ref-name>` | The full name of the `ref`. |
| `<old-value>` | Old object name stored in the ref.<br> When you create a new ref, the value is 40 zeroes. |
| `<new-value>` | New object name to be stored in the ref.<br> When you delete a ref, the value is 40 zeroes. |
| `<ref-name>` | The full name of the ref. |

For more information on `git-receive-pack` see "[git-receive-pack](https://git-scm.com/docs/git-receive-pack)" in the Git documentation.
For more information about `refs` see "[Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References)" in *Pro Git*.
For more information about `git-receive-pack`, see "[git-receive-pack](https://git-scm.com/docs/git-receive-pack)" in the Git documentation. For more information about refs, see "[Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References)" in *Pro Git*.

#### Output (stdout)
#### Output (`stdout`)

The script output (`stdout`) is passed back to the client, so any `echo` statements are visible to the user on the command line or in the user interface.
The standard output for the script, `stdout`, is passed back to the client. Any `echo` statements will be visible to the user on the command line or in the user interface.

#### Exit-status
#### Exit status

The `exit-status` of a pre-receive script determines if the push will be accepted.
The exit status of a pre-receive script determines if the push will be accepted.

| Exit-status Value | Action |
| :-------------: | :-------------: |
| 0 | The push will be accepted. |
| Exit-status value | Action |
| :- | :- |
| 0 | The push will be accepted. |
| non-zero | The push will be rejected. |

#### Environment variables
Outside of the values that are provided to `stdin`, there are additional variables that are available to a pre-receive hook script running on {% data variables.product.prodname_ghe_server %}.

| Variable | Description |
| :------------- | :------------- |
| $GITHUB_USER_LOGIN | The user id who created the `ref`. |
| $GIT_DIR | The path of the remote repository on the appliance. |
| $GITHUB_USER_IP | The IP Address of the user performing the push. |
| $GITHUB_REPO_NAME | The name in `owner`/`repo` format of the repository being updated. |
| $GITHUB_PULL_REQUEST_AUTHOR_LOGIN | The user ID for the author of a pull request opened on your instance. |
| $GITHUB_REPO_PUBLIC | A boolean value that when `true` represents a public repository, and when `false` represents a private repository. |
| $GITHUB_PUBLIC_KEY_FINGERPRINT | The user's public key fingerprint. |
| $GITHUB_PULL_REQUEST_HEAD | A string in the format: `user:branch` for the HEAD of the PR.<br> Example: `octocat:fix-bug` |
| $GITHUB_PULL_REQUEST_BASE | A string in the format: `user:branch` for the BASE of the PR.<br> Example: `octocat:main` |
| $GITHUB_VIA | Method used to create the ref.<br> **Possible values:**<br> - `auto-merge deployment api` <br> - `blob edit` <br> - `branch merge api` <br> - `branches page delete button` <br> - `git refs create api` <br> - `git refs delete api` <br> - `git refs update api` <br> - `merge api` <br> - `pull request branch delete button` <br> - `pull request branch undo button` <br> - `pull request merge api` <br> - `pull request merge button` <br> - `pull request revert button` <br> - `releases delete button` <br> - `stafftools branch restore` <br> - `slumlord (#{sha})` |
| $GIT_PUSH_OPTION_COUNT | The number of push options that were sent by the client. For more information about push options, see "[git-push](https://git-scm.com/docs/git-push#git-push---push-optionltoptiongt)" in the Git documentation. |
| $GIT_PUSH_OPTION_N | Where <em>N</em> is an integer starting at 0, this variable contains the push option string that was sent by the client. The first option that was sent is stored in GIT_PUSH_OPTION_0, the second option that was sent is stored in GIT_PUSH_OPTION_1, and so on. For more information about push options, see "[git-push](https://git-scm.com/docs/git-push#git-push---push-optionltoptiongt)" in the Git documentation. |{% if currentVersion ver_gt "[email protected]" %}
| $GIT_USER_AGENT | The user-agent string sent by the client that pushed the changes. |{% endif %}
In addition to the standard input for your pre-receive hook script, `stdin`, {% data variables.product.prodname_ghe_server %} makes the following variables available in the Bash environment for your script's execution. For more information about `stdin` for your pre-receive hook script, see "[Input (`stdin`)](#input-stdin)."

Different environment variables are available to your pre-receive hook script depending on what triggers the script to run.

- [Always available](#always-available)
- [Available for pushes from the web interface or API](#available-for-pushes-from-the-web-interface-or-api)
- [Available for pull request merges](#available-for-pull-request-merges)
- [Available for pushes using SSH authentication](#available-for-pushes-using-ssh-authentication)

##### Always available

The following variables are always available in the pre-receive hook environment.

| Variable | Description | Example value |
| :- | :- | :- |
| <pre>$GIT_DIR</pre> | Path to the remote repository on the instance | /data/user/repositories/a/ab/<br>a1/b2/34/100001234/1234.git |
| <pre>$GIT_PUSH_OPTION_COUNT</pre> | The number of push options that were sent by the client with `--push-option`. For more information, see "[git-push](https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt)" in the Git documentation. | 1 |
| <pre>$GIT\_PUSH\_OPTION\_<em>N</em></pre> | Where _N_ is an integer starting at 0, this variable contains the push option string that was sent by the client. The first option that was sent is stored in `GIT_PUSH_OPTION_0`, the second option that was sent is stored in `GIT_PUSH_OPTION_1`, and so on. For more information about push options, see "[git-push](https://git-scm.com/docs/git-push#git-push---push-optionltoptiongt)" in the Git documentation. | abcd |{% if currentVersion ver_gt "[email protected]" %}
| <pre>$GIT_USER_AGENT</pre> | User-agent string sent by the Git client that pushed the changes | git/2.0.0{% endif %}
| <pre>$GITHUB_REPO_NAME</pre> | Name of the repository being updated in _NAME_/_OWNER_ format | octo-org/hello-enterprise |
| <pre>$GITHUB_REPO_PUBLIC</pre> | Boolean representing whether the repository being updated is public | <ul><li>true: Repository's visibility is public</li><li>false: Repository's visibility is private or internal</li></ul>
| <pre>$GITHUB_USER_IP</pre> | IP address of client that initiated the push | 192.0.2.1 |
| <pre>$GITHUB_USER_LOGIN</pre> | Username for account that initiated the push | octocat |

##### Available for pushes from the web interface or API

The `$GITHUB_VIA` variable is available in the pre-receive hook environment when the ref update that triggers the hook occurs via either the web interface or the API for {% data variables.product.prodname_ghe_server %}. The value describes the action that updated the ref.

| Value | Action | More information |
| :- | :- | :- |
| <pre>auto-merge deployment api</pre> | Automatic merge of the base branch via a deployment created with the API | "[Repositories](/rest/reference/repos#create-a-deployment)" in the REST API documentation |
| <pre>blob edit</pre> | Change to a file's contents in the web interface | "[Editing files in your repository](/github/managing-files-in-a-repository/editing-files-in-your-repository)" |
| <pre>branch merge api</pre> | Merge of a branch via the API | "[Repositories](/rest/reference/repos#merge-a-branch)" in the REST API documentation |
| <pre>branches page delete button</pre> | Deletion of a branch in the web interface | "[Creating and deleting branches within your repository](/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository#deleting-a-branch)" |
| <pre>git refs create api</pre> | Creation of a ref via the API | "[Git database](/rest/reference/git#create-a-reference)" in the REST API documentation |
| <pre>git refs delete api</pre> | Deletion of a ref via the API | "[Git database](/rest/reference/git#delete-a-reference)" in the REST API documentation |
| <pre>git refs update api</pre> | Update of a ref via the API | "[Git database](/rest/reference/git#update-a-reference)" in the REST API documentation |
| <pre>git repo contents api</pre> | Change to a file's contents via the API | "[Repositories](/rest/reference/repos#create-or-update-file-contents)" in the REST API documentation |
| <pre>merge base into head</pre> | Update of the topic branch from the base branch when the base branch requires strict status checks (via **Update branch** in a pull request, for example) | "[About protected branches](/github/administering-a-repository/about-protected-branches#require-status-checks-before-merging)" |
| <pre>pull request branch delete button</pre> | Deletion of a topic branch from a pull request in the web interface | "[Deleting and restoring branches in a pull request](/github/administering-a-repository/deleting-and-restoring-branches-in-a-pull-request#deleting-a-branch-used-for-a-pull-request)" |
| <pre>pull request branch undo button</pre> | Restoration of a topic branch from a pull request in the web interface | "[Deleting and restoring branches in a pull request](/github/administering-a-repository/deleting-and-restoring-branches-in-a-pull-request#restoring-a-deleted-branch)" |
| <pre>pull request merge api</pre> | Merge of a pull request via the API | "[Pulls](/rest/reference/pulls#merge-a-pull-request)" in the REST API documentation |
| <pre>pull request merge button</pre> | Merge of a pull request in the web interface | "[Merging a pull request](/github/collaborating-with-issues-and-pull-requests/merging-a-pull-request#merging-a-pull-request-on-github)" |
| <pre>pull request revert button</pre> | Revert of a pull request | "[Reverting a pull request](/github/collaborating-with-issues-and-pull-requests/reverting-a-pull-request)" |
| <pre>releases delete button</pre> | Deletion of a release | "[Managing releases in a repository](/github/administering-a-repository/managing-releases-in-a-repository#deleting-a-release)" |
| <pre>stafftools branch restore</pre> | Restoration of a branch from the site admin dashboard | "[Site admin dashboard](/admin/configuration/site-admin-dashboard#repositories)" |
| <pre>tag create api</pre> | Creation of a tag via the API | "[Git database](/rest/reference/git#create-a-tag-object)" in the REST API documentation |
| <pre>slumlord (#<em>SHA</em>)</pre> | Commit via Subversion | "[Support for Subversion clients](/github/importing-your-projects-to-github/support-for-subversion-clients#making-commits-to-subversion)" |
| <pre>web branch create</pre> | Creation of a branch via the web interface | "[Creating and deleting branches within your repository](/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository#creating-a-branch)" |

##### Available for pull request merges

The following variables are available in the pre-receive hook environment when the push that triggers the hook is a push due to the merge of a pull request.

| Variable | Description | Example value |
| :- | :- | :- |
| <pre>$GITHUB_PULL_REQUEST_AUTHOR_LOGIN</pre> | Username of account that authored the pull request | octocat |
| <pre>$GITHUB_PULL_REQUEST_HEAD</pre> | The name of the pull request's topic branch, in the format `USERNAME:BRANCH` | <nobr>octocat:fix-bug</nobr> |
| <pre>$GITHUB_PULL_REQUEST_BASE</pre> | The name of the pull request's base branch, in the format `USERNAME:BRANCH` | octocat:main |

##### Available for pushes using SSH authentication

| Variable | Description | Example value |
| :- | :- | :- |
| <pre>$GITHUB_PUBLIC_KEY_FINGERPRINT</pre> | The public key fingerprint for the user who pushed the changes | a1:b2:c3:d4:e5:f6:g7:h8:i9:j0:k1:l2:m3:n4:o5:p6 |

### Setting permissions and pushing a pre-receive hook to {% data variables.product.prodname_ghe_server %}

A pre-receive hook script is contained in a repository on the {% data variables.product.prodname_ghe_server %} appliance. A site administrator must take into consideration the repository permissions and ensure that only the appropriate users have access.
A pre-receive hook script is contained in a repository on {% data variables.product.product_location %}. A site administrator must take into consideration the repository permissions and ensure that only the appropriate users have access.

We recommend consolidating hooks to a single repository. If the consolidated hook repository is public, the `README.md` can be used to explain policy enforcements. Also, contributions can be accepted via pull requests. However, pre-receive hooks can only be added from the default branch. For a testing workflow, forks of the repository with configuration should be used.

Expand All @@ -80,7 +131,7 @@ We recommend consolidating hooks to a single repository. If the consolidated hoo
git update-index --chmod=+x <em>SCRIPT_FILE.sh</em>
```

2. Commit and push to your designated pre-receive hooks repository on the {% data variables.product.prodname_ghe_server %} instance.
2. Commit and push to the designated repository for pre-receive hooks on {% data variables.product.product_location %}.

```shell
$ git commit -m "<em>YOUR COMMIT MESSAGE</em>"
Expand All @@ -90,36 +141,36 @@ We recommend consolidating hooks to a single repository. If the consolidated hoo
3. [Create the pre-receive hook](/enterprise/{{ currentVersion }}/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-server-appliance/#creating-pre-receive-hooks) on the {% data variables.product.prodname_ghe_server %} instance.

### Testing pre-receive scripts locally
You can test a pre-receive hook script locally before you create or update it on your {% data variables.product.prodname_ghe_server %} appliance. One method is to create a local Docker environment to act as a remote repository that can execute the pre-receive hook.
You can test a pre-receive hook script locally before you create or update it on {% data variables.product.product_location %}. One method is to create a local Docker environment to act as a remote repository that can execute the pre-receive hook.

{% data reusables.linux.ensure-docker %}

2. Create a file called `Dockerfile.dev` containing:

```dockerfile
FROM gliderlabs/alpine:3.3
RUN \
apk add --no-cache git openssh bash && \
ssh-keygen -A && \
sed -i "s/#AuthorizedKeysFile/AuthorizedKeysFile/g" /etc/ssh/sshd_config && \
adduser git -D -G root -h /home/git -s /bin/bash && \
passwd -d git && \
su git -c "mkdir /home/git/.ssh && \
ssh-keygen -t ed25519 -f /home/git/.ssh/id_ed25519 -P '' && \
mv /home/git/.ssh/id_ed25519.pub /home/git/.ssh/authorized_keys && \
mkdir /home/git/test.git && \
git --bare init /home/git/test.git"

VOLUME ["/home/git/.ssh", "/home/git/test.git/hooks"]
WORKDIR /home/git

CMD ["/usr/sbin/sshd", "-D"]
```

3. Create a test pre-receive script called `always_reject.sh`. This example script will reject all pushes, which is useful for locking a repository:

```shell
#!/usr/bin/env bash
```dockerfile
FROM gliderlabs/alpine:3.3
RUN \
apk add --no-cache git openssh bash && \
ssh-keygen -A && \
sed -i "s/#AuthorizedKeysFile/AuthorizedKeysFile/g" /etc/ssh/sshd_config && \
adduser git -D -G root -h /home/git -s /bin/bash && \
passwd -d git && \
su git -c "mkdir /home/git/.ssh && \
ssh-keygen -t ed25519 -f /home/git/.ssh/id_ed25519 -P '' && \
mv /home/git/.ssh/id_ed25519.pub /home/git/.ssh/authorized_keys && \
mkdir /home/git/test.git && \
git --bare init /home/git/test.git"

VOLUME ["/home/git/.ssh", "/home/git/test.git/hooks"]
WORKDIR /home/git

CMD ["/usr/sbin/sshd", "-D"]
```

3. Create a test pre-receive script called `always_reject.sh`. This example script will reject all pushes, which is useful for locking a repository:

```
#!/usr/bin/env bash

echo "error: rejecting all pushes"
exit 1
Expand Down