前言
大家好,我是加权,在37手游负责安卓SDK业务。
本文旨在介绍在工作中使用git的一些经验,分享下我自己日常开发中git的高频场景。希望对大家有帮助。
场景0:找回历史commit
git reflog
git的一个重要作用就是记录文件变化,正常情况下应该能看到文件所有时间内的修改历史,让我们可以随时回退修改。不过在实际工作中,总会有意外发生,导致历史丢失。比如误删除了本地未提交的分支。
这时候,我们可以使用reflog
命令来查看被删掉的commit,然后通过reset
/cherry pick
恢复。
张三接到需求,需要给工程的user模块增加昵称和缓存逻辑,张三效率爆表,一下就完成了,提交记录如下
突然,因为不明原因,产品说昵称和缓存不要了,张三也是比较熟悉git的,所以他直接使用git reset --hard f948cc5
命令来回退代码,回退后,提交记录如下
可见这时的提交记录已经没有昵称和缓存的痕迹了,没有任何问题。
这时,因为不明原因,产品又希望把昵称和缓存加回来,张三不可能又重新写一次代码,但是在日志里面又找不到之前昵称和缓存的提交记录了,这时,就可以通过git reflog
命令来查看操作历史,如下图
通过记录我们就可以看到之前消除掉的昵称和缓存的提交,然后通过git reset ---hard 5aa4345
来恢复昵称和缓存的代码了。
那么假如产品只是想加回来昵称,还是继续砍掉缓存呢,那么我们可以使用git cherry-pick e5bb629
来只恢复昵称的提交。
通过这个示例我们还可以看到清晰、有意义的提交日志 非常有帮助。假如提交日志都是无明确意义的说明,那么即使有git reflog
这样的工具,想找回目标内容也不是一件简单的事。
虽然reflog是找回历史的强力工具,但也不是万能的,有些情况它仍然无能为力:
- 非本地操作的记录
git reflog
能管理的是本地工作区操作记录,所以其他机器上的记录是无法找回的
- 未commit的内容
- 太久远的内容
git reflog
保留的记录有一定时间限制(默认 90 天),超时的会被自动清理。另外如果主动执行清理命令也会提前清理掉。
记录SHA1
由于reflog的限制,超出一定时间的commit记录,也会被删除,导致无法找到记录。
例如,我们发了一个特殊的小版本,没有合并到主分支,经过一段时间后,这个特殊小版本的分支被清理掉了。过了N久,突然需要用到这个特殊的小版本,这时候通过reflog可能已经找不到提交记录了,也就很难恢复代码了。
为了应对这种情况,我们可以记录提交的SHA1到发版文件里面,那么即使reflog已经无记录,我们也有机会找回commit。
实际中,我们可以使用git rev-parse --short HEAD
来记录发版时的SHA1,并写入到发版文件中。
需要注意的是,如果记录的commit在仓库中无任何引用,是有可能被gc彻底删除的,那么即使我们有SHA1也没用了。
但是记录这个SHA1仍是一个比较好的实践,当用到的时候可以节省非常多的时间,而且几乎没有成本,何乐而不为呢。
场景1:不同模块的重合功能
在多人协作中,常常会遇到需求之间有依赖的情况。那么怎样才能保证开发效率的同时,可以保持提交记录的逻辑性呢,我们来看案例。
张三和李四这次接到需求,产品需要给用户增加一个vip标识,当用户是vip的时候,登录后有酷炫的效果动画,同时支付的时候会有特殊的弹窗提示。那么按照功能划分,涉及用户模块,登录模块和支付模块,并且登录模块和支付模块依赖用户模块的vip标识。
按模块熟悉程度分工,张三负责用户模块和登录模块,李四负责支付模块,这时候李四支付相关的功能需要依赖张三负责的用户模块中的vip标识,这时候他们有两种选择。
cherry pick
如果重合功能的代码量比较低,可以压缩到一个commit,那么可以
- 张三新建分支,快速开发vip标识功能,提交推送到远端,然后续集开发自己的分支
- 李四新建分支,直接cherry pick张三提交的commit,然后继续开发自己的分支
- 所有功能开发完毕后合并
这种方式适合重合部分较简单的场景,缺点是代码很早就推送到远端,后续无法自由修改commit。
rebase
当重合功能的代码开发较复杂的时候,可以先开发,然后联调时rebase
- 张三新建vip标识功能分支,开发
- 李四新建分支,同步开发,使用到vip标识时留空
- 张三开发完毕vip标识功能,推送到远端,并继续新建分支开发登录模块功能
- 李四将本地分支rebase到vip标识功能分支,联调
- 所有功能开发完毕后合并
这种方式的缺点,因为本质上是按依赖关系按顺序开发,所以有可能会阻塞开发,影响整体效率,解决这个问题比较依赖李四的能力,如果能较好地留空,进行同步开发,理论上是不怎么会影响效率的。
优点是通过分支和rebase进行功能同步,提交逻辑更加清晰,重合功能的分支也留有更多修改空间。
场景2:修改提交历史
我们一直提到,提交记录需要合适的颗粒度,可读的提交日志,能够体现代码、需求的变更,而做到这几点并不容易,特别是一次提交就不再更改,在实际开发中几乎无法做到,所以就需要有手段能够修改提交历史,让我们可以调整提交历史,让它们更符合上述要求。
rebase -i
rebase -i
的意思是互动式rebase,使用该命令时,git会打开编辑器,提供指定的提交信息,让我们可以修改这些提交信息,常见操作
- 调换commit的顺序
- 修改commit的提交日志(r)
- 删除commit(d)
- 合并commit(f)
调换commit顺序
虽然我们说提交commit得有逻辑,不过实际工作中,经常会出现写着A需求的时候,看到一个遗留问题,不改吧,可能就忘了,那就顺手改了吧,然后,不提交吧,就一直在那里,影响其他git操作,提交吧,又跟A需求没关系。
这时候我们就可以先提交,然后最后再通过rebase -i来调换提交顺序,来让这个和A需求无关的提交移动到最后。
实际操作只需要移动对应的commit即可。
修改commit日志
使用git越久,就会越发现清楚明了的提交日志作用很大,所以值得花时间打磨描述,通过rebase -i
,可以修改任意提交的日志。
rebase -i
- 修改指定提交的前缀
pick
,改为r
- 保存退出编译器,git会再次打开编辑器显示指定提交的日志
- 修改日志,并保存退出
通过
git commit --amend
可以修改最近一次的提交日志
删除commit
需求在发版之前总是处于薛定谔状态,所以删除commit也是家常便饭了
rebase -i
- 直接删除指定commit的行
- 保存退出编译器
合并commit
在实际开发中,大部分功能我们都很难一步到位,不再修改,比如,开发到最后,才想起补方式注释;比如开发到一半才发现刚才的提交中有BUG,这时候补充提交固然可以,但是也让提交记录变得有点零碎,这时候就可以使用rebase -i合并这些commit,让代码记录看起来是"一步到位"的。
rebase -i
- 移动需要被合并commit到目标commit的下方
- 修改需要被合并commit的前缀
pick
,改为f
- 保存退出编译器
最终,补充提交的内容就会合并到之前的提交,就好像一开始就提交了所有内容一样,完美~
注意:推送到远端后就不能再使用rebase,否则会导致本地和远端不一致
以上就是本次要分享的所有内容啦,欢迎大家在评论区说出你遇到的git问题,一起讨论