有時候我們在執行完 commit 之後,發現有一些「小地方」應該也要記錄在同一個版本,或是發現版本訊息有錯字想要改一下,這些都算是很常見的狀況,畢竟只要是人都一定會有小疏失。
「雖然」 之前把 Commit 訊息比喻成訂單紀錄,在網購世界想操作歷史訂單,只要找到特定訂單就能修改。
不過在 Git 的世界,除了最新一筆的 Commit 之外, 沒辦法 「單獨修改」 其中一個 Commit 。
原因其實也很簡單:每個 Commit 都是緊緊相依的!
Git 的 Commit 視同「資料的變化」,每一版的「變化」都是跟「上一版」相比,更動歷史的 Commit 的行為,很有可能牽一髮而動全身,一個不小心就是所有版本全部壞掉,這句話沒有在浮誇,真的就是會這樣。
值得慶幸的是,最新的 Commit 之後已經沒有任何 Commit (廢話) ,直接操作它,並不會對任何 Commit 帶來影響,畢竟根本沒東西可以給它影響。
進入今天的主題:
修改最近一次的 Commit
老樣子,GUI 派使用者請跳到 Fork 修改的內容
想修改最近一次的 commit ,Git 提供我們一個叫 --amend
的參數來修改。
說文解字一下:amend 中文是「修正」。
由於是用來修正「最近一次」的 commit,所以撰寫位置會在我們已經很熟的 git commit
之後:
git commit --amend
後續的內容都會以這串指令為基底增加東西。
要更新最近一次 commit 的「標題訊息」,只要在 git commit --amend
之後,繼續接著寫 -m
加 要改的訊息 即可:
git commit --amend -m "新的訊息"
假設目前的最新訊息長這樣:
接著執行這個指令:
git commit --amend -m "這是我改完的新訊息"
訊息就會變成這樣,讚吧 (?):
這個操作應該算是開發時會 「很常用」 的功能!
舉例來說:在 commit 一個版本之後,發現某個檔案console.log()
沒刪到,或是某個排版看了不滿意,就可以用下列說明步驟來修改:
git add
把檔案加到暫存區。git add "修改好的檔案"
git commit --amend --no-edit
這裡的 --no-edit
參數,用意是告訴 Git,這次的修改沒有要輸入訊息,這樣 Git 就不會跳出視窗要求我們修改訊息了。
其實 --amend
的本意是拿來讓我們 補資料 ,它並沒有辦法讓我們直接移除某一個檔案,如果想用指令處理這個需求,並不建議使用 --amend
,而是直接用 reset 指令。
如果堅決想使用 --amend
來操作,其實也還是需要搭配 reset
指令繞道而行。
示範一下操作步驟:
git reset
將上一個版本的檔案狀況重置回索引 :git reset HEAD^ 想移除的檔案名稱
git commit --amend --no-edit
修正最近一次的 commit。git commit --amend --no-edit
我知道有人腦袋中已經跑過千萬頭草尼馬,快要停止運轉了, 我來解釋解釋:
因為沒辦法直接執行 --amend
刪除 commit 的某個檔案 ,我們需要「繞一圈」處理。
假設目前有 A
=> B
=> C
三個 commit,C
裡面的 readme.txt
是我想刪除的。
那我可以把 B
所記錄的 readme.txt
放到暫存區,再透過 git commit --amend --no-edit
,把這個狀態存到 C
。
由於 Git 是用「比較差異」的方式來記錄新一版的資料 (俗稱差異備份) ,這時 B
跟 C
兩個版本的 readme.txt
又沒有任何差異,這樣 C
就不會顯示 readme.txt
了。
整個動作看起來是這樣:
發現 add style.css commit 出現一個 readme.txt
想把它刪掉。
執行 reset 指令把上一個版本的 readme.txt
狀態還原到暫存區:
$ git reset HEAD^ readme.txt # HEAD^ 表示目前位置的前一個版本
Unstaged changes after reset:
M readme.txt
此時查看 git status
,會在 Changes to be committed 區塊看到一個 readme.txt
檔案,它記錄著「上一版」 readme.txt
的內容:
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: readme.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: readme.txt
git commit --amend --no-edit
readme.txt
檔案已經不見了。readme.txt
保留著原本存在 commit 的資料,並且處於「未暫存」的狀態。如果立志要成為指令派玩家(?)的你完全搞不懂為什麼可以這樣操作,還是建議參考預計在下一篇提到的 reset 指令 來處理。
或者可以繼續閱讀 Fork GUI 的說明,這個行為用 Fork GUI 操作,超~ 方~ 便~
雖然我是指令派使用者,但還是必須說:用 Fork 操作近一次的 Commit 真的很直覺!
首先提一下,只要是「修改」工作目錄的行為,都要記得到左上角切換到 Local Changes 的內容:
其他詳細操作,我們直接給他看下去!
先截圖讓大家看看,目前最近一次的訊息叫 add style.css
要更改訊息,請執行這幾個步驟:
詳細操作請參考圖片:
想要用 Fork 把修改的檔案紀錄回最近一次的 Commit ,動作跟原本 Commit 的行為大同小異,唯一差別就只有 Amend 要打勾而已。
詳細操作請參考圖片:
用 Fork 移除某個版本的資料,有幾個步驟要做:
直接來做示範,把最近一次的版本中的 readme.txt
移掉:
注意到一件事情,當移除完 readme.txt
之後,由於資料還是保留在 未暫存區,並沒有遺失,如果有需要,還可以直接將他提交一個新版本:
如果你是因為我上面寫的那句話而跑來看這段的指令派玩家,歡迎進入 GUI 的世界 XDD!
使用 --amend
參數有一個重點在文章中沒有特別提到:
每次執行 git commit --amend ,Git 都會更改 CommitID!
即便我們看起來好像只是「修改」最新一次的 Commit,不過這個 Commit 對 Git 來說已經是全新的 Commit 了。
你可能會感覺疑惑:這樣有差嗎,反正資料對就好啦?
如果只是在地端操作版控,那這個 CommitID 被改幾次都無所謂,不過當地端的資料已經上傳到遠端 (註1) 之後,這個異動可能造成幾種影響:
地端儲存庫的資料無法推回遠端:
因為遠端儲存庫的 CommitID 跟地端儲存庫 CommitID 不一致,Git 直接報衝突,要求我們必須先同步兩邊的資料才能執行。
強制推回遠端,可能會造成團隊成員的困擾:
因為原本遠端的資料可能已經被團隊成員複製回電腦使用了,把不一樣的 CommitID 強制推回遠端的行為,等同於讓全部團隊成員都面臨 第一點 的問題。
那所以應該怎麼避免?
最簡單的方式,是跟團隊人員溝通好分支的操作行為 (註2),未來會介紹到分支的操作,每個人在開發功能的時候,理應在自己的分支進行,而不是直接操作專案的「主要分支」。
上面說到會造成別人困擾的行為,就是直接更動「主要分支」的 Commit 之後,強制上傳回遠端,因為每個人的電腦一定都存有主要分支,這種行為會直接影響到所有人的版控。
不過如果只是在「自己的分支」修改內容,把資料強制推回到遠端,由於沒有人會去亂動自己的分支,這個行為就不會影響到其他人開發了。
註1. 雖然目前尚未說明如何將地端與遠端的儲存庫進行同步,不過既然已經聊到「修改歷史 Commit」 的議題,就不得不把這個 重要觀念 一起說明。
註2. 進行團隊版控除了要開立自己分支外,可能還會牽扯到「Git Flow」、「GitHub Flow」之類的專案版控模式,這些都是基於「分支操作」的合作模式。