缘起
最近一次新疆采编发版时,发现代码中有一处配置的一个变量没有了,经过查询Git的提交历史发现这次改动涉及的提交bbb
的作者是开发人员A,但A君表示从来没有动过这块代码。
这就很奇怪了,明明Git上显示的作者是A君,但A又表示不是自己改的,那到底是Git在说谎还是A君在说谎?
经过线下询问其他采编开发人员,并对比Git的分支图谱,多方查证,终于弄清楚了原因:
- 开发人员B君要开发的一个功能正好在产品分支上已经有了,所以采用了
cherry-pick
的方式从产品分支将这个功能的那次提交000
(作者是D,提交者是A)从产品分支复制到了新疆开发分支1上面。复制时产生了冲突,B君解决了冲突,进行了提交aaa
。 - 在解决冲突的过程中,由于那个丢失的变量在产品分支上没有,而在新疆分支上有,但是那个变量上面便是要从产品分支上复制过来的代码,所以B君直接通过工具采用了产品分支的代码,导致新疆分支上有的那个变量被产品分支代码所替换而丢失。
- B君继续在开发分支1上进行了其它功能的开发,进行了两次提交,然后提了
MR
给C君,请求将开发分支1合并到主开发分支0上面。 - 这时D君说,不用合并了,他要直接
rebase
,所以C君关闭了MR
,而D君使用rebase
命令直接将开发分支1上的这几次提交复制到了主开发分支0上,复制后提交aaa
变为了提交bbb
。
然后就是后来,我们开头说的,发版时发现了变量丢失的问题。
这个过程中,我们产生过两个疑问:
- 我们通过历史提交查询的时候为什么没有查询到提交
aaa
? - 为什么提交
bbb
的作者不是B君而是A君。
破案
解决这两个问题,我们先要弄清楚:
当我们查看提交历史的时候,所显示的Author
(作者)真的就是这次提交中那些文件的真正改动者吗?
$ git log
commit af7af21595aba7d6ac02b34c3a852ed267a395b7 (HEAD -> master, origin/master, origin/HEAD)
Author: chen.chen <chenchen@trs.com.cn>
Date: Thu Aug 5 17:55:12 2021 +0800
修复全部栏目检索问题
首先,我们应该知道,Git中所谓的【提交】在底层实现上就是一个二进制对象,它存储在.git/objects
目录下。
就比如上例中af7af21
这次提交,其实指向的是.git/objects/af/7af21595aba7d6ac02b34c3a852ed267a395b7
这个二进制对象,我们可以通过Git的底层命令来查看一下这个二进制对象的内容:
$ git cat-file -p af7af21
tree 3ab963b8fca3658404126787e075b4d4dbdfc697
parent ab8c90a1ca8544802c3e00b2ea5ccaf01d20976b
author chen.chen <chenchen@trs.com.cn> 1628157312 +0800
committer chen.chen <chenchen@trs.com.cn> 1628157312 +0800
修复全部栏目检索问题
可以看到,这个提交对象了有两个和人员有关的属性:
author
:代表这次提交所涉及的更改真正的作者committer
: 创建这个提交的人。
从这一点来看,一般情况下,提交都是由作者进行的,那作者和提交者应该是同一个人。
但是,总有不一般的情况,就比如:
cherry-pick
操作:A将另一个分支上的提交a
复制到自己的分支时,会在当前分支上重新产生一个新的提交b
,这个提交b
的内容和提交a
的内容一模一样,但是提交a
的提交者将变成提交b
的作者,而A将成为提交a
的提交者。rebase
操作: 当A将另一个分支上的提交通过rebase
复制到自己分支时,那个提交的作者不变,但提交者将由原来的提交者变为A。MR
操作:A在一个分支上进行了提交若干次提交后,发送了一个MR
请求给B,B在合并时勾选了合并提交
选项,这时A做的若干次提交将被合并为一个新的提交(相当于rebase -i
操作),此时,这个新的提交的作者是A,提交者是B。
而在本文开头所述那次事故,正好就是踩了这几个坑,我们来梳理一下整个过程:
- C君在产品分支1上开发了若干功能(其中就包含B君要的那个),进行了提交,发送了一个
MR
请求给A君,A君勾选了【合并提交】选项后将产品分支1合并入了产品分支。此时D君做的所有提交被合并后生成了一个新的提交000
,这个提交的作者是C,提交者是A。 - B君在新疆开发分支1上进行了
cherry-pick
,将000
分支复制到了开发分支1,解决冲突后提交,生成了提交aaa
,此时aaa
提交的作者是A,提交者是B。 - D君使用了
rebase
操作将aaa
从开发分支1复制到了开发分支0上,此时开发分支0上产生了提交bbb
,这个提交的作者还是A,但提交者变成了D。
当发现代码问题时,我们顺着开发分支0的提交历史去看,自然看到的作者是A。
所以,Git没有说谎,A也没有说谎,这是一场由A、B、C、D共同参与的【无心的共谋】。
教训
这次事故的原因找到后,我们从中应该学到一些东西。
rebase
合并提交操作会重写提交历史。(MR
时的合并提交其实就是rebase -i
)rebase
复制提交操作后,提交历史无法反映提交来源自哪个分支以及被复制提交的原始信息cherry-pick
复制提交操作后,提交历史无法反映提交来源自哪个分支以及被复制提交的原始信息,并且无法真实反映被复制提交原来的作者信息。(当被复制提交作者与提交者不同时)
这些特性的存在,会严重影响代码出问题时的溯源工作。
所以,这里做出如下建议,供大家讨论决定:
rebase
操作不允许应用在共享分支上。(可以在自己的私有分支间随便折腾)cherry-pick
操作必须记录到单独日志文件里,注明原始分支和提交的相关信息。
·