Enforce Custom Commit Message Formats in GitLab with Server Hooks
This guide explains how to create repository‑specific and global GitLab server hooks that validate commit messages against a custom pattern, showing step‑by‑step configuration, a Go pre‑receive script, and verification commands to ensure only properly formatted commits are accepted.
Introduction
Git provides server‑side hooks such as
pre-receive,
post-receiveand
updatethat can enforce commit policies.
These hooks can be configured per repository or globally on the GitLab server.
Creating a repository‑specific hook
Locate the repository path (e.g.,
/home/git/repositories/<group>/<project>.gitfor source installations or
/var/opt/gitlab/git-data/repositories/<group>/<project>.gitfor Omnibus installations). For hashed storage, find the hashed directory under
/var/opt/gitlab/git-data/repositories/@hashed/….
Select Admin Area → Projects → your project.
Find the Gitaly relative path and create a
custom_hooksdirectory under that path.
Inside
custom_hooks, add a file named
pre-receive(no extension) and make it executable, owned by
git.
The hook receives three parameters (old commit ID, new commit ID, ref) via
stdin. If the hook exits with a non‑zero status, GitLab rejects the push and returns the error message.
Sample pre‑receive hook
The following Go program allows only commit messages that match
(.*build=(yes|no).*deploy=(yes|no).*)|^Merge\ branch(.*). If the pattern does not match, it prints a
GL‑HOOK‑ERRblock and exits with status 1.
<code>package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strings"
)
type CommitType string
const CommitMessagePattern = `(.*build=(yes|no).*deploy=(yes|no).*)|^Merge\ branch(.*)`
const checkFailedMessage = `GL-HOOK-ERR:########################
GL-HOOK-ERR: Commit message format check failed!
GL-HOOK-ERR: Expected pattern:
GL-HOOK-ERR: (.*build=(yes|no).*deploy=(yes|no).*)|^Merge\ branch(.*)
GL-HOOK-ERR: Example: Update date.html build=no,deploy=yes
GL-HOOK-ERR:########################`
const strictMode = false
var commitMsgReg = regexp.MustCompile(CommitMessagePattern)
func main() {
input, _ := ioutil.ReadAll(os.Stdin)
param := strings.Fields(string(input))
// allow branch/tag delete
if param[1] == "0000000000000000000000000000000000000000" {
os.Exit(0)
}
commitMsg := getCommitMsg(param[0], param[1])
for _, tmpStr := range commitMsg {
commitTypes := commitMsgReg.FindAllStringSubmatch(tmpStr, -1)
if len(commitTypes) != 1 {
checkFailed()
} else {
fmt.Println(" ")
}
if !strictMode {
os.Exit(0)
}
}
}
func getCommitMsg(oldCommitID, commitID string) []string {
cmd := exec.Command("git", "log", oldCommitID+".."+commitID, "--pretty=format:%s")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
b, err := cmd.Output()
if err != nil {
fmt.Print(err)
os.Exit(1)
}
return strings.Split(string(b), "\n")
}
func checkFailed() {
fmt.Fprintln(os.Stderr, checkFailedMessage)
os.Exit(1)
}
</code>Testing the hook
After placing the script and making it executable, a push with an invalid message is rejected with the formatted error block. Amending the commit to include
[build=no,deploy=no]satisfies the pattern and the push succeeds.
Creating a global hook
To apply a hook to all repositories, place it in the global server‑hook directory (
/home/git/gitlab-shell/hooksfor source installs or
/opt/gitlab/embedded/service/gitlab-shell/hooksfor Omnibus). Configure
custom_hooks_dirin the Gitaly configuration (
gitlab.rbfor older versions or
gitaly/config.tomlfor newer) to point to the desired directory.
Navigate to the global hooks directory on the GitLab server.
Create a subdirectory such as
pre-receive.d,
post-receive.dor
update.d.
Copy your hook script into this directory and ensure it is executable and owned by
git.
References:
https://docs.gitlab.com/ee/administration/server_hooks.html
https://mritd.com/2018/05/11/add-commit-message-style-check-to-your-gitlab/
Ops Development Stories
Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.