主頁 > 後端開發 > 如何用 Git 優雅回退代碼,別搞錯了!

如何用 Git 優雅回退代碼,別搞錯了!

2020-10-08 09:10:56 後端開發

作者:枕邊書
www.cnblogs.com/zhenbianshu/p/12018714.html

從接觸編程就開始使用 Git 進行代碼管理,先是自己玩 Github,又在作業中使用 Gitlab,雖然使用時間挺長,可是也只進行一些常用操作,如推拉代碼、提交、合并等,更復雜的操作沒有使用過,看過的教程也逐漸淡忘了,有些對不起 Linus 大神,

出來混總是要還的,前些天就遇到了 Git 里一種十分糟心的場景,并為之前沒有深入理解 Git 命令付出了一下午時間的代價,

先介紹一下這種場景,我們一個專案從 N 版本升到 A 版本時引入了另一專案的 jar 包,又陸續發布了 B、C 版本

但在 C 版本后忽然發現了 A 版本引入的 jar 包有極大的性能問題,B、C 版本都是基于 A 版本發布的,要修復 jar 包性能問題,等 jar 包再發版還得幾天,可此時線上又有緊急的 Bug 要修,于是就陷入了進退兩難的境地,

最后決定先將代碼回退到 A 版本之前,再基于舊版本修復 Bug,也就開始了五個小時的受苦之路,


基礎試探

revert

首先肯定的是 revert,git revert commit_id 能產生一個 與 commit_id 完全相反的提交,即 commit_id 里是添加, revert 提交里就是洗掉,

但是使用 git log 查看了提交記錄后,我就打消了這種想法,因為提交次數太多了,中途還有幾次從其他分支的 merge 操作,

”利益于”我們不太干凈的提交記錄,要完成從 C 版本到 N 版本的 revert,我需要倒序執行 revert 操作幾十次,如果其中順序錯了一次,最終結果可能就是不對的,

另外我們知道我們在進行代碼 merge 時,也會把 merge 資訊產生一次新的提交,而 revert 這次 merge commit 時需要指定 m 引數,以指定 mainline

這個 mainline 是主線,也是我們要保留代碼的主分支,從 feature 分支往 develop 分支合并,或由 develop 分支合并到 master 的提交還好確定,但 feature 分支互相合并時,我哪知道哪個是主線啊,

所以 revert 的文案被廢棄了,

Reset

然后就考慮 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 恢復,但也是一番折騰,推薦閱讀:大廠 Git 提交規范,

關注微信公眾號:Java技術堆疊,在后臺回復:git,可以獲取我整理的 N 篇最新Git教程,都是干貨,

另外,reset 畢竟太野蠻,我們還是想能保留提交歷史,以后排查問題也可以參考,


升級融合

rebase

只好用搜索引擎繼續搜索,看到有人提出可以先使用 rebase 把多個提交合并成一個提交,再使用 revert 產生一次反提交,這種方法的思路非常清晰,把 revert 和 rebase 兩個命令搭配得很好,相當于使用 revert 回退的升級版,

先說一下 rebase,rebase 是”變基”的意思,這里的”基”,在我理解是指[多次] commit 形成的 git workflow,使用 rebase,我們可以改變這些歷史提交,修改 commit 資訊,將多個 commit 進行組合,

介紹 rebase 的檔案有很多,我們直接來說用它來進行代碼回退的步驟,

1、首先,切出一個新分支 F,使用 git log 查詢一下要回退到的 commit 版本 N,

2、使用命令 git rebase -i N, -i 指定互動模式后,會打開 git rebase 編輯界面,形如:

pick 6fa5869 commit1
pick 0b84ee7 commit2
pick 986c6c8 commit3
pick 91a0dcc commit4

3、這些 commit 自舊到新由上而下排列,我們只需要在 commit_id 前添加操作命令即可,

在合并 commit 這個需求里,我們可以選擇 pick(p) 最舊的 commit1,然后在后續的 commit_id 前添加 squash(s) 命令,將這些 commits 都合并到最舊的 commit1 上,

4、保存 rebase 結果后,再編輯 commit 資訊,使這次 rebase 失效,git 會將之前的這些 commit 都洗掉,并將其更改合并為一個新的 commit5,

如果出錯了,也可以使用 git rebase --abort/--continue/--edit-todo對之前的編輯進行撤銷、繼續編輯,

5、這個時候,主分支上的提交記錄是 older, commit1, commit2, commit3, commit4,而 F 分支上的提交記錄是 older, commit5,由于 F 分支的祖先節點是 older,明顯落后于主分支的 commit4,將 F 分支向主分支合并是不允許的,

所以我們需要執行 git merge master 將主分支向 F 分支合并,合并后 git 會發現 commit1 到 commit4 提交的內容和 F 分支上 commit5 的修改內容是完全相同的,會自動進行合并,內容不變,但多了一個 commit5,

6、再在 F 分支上對 commit5 進行一次 revert 反提交,就實作了把 commit1 到 commit4 的提交全部回退,

這種方法的取巧之處在于巧妙地利用了 rebase 操作歷史提交的功能和 git 識別修改相同自動合并的特性,操作雖然復雜,但歷史提交保留得還算完整,

rebase 這種修改歷史提交的功能非常實用,能夠很好地解決我們遇到的一個小功能提交了好多次才好使,而把 git 歷史弄得亂七八糟的問題,只需要注意避免在多人同時開發的分支使用就行了,

遺憾的是,當天我并沒有理解到 rebase 的這種思想,又由于試了幾個方法都不行太過于慌亂,在 rebase 完成后,向主分支合并被拒之后對這些方式的可行性產生了懷疑,又加上有同事提出聽起來更可行的方式,就中斷了操作,

檔案操作

這種更可行的方式就是對檔案操作,然后讓 git 來識別變更,具體是:

  1. 從主分支上切出一個跟主分支完全相同的分支 F,

  2. 從檔案管理系統復制專案檔案夾為 bak,在 bak 內使用 git checkout N 將代碼切到想要的歷史提交,這時候 git 會將 bak 內的檔案恢復到 N 狀態,

  3. 在從檔案管理系統內,將 bak 檔案夾下 除了 .git 檔案夾下的所有內容復制粘貼到原專案目錄下,git 會純從檔案級別識別到變更,然后更新作業區,

  4. 在原專案目錄下執行 add 和 commit,完成反提交,

這種方式的巧妙之處在于利用 git 本身對檔案的識別,不牽涉到對 workflow 操作,


小結

最后終于靠著檔案操作方式成功完成了代碼回退,事后想來真是一把心酸淚,

為了讓我的五個小時不白費,復盤一下當時的場景,學習并總結一下四種代碼回退的方式:

  • revert 適合需要回退的歷史提交不多,且無合并沖突的情景,

  • 如果你可以向 master 強推代碼,且想讓 git log 里不再出現被回退代碼的痕跡,可以使用 git reset --hard + git push --force的方式,

  • 如果你有些 geek,追求用”正規而正統”的方式來回退代碼,rebase + revert 滿足你的需求,

  • 如果你不在乎是否優雅,想用最簡單,最直接的方式,檔案操作正合適,

git 真的是非常牛逼的代碼管理工具,入手簡單,三五個命令組合起來就足夠完成作業需求,又對 geeker 們非常友好,你想要的騷操作它都支持,學無止境啊,

推薦去我的博客閱讀更多:

1.Java JVM、集合、多執行緒、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/162680.html

標籤:Java

上一篇:面試官:執行緒池如何按照core、max、queue的執行循序去執行?(內附詳細決議)

下一篇:為什么 Spring Boot 2.3.0 放棄Maven最終擁抱Gradle

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more