除了前面兩種方式之外,第三種方式是使用 git rebase
,git rebase
和前面兩種方式不同的地方在於可以更靈活地去根據需求來修改 commit。
首先我們來看一下 commit:
$ git log --oneline
ffc541b (HEAD -> master) 新增 work 1
c8ba232 新增 works.txt
aa29fdd 新增 rule 2
feff33e 新增 rule 1
1551d95 init
接下來我們使用 git rebase
:
$ git rebase -i 1551d95
git rebase
後面加上參數 -i
會進入互動模式,而 1551d95 則是想要修改到哪裡,也就是要修改的範圍為「現在(ffc541b)到 1551d95」。
輸入之後會跳出 Vim 編輯器:
pick feff33e 新增 rule 1
pick aa29fdd 新增 rule 2
pick c8ba232 新增 works.txt
pick ffc541b 新增 work 1
# Rebase 1551d95..ffc541b onto ffc541b (4 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
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# 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
最上面的是我們的 commit 紀錄,順序和 git log
是相反的,從上到下是最舊到最新的 commit。
下面的 Commands 是我們等下要使用的指令,上面的 commit 前面的 pick 就是其中一個指令,意思是「保留此 commit,不做任何修改」。
等下會用到 Vim 來編輯,這邊稍微介紹 Vim 的操作:
- h, j, k, l:分別對應「左、下、上、右」
- i:進入輸入模式,此模式下可以輸入文字,移動要切回普通模式
- esc:回到普通模式
- v:進入可視模式,可以選取文字,移動方式使用 hjkl
- x:刪除文字,普通模式下會刪除光標所在的文字,可視模式會刪除所選取的文字
- :wq:在普通模式下輸入,會儲存並離開編輯器
修改歷史紀錄
在這邊我想要對 c8ba232 新增 works.txt
和 pick ffc541b 新增 work 1
這兩個 commit 的訊息進行修改,所以要將這兩行前面的 pick 改成 reword 或是 r:
pick feff33e 新增 rule 1
pick aa29fdd 新增 rule 2
reword c8ba232 新增 works.txt
reword ffc541b 新增 work 1
修改完後儲存離開,這時會跳出令一個 Vim 編輯器的畫面:
新增 works.txt
# ...略
這是 c8ba232 的訊息內容,現在我們可以來對它進行修改,在這邊我將 works 改成 jobs。
而在修改完之後一樣儲存離開後會發現又跳出另一個 Vim 編輯器,還記得我們要修改兩個 commit 嗎?這就是第二個 commit 的內容:
新增 work 1
# ...略
這裡的 work 也改成 job,好了之後儲存離開,Git 會開始執行剩下的工作:
[detached HEAD a68373d] 新增 jobs.txt
Date: Mon May 16 13:56:42 2022 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 works.txt
[detached HEAD 8792769] 新增 job 1
Date: Mon May 16 13:57:13 2022 +0800
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.
有注意到 commit 的 SHA-1 值不一樣了嗎?
使用 git rebase
修改完上面的 commit 後,現在使用 git log --oneline
來看一下有沒有修改成功:
$ git log --oneline
8792769 (HEAD -> master) 新增 job 1
a68373d 新增 jobs.txt
aa29fdd 新增 rule 2
feff33e 新增 rule 1
1551d95 init
有發現剛才最上面兩個 commit 的 SHA-1 值不一樣了嗎?還記得在 學習Git(5)_-_檢視_commit_紀錄.md 有提到 Git 產生物件的時候會根據時間、內容等來計算 SHA-1 值,而我們剛才修改了 commit 的訊息,並且時間也不一樣,所以計算出來的 SHA-1 值也會跟原本的不同。
而在之前介紹的 git commit --amend -m
與 git reset
也是一樣修改之後的 commit 的 SHA-1 都會跟原本的不同。
想取消剛剛的 Rebase
如果在執行完 git rebase
後又不想要剛才的修改的話,我們可以使用 git reset
回到 ORIG_HEAD:
$ git reset ORIG_HEAD --hard
而 ORIG_HEAD 是什麼呢?
當我們在進行一些有風險的操作時,例如:reset、rebase 或合併分支的 merge 時,Git 會將 HEAD(HEAD 之後在介紹分支時會詳細介紹)指向的 commit 物件的 SHA-1 值存放在 .git/ORIG_HEAD 檔案裡,我們可以使用 cat
指令來看裡面的內容:
$ cat .git/ORIG_HEAD
feff33e13511c41fbb8681f56e92945e380bcdc8
對應前面的 commit 紀錄會是指 feff33e 新增 rule 1
,也就是使用 git reset ORIG_HEAD --hard
時會回到 feff33e 這個 commit 的狀態。
而使用 git reset ORIG_HEAD --hard
對 Git 來說也是一個危險操作,所以如果恢復到執行 git reset ORIG_HEAD --hard
之前的狀態時只要再次執行一樣的指令即可。
合併 Commit
這次我想要將關於 rule 的 commit 都合併在一起,一樣先使用 git rebase
:
$ git rebase -i 1551d95
接著會跳出 Vim 編輯器畫面:
pick feff33e 新增 rule 1
pick aa29fdd 新增 rule 2
pick a68373d 新增 jobs.txt
pick 8792769 新增 job 1
...略
現在我們要將 feff33e 和 aa29fdd 這兩個 commit 合併在一起,所以要將 aa29fdd 前面的 pick 改成 squash 或是 s,squash 是「將此 commit 合併到前一個 commit」,要注意 git rebase
的順序和 git log
是相反的,所以是將 aa29fdd 的改成 squash。
儲存離開之後,跟剛才修改 commit 時一樣會再跳出 Vim 編輯器畫面:
# This is a combination of 2 commits。
# This is the 1st commit message:
新增 rule 1
# This is the commit message #2:
新增 rule 2
...略
將不用的 commit 訊息刪除,並修改成 「新增 all rules」:
# This is a combination of 2 commits。
# This is the 1st commit message:
新增 all rules
儲存離開之後,Git 就會繼續執行:
[detached HEAD 1d15ca4] 新增 all rules
Date: Mon May 16 13:54:39 2022 +0800
1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/master.
我們也可以同時合併多個 commit:
pick feff33e 新增 rule 1
squash aa29fdd 新增 rule 2
squash a68373d 新增 jobs.txt
squash 8792769 新增 job 1
...略
儲存離開之後,跳出 Vim 編輯器,這次可以看到有 4 個 commit 訊息:
# This is a combination of 4 commits。
# This is the 1st commit message:
新增 rule 1
# This is the commit message #2:
新增 rule 2
# This is the commit message #3:
新增 jobs.txt
# This is the commit message #4:
新增 job 1
...略
我們將訊息修改為「新增 rules 和 jobs」:
# This is a combination of 4 commits。
# This is the 1st commit message:
新增 rules 和 jobs
儲存離開後我們用 git log --oneline
來看 commit 有沒有合併成功:
[detached HEAD 28ce6b7] 新增 rules 和 jobs
Date: Mon May 16 13:54:39 2022 +0800
2 files changed, 3 insertions(+)
create mode 100644 works.txt
Successfully rebased and updated refs/heads/master.
$ git log --oneline
28ce6b7 (HEAD -> master) 新增 rules 和 jobs
1551d95 init
發現剛才 4 個都合併一起並產生 1 個 28ce6b7 的新 commit。
拆解之前的 commit
我們先來看一下 commit:
$ git log --oneline
0a4944c (HEAD -> master) 新增 job 1
f609649 新增 jobs.txt
3f0d4b9 新增 rules
deb6d18 init
d86bb1a 這個 commit 一次新增太多了,我想要個拆成一個 commit,這時一樣先使用 git rebase
:
$ git rebase -i deb6d18
跳出的 Vim 編輯器畫面:
pick 3f0d4b9 新增 rules
pick f609649 新增 jobs.txt
pick 0a4944c 新增 job 1
...略
這次我們使用 edit 這個指令,這會讓 Git 在執行到指定的 commit 時會停止:
edit 3f0d4b9 新增 rules
pick f609649 新增 jobs.txt
pick 0a4944c 新增 job 1
接下來 Git 在執行到 d86bb1a 時就會停下,我們可以使用 git log
來看一下:
$ git rebase -i deb6d18
Stopped at 3f0d4b9... 新增 rules
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
$ git log --oneline
3f0d4b9 (HEAD) 新增 rules
deb6d18 init
0a4944c 和 f609649 不見了,並且 3f0d4b9 旁邊出現 HEAD,代表 HEAD 現在指向 3f0d4b9。
現在要來拆掉 3f0d4b9,可以使用之前介紹過得 git reset
:
$ git reset HEAD^
還記得 ^ 是什麼意思嗎?是指前一個 commit,而 HEAD^ 就是指回到 HEAD 指向的 commit 的前一個 commit,也就是 deb6d18,如果忘記 git reset
的用法可以回去參考學習 Git (6) - 修改 commit 紀錄 part 2:_Reset。
執行完 git reset
後,接下來使用 git status
來檢查狀態:
$ git status
interactive rebase in progress; onto deb6d18
Last commands done (1 commands done):
edit 3f0d4b9 新增 rules
Next commands to do (2 remaining commands):
pick f609649 新增 jobs.txt
pick 0a4944c 新增 job 1
(use "git rebase --edit-todo" to view and edit)
You are currently editing a commit while rebasing branch 'master' on 'deb6d18'.
Changes not statged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: rules.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
rule2.txt
發現 rules.txt 和 rule2.txt 已經被拆出來放到暫存區了,然後分別對這 2 個檔案執行 git add
和 git commit
:
提交 rules.txt:
$ git add rules.txt $ git commit -m '新增 rules.txt 內容' [detached HEAD 7cf2baa] 新增 rules.txt 內容 1 file changed, 1 insertion(+)
提交 rule2.txt:
$ git add rule2.txt $ git commit -m '新增 rule2.txt' [detached HEAD c8699fa] 新增 rule2.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rule2.txt
最後執行 git rebase --continue
讓 Git 繼續執行接下來的動作:
$ git rebase --continue
Successfully rebased and updated refs/heads/master.
用 git log
來看一下有沒有成功:
$ git log --oneline
ce85586 (HEAD -> master) 新增 job 1
65695a6 新增 jobs.txt
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
發現 3f0d4b9 不見了,並新增了剛剛提交的 7cf2baa 和 c8699fa 這樣就成功將「新增 rules」這個 commit 拆成 2 個 commit 囉。
想刪除某個 commit 或是調整 commit 位置
最後要來介紹怎麼刪除 commit 或是調整 commit 位置,我們沿用上面拆解 commit 範例:
$ git log --oneline
ce85586 (HEAD -> master) 新增 job 1
65695a6 新增 jobs.txt
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
我們在這邊新增一個 rule3.txt,並提交 commit 上去:
$ touch rule3.txt
$ git add rule3.txt
$ git commit -m '新增 rule 3'
[master 852a230] 新增 rule 3
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 rule3.txt
完成之後,commit 紀錄會多出一個「新增 rule 3」:
$ git log --oneline
852a230 (HEAD -> master) 新增 rule 3
ce85586 新增 job 1
65695a6 新增 jobs.txt
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
這時我想要將 rule 相關的 commit 放在一起,一樣可以使用 git rebase
:
$ git rebase -i deb6d18
執行之後跳出的 Vim 畫面:
pick 7cf2baa 新增 rules.txt 內容
pick c8699fa 新增 rule2.txt
pick 65695a6 新增 jobs.txt
pick ce85586 新增 job 1
pick 852a230 新增 rule 3
接著只要調整 852a230 的位置,要記住在 rebase 中時間順序是和 log 相反的:
pick 7cf2baa 新增 rules.txt 內容
pick c8699fa 新增 rule2.txt
pick 852a230 新增 rule 3
pick 65695a6 新增 jobs.txt
pick ce85586 新增 job 1
修改完後儲存離開,接下來使用 git log
來檢查有沒有成功調換位置:
$ git log --oneline
7f34f07 (HEAD -> master) 新增 job 1
cde5774 新增 jobs.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
ccf65d1 有移到 c8699fa 後面了。
刪除指定 commit
使用 git rebase
也很簡單,這次我們來刪除 jobs 相關的 commit,這次不會修改到 rules 相關的 commit,所以範圍指定到 ccf65d1:
$ git rebase -i ccf65d1
Vim 的畫面如下:
pick cde5774 新增 jobs.txt
pick 7f34f07 新增 job 1
接下來只要把不要的地方刪掉,或是把 pick 改成 drop:
drop cde5774 新增 jobs.txt
這邊一個使用 drop,一個直接刪除,存檔後離開再使用 git log
:
$ git log --oneline
ccf65d1 (HEAD -> master) 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init
jobs 相關的 commit 都不見了,這樣就刪除成功了。
參考資料
- 高見龍,《為你自己學 Git》