March 5, 2017

记一次git merge和gitattributes配置的坑

为了保证项目在已经上线并且继续迭代的过程中,方便的管理不同环境的配置文件,减少人工切换配置文件的失误。研究了一下git merge与attributes的配置,踩了一次小小的坑,也去了解了一下git merge的原理。

为了保证生产环境的稳定,在项目已经上线并且继续迭代的情况下,会开一个新的隔离环境作为测试环境,实验和测试新的代码,而这时往往就会产生不同环境的配置文件不同的问题。在使用git分支进行迭代开发的过程中,至少会存在两个分支,一个master,一个dev,分别对应生产环境的代码和新功能的迭代代码,为了使用不同的数据库地址、后端api或者其他的配置项时就会产生下面的需求:

  1. 在不同分支中的同一个文件要保持不同的内容
  2. 这个文件需要被跟踪(比如前端的配置文件要随项目一起发布上线,至于是否提交远端是另一个问题)
  3. 这个文件在合并的时候应该忽略

有了上面的需求,相应的使用gitattributes能够配置merge的策略进行解决。

参考的下面的文档(在页面的最底下一节):

https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes

实验

于是我们可以实验一下

创建一个仓库,加入一个文件

git init
touch text.txt .gitattributes
echo "master" > text.txt
echo "text.txt merge=ours" > .gitattributes
git add -A
git commit -m 'init'
git checkout -b dev
echo "dev" > text.txt
git commit -a -m 'modify'
git checkout master
git merge dev

其中这一行就是配置合并策略的关键内容

echo "text.txt merge=ours" > .gitattributes

最后merge的时候,期待着text.txt文件保持内容”master”不变,然而执行merge之后,text.txt的内容变成了dev

这并不是我们想要的。

到底发生了什么事?

这要从git merge的方式进行解释。

git如何merge

git merge

在上面图中,当dev在合并到master时,git会从master与dev分开的那个commit(即是B)开始,应用dev分支上的所有commit更改(D和E),应用更改后与HEAD所指的commit(master上的C)的文件进行比较,没有发生冲突的文件自动合并,发生冲突的文件会在文件中标记出冲突的内容,以便解决。

而.gitattributes实际上是定义在发生冲突时,应该采取的行动,比如merge=ours就表示文件冲突时使用原文件内容,merge=theirs表示使用其他分支的文件内容。

然而实际上上面实验过程中的text.txt文件在合并时并没有发生冲突(master中的text.txt文件在master分支上没有更改过,所以dev上的commit可以顺利的应用到这个文件上,最后也不发生冲突),导致git不会进入冲突解决流程,自然不会应用.gitattributes中定义的合并策略。

那么解决方法其实就是让这个文件发生冲突,可以把master分支上的这个文件修改一下commit,然后再merge就会产生冲突了,从而在合并时应用.gitattributes中定义的规则,达到预期的效果。