← 上一章:【狀況題】我可以從過去的某個 Commit 再長一個新的分支出來嗎? 下一章:【狀況題】把多個 Commit 合併成一個 Commit →


【狀況題】修改歷史訊息

要修改歷史訊息,在「【狀況題】修改 Commit 紀錄」章節曾提過可使用 --amend 參數來修改最後一次 Commit 的訊息,但這僅限於最後一次,如果想要修改其它更早的訊息,就得使用別的方法了。

前面曾經介紹過的 git rebase 指令,它有一個很厲害的互動模式,接下來這幾個章節都是介紹怎麼使用這個模式來修改過去的歷史。先看一下目前的狀況:

$ git log --oneline
27f6ed6 (HEAD -> master) add dog 2
2bab3e7 add dog 1
ca40fc9 add 2 cats
1de2076 add cat 2
cd82f29 add cat 1
382a2a5 add database settings
bb0c9c2 init commit

互動模式,啟動!

讓我們使用 rebase 指令來整理一下吧:

$ git rebase -i bb0c9c2

-i 參數是指要進入 Rebase 指令的「互動模式」,而後面的 bb0c9c2 是指這次的 Rebase 指令的應用範圍會「從現在到 bb0c9c2 這個 Commit」,也就是最一開始的那個 Commit。這個指令會跳出一個 Vim 編輯器:

pick 382a2a5 add database settings
pick cd82f29 add cat 1
pick 1de2076 add cat 2
pick ca40fc9 add 2 cats
pick 2bab3e7 add dog 1
pick 27f6ed6 add dog 2

# Rebase bb0c9c2..27f6ed6 onto bb0c9c2 (6 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

說明:

  1. 上面的順序,跟 git log 指令的結果是相反的,但在 SourceTree 介面是一樣的喔。
  2. 前面的 pick 的意思是「保留這次的 Commit,不做修改」,其它指令在稍後會再介紹。

這裡,我把這兩行的內容:

pick cd82f29 add cat 1
pick 1de2076 add cat 2

前面的 pick 改成 reword,或是懶得打字也可以只用 r 就好:

reword cd82f29 add cat 1
reword 1de2076 add cat 2

表示待會我要來修改這兩次 Commit 的訊息。存檔並離開之後,立馬就會再跳另一個 Vim 編輯器畫面:

rebase reword

還記得剛剛我們說要對那兩次的 Commit 修改訊息嗎?這就是第一次的 reword。我把內容改成 add cat "kitty",並存檔、離開,它又陰魂不散的跳出第二個 Vim 編輯器畫面:

rebase reword

這是第二次的 reword,這回我把內容編輯成 add cat "sherly",存檔、離開之後,Git 就會完成剩下的工作:

$ git rebase -i bb0c9c2
[detached HEAD 76271f2] add cat "kitty"
 Date: Sun Aug 20 05:08:25 2017 +0800
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 cat1.html
[detached HEAD 7121ac2] add cat "sherly"
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 cat2.html
Successfully rebased and updated refs/heads/master.

這時用 SourceTree 看一下歷史紀錄:

rebase reword

就這樣把那兩次的紀錄修掉囉。

只是改訊息,不會怎樣吧?

看起來好像只有改訊息,但這個並不是只有單純的改字這麼單純。如果你仔細看,那兩次 Commit 的 SHA-1 值都變了,原本是 cd82f291de2076,現在變成 76271f27121ac2,這兩次的 Commit 根本就是全新的 Commit 物件了。

在「另一種合併方式(使用 rebase)」章節也曾介紹到在進行 Rebase 的時候,Commit 物件並不是剪下、貼上而已,而因為要接的前一個 Commit 不同(其實時間也不同),所以會重新計算並做出一顆新的 Commit。

這裡也是一樣,看起來只是改字,但因為 Commit 物件的訊息也會影響 SHA-1 的計算,因為訊息改了,所以 Git 會做出新的 Commit 物件來替代原本舊的 Commit。

不只這樣,在剛剛這個例子裡,因為這兩顆 Commit 物件換掉了,在它之後的 Commit 因為前面的歷史改了,所以後面整串的 Commit 全部都重新做出新的 Commit 出來替代舊的 Commit。

看過卡通「多啦 A 夢」的人應該都知道,當改變了過去的歷史之後,可能會造出新的平行時空出來,大概就跟剛剛這個概念差不多吧。

啊,那想取消剛剛這次 Rebase 的話…

在「另一種合併方式(使用 rebase)」跟「【冷知識】Git 怎麼知道現在是在哪一個分支?」這兩個章節都有介紹過關於 ORIG_HEAD 這東西,如果想要取消這回的 Rebase 的話,只要這樣:

$ git reset ORIG_HEAD --hard

就會回到 Rebase 之前囉。

使用 SourceTree

使用 SourceTree 來修改歷史訊息會比使用指令來得簡單一些,就選擇你想要修改的範圍,在指定的 Commit 上按滑鼠右鍵,選擇「Rebase children of SHA-1 interactively…」:

rebase reword

接著找到你想要修改訊息的 Commit 上,按滑鼠右鍵選擇「Edit message…」:

rebase reword

編輯內容…

rebase reword

可以再繼續選擇其它 Commit 繼續進行修改訊息,待全部都完成之後,按下右下角的 OK 鈕,即可開始進行 Rebase。


← 上一章:【狀況題】我可以從過去的某個 Commit 再長一個新的分支出來嗎? 下一章:【狀況題】把多個 Commit 合併成一個 Commit →

Comments