用了好幾年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
有了後悔藥,心裏踏實。