原文:https://www.mdeditor.tw/pl/pMHD
使用git參與多人之間的合作開發大概有三年的時間,大多數場景下使用的git命令一隻手多一點就能數的過來
理論上來說,只要能合理管理項目分支,這幾個命令已經足以應付所有的日常開發工作。但是如果我們偶爾看一下自己的git graph,我的天吶,為什麼會這麼亂。
鑑於分支管理的混亂(或者根本就沒有進行過分支管理),我們經常遇到一些意想不到的問題,因此需要使用很多面生的git命令來解決我們的問題,比如說本文講到的git rebase。
git rebase 和 git merge 區別Git rebase 的中文名是變基,就是改變一次提交記錄的base。在這一環節,我們不妨帶著這樣一個假設:git rebase ≈ git merge,並用兩種命令實現同一工作流來對比他們之間的異同。
回想我們日常的工作流,假設a和b兩人合作開發,三個分支:develop, develop_a, develop_b。兩個人分別在develop_a和develop_b分支上進行日常開發,階段性地合入到develop。
那麼從a的角度來看,可能的工作流是這樣的:
(1)個人在develop_a分支上開發自己的功能
(2)在這期間其他人可能不斷向develop合入新特性
(3)個人功能開發完畢後通過merge 的方式合入別人開發的功能
git merge上圖為日常merge 工作流,對應的git操作命令如下:git checkout develop_a
// 本地功能開發...
git pull origin develop = git fetch origin develop + git merge develop複製代碼
git rebase同樣走完這樣一個工作流如果我們使用git rebase來實現,結果如下:
git rebase 之前,如圖:
git rebase 之中,如圖:
git rebase 之後,如圖:
git rebase 操作對應命令如下:
git checkout develop_a
// 本地功能開發...
git fetch origin develop
git rebase develop
git checkout develop
git merge develop_a
git br -d develop_a 複製代碼由此可見,git rebase 和git merge的異同之處如下:
(1)兩者都可以用於本地代碼合併
(2)git merge 保留真實的用戶提交記錄,且在merge時會生成一個新的提交
(3)git rebase 會改寫歷史提交記錄,這裡的改寫不僅限於樹的結構,樹上的節點的commit id也會別改寫,因此圖3和圖4用e'代表圖2的e',收益是可以保證提交記錄非常清爽
如何使用git rebase -i 修改歷史提交記錄git rebase -i,中文名叫交互式變基。意思就是在變基的過程中是可以摻入用戶交互的,通過交互過程我們可以主動改寫歷史提交記錄,包括修改、合併和刪除等。我們以上面使用rebase後得到的提交記錄為例,來進行歷史提交記錄的修改,在修改之前,提交記錄是這個樣子的。
使用git rebase -i 修改歷史提交的過程主要包含三步:
(1)列出一個提交記錄的範圍,並指出你在這個範圍內需要對哪些記錄進行什麼樣的修改
(2)以次執行上述的修改,如果遇到衝突需要解決
(3)完成rebase 操作
以上面截圖中的提交記錄為例,來對歷史提交的commit msg進行修改,操作步驟如下:
// 查看最近6次提交記錄,選擇對哪一條記錄進行修改
git rebase -i HEAD~6複製代碼執行完上述命令後,會以vim的方式打開一個文件,文件中顯示了最近6次的提交信息,從上到下,由遠到近。
從下面的注釋可以看到,我們分別把每一行前面的pick修改成r, s, d的方式就可以實現對歷史記錄的修改,合併和刪除。
首先我們嘗試修改提交信息,把第二行前面的pick改成r,保存退出。當前頁面關閉的同時會打開一個新的頁面,讓你對選中的提交信息進行編輯。
編輯完信息之後保存退出,就完成了對歷史提交記錄的修改。通過觀察下圖可以發現,develop_a的提交記錄中的commit msg 仍然是feat_c,但是develop 分支中對應的提交記錄,commit msg 已經變成了feat: c-update.。
這裡需要留意到的一個現象是develop 和develop_a 分支上相同提交的commit id 已經發生了變化,這個在後面會再次提到。
除了修改提交的commit msg 之外,我們也可以通過把pick 改為e,結合git reset --soft HEAD^ 的方式對檔次提交的改動內容進行修改。
合併與刪除歷史提交的操作步驟與編輯類似,只需要把pick分別改為s 和d 即可,各位看官可以自行嘗試。如果在rebase的過程中遇到了衝突,需要手工解決,然後使用git rebase --continue 完成rebase 操作。
git rebase 的提示還是非常友好的,它會告訴你需要進行哪些操作解決當前的問題。
使用git rebase -i 必須遵循的規則是什麼?從修改歷史提交記錄這個功能來看,交互式變基是一個非常強大的功能。但是使用這個功能必須要遵循一個鐵則:不要對線上分支的提交記錄進行變基!
引用git 官方指導文檔的話來說大概是這樣:
如果你遵循這條金科玉律,就不會出差錯。否則,人民群眾會仇恨你,你的朋友和家人也會嘲笑你,唾棄你。
在說為什麼不能對線上提交執行交互式變基之前,先說一下如果要對線上功能執行這個操作要怎麼做。
首先,你需要在自己本地變基成功,然後使用git push -f 強行push 並覆蓋遠程對應分支,之所以需要執行覆蓋式push 是因為如果你不覆蓋,當前變基過後產生的新提交會與遠程合併,導致你在本地的變基行為失去意義。
因為我們上面提到過,從變基那個節點開始往後的所有節點的commit id 都會發生變化。
同樣的原因,即使你使用git push -f 使遠程分支發生了變基,如果你的同事的開發分支中還存在你執行變基操作(不論是修改、合併還是刪除)時針對的那些分支,那麼當你的同事merge 你的提交之後,你所有想使用變基改變的東西都回來了!
如果打破了git rebase -i 的使用規則應該如何補救此處我們嘗試通過要點描述的方式,說明線上提交執行變基會導致什麼結果以及如何避免這個結果:
(1)你在本地對部分線上提交進行了變基,這部分提交我們稱之為a,a在變基之後commit id 發生了變化
(2)你在本地改變的這些提交有可能存在於你的同事的開發分支中,我們稱之為b,他們與a的內容相同,commit id 不同
(3)如果你把變基結果強行push 到遠程倉庫後,你的同事在本地執行git pull 的時候會導致a 和b 發生融合,且都出現在了歷史提交中,導致你的變基行為無效
(4)我們想要的是你的同事拉取線上代碼時跳過對a 和b 的合併,只是把他本地分支上新增的修改合併進來
講了這麼多,最終的結論就是,使用變基解決變基帶來的問題。即你的同事使用git rebase 的方式把他本地的修改rebase 到遠程你執行過rebase 的分支上。
簡言之,就是你的同事使用git pull --rebase 而不是git pull 來拉取遠程分支。在這個操作的過程中,git 會對我們上面提到幾個要點的信息進行檢查並把真正屬於同事本地的修改合入遠程分支的最後。
文字描述可能有些乏力,更多詳細信息可以參考這裡:git-scm.com/book/zh/v2/…
所以我們應該如何使用git rebase鑑於上面描述的git rebase 可能帶來的問題,最後要回答的一個問題是我們應該如何在日常工作中使用git rebase,同樣借用git 官方文檔中的一句話:
總的原則是,只對尚未推送或分享給別人的本地修改執行變基操作清理歷史, 從不對已推送至別處的提交執行變基操作,這樣,你才能享受到兩種方式(rebase 和merge)帶來的便利。
架構擺渡人,助你通往架構師方向的領路人。本號會定期分享架構相關的文章,專注於架構方向,關注我們,下一個架構師就是你。