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-receive and update that 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>.git for 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_hooks directory 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‑ERR block and exits with status 1.
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), "
")
}
func checkFailed() {
fmt.Fprintln(os.Stderr, checkFailedMessage)
os.Exit(1)
}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/hooks for source installs or /opt/gitlab/embedded/service/gitlab-shell/hooks for Omnibus). Configure custom_hooks_dir in the Gitaly configuration ( gitlab.rb for older versions or gitaly/config.toml for 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.d or 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/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
