Git是个好东西,大家可以方便的把本地变更的文件上传,但是在维护版本仓库的时候,经常会遇到一个问题,就是对于一些重要的文件,希望只有少数情况下可以修改。

对于我们这种大型的游戏项目来说,动辄一个团队几十人,这个问题尤为突出。对于类似SVN的集中式版本控制工具,是可以方便的对目录或者文件设置访问权限。但是对于git之类的分布式工具,是没有这个功能的,也不符合它的设计初衷。那么如何解决这个问题呢?

最近我在工作中经常发现Unity的「ProjectSettings.asset」有冲突,它存放了项目的关键配置信息。在一个项目被打开之后,引擎对这个文件会自发的做一些无意义的改动。实际工作中,这个文件时常被不知情的同事改动,从而混淆了真正有意义的提交。

为了节约每次检查问题,revert这个文件的精力,以及规范提交,我决定从源头遏制,对这个文件的commit做一些条件限制。


我这个功能希望保证每个人都可以提交文件,即不希望修改权限,无论美术策划还是程序都可以提交。我也不想单纯的把它放到忽略文件中,因为有些时候想改的时候看不见还是很麻烦的。我希望这个拦截是可以帮助非工程师,以及新人,检查他们的提交,规范他们的提交。


先来看一下最终实现的效果:



我的设计是:

  • 当index中(也就是选中了准备提交的)有「ProjectSettings.asset」这个文件在的时候,去判断message(也就是描述本次提交的注释comment)语句中,是否包含了该文件名“ProjectSettings”. 如果不包含,很可能是提交人误选择此文件,所以报错误log,提示提交者需加上文件名,并中断提交。如果message中加上了文件名,则表示用户清楚自己在做什么,可以顺利提交。


总之,就是检查对比,commit的message中是否包含对应文件名。


我们经过研究发现,采用git的hook机制是一个合理的解决方案。hook可以在提交阶段触发一些逻辑上的保护,常用的有pre-commit和commit-msg。

我先尝试了续写工程里shell语言的“pre-commit”文件。


(这样的代码第一眼看过去,我的内心很复杂)


在Pre-commit中实现了查找尝试提交的文件是否有“ProjectSettings”并作拦截之后,我遇到了问题,如何在点击Commit的时候获取输入框中的message呢?

原来,如果想获取message的内容,需要在commit-msg中做操作,而不是pre-commit。pre-commit是第一步,是在用户输入任何message之前就执行的,而后才执行到commit-msg这个文件。而这里面的message实际上是存在了COMMIT_EDITMSG这个文件中了。这里是我参考的一篇文章。从commit-msg的sample中就可以看到,$1就是这个message信息。




在pre-commit中轻松的获取到message信息之后就很简单了,只需要在前期写好的判断文件的判断里,再判断一次message是否符合规范即可。

下面是我的代码,第一次写shell,写的不够好,大家看个逻辑就好。

git diff --cached --name-only --diff-filter=M -z $against -- | while read -d $'\0' f; do
if [[ ${f} == *"trunk"* ]]
then

	project="${f%.*}"
	project="${project##*/}"

	if [ "$project" = "ProjectSettings" ]; then
		while read line; do
			if [[ "$line" != *"ProjectSettings"* ]]
			then
					cat <<EOF
Error: 注意啦,这是一个特别的文件,你真的要提交吗?

file \`$f' is added, but \`$f' s name is not in the commit message.

如果你确定是要提交这个文件的修改,请对本次提交的描述进行修改,详见下句:

Please add \`$project' to your message line as well.
EOF
			exit 1
		   fi
		
		done < $1

	fi
fi
done


我这里仅仅是针对了叫这个名字的文件,其实完全也可以比较文件类型,就是对比文件后缀名是否符合,再进行下一步判断,这样的好处是可以限制更多的文件。

最后,这里有一篇文章提到如果不会写shell怎么办,我没试,但是写完这个功能之后,回头看看很多坑是因为多了个空格或者少了个空格,也许我应该试试转换代码。。。