Git server hooks

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab Self-Managed

Git server hooks run custom logic on the GitLab server. You can use them to run Git-related tasks such as:

  • Enforcing specific commit policies.
  • Performing tasks based on the state of the repository.

Git server hooks use pre-receive, post-receive, and update Git server-side hooks.

GitLab administrators configure server hooks using the gitaly command, which also:

  • Is used to launch a Gitaly server.
  • Provides several subcommands.
  • Connects to the Gitaly gRPC API.

If you don’t have access to the gitaly command, alternatives to server hooks include:

For GitLab Helm chart instances, see information about global server hooks in the Gitaly chart.

Geo doesn’t replicate server hooks to secondary nodes.

Prerequisites

  • The storage name, path to the Gitaly configuration file (default is /var/opt/gitlab/gitaly/config.toml on Linux package instances), and the repository relative path for the repository.
  • Any language runtimes and utilities that are required by the hooks must be installed on each of the servers that run Gitaly.

Set server hooks for a repository

To set server hooks for a repository:

  1. Create tarball containing custom hooks:

    1. Write the code to make the server hook function as expected. Git server hooks can be in any programming language. Ensure the shebang at the top reflects the language type. For example, if the script is in Ruby the shebang is probably #!/usr/bin/env ruby.

      • To create a single server hook, create a file with a name that matches the hook type. For example, for a pre-receive server hook, the filename should be pre-receive with no extension.
      • To create many server hooks, create a directory for the hooks that matches the hook type. For example, for a pre-receive server hook, the directory name should be pre-receive.d. Put the files for the hook in that directory.
    2. Ensure the server hook files are executable and do not match the backup file pattern (*~). The server hooks be in a custom_hooks directory that is at the root of the tarball.

    3. Create the custom hooks archive with the tar command. For example, tar -cf custom_hooks.tar custom_hooks.

  2. Run the hooks set subcommand with required options to set the Git hooks for the repository. For example:

    cat custom_hooks.tar | sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set --storage <storage> --repository <relative path> --config <config path>
    • A path to a valid Gitaly configuration for the node is required to connect to the node and provided to the --config flag.

    • Custom hooks tarball must be passed through stdin. For example:

      cat custom_hooks.tar | sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set --storage <storage> --repository <relative path> --config <config path>
  3. If you are using Gitaly Cluster (Praefect), you must run hooks set subcommand on all Gitaly nodes.

If you implemented the server hook code correctly, it should execute when the Git hook is next triggered.

Server hooks on a Gitaly Cluster (Praefect)

If you use Gitaly Cluster (Praefect), an individual repository may be replicated to multiple Gitaly storages in Praefect. Consequently, the hook scripts must be copied to every Gitaly node that has a replica of the repository. To accomplish this, follow the same steps for setting custom repository hooks for the applicable version and repeat for each storage.

The location to copy the scripts to depends on where repositories are stored. New repositories are created using Praefect-generated replica paths which are not the hashed storage path. To identify the replica path, query the Praefect repository metadata by using -relative-path option to specify the expected GitLab hashed storage path.

Create global server hooks for all repositories

To create a Git hook that applies to all repositories, set a global server hook. Global server hooks also apply to:

  • Project and group wiki repositories. Their storage directory names are in the format <id>.wiki.git.
  • Design management repositories under a project. Their storage directory names are in the format <id>.design.git.

Choose a server hook directory

Before creating a global server hook, you must choose a directory for it.

The directory is set in gitlab.rb under gitaly['configuration'][:hooks][:custom_hooks_dir]. You can either:

  • Use the default suggestion of the /var/opt/gitlab/gitaly/custom_hooks directory by uncommenting it.
  • Add your own setting.
  • The directory is set in gitaly/config.toml under the [hooks] section. However, GitLab honors the custom_hooks_dir value in gitlab-shell/config.yml if the value in gitaly/config.toml is blank or non-existent.
  • The default directory is /home/git/gitlab-shell/hooks.

Create the global server hook

To create a global server hook for all repositories:

  1. On the GitLab server, go to the configured global server hook directory.
  2. In the configured global server hook directory, create a directory for the hooks that matches the hook type. For example, for a pre-receive server hook, the directory name should be pre-receive.d.
  3. Inside this new directory, add your server hooks. Git server hooks can be in any programming language. Ensure the shebang (#!) at the top reflects the language type. For example, if the script is in Ruby the shebang is probably #!/usr/bin/env ruby.
  4. Make the hook file executable, ensure that it’s owned by the Git user, and ensure it does not match the backup file pattern (*~).

If the server hook code is properly implemented, it should execute when the Git hook is next triggered. Hooks are executed in alphabetical order by filename in the hook type subdirectories.

Remove server hooks for a repository

To remove server hooks, pass an empty tarball to hook set to indicate that the repository should contain no hooks. For example:

cat empty_hooks.tar | sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set --storage <storage> --repository <relative path> --config <config path>

Chained server hooks

GitLab can execute server hooks in a chain. GitLab searches for and executes server hooks in the following order:

  • Built-in GitLab server hooks. These server hooks are not customizable by users.
  • <project>.git/custom_hooks/<hook_name>: Per-project hooks. This location is kept for backwards compatibility.
  • <project>.git/custom_hooks/<hook_name>.d/*: Location for per-project hooks.
  • <custom_hooks_dir>/<hook_name>.d/*: Location for all executable global hook files except editor backup files.

In a server hooks directory, hooks:

  • Are executed in alphabetical order.
  • Stop executing when a hook exits with a non-zero value.

Environment variables available to server hooks

You can pass any environment variable to server hooks, but you should only rely on supported environment variables.

The following GitLab environment variables are supported for all server hooks:

Environment variableDescription
GL_IDGitLab identifier of user or SSH key that initiated the push. For example, user-2234 or key-4.
GL_PROJECT_PATHGitLab project path.
GL_PROTOCOLProtocol used for this change. One of: http (Git push using HTTP), ssh (Git push using SSH), or web (all other actions).
GL_REPOSITORYGitLab project ID with a prefix of project-. For example, project-1234
GL_USERNAMEGitLab username of the user that initiated the push.

The following Git environment variables are supported for pre-receive and post-receive server hooks:

Environment variableDescription
GIT_ALTERNATE_OBJECT_DIRECTORIESAlternate object directories in the quarantine environment.
GIT_OBJECT_DIRECTORYGitLab project path in the quarantine environment.
GIT_PUSH_OPTION_COUNTNumber of push options.
GIT_PUSH_OPTION_<i>Value of a specific push option where <i> is from 0 to one less than the value defined in GIT_PUSH_OPTION_COUNT.

Custom error messages

When server hooks reject a push, provide clear error messages to help users understand why the push was rejected and how to fix the issue. Custom error messages appear in the GitLab UI and in the user’s terminal when a hook declines a push.

Without custom error messages, users only see generic messages like (pre-receive hook declined). Clear error messages help users:

  • Understand why their push was rejected.
  • Fix the issue without contacting an administrator.
  • Reduce support requests.

To display a custom error message, your script must:

  • Send the custom error messages to either the script’s stdout or stderr.
  • Prefix each message with GL-HOOK-ERR: with no characters appearing before the prefix.

For example:

# Bad: Generic message
echo "GL-HOOK-ERR: Commit rejected.";

# Good: Specific message with action
echo "GL-HOOK-ERR: Commit rejected: Commit message must include an issue reference (for example, #1234).";

Troubleshooting

When working with Git server hooks, you might encounter the following issues.

Error: pre-receive hook declined

When a user pushes to a GitLab repository, they might receive an error message that includes (pre-receive hook declined). For example:

! [remote rejected] main (pre-receive hook declined)
error: failed to push some refs to 'https://gitlab.example.com/group/project'

This error indicates that a pre-receive hook rejected the push. Pre-receive hooks run before any references are updated in the repository. Git provides three server-side hooks that can reject pushes:

  • pre-receive: Runs before any references are updated. Can reject the entire push.
  • update: Runs once per branch being updated. Can reject individual branches.
  • post-receive: Runs after all references are updated. Cannot reject pushes, but can cause errors if the hook fails.

The (pre-receive hook declined) error usually comes from either the pre-receive or update hook. To identify the issue:

  1. Check the output immediately before the (pre-receive hook declined) message. The output often contains information about why the push was rejected. For example:

    remote: GitLab: The default branch of a project cannot be deleted.
    ! [remote rejected] main (pre-receive hook declined)
  2. Check the Gitaly logs for more details about why the hook failed:

    sudo grep PreReceiveHook /var/log/gitlab/gitaly/current | jq .
  3. If the repository has custom server hooks configured, review the custom hook code for issues.

The following are common causes of pre-receive hook failures:

  • Default branch protection: Pushes that delete or force-update the default branch are rejected. This occurs with git push --mirror when the source repository has a different default branch than the target repository.
  • Push rules: The push violates configured push rules, such as commit message requirements, file size limits, or author email restrictions.
  • Custom server hooks: A custom server hook script rejected the push. Review your custom hook code and error messages.
  • Timeout: The hook took too long to run and was terminated. Check the Gitaly logs for timeout errors.
  • LFS objects: Required Git LFS objects are missing from the repository.

To help users understand hook failures use custom error messages to provide clear feedback about why a push was rejected. Custom error messages appear in the GitLab UI and in the user’s terminal.