轉自:juejin.im/post/5e377e…git
從接觸編程就開始使用 Git 進行代碼管理,先是本身玩 Github,又在工做中使用 Gitlab,雖然使用時間挺長,但是也只進行一些經常使用操做,如推拉代碼、提交、合併等,更復雜的操做沒有使用過,看過的教程也逐漸淡忘了,有些對不起 Linus 大神。編程
出來混老是要還的,前些天就遇到了 Git 裏一種十分糟心的場景,併爲以前沒有深刻理解 Git 命令付出了一下午時間的代價。bash
先介紹一下這種場景,咱們一個項目從 N 版本升到 A 版本時引入了另外一項目的 jar 包,又陸續發佈了 B、C 版本app
但在 C 版本後突然發現了 A 版本引入的 jar 包有極大的性能問題,B、C 版本都是基於 A 版本發佈的,要修復 jar 包性能問題,等 jar 包再發版還得幾天,可此時線上又有緊急的 Bug 要修,因而就陷入了進退兩難的境地。工具
最後決定先將代碼回退到 A 版本以前,再基於舊版本修復 Bug,也就開始了五個小時的受苦之路。gitlab
首先確定的是 revert,git revert commit_id
能產生一個 與 commitpost
可是使用 git log
查看了提交記錄後,我就打消了這種想法,由於提交次數太多了,中途還有幾回從其餘分支的 merge 操做。性能
」利益於」咱們不太乾淨的提交記錄,要完成從 C 版本到 N 版本的 revert,我須要倒序執行 revert 操做幾十次,若是其中順序錯了一次,最終結果可能就是不對的。學習
另外咱們知道咱們在進行代碼 merge 時,也會把 merge 信息產生一次新的提交,而 revert 此次 merge commit
時須要指定 m 參數,以指定 mainline
ui
這個 mainline 是主線,也是咱們要保留代碼的主分支,從 feature 分支往 develop 分支合併,或由 develop 分支合併到 master 的提交還好肯定,但 feature 分支互相合並時,我哪知道哪一個是主線啊。
因此 revert 的方案被廢棄了。
而後就考慮 reset 了, reset 也能使代碼回到某次提交,但跟 revert 不一樣的是, reset 是將提交的 HEAD 指針指到某次提交,以後的提交記錄會消失,就像從沒有過這麼一次提交。
但因爲咱們都在 feature 分支開發,我在 feature 分支上將代碼回退到某次提交後,將其合併到 develop 分支時卻被提示報錯。
這是由於 feature 分支回退了提交後,在 git 的 workflow 裏,feature 分支是落後於 develop 分支的
而合併向 develop 分支,又須要和 develop 分支保持最新的同步,須要將 develop 分支的數據合併到 feature 分支上,而合併後,原來被 reset 的代碼又回來了。
這個時候另外一個可選項是在 master 分支上執行 reset,使用 --hard 選項徹底拋棄這些舊代碼,reset 後再強制推到遠端。
master> git reset --hard commit_id
master> git push --force origin master複製代碼複製代碼
可是仍是有問題,首先,咱們的 master 分支在 gitlab 裏是被保護的,不能使用 force push
,畢竟風險挺大了,萬一有人 reset 到最開始的提交再強制 push 的話,雖然可使用 reflog 恢復,但也是一番折騰。
另外,reset 畢竟太野蠻,咱們仍是想能保留提交歷史,之後排查問題也能夠參考。
只好用搜索引擎繼續搜索,看到有人提出能夠先使用 rebase 把多個提交合併成一個提交,再使用 revert 產生一次反提交,這種方法的思路很是清晰,把 revert 和 rebase 兩個命令搭配得很好,至關於使用 revert 回退的升級版。
先說一下 rebase,rebase
是」變基」的意思,這裏的」基」,在我理解是指[屢次] commit 造成的 git workflow,使用 rebase,咱們能夠改變這些歷史提交,修改 commit 信息,將多個 commit 進行組合。
介紹 rebase 的文檔有不少,咱們直接來講用它來進行代碼回退的步驟。
git rebase -i N, -i
指定交互模式後,會打開 git rebase 編輯界面,形如: pick 6fa5869 commit1
pick 0b84ee7 commit2
pick 986c6c8 commit3
pick 91a0dcc commit4複製代碼複製代碼
在合併 commit 這個需求裏,咱們能夠選擇 pick(p) 最舊的 commit1,而後在後續的 commit_id 前添加 squash(s) 命令,將這些 commits 都合併到最舊的 commit1 上。
若是出錯了,也可使用 git rebase --abort/--continue/--edit-todo
對以前的編輯進行撤銷、繼續編輯。
而 F 分支上的提交記錄是 older, commit5,因爲 F 分支的祖先節點是 older,明顯落後於主分支的 commit4,將 F 分支向主分支合併是不容許的
因此咱們須要執行 git merge master 將主分支向 F 分支合併,合併後 git 會發現 commit1 到 commit4 提交的內容和 F 分支上 commit5 的修改內容是徹底相同的,會自動進行合併,內容不變,但多了一個 commit5。
這種方法的取巧之處在於巧妙地利用了 rebase 操做歷史提交的功能和 git 識別修改相同自動合併的特性,操做雖然複雜,但歷史提交保留得還算完整。
rebase 這種修改歷史提交的功能很是實用,可以很好地解決咱們遇到的一個小功能提交了好屢次纔好使,而把 git 歷史弄得亂七八糟的問題,只須要注意避免在多人同時開發的分支使用就好了。
遺憾的是,當天我並無理解到 rebase 的這種思想,又因爲試了幾個方法都不行太過於慌亂,在 rebase 完成後,向主分支合併被拒以後對這些方式的可行性產生了懷疑,又加上有同事提出聽起來更可行的方式,就中斷了操做。
這種更可行的方式就是對文件操做,而後讓 git 來識別變動,具體是:
這種方式的巧妙之處在於利用 git 自己對文件的識別,不牽涉到對 workflow 操做。
最後終於靠着文件操做方式成功完成了代碼回退,過後想來真是一把心酸淚。
爲了讓個人五個小時不白費,覆盤一下當時的場景,學習並總結一下四種代碼回退的方式:
git 真的是很是牛逼的代碼管理工具,入手簡單,三五個命令組合起來就足夠完成工做需求,又對 geeker 們很是友好,你想要的騷操做它都支持,學無止境啊。