Learn Git in 30 days——第 18 天:修正 commit 過的版本歷史記錄 Part 1

寫的非常好的一個Git系列文章,強烈推薦

原文鏈接:https://github.com/doggy8088/Learn-Git-in-30-days/tree/master/zh-cn

當你使用 Git 進行版本控制時,我們會利用 git commit 建立許多版本,由於 Git 屬分佈式版本控制機制,對於版本控制方面沒有太多的權限設計,跟其他如 Subversion 或 TFVC 這類版控系統相比,Git 提供更多「修正版本記錄」的機制,讓你在「分享」版本給其他人的時候,能夠預先做個整理。

版本控制的基本原則

我們在進行版本控制時,無論是 Git, Subversion 或 TFVC 都一樣,維持一個良好的版本記錄有助於我們追蹤每個版本的更新曆程 (當有需要做這件事的時候)。以我個人的經驗,我們很難有機會,也不太想去追蹤我們某個項目中軟件開發的進程,我們許多項目累積的版本記錄數量有多達數千筆,誰會有這種閒工夫去追查歷史呢?

然而實際上,當軟件的臭蟲(Bug)發生的時候,我們會需要去追蹤特定臭蟲的歷史記錄,以查出該臭蟲真正發生的原因,這個時候就是版本控制帶來最大價值的時候。

也因此,要怎樣維持一個好的「版本記錄」也是非常重要的,這邊有一些控制原則可以分享給大家:

  • 做一個小功能修改就建立版本,這樣才容易追蹤變更
  • 千萬不要累積一大堆修改後才建立一個「大版本」
  • 有邏輯、有順序的修正功能,確保相關的版本修正可以按順序提交(commit),這樣才便於追蹤

不過,人非聖賢、孰能無過,哪個人能確保團隊所有人都能時時刻刻照着上述原則進行版控?哪個人不是「想到哪改到哪」呢?這樣的要求變得有點緣木求魚、不切實際。所以,我們需要有一套「修改版本」的機制,讓版本提交到遠端服務器上的時候,就已經是完美的版本狀態。

修正 commit 歷史記錄的理由

到目前爲止,我還沒提到關於「遠端倉庫」的細節,所以大部分的 Git 操作都還專注在本地端,也就是在工作目錄下的版本管控,這個倉庫就位於你的 .git/ 目錄下。然而,之後我們即將提到「遠端倉庫」的應用,到時就不只一個人擁有倉庫,所需要注意的細節也就更多。

完全開放每個人都能夠任意的修正 commit 歷史記錄,這個概念對於熟悉 Subversion 或 TFVC 的人來說或許聽起來非常很奇怪,因爲以往大家都集中連接到版本控制的服務器上,用的是集中式的倉庫,如果有人可以任意竄改歷史記錄,那版控還叫做版控嗎?

其實在 Git 版本控制中,概念是一樣的,只要同一份倉庫有多人共用的情況下,若有人任意竄改版本,那麼 Git 版本控制一樣會無法正常運作。

所以,到底什麼樣的使用情境會需要去修改版本記錄呢?以下幾點各位可以參考看看。

假設我們現在有 [A] -> [B] -> [C] 三個版本:

  • 可能 [C] 版本你發現 commit 錯了,必須刪除這一版本所有變更
  • 你可能 commit 了之後才發現 [C] 這個版本其實只有測試代碼,你也想刪除他
  • 其中有些版本的記錄消息有錯字,你想修改消息文字,但不影響文件的變更歷程
  • 你可能想把這些版本的 commit 順序調整爲 [A] -> [C] -> [B],讓版本演進更有邏輯性
  • 你發現 [B] 這個版本忘了加入一個重要的文件就 commit 了,你想事後補救這次變更
  • 在你打算「分享」分支出去時,發現了代碼有瑕疵,你可以修改完後再分享出去

修正 commit 歷史記錄的注意事項

Git 保留了「修改版本歷史記錄」的機制,主要是希望你能在「自我控制版本」到了一定程度後,自己整理一下版本記錄的各種信息,好讓你將版本「發佈」出去後,讓其他人能夠更清楚的理解你對這些版本到底做了哪些修改。

所以,修改版本歷史記錄時,有些事情必須特別注意:

  • 一個倉庫可以有許多分支 (預設分支名稱爲 master)
  • 分享 Git 原始碼的最小單位是以「分支」爲單位
  • 你可以任意修改某個支線上的版本,只要你還沒「分享」給其他人
  • 當你「分享」特定分支給其他人之後,這些「已分享」的版本歷史記錄就別再改了!

準備本日練習用的版本庫

之前我們曾在【第 04 天:常用的 Git 版本控制指令】學過 git reset 的用法,主要用來 重置目前的工作目錄。不過,相同的指令,也可以用來修正版本歷史記錄。

在開始說明前,我們一樣先用以下指令建立一個練習用的工作目錄與本地倉庫:

mkdir git-reset-demo
cd git-reset-demo
git init

echo. > a.txt
git add .
git commit -m "Initial commit (a.txt created)"

echo 1 > a.txt
git add .
git commit -m "Update a.txt!"

echo 1 > b.txt
git add .
git commit -m "Add b.txt!"
 

image

以上建立了三個版本,執行 git log 的結果如下圖示:

image

刪除最近一次的版本

我們參考上圖,用文字表達這三個版本的順序如下:

[83a841] > [0576e0] > [aef2a5] 
 

現在,我想把最後一個版本刪除,變成:

[83a841] > [0576e0]
 

那麼,你可以執行 git reset --hard "HEAD^" 即可刪除 HEAD 這個版本: 請注意:在「命令提示字元下」 ^ 是特殊符號,所以必須用雙引號括起來!

image

此時你可以看見,原本的最新版被刪除了,那是因爲剛剛我們執行 git reset --hard "HEAD^" 這個動作,把 HEAD 指向的位址改到了前一個版本 ( HEAD^ ),所以你打 git log 就看不到這個版本了。

事實上,原本你感覺被刪除的版本,其實一直儲存在 Git 的物件儲存區(object storage)裏,也就是這筆資料一直躺在 .git\objects\ 目錄下。我們還是可以用 git show 83a841 取得該版本 ( 即 commit 物件 ) 的詳細資料:

image

刪除最近一次的版本,但保留最後一次的變更

還記得嗎?無論你對 Git 倉庫做了什麼事,都是可以還原的,只要執行 git reset --hard ORIG_HEAD 即可。

image

另一個刪除版本的技巧,則是「刪除最近一次的版本記錄,但留下最後一次版本變更的異動內容」,這時你可以執行 git reset --soft "HEAD^" 達成這個任務:

image

這代表着,你可以保留最後一次的變更,再加上一些變更後,重新執行 git commit 一次,並重新設定一個新的記錄消息。

重新提交一次最後一個版本 (即 HEAD 版本)

如果你發現不小心執行了 git commit 動作,但還有些文件忘了加進去 (git add [filepath]) 或只是記錄消息寫錯,想重新補上的話,直接執行 git commit --amend 即可。這個動作,會把目前記錄在索引中的變更文件,全部添加到當前最新版之中,並且要求你修改原本的記錄消息。

我們再執行一次 git reset --hard ORIG_HEAD 復原到原本的狀態。

底下我試着多新增一個 c.txt 文件上去,然後直接執行 git commit --amend 命令,這時會跳出指定的文字編輯器進行編輯,且預設會把目前這次的消息也給填上,你只要修改一下就可以了

image

我把記錄消息修改成以下文字,並且存檔後退出,版本就會建立完成:

Add b.txt!
Add c.txt!
 

執行的結果如下,但最值得注意的是,最新版的 HEAD 已經是完全不同的 commit 物件了,所以用 git log 所看到的 commit 物件絕對名稱跟之前已經不一樣了。

image

今日小結

今天簡單的學到如何對【最新版】(HEAD)進行版本的變更,大多用在不小心 git commit 錯的情況,事實上還會有更多調整版本歷史記錄的方式,這些會在之後的文章中出現。

我重新整理一下本日學到的 Git 指令與參數:

  • git reset --hard "HEAD^"
  • git reset --soft "HEAD^"
  • git reset --hard ORIG_HEAD
  • git commit --amend
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章