撤銷rebase與git原理

luozx207發表於2020-10-29

git物件

git是物件導向的,物件儲存在.git/objects資料夾中。此資料夾中,一個物件就是一個檔案,檔名就是物件的id

提交commit的時候,每個檔案都是一個資料物件,一個樹物件會用來維護一次提交的所有資料物件,如果提交的內容包含資料夾,那麼這個資料夾也會是一個樹物件

一次提交就是一個提交物件,這個物件包括了表示此次提交所有資料物件的樹物件,以及對上一個提交物件的指標

# -t 列印物件型別;-p 列印物件內容
$ git cat-file -t 458ed8e
commit # 458ed8e是一個提交物件

$ git cat-file -p 458ed8e
tree 0ce2ac7bb5b76a74a03d1af4b3b87337ecb15e88 # 本次提交的樹物件
parent dd5736397cacc07ba8fbf73201a53a0d733063b2 # 上一個提交物件的指標
author luozixi <luozixi@xxx.net> 1592392241 +0800
committer luozixi <luozixi@xxx.net> 1592392241 +0800

fix

$ git cat-file -t 0ce2ac7bb5b76a74a03d1af4b3b87337ecb15e88
tree # 檢視本次提交的樹物件型別

$ git cat-file -t dd5736397cacc07ba8fbf73201a53a0d733063b2
commit # 檢視上一個提交物件的型別

$ git cat-file -p 0ce2ac7bb5b76a74a03d1af4b3b87337ecb15e88
100644 blob d45dda6a2a0d66f808c4999bb2b54ff0d931dde4    .editorconfig
040000 tree dc8ccda293623ed2a7f173c0c9714e95044a9125    utils
# 本次提交的樹物件中,有一個資料物件(對應檔案)和一個樹物件(對應資料夾)

git指標

所有的分支和標籤都是指向提交物件的指標

所有的本地分支指標都儲存在 git/refs/heads 目錄下,每一個分支對應一個檔案,檔案的內容就是本地分支最後一次提交的提交物件

$ cat .git/refs/heads/feature/feature1
458ed8e77466a5f5be0167ab7ab8977060ec21cf

HEAD指標,是一個指向指標的指標

$ cat .git/HEAD
ref: refs/heads/feature/feature1

它指向了feature/feature1的分支指標儲存位置,而分支指標又指向分支最後一次提交的提交物件,所以說HEAD是一個指向指標的指標

它的作用是標識當前使用的是那個分支,使用git checkout切換分支的時候,實際上就是修改了HEAD指標的值

checkout和reset的區別:這兩個命令都會改變 HEAD 的指向,區別是 checkout 不改變 HEAD 所指向的分支指標的指向,而 reset 會

git日誌

.git/logs/HEAD檔案記錄了HEAD指標的變更記錄

.git/logs/refs/heads則記錄了所有分支指的變更記錄

使用$ git reglog指令實際上就是讀取了.git/logs/HEAD這個檔案

$ git log指令就不是直接讀取檔案了,這個指令的輸出是由分支所指的提交物件一層層向前找父提交物件繪製而成的

撤銷rebase

git中並不存在某次提交時的分支快照

但是通過HEAD的變更記錄,我們可以找到rebase之前當前分支所指的提交物件,然後使用reset將當前分支指向那時的提交物件就可以了

$ git reset --hard HEAD@{30}

HEAD@{30}是需要回退的提交物件的簡寫

執行這個操作之後,可以發現HEAD的日誌裡多了一行

 HEAD@{0}: reset: moving to HEAD@{30}

檢視當前分支的日誌,可以發現很有趣的事情:

0000000000000000000000000000000000000000 c77a9670c5cfdb8abd0ea05fdc4bfe71725c8ff4 luozixi 1591684691 +0800	branch: Created from develop
...
dd5736397cacc07ba8fbf73201a53a0d733063b2 458ed8e77466a5f5be0167ab7ab8977060ec21cf luozixi 1592392241 +0800	commit: fix
458ed8e77466a5f5be0167ab7ab8977060ec21cf b271f35c4e9552d23669a05509067a3cd8b7dd03 luozixi 1592536842 +0800	rebase finished: refs/heads/feature/feature1 onto e5168c3c5e2f8219e51ede4ccd40136e2f69777b
b271f35c4e9552d23669a05509067a3cd8b7dd03 458ed8e77466a5f5be0167ab7ab8977060ec21cf luozixi 1592538987 +0800	reset: moving to HEAD@{30}

每一行的第一個位置是移動分支指標前分支指標指向的提交物件的id,第二個位置是當前分支指標指向的提交物件的id

可以看到日誌的一開始,父物件的id是全0的,這是所有分支的起點

458ed8e77466a5f5be0167ab7ab8977060ec21cf是此分支最後一次提交,然後就進行了rebase操作

rebase finished: refs/heads/feature/feature1 onto
e5168c3c5e2f8219e51ede4ccd40136e2f69777b

這個e5168c3c5e2f8219e51ede4ccd40136e2f69777b正是rebase目標分支(debelop)所指提交物件的id,即develop最新的提交

最後,我們執行了reset操作,使feature1的分支指標再次指向了458ed8e77466a5f5be0167ab7ab8977060ec21cf,即此分支的最後一次提交

git log裡也沒有了rebase的相關提交資訊

那麼表示rebase操作的b271f35c4e9552d23669a05509067a3cd8b7dd03這個提交物件消失了嗎?

$ git cat-file -p b271f35c4e9552d23669a05509067a3cd8b7dd03
tree 1ee51eedb07f6ac0144e590668977eb19696a2c8
parent 64a34f53657544530961c8aadcf60091e3912b74
author luozixi <luozixi@xxx.net> 1592392241 +0800
committer luozixi <luozixi@xxx.net> 1592536842 +0800

可以看到,它沒有消失,其實rebase之後形成的所有提交物件都沒有消失,只不過已經沒有分支指標指向它們了,它們變成了不可見的物件,也就是懸空物件(dangling objects)

如果我們還想再找回它們,去log裡找到它的id就行

如果需要徹底刪除這些懸空物件,見:http://liuhui998.com/2010/11/06/remove_commits_completely/

參考資料

https://juejin.im/post/5a65ac67f265da3e330473f7

相關文章