撤销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