[toc]
引用
抄自 https://www.bilibili.com/video/BV11z4y1X79p?spm_id_from=333.337.search-card.all.click 该链接
并且结合 PRO_GIT 一书总结
git 不关注差异,只关注当前快照
git和svn的一大区别是,svn关注的是修改,git关注当前现状。
比方说,一个空房间里有两个箱子。每次这两个箱子移动,svn都会记录他们的移动路线,但是git不在乎,它只会在你commit的时候,拿着相机进屋拍个照,箱子在哪就是哪里,爱咋咋地。
Git 不按照以上方式对待或保存数据。反之,Git 更像是把数据看作是对小型文件系统的一系列快照。 在 Git
中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。
git commit tree blob 结构图
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个 树 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
./git 中的对象 commit tree blob
blob自行百度,总之这里你理解成个存储数据的格式就行了
仿照视频里面的步骤,我们也用个窗口,来观察当你执行 add commit操作的时候, ./git 里面发生了什么。同样我们也删除 hooks
git init后
.git
├── branches
├── config
├── description
├── HEAD
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
8 directories, 4 files
变换一:新建test1.c, 之后add --生成blob
vi test1.c
写一行 The Master Chief
,然后 wq
这个时候没有任何变化
git add test1.c
我们发现多了下面的object
.git
├── branches
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── objects
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
9 directories, 6 files
git cat-file -t c267
发现是 blob 类型
git cat-file -p c267
会显示 The Master Chief
变化二:第一次 commit --生成tree和commit
git commit -m "first commit"
该操作会生成两个新的 objects 作为 tree 和 commit
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 12
│ │ └── 6dbd7bfa165928164e2807e0e6a42b4d85ac37
│ ├── 55
│ │ └── d9df99408ea612721fbe8ba7e18be1b7a3c594
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
14 directories, 12 files
这里我们分别的 git cat-file -t
git cat-file -p
看下
git cat-file -t 126d
显示 commit
git cat-file -p 126d
显示四行,分别是 tree/author/committer 和 和最后的commit信息
git cat-file -t 55d9
显示 tree
git cat-file -p 55d9
显示 100644 blob c2673408f719c6224ae85b27c9d4245ad96e55d6 test1.c
这里面的blob 刚好就是我们之前 add 的blob 对应的文件
完全相同的文件,将会使用同一个blob
创建一个test2.c,内容完全和 test1.c 一致,观察
vi test2.c
写一行 The Master Chief
,然后 wq
这个时候没有任何变化
git add test2.c
结果.git文件夹里面的内容毫无变化
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 12
│ │ └── 6dbd7bfa165928164e2807e0e6a42b4d85ac37
│ ├── 55
│ │ └── d9df99408ea612721fbe8ba7e18be1b7a3c594
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
14 directories, 12 files
第二次commit
git commit -m "second commit"
该操作会再生成两个新的 objects 作为 tree 和 commit
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 12
│ │ └── 6dbd7bfa165928164e2807e0e6a42b4d85ac37
│ ├── 55
│ │ └── d9df99408ea612721fbe8ba7e18be1b7a3c594
│ ├── 8f
│ │ └── ddf9dedce34df33de41c544e7bf6213494e9e4
│ ├── b9
│ │ └── be6a158220da6aa0cd39e64402d7909a217773
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
16 directories, 14 files
这里我们分别的 git cat-file -t
git cat-file -p
看下
git cat-file -t 8fdd
显示 commit
git cat-file -p 8fdd
显示五行,分别是 tree/parent/author/committer 和 和 second commit
git cat-file -t b9be
显示 tree
git cat-file -p b9be
显示
100644 blob c2673408f719c6224ae85b27c9d4245ad96e55d6 test1.c
100644 blob c2673408f719c6224ae85b27c9d4245ad96e55d6 test2.c
可以看到tree里面引用了 一模一样的blob
修改同一个文件,将生成额外的blob
修改 test1.c,然后add
现在我们修改 vi test1.c
,另起一行加入一句 Spartan 117
,然后 wq
git add test1.c
多了一条object ``38a1```
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 12
│ │ └── 6dbd7bfa165928164e2807e0e6a42b4d85ac37
│ ├── 38
│ │ └── a13225d42cba196f7f183ce0b17e0ec31b7cd9
│ ├── 55
│ │ └── d9df99408ea612721fbe8ba7e18be1b7a3c594
│ ├── 8f
│ │ └── ddf9dedce34df33de41c544e7bf6213494e9e4
│ ├── b9
│ │ └── be6a158220da6aa0cd39e64402d7909a217773
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
17 directories, 15 files
检查一下这个 38a1
git cat-file -t 38a1
显示 blob
git cat-file -p 38a1
显示
The Master Chief
Spartan 117
第三次 commit,生成新的 tree 和 commit
git commit -m "third commit"
该操作会再生成两个新的 objects 作为 tree 和 commit
4d65
5385
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 12
│ │ └── 6dbd7bfa165928164e2807e0e6a42b4d85ac37
│ ├── 38
│ │ └── a13225d42cba196f7f183ce0b17e0ec31b7cd9
│ ├── 4d
│ │ └── 65a099692964508d4cab2dc6f4764e2e8bfeae
│ ├── 53
│ │ └── 854de4bae0ad5aaeb7f841da470fbec0008a78
│ ├── 55
│ │ └── d9df99408ea612721fbe8ba7e18be1b7a3c594
│ ├── 8f
│ │ └── ddf9dedce34df33de41c544e7bf6213494e9e4
│ ├── b9
│ │ └── be6a158220da6aa0cd39e64402d7909a217773
│ ├── c2
│ │ └── 673408f719c6224ae85b27c9d4245ad96e55d6
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
19 directories, 17 files
git cat-file -t 4d65
显示 commit
git cat-file -p 4d65
显示五行,分别是 tree/parent/author/committer 和 third commit
git cat-file -t 5385
显示 tree
git cat-file -p 5385
显示
100644 blob 38a13225d42cba196f7f183ce0b17e0ec31b7cd9 test1.c
100644 blob c2673408f719c6224ae85b27c9d4245ad96e55d6 test2.c
当前的tree引用了 test1.c的新blob 38a1,但是原先的 test1.c blob并没有消失,依然保存在 ./git
这也就是为什么 git reset 的时候你能很快的回到当前commit点,因为直接通过指针,找到了对应的tree和blob
test1.c 又改回到第一次commit的状态,新commit一次,tree会怎么变
tree会回到第一次的状态,虽然他们上面的commit点哈希值不一样,tree本身的哈希值也不一样,但是tree的内容会一样
reset --hard 对 .git 内 objects 的影响
事实上是没有影响,所有已经存在的 objects 都会被保留。所以reset --hard 并不是不能恢复的,但是如果你删除掉本地的库,又没有远程库保存,那么就彻底不能恢复了,因为你的 objects 都被干掉了
刚才有三次commit,我reset --hard 到第一次,再cherry之前的commit点哈希,发现是可以cherry-pick的。
所以,如果不小心 reset --hard,远端也没有保存,
千万不要随便删除本地库的文件夹,没有这个.git你就彻底恢复不了了
git分支原理 && 为什么 git 分支切换速度快
svn如果你创建一个分支,它会将自己的所有的代码全部复制一份,然后建立一个新分支。
git的分支不同,它只是一个指针,指向了你的某个commit点,相当于你创建一个新分支的时候,只不过是创建了一个指针而已。
之前提过,或者结合下图,我们打印commit点的时候,会生成5行内容
分别是 tree/parent/author/committer
其中parent就是指向父commit点的指针。
首次提交产生的提交对象没有父对象,普通提交操
作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。
git在管理代码时,如果出现reset rebase pull merge
等等改变当前commit点的操作时,会根据最终的commit点指针,找到它下面连着的tree,再找到tree对应的blob文件,瞬间恢复到该commit点应该有的状态。