博客 / 詳情

返回

Git高級技巧:rebase、cherry-pick、bisect實戰

用了好幾年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

有了後悔藥,心裏踏實。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.