做者:咕咚移動技術團隊-nanchengit
題目看起來很像是提供解決方案的文章,但實際上我並不會給你們直接提供解決方案,咱們追求的歷來不該該是答案,而是探索的過程。固然,若是你只想查看答案的話,請直接拉到文章最底部。vim
相信你們都知道,Git 相比於 SVN,優點不言而喻,以至於如今大多數公司的項目都在採用 Git 進行管理。做爲一個開發人員,對 Git 的使用天然應該是駕輕就熟。post
若是你還不會使用 Git 的話,那我勸你仍是不要聲張,好好的去學習一番,再本身弄個實驗項目走一下流程,以避免遭到同事的鄙視。學習
每一個公司都會有本身不同的 Git 分支管理規範,特別是在開發人員較多的公司,Git 的分支管理規範就顯得更加劇要。前面比較出名的 Git Flow 分支管理策略相信很多人都已經瞭解了,不熟悉的固然也能夠去看看:nvie.com/posts/a-suc…code
Git Flow 管理方式把項目分爲 5 條線,一般會是下面的管理方式。cdn
實際上,若是你熟悉 Git 的話,你會很快發現上面的管理方式會存在歷史提交很是混亂的缺點,但以爲不失爲一個 Git 分支管理的經典。實際上,咱們能夠用 rebase 去替換 merge 讓 commit 看起來更加清晰。對 rebase 和 merge 的優劣對比這裏暫不作講解,感興趣的能夠直接 Google 搜索。blog
下面就給你們分享一下發生在咕咚項目的一次坑爹的 Git 體驗。生命週期
咕咚項目組並無對開發者限制 Develop 分支和 Master 分支的權限,咱們暫時並無一個專門作代碼 Review 和 PR 的角色,其實必定意義上也提現了團隊對每一個人的信任。開發
咱們依然會基於 Develop 作開發主線,每一個需求迭代期,團隊成員會從 Develop 拉取本身的分支,並命名於 feture/XX,而後各自在本身的分支上進行開發。get
因爲你們開發業務上的不一樣,因此在需求開發完畢,整合代碼到 Develop 分支的時候,通常不會出現太多衝突的狀況。
而我這邊交接一個需求時,採用 merge 的時候出現了一個奇怪的問題,咱們姑且來重現一下事故現場。
首先使用 git branch
查看一下當前咱們的本地分支。
這裏先簡單提一下咱們要作的操做。
"feature8.28_buyGifts" 是咱們同事的分支,基於 "release8.27.0" 拉取,而 "feature8.29.0_nanchen" 是個人分支,基於 "release8.28.0" 分支拉取,因此我這邊的分支包含了最新的代碼。
如今因爲某些緣由,我須要把同事的 "feature8.28_buyGifs" 分支代碼合併到個人分支上,直接接手他的代碼進行開發。
就不要吐槽爲啥不按照功能搞分支開發了,緣由是由於他那邊代碼基本已經完成,如今只須要少許修改。
因此咱們就採用 git merge <branch>
命令進行 merge 操做。
咱們用 git status
更容易看明白衝突了什麼。
能夠看到,上面衝突的文件全是和同事開發的需求出現的衝突,因此出現這個衝突其實使人很是懊惱,由於是不可能有其餘同事改動到這些文件的。
爲了驗證本身的想法,咱們隨意打開一個文件查看。這裏就採用 vim <filename>
查看第一個文件。
正如咱們所想,確實和同事編寫的需求 Presents
類有關係,但看衝突內容就更一臉懵逼了,由於看起來,這應該是一個不會衝突的 merge。
因而趕忙使用 git merge --abort
撤銷此次 merge。再在 "origin/feature8.29.0_nanchen" 查看咱們剛剛的文件提交歷史。
能夠很清晰的看到,確實是最近沒有任何的修改記錄。
一個 7 個月都沒人動的文件,竟然 merge 的時候發生了衝突!這讓我一臉懵逼。(手動黑人問號)
使用 git lg
查看一下該分支的提交歷史,咱們但願從中能獲得某些思路。
注意其中紅框中的 commit,咱們這位同事以前想往 "release8.28.0" 合併他分支的代碼,後面又由於某些緣由,但願撤銷此次提交,他採用了 revert 進行處理。雖然 revert 對文件沒有提交記錄,但 Git 卻認爲咱們在當前分支更改了這些文件,因此在咱們 git merge
的時候,Git 認爲這是一次衝突,並選擇了告知咱們。
如若如咱們所想,那咱們只須要撤銷此次 revert 操做便可。
咱們固然知道,能夠經過 reset 命令放棄此次提交,但這裏後面已經有了很是多的 commit,顯然咱們這樣是不行的,咱們須要另闢蹊徑。
最容易想到的大概就是直接在 merge 的時候解決衝突了,但經過一系列查看之後,咱們發現文件改動量很是大,直接解決衝突並不是易事。因此咱們仍是得 想辦法取消掉此次 revert 的 commit,再進行 merge。
咱們知道,代碼回滾有三種方式:reset、checkout,還有咱們的 revert。直觀感覺,咱們應該在 reset 上想辦法。
咱們來看看 reset 有些怎樣的操做方法。
主要想給你們講講:--soft 和 --hard 的區別。
咱們常常會用到 git reset --hard <commit>
作「毀屍滅跡」的操做,經常爽到不能自已,由於這不只能夠回退到咱們想要的版本,並且還「直接丟棄」了後面提交的代碼,真正的「毀屍滅跡」級別的操做。
而另一個 --soft 處理,實際上還具有點人性,雖然一樣能夠回退到咱們想要的版本,但目標版本後面的提交都還會存放在 stage 區域中,以便後面找出證據。
說到這,彷佛咱們已經有了思路。
git reset --soft <revert 操做的 commit ID>
回退到 revert 操做的版本;git reset --hard <revert 操做的前一個 commit>
幹掉那次 revert 提交;固然,細心的你必定會發現,在第 1 步操做後,咱們還必須執行 git stash
命令把全部的改動存到暫存區,再在第 2 步操做後使用 git stash pop
命令取出來,直接進行第 2 步操做確定仍是會毀滅證據的。
這樣彷佛能夠解決咱們的問題,不過有個弊端:咱們後面那麼多的提交被合併成一個提交了,之後咱們就沒辦法看到了,萬一...
很多小夥伴會想到進階方案:
git checkout feature8.29.0_nanchen
回到咱們的分支;改寫歷史?等等,好像還有一個操做:rebase。
rebase 是 Git 的一個神奇的命令,前面我也說了,總會有人不喜歡 merge 以後歷史的分叉,這種分叉再匯合後會讓結構看起來很是混亂,以至於沒法管理。若是你不喜歡 commit 歷史出現分叉,那 rebase 絕對是你的救星。
改寫歷史是 rebase 與生俱來的能力。咱們能夠用 git rebase -i <commit>
進行歷史的改寫。
咱們試試看在咱們的項目中直接使用 git rebase -i <commit>
會怎樣。
咱們會拿到分支後面的提交歷史,而且前面還有一個 Commands。咱們能夠從提示中看到,上面全寫的 pick 就是表明保持這個提交的意思,edit 表明編輯這次提交...
咱們但願刪除這次 revert 此次提交,那固然咱們最關心的就是 drop 了,甚至咱們能夠更加簡單粗暴:直接刪掉這一行。
而後咱們便開始處理了。
過程當中可能會出現衝突,咱們只須要解決就好。
解決掉衝突後,再使用 git add <filename>
把它們 merge 進去。
oh,咱們看到咱們已經 rebase 成功了。咱們再使用 git lg
查看一下提交歷史。
咱們成功改寫了歷史!
歷史改寫結束,咱們還要作咱們最開始想作的事情,進行 merge 操做。
能夠看到,此次咱們 merge 確實如咱們預期的再也不發生衝突,方案親測有效!
寫了這麼多,想必你們對解決方案也算比較清楚了。咱們主要即是採用 git rebase -i <>
操做進入到 commit 歷史編輯頁面,而後進行歷史改寫處理!