今天公司群里又看到同事做了 force push,手不抖也不潮,他是修改了repo设置,解锁那个branch之后,再用git push -f 强推入库。篡改repo历史,是十分容易遭到谴责的,99%以上的情况,篡改都是错误的操作姿势引起的,并非出自本意。
强推害人不浅,最典型的副作用是让人困惑它让所有pull过被强推版本的小伙伴困惑,下一次没法正常pull和push了
它让集成版本控制系统的系统困惑,例如CI和发布系统,它不知道该怎么做
了解了一下同事的真实需求,他原本只是想把代码回退到某个历史版本中的样子,并没有修改历史的意思,但是不熟悉 git 的概念,用 reset --hard 做了回退,发现 push 不了,不得已才用了强推。
其实 git 的回退是很简单的,假设现在 repo 中master分支的历史是这样子的
A --- B --- C
(HEAD)
其中最后一个 commit C,我们可以称之为HEAD,它是一个可以移动的指针,当你在C的基础上修改 commit 了 D,并且push进去了,repo就会变成这样
本地: A --- B --- C --- D
(HEAD)
repo: A --- B --- C
(HEAD)
结果: A --- B --- C --- D
(HEAD)
这种情况下的 push,是畅通无阻的,因为 repo 的 HEAD C 在你要 push 的 branch中,它只要接纳你的D,并且把 HEAD 指针移动到 D 上就完成了,这个过程也就是 fast forward,git唯一允许的合法提交就是 fast forward,这个 HEAD 指针只能单向的向前移动,不能倒退。
假如你做了 reset --hard
git reset --hard B
本地: A --- B
(HEAD)
repo: A --- B --- C
(HEAD)
此时 repo 中的 C 该何去何从?丢掉吗?这就是篡改历史的过程了。如果别人正好pull过带C的版本,你强推之后,C不见了,他下次pull的时候,他本地git如何面对篡改了的版本历史?
那么需要回退的时候该用什么姿势呢?总结一下最典型的几种情况。
把少量文件回退到B版本
可能这是最简单也最常见的情况了,
git checkout B -- 要回退的文件1 要回退的文件2
然后commit这两个文件得到新的版本D
本地: A --- B --- C --- D
(HEAD)
repo: A --- B --- C
(HEAD)
此时 push,repo可以快速移动指针 HEAD,从C移动到D,push 成功。如果B版本中这个文件不存在,只是想删除呢?更简单,你直接rm删了再commit好了。
回退前1次,或者几次commit的所有变更
这种情况也非常简单,使用 revert undo 掉之前的 commit 就行了
git revert C 产生跟 B 内容一样的新commit B',repo 快速移动到B'就行了
A --- B --- C --- B'
(HEAD)
revert 是逆向回放,如果revert 的是B,由于C的一些变更,可能undo会有冲突,无法自动完成,所以它不是万能的,只适用于某些情况。
回退到某个指定版本
这是最容易引起force push的场景了,那么how to?假设repo如下,想回退到B
A --- B --- C --- D
(HEAD)
git reset --hard B
git reset --soft D
第一个操作把 working, index, HEAD 都强行置为 B,状态如下
A --- B
(HEAD)
第二个操作,把 HEAD 设置回 D,看起来状态如下,B'是跟B内容一样的状态
A --- B --- C --- D --- B'
(HEAD)
此时若 commit,得到如下状态,满足 fast forward
A --- B --- C --- D --- B'
(HEAD)
更好的姿势,不用 git reset --hard B,直接起个新分支
A --- B --- C --- D
(HEAD)
git checkout -b ttt B
git reset --soft D
然后 commit 得到
ttt: A --- B --- C --- D --- B'
(HEAD)
把这个branch推到repo的master
git push origin HEAD:master 或者
git push origin ttt:master 都行
完事之后删掉这个临时的ttt branch即可
开临时新分支的最大的好处,是不会影响master分支,你可以更方便的查看或者切回master。不像hard reset后那样,丢掉了master的历史,只能借助reflog查看和恢复历史。
到底什么时候可以用强推?
纠正错误的历史的时候,比如把密码或者隐私文件入库了
把病毒或者恶意的东西入库了
把苍老师.avi入库了
发生这种状况时,只能rewrite整个库了,然后写邮件道歉并通知所有所有被困惑了的人。