撤銷rebase與git原理

  • 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 <[email protected]> 1592392241 +0800
committer luozixi <[email protected]> 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 <[email protected]> 1592392241 +0800
committer luozixi <[email protected]> 1592536842 +0800

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

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

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

參考資料

//juejin.im/post/5a65ac67f265da3e330473f7