背景

使用GitLab的时候,开发者是可以随意设置其用户名和邮箱的。

git config --global user.name "abc"
git config --global user.email "123"

像我这样设置,代码依然可以正常提交,在gitlab上查看的时候显示的用户名是“abc”。

git-commit-username.png

Git本身精神就是协作,是自由、平等,而非集权式的代码库。这样设计也没毛病,但是在公司的代码管理中,这样就很不好,尤其是在开发 Leader review 代码的时候,如果没有正确配置用户名可能都不知道这代码是谁写的。

人的问题不一定都要用技术来解决,用行政手段也可以达到目的。但是咱们是搞技术的,当然首先想到的是技术能不能解决咯。

使用hook机制校验提交

git 中 hook 有很多种,有系统钩子,服务器钩子,客户端钩子等等。要实现这个需求,使用服务器钩子最为合适。

服务器钩子又分为可应用于全局的和单个仓库的,更多的介绍官方文档:Git server hooks | GitLab

这里我要为所有仓库建立统一的规范,因此使用服务器全局钩子,在 custom_hooks/pre-receive.d 目录下编写脚本实现校验。

#!/usr/bin/env bash

if [[ "$GL_PROTOCOL" == "web" ]]; then
  exit 0
fi

ACCESS_TOKEN=XXXXXXXX # 用高权限的用户建一个访问令牌(access token),拿到token放这里

GL_URL=http://gitlab-webservice-default:8181 # GitLab访问URL
res=`curl -s -H "PRIVATE-TOKEN:${ACCESS_TOKEN}" ${GL_URL}/api/v4/users/${GL_ID#user-}`
GL_USER_EMAIL=`echo ${res} | jq -r '.email'` # 依赖jq,如果不想用jq,可以参考我另一篇文章 纯shell解析json
GL_NICKNAME=`echo ${res} | jq -r '.name'`

if [[ ${GL_USER_EMAIL} == "" ]] || [[ ${GL_USER_EMAIL} == null ]]; then
  echo "ERROR: User not set email."
  exit 1
fi

if [[ ${GL_NICKNAME} == "" ]] || [[ ${GL_NICKNAME} == null ]]; then
  echo "ERROR: User not set username."
  exit 1
fi

zero_commit="0000000000000000000000000000000000000000"
excludeExisting="--not --all"

while read oldrev newrev refname; do
  if [ "$newrev" = "$zero_commit" ]; then
    continue
  fi

  if [ "$oldrev" = "$zero_commit" ]; then
    span=`git rev-list $newrev $excludeExisting`
  else
    span=`git rev-list $oldrev..$newrev $excludeExisting`
  fi

  for COMMIT in $span;  do
    AUTHOR_USER=`git log --format=%an -n 1 ${COMMIT}`
    AUTHOR_EMAIL=`git log --format=%ae -n 1 ${COMMIT}`
    COMMIT_USER=`git log --format=%cn -n 1 ${COMMIT}`
    COMMIT_EMAIL=`git log --format=%ce -n 1 ${COMMIT}`

    if [[ ${AUTHOR_USER} != ${GL_NICKNAME} ]]; then
      echo -e "ERROR: Commit author (${AUTHOR_USER}) does not match the current gitlab user (${GL_NICKNAME})"
      exit 20
    fi

    if [[ ${COMMIT_USER} != ${GL_NICKNAME} ]]; then
      echo -e "ERROR: Commit user (${COMMIT_USER}) does not match the current gitlab user (${GL_NICKNAME})"
      exit 30
    fi

    if [[ ${AUTHOR_EMAIL,,} != ${GL_USER_EMAIL,,} ]]; then
      echo -e "ERROR: Commit author's email (${AUTHOR_EMAIL,,}) does not match the current gitlab user's email (${GL_USER_EMAIL})"
      exit 40
    fi

    if [[ ${COMMIT_EMAIL,,} != ${GL_USER_EMAIL,,} ]]; then
      echo -e "ERROR: Commit user's email (${COMMIT_EMAIL,,}) does not match the current gitlab user's email (${GL_USER_EMAIL,,})"
      exit 50
    fi
  done
done

验证

[root@host test]# git config --global user.name "abc"
[root@host test]# git config --global user.email "123"

[root@host test]# echo test >> README.md
[root@host test]# git add README.md
[root@host test]# git commit -m "test"
[main a6ff85c] test
 1 file changed, 1 deletion(-)

[root@host test]# git log

commit a6ff85c1588b3c84b15859651874938fdca834fd
Author: abc <123>
Date:   Tue Dec 13 11:09:55 2022 +0800

    test

commit b14089d2fe55763d589da7702b06e7ffd57d63ba
Author: 陈日志 <[email protected]>
Date:   Mon Dec 12 14:49:37 2022 +0800

    first commit

[root@host test]# git push

Counting objects: 5, done.
Writing objects: 100% (3/3), 220 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: ERROR: Commit author (abc) does not match the current gitlab user (陈日志)
To https://git.test.com/chenrizhi/test.git
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'https://git.test.com/chenrizhi/test.git'

可以看到,修改了用户名和邮箱,提交代码会被拒绝。

参考:platform-samples/commit-current-user-check.sh at master · github/platform-samples · GitHub

标签: none

已有 3 条评论

  1. 大佬牛逼!

  2. devops_user devops_user

    请教您一下。
    在上文脚本中的用户校验逻辑,是建立在 “Push提交人的user信息 == commit中的 author 信息 == commit中的committer信息”之上。

    可是如果在 git merge 或者 cherry pick 的场景下,是否可能存在 committer 与 author 不相同,导致校验失败,错误拦截了的情况?

    1. 不会,只会校验新的commit。你可以试下各种场景,有问题再沟通。

添加新评论