Git 如何遺棄已經 Push 的提交

做者:咕咚移動技術團隊-nanchengit

題目看起來很像是提供解決方案的文章,但實際上我並不會給你們直接提供解決方案,咱們追求的歷來不該該是答案,而是探索的過程。固然,若是你只想查看答案的話,請直接拉到文章最底部。vim

寫在前面

相信你們都知道,Git 相比於 SVN,優點不言而喻,以至於如今大多數公司的項目都在採用 Git 進行管理。做爲一個開發人員,對 Git 的使用天然應該是駕輕就熟。post

若是你還不會使用 Git 的話,那我勸你仍是不要聲張,好好的去學習一番,再本身弄個實驗項目走一下流程,以避免遭到同事的鄙視。學習

每一個公司都會有本身不同的 Git 分支管理規範,特別是在開發人員較多的公司,Git 的分支管理規範就顯得更加劇要。前面比較出名的 Git Flow 分支管理策略相信很多人都已經瞭解了,不熟悉的固然也能夠去看看:nvie.com/posts/a-suc…code

分支管理

Git Flow 管理方式把項目分爲 5 條線,一般會是下面的管理方式。cdn

  • Master:做爲穩定主分支,長期有效。不能夠在此分支進行任何提交,只能接受從 Hotfix 分支或者 Release 分支發起的 merge request,該分支上的每個提交都對應一個 Tag。
  • Develop:開發主分支,長期有效。不能夠在此分支上作任何提交,只接受從 Feature 分支發起的 merge request。全部的 Alpha Release 都應該在這個分支發佈。
  • Feature:功能分支,生命週期爲產品迭代週期,每一個分支對應一期的需求。只能夠從 Develop 分支進行 Kick Off。能夠 merge Release 分支的代碼,生命週期結束後,須要 merge 回 Develop 分支。方式須要採用 merge request。
  • Release:發佈分支,聲明週期重新需求的預發佈到正式發佈,每個分支對應一個新版本的版本號。只能夠從 Develop 分支 Kick Off。聲明週期結束後,須要 Merge 回 Master 及 Develop 分支,方式一樣須要採用 merge request。全部的 Beta Release 均須要在該分支發佈。
  • Hotfix:熱修復分支,生命週期對應一個或者多個須要緊急修復並上線的 Bug,每個分支對應一個小版本號。只能夠從 Master 分支進行 Kick Off。聲明週期結束後,須要 merge 回 Master 分支和 Develop 分支,方式固然也是採用 merge request。

實際上,若是你熟悉 Git 的話,你會很快發現上面的管理方式會存在歷史提交很是混亂的缺點,但以爲不失爲一個 Git 分支管理的經典。實際上,咱們能夠用 rebase 去替換 merge 讓 commit 看起來更加清晰。對 rebase 和 merge 的優劣對比這裏暫不作講解,感興趣的能夠直接 Google 搜索。blog

下面就給你們分享一下發生在咕咚項目的一次坑爹的 Git 體驗。生命週期

從 git revert 提及

咕咚項目組並無對開發者限制 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 操做。

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 區域中,以便後面找出證據。

說到這,彷佛咱們已經有了思路。

  1. 使用 git reset --soft <revert 操做的 commit ID> 回退到 revert 操做的版本;
  2. 使用 git reset --hard <revert 操做的前一個 commit> 幹掉那次 revert 提交;
  3. 最後再把 stage 區域的全部改動匯聚成一個新的提交 commit 到咱們的項目倉庫中。

固然,細心的你必定會發現,在第 1 步操做後,咱們還必須執行 git stash 命令把全部的改動存到暫存區,再在第 2 步操做後使用 git stash pop 命令取出來,直接進行第 2 步操做確定仍是會毀滅證據的。

咱們後面的提交不見了。

這樣彷佛能夠解決咱們的問題,不過有個弊端:咱們後面那麼多的提交被合併成一個提交了,之後咱們就沒辦法看到了,萬一...

很多小夥伴會想到進階方案:

  1. 對 "feature8.29.0_nanchen" 的最新代碼 checkout -b 一個分支 feature_copy;
  2. 而後使用 git checkout feature8.29.0_nanchen 回到咱們的分支;
  3. 而後直接對當前分支 reset 到 revert 的前一個 commit 後,咱們採用 cherry-pick 方式進行傻瓜式改寫即可以把歷史重寫了。(誰說的咱們不能改寫歷史?)

改寫歷史?

改寫歷史?等等,好像還有一個操做: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 歷史編輯頁面,而後進行歷史改寫處理!

相關文章
相關標籤/搜索