用了好几年Git,大部分人的操作可能就是add、commit、push、pull、merge。够用是够用,但遇到一些复杂场景就抓瞎了。
这篇聊几个进阶操作,都是我实际工作中用得上的。
rebase:让提交历史干净点
合并多个commit
开发一个功能,写着写着提交了七八次,有些commit message还写得很随意,比如"fix"、"xxx"、"临时提交"。
合到主分支之前,最好把这些合成一个有意义的提交。
# 合并最近4个commit
git rebase -i HEAD~4
会打开一个编辑器:
pick abc1234 添加用户模块
pick def5678 fix
pick ghi9012 临时提交
pick jkl3456 完善用户模块
把后面几个的pick改成squash(或者s):
pick abc1234 添加用户模块
s def5678 fix
s ghi9012 临时提交
s jkl3456 完善用户模块
保存退出,会让你重新编辑commit message,这时候写一个完整的描述就行了。
最后这个功能只有一个干净的commit。
修改某个历史commit
发现之前某个commit有问题,想改一下,但不是最新的那个。
# 找到要改的commit的前一个
git rebase -i <commit-hash>^
# 把要改的那行pick改成edit
# 保存退出后,git会停在那个commit
# 做你的修改
git add .
git commit --amend
git rebase --continue
有风险,改完历史commit后需要force push,别在公共分支上干这事。
rebase代替merge
有些团队要求用rebase而不是merge来同步主分支,保持线性历史。
# 在feature分支上
git fetch origin
git rebase origin/main
# 有冲突就解决,然后
git rebase --continue
我个人习惯是自己的分支用rebase,合到主分支用merge。各有利弊,看团队规范。
cherry-pick:摘樱桃
把某个commit单独拿过来,不带整个分支的其他东西。
场景:hotfix需要同步到多个分支
线上有个bug,在main分支修了。但release/1.0分支也需要这个修复。
# 先找到修复的commit hash
git log --oneline main
# 假设是 abc1234
# 切到需要同步的分支
git checkout release/1.0
git cherry-pick abc1234
如果有冲突,解决后:
git add .
git cherry-pick --continue
摘多个commit
# 连续的几个
git cherry-pick abc1234^..def5678
# 不连续的
git cherry-pick abc1234 def5678 ghi9012
只摘代码不提交
有时候只想把改动拿过来,但不想直接提交,想再改改。
git cherry-pick -n abc1234
# 改动会放到暂存区,不会自动commit
bisect:二分法找bug
这个真的救过我命。
有一天线上报了个bug,但不知道是哪个版本引入的。几百个commit一个个看太慢了。
git bisect用二分法快速定位。
# 开始bisect
git bisect start
# 告诉git当前版本有bug
git bisect bad
# 告诉git某个老版本没bug(比如上周的release)
git bisect good v1.2.0
然后git会checkout到中间的某个commit,你测试一下有没有bug:
# 如果这个版本有bug
git bisect bad
# 如果这个版本没bug
git bisect good
git会继续二分,几次之后就能定位到具体是哪个commit引入的bug。
# 找到后,git会告诉你
# abc1234 is the first bad commit
# 结束bisect
git bisect reset
如果测试可以自动化,还可以:
git bisect run ./test.sh
# test.sh返回0表示good,非0表示bad
# git会全自动找到问题commit
stash:临时存一下
写到一半,突然要切分支处理别的事。
# 存起来
git stash
# 切分支干活...
# 回来后恢复
git stash pop
stash可以存多个:
git stash list
# stash@{0}: WIP on feature: abc1234 xxx
# stash@{1}: WIP on main: def5678 yyy
# 恢复指定的
git stash apply stash@{1}
# 删除
git stash drop stash@{0}
给stash加个描述,不然多了分不清:
git stash push -m "用户模块写了一半"
reflog:后悔药
误操作把commit搞丢了?别慌,git其实都记着。
git reflog
会显示所有操作历史,包括那些"丢失"的commit:
abc1234 HEAD@{0}: reset: moving to HEAD~1
def5678 HEAD@{1}: commit: 重要的提交
找到要恢复的commit hash,checkout或reset回去就行:
git checkout def5678
# 或
git reset --hard def5678
reflog默认保留90天,只存在本地,是最后的救命稻草。
几个实用alias
配到~/.gitconfig里:
[alias]
co = checkout
br = branch
ci = commit
st = status
# 好看的log
lg = log --oneline --graph --decorate
# 上次commit改了啥
last = log -1 --stat
# 撤销上次commit但保留改动
undo = reset --soft HEAD~1
# 暂存所有并commit
ac = !git add -A && git commit -m
用起来:
git lg
git last
git undo
git ac "fix: 修复登录问题"
几个坑
公共分支别rebase
rebase会改变commit历史。你rebase了,别人pull的时候会很惨,各种冲突。
自己的分支随便rebase,公共分支别动。
force push要小心
# 这个会覆盖远程,别人的提交可能丢失
git push -f
# 稍微安全一点,只有远程没新提交才会成功
git push --force-with-lease
merge还是rebase
这个争论没意义。merge保留完整历史,rebase保持线性。看团队规范,统一就行。
我的习惯:
- 自己feature分支同步main:rebase
- feature合到main:merge(保留分支历史)
- hotfix同步到多个分支:cherry-pick
Git的命令很多,但真正常用的就这些。把这几个场景搞明白,基本够用了。
遇到不确定的操作,记得先备份分支:
git branch backup-xxx
有了后悔药,心里踏实。