介绍git
怎么管理和实现的
hash
-文件内容: 可以通过文件路径定位位置, 也可以通过hash
定位位置;hash
,对应一个文件, 根节点对应文件夹; 一棵树就是一个快照;commit
是tree
, tree
将文件串联, 每个历史版本都完整的存在, 包含所有, 而不是diff
的形式;(不存diff会不会浪费空间?是有点浪费空间, 但是空间换时间嘛, 而且对于不常用的, 你可以将其打包压缩, 以 diff 的形式保存;)
git log,git pull,git push
, 这些都是基于基础操作而组合形成的指令;git
使用;https://git-scm.com/docs 右下角部分的 Plumbing Commands
就是底层指令的意思, 可以看到只有寥寥几个, 其他的就是外部用户指令;
这些指令主要和.git
打交道;
.git/
├── HEAD
├── branches
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
HEAD
一个文件, 存放尾指针;branches
文件夹, 存放若干分支的尾指针;config
配置文件, 当前git
的工作环境配置, 比如当前使用者邮箱姓名之类的description
gitweb program
使用的;hooks
插件脚本和案例;info
不跟踪哪些文件, 比如生成的中间文件, build
生成的这些文件,看都不想看;objects/xx/xxx
某个文件被压缩后的样子, 每个文件的每个历史版本都能在这儿找到, 所以有很多;objects/info
信息objects/pack
上面那些文件被压缩后存放位置;refs/heads/
文件夹, 存放某个分支尾指针;refs/tags/
文件夹, 存放一个指针地址, 一般对应某个提交, 即指针指向中间位置, 这个文件名就是变量名, 文件内容就是地址;index
就是git add
后缓存的内容git objects
就是.git/objects
目录的内容介绍;
git
是一个可以根据文件内容定位文件位置的系统就是通过内容, 计算hash
, 可以知道这个文件是否存在;
说白了通过hash
获取content
; 即key:value
;
hash
生成器git hash-object
https://git-scm.com/docs/git-hash-object
根据内容生成hash
; 然后存在.git/objects
中, hash
前两个字母做文件夹名, 剩下的做文件名;
--stdin
表示从stdio
中获取内容作为git hash-object
的hash
计算结果, -w
表示写盘, 内容通过zlib
加密;$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
$ ls .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
$ ./unzlib.py .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
b'blob 13\x00test content\n'
$ cat unzlib.py
#!/usr/bin/python3
import zlib,sys
for i in sys.argv[1:]:
with open(i,"rb") as fd:
data = fd.read()
print(zlib.decompress(data))
blob ${长度}\x00${内容}
, 作为hash object
内容, 然后zlib
压缩;hash
=> .git/objects/hash[:2]/hash[2:]
仅仅是生成了一个object
, 不属于任何commit
, 也没有关联;
因为是key-value
, 所以没有新文件生成;
$ tree .git/objects/
.git/objects/
├── 5e
│ └── 412e47a7e939bd216626ad60241d9686ac4ac7
├── 67
│ └── e4c485fa8f187ccd1853a39e36969f9e822008
├── a4
│ └── bb18bcd9e7595e77e31a745142c49c9c31a8bc
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
6 directories, 4 files
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
$ tree .git/objects/
.git/objects/
├── 5e
│ └── 412e47a7e939bd216626ad60241d9686ac4ac7
├── 67
│ └── e4c485fa8f187ccd1853a39e36969f9e822008
├── a4
│ └── bb18bcd9e7595e77e31a745142c49c9c31a8bc
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
object
内容git cat-file
https://git-scm.com/docs/git-cat-file
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
根据hash
定位到文件.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
, 解压并去除前置内容;
hash
前面是介绍标准输入, 这里介绍更符合实际的文件生成;
$ echo 'test content' > test.txt
$ git hash-object -w test.txt
d670460b4b4aece5915caf5c68d12f560a9fe3e4
这时就是使用test.txt
生成hash
, 并将备份内容存储到.git/objects
下, 即就有了一个object
的备份, 删除这个文件, 后续也可以通过这个object
恢复;
前面存的都是内容, 文件名呢?
tree-object
object
是文件, 是叶子结点, 而tree-object
则就是跟结点; 而这个tree-object
则存放了filename-hash
;
文件名 => hash
, 文件对应路径; 或index
查询, index
有filename
=> object
的hash
, 即使删除也可以使用hash
恢复;
hash
=> 文件名, 不行, 因为一个hash
可能对应多个文件名;
tree
对应一系列文件名; 树可能也有子树;
tree
是文件夹, 有子文件夹也有文件$ mkdir temp
$ touch temp/ts.txt
$ git add temp/
$ git commit -m "second"
[master 76cf19e] second
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 temp/ts.txt
$ git status
On branch master
nothing to commit, working tree clean
$ git cat-file -p HEAD^{tree}
040000 tree f8c5561b21039d64936b2452667fa9b9d135a84d temp
100755 blob 5e412e47a7e939bd216626ad60241d9686ac4ac7 unzlib.py
$ git cat-file -p f8c5561b21039d64936b2452667fa9b9d135a84d^{tree}
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ts.txt
$ ls .git/objects/f8/c5561b21039d64936b2452667fa9b9d135a84d
$ ./unzlib.py .git/objects/f8/c5561b21039d64936b2452667fa9b9d135a84d
b'tree 34\x00100644 ts.txt\x00\xe6\x9d\xe2\x9b\xb2\xd1\xd6CK\x8b)\xaewZ\xd8\xc2\xe4\x8cS\x91'
temp
类型是文件夹类型;HEAD^{tree}
输出temp
是文件夹; 对应object:f8c5561b21039d64936b2452667fa9b9d135a84d
在.git/objects/f8/c5561b21039d64936b2452667fa9b9d135a84d
tree
之index
index=tree
, index
就是修改之前的tree
, 然后被用来修改的, 即add
修改, 然后根据需要提交生成新的commit tree
.
主要是修改index
, 然后修改index
, index
则是一次快照的整个缩影;
类似filename:object
$ git ls-files | wc -l
123
$ file .git/index
.git/index: Git index, version 2, 123 entries
即每一次的切换都会为这次快照创建缩影; path:object
这种映射, 记录原始是否被修改;
tree
前面介绍了index
, 这里就是修改index
映射;
比如将objecta
这个hash
和path
强行配对, 就相当于修改了文件映射, 即cp xxx dst
; 完成了修改;
git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
commit objects
也是个obj
, 这个里面存放的也是文本, 记录了一些信息;
tree object
是哪个: 即文件快照;git log
;patch
; 邮箱姓名patch
进行了简单的修改后再提交, 比如review
; 邮箱姓名;commit message
;tree
$ git log -1
commit 98887870761fff00823a2ff1554285bd910902f0 (HEAD -> master, origin/master, origin/HEAD)
Author: ch <ch>
Date: Thu Jan 19 08:44:17 2023 +0800
[clean] remove html and app
$ git cat-file -p 98887870761fff00823a2ff1554285bd910902f0
tree f0d674f24cea3784d112d5396c24558c84be4d9e
parent 88aa20bfa61b1c665e37a37607ac51d46af983c6
author ch <ch> 1674089057 +0800
committer ch <ch> 1674089057 +0800
[clean] remove html and app
$ git cat-file -p f0d674f24cea3784d112d5396c24558c84be4d9e^{tree}
100644 blob 259148fa18f9fb7ef58563f4ff15fc7b172339fb .gitignore
040000 tree 00eac242d190c998d0fb91b53f2b490eee4bf9de IO
040000 tree 156202f111559d5e4ee4352ef804a4865a6b674c IPC
100644 blob dbbe3558157f5861bff35dcb37b328b679b0ccfd LICENSE
040000 tree 26482eb2cb6c72c2fa2c4bc0694fc2f49cf9ff84 algorithm
040000 tree 0f216977bd545fd6480a31e43afc008299405ee3 auditctl
040000 tree 6e93a5719742a1acd6af4d4d6212b52c6f5a649e binutils
040000 tree a3832375113a829d9bab7173787c7bf9ab14ee0b casual
040000 tree de69626818be28015974e1e0e6a46881ad5196a2 coder
040000 tree 311e1d5e0b053d5aee7c81c1a31a5643c0d344d2 compile
040000 tree e4c613864329612f00b10ed9734c13b1f9f439db gcc
040000 tree fdb53a2d8c44c612a3914e9a47507dd8d056f76f gdb
040000 tree 39e9e8f7128ba2463e9545e67ac188e17c4f9c91 git
100644 blob b14fd09c0a3e322cfff346597683cc4957841bb2 index.md
040000 tree 10dafd9caf20cc051738589dcb059df295ee9304 kernel
040000 tree 7c4ab30451e05603904b75baa0422a23ada77f37 linuxprograminterface
040000 tree ca0e3850d31a6b69e4b37b472dcf439025514af3 makefile
040000 tree 85c3816a31c677a1d7f55cfd300d36c20bf49a22 memorycache
040000 tree 0a06168280fe0e1620f899541a8f8cdf682d387f openssl
040000 tree c6474c1c59bba0a0d0b40301789a14bec6fdfb08 perfromanceanalysis
040000 tree b485e7401bc48e1e619ec1fb46178667b1c6fecc python
040000 tree 49b82755de974464d587798f43c6e8d1c2fad1f6 signal
040000 tree d8e6fa9408a841bb6c73bb46da92f03a07d3b805 software
040000 tree 172dfe01d292e87468b05bdb902f74996bf7c442 systemd
040000 tree 0785fb2a1d5d2f30cf2084be3ef14d1d45ea3217 tcpip
040000 tree f6e7a3458eb5e76a14b67223a8cacf011c82b848 thread
040000 tree 8d1aea04a0988ca81a3f55848c4179c7aedea952 tools
040000 tree ab85272c012a8849219633179e118f8130077d8d understandinglinuxkernel
https://git-scm.com/docs/git-read-tree
https://git-scm.com/docs/git-commit-tree
$ echo 'First commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <[email protected]> 1243040974 -0700
committer Scott Chacon <[email protected]> 1243040974 -0700
First commit
$ echo 'Second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'Third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
git commit-tree 0155eb
这里的0155eb
对应引用的tree object
, 而-p
则指定前一个结点, 即parent
;commit
本身也是个object
, 和tree object
独立, 但引用了tree object
;引用即记录了自己这个提交对象对应tree
快照;
object
生成content=${origion_data}
object_data=${type} ${content_length}\x00%${content}
object_hash=hash(content)
write(zlib(${object_data}), ${object_hash})
hash
作为文件名zlib
压缩后写入hash
文件名git ref
sha1
是地址, ref
则是变量名一般存放在.git/refs
目录下, 而这个目录一般也是空的;
ref
$ echo 1a410efbd13591db07496601ebc7a059dd55cfe9 > .git/refs/heads/master
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 Third commit
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
$ git update-ref refs/heads/test cac0ca
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d Second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d First commit
update-ref header value
, 即header=value
, 创建对应文件, 存在则更新;
git branch branch-name
的底层就是update-ref
创建新的ref
;
$ cat .git/HEAD
ref: refs/heads/master
类似c++
int *a;
int* (&HEAD)=a;
引用如果是hash
, 则就是直接引用;
HEAD
对应.git/HEAD
, 一般是软链接, 除非是个detached head
才可能是sha
;
https://git-scm.com/docs/git-symbolic-ref
$ git symbolic-ref HEAD
refs/heads/master
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/
refs/xxx/xx
的风格;TAG
存sha1
的轻量级tag
, 和单独形成一个object
, 带注释的tag
;主要介绍后者;
和blob,tree,commit
都一样, 这个则是tag
类型, 也记录了相关信息;
有相关信息: 指向commit
, commit
指向tree
; 和branch
比, 这个用不修改;
# simple light weight
$ git tag temp 76cf19eb3ff36d67a0b1db98326aa0584d968222
$ cat .git/refs/tags/temp
76cf19eb3ff36d67a0b1db98326aa0584d968222
# annotated
$ git tag -a a_temp 76cf19eb3ff36d67a0b1db98326aa0584d968222 -m "anot"
$ cat .git/refs/tags/a_temp
79c3f9f60e5e5bdef3f65239a1cf36ab144a93de
# result
$ git cat-file -p 76cf19eb3ff36d67a0b1db98326aa0584d968222
tree 99ab30bd82c816fbe37edf9113f23b9b2f2aec0e
parent a4bb18bcd9e7595e77e31a745142c49c9c31a8bc
author ch <[email protected]> 1677161546 +0000
committer ch <[email protected]> 1677161546 +0000
second
$ git cat-file -p 79c3f9f60e5e5bdef3f65239a1cf36ab144a93de
object 76cf19eb3ff36d67a0b1db98326aa0584d968222
type commit
tag a_temp
tagger ch <[email protected]> 1677293657 +0000
simple
类型的和输入引用的sha1
一样;annotated
则指向的是一个tag
对象;还可以tag
一个object
$ git cat-file -p 76cf19eb3ff36d67a0b1db98326aa0584d968222^{tree}
040000 tree f8c5561b21039d64936b2452667fa9b9d135a84d temp
100755 blob 5e412e47a7e939bd216626ad60241d9686ac4ac7 unzlib.py
$ git tag tag-file 5e412e47a7e939bd216626ad60241d9686ac4ac7
$ git cat-file blob tag-file
#/usr/bin/python3
import zlib,sys
for i in sys.argv[1:]:
with open(i,"rb") as fd:
data = fd.read()
print(zlib.decompress(data))
remote
https://git-scm.com/docs/git-remote
和其他的引用有差异的是, 只读; 存的值是push
的最新commit
;
$ git remote add origin [email protected]:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To [email protected]:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
添加的可能在.git/config
中;
packfiles
git
每个版本的文件都会当成一个独立的文件; 那么可能就有很多地方的重复, 浪费空间, 但是效率高; 每次修改提交, 都会生成新的差异不大的文件, 如果修改次数多, 重复率就非常高了;
这种情况就有了一种机制, git gc
, 将内容打包, 这时存放的就是diff
;
idx
索引, pack
则是内容, idx
记录了文件在pack
中的位置;
https://git-scm.com/docs/git-verify-pack
https://git-scm.com/docs/git-gc
idx,pack
分析$ git gc
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 4 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), done.
Total 11 (delta 1), reused 0 (delta 0), pack-reused 0
$ ls .git/objects/pack/
pack-8d08051a03f3cfca63bf388d0af8be0183bac891.idx pack-8d08051a03f3cfca63bf388d0af8be0183bac891.pack
$ git verify-pack -v .git/objects/pack/pack-8d08051a03f3cfca63bf388d0af8be0183bac891.idx
fc10a0ec4314fc6905a2875707e08fe1b4723670 commit 179 133 12
76cf19eb3ff36d67a0b1db98326aa0584d968222 commit 183 133 145
79c3f9f60e5e5bdef3f65239a1cf36ab144a93de tag 116 114 278
9978ea2686975114b5378c7a78b7235fc6e75b86 blob 149 130 392
5e412e47a7e939bd216626ad60241d9686ac4ac7 blob 9 20 522 1 9978ea2686975114b5378c7a78b7235fc6e75b86
a4bb18bcd9e7595e77e31a745142c49c9c31a8bc commit 136 105 542
3ea245ed3545d93383955fd223398611a6ddad9a tree 68 78 647
f8c5561b21039d64936b2452667fa9b9d135a84d tree 34 45 725
99ab30bd82c816fbe37edf9113f23b9b2f2aec0e tree 68 78 770
67e4c485fa8f187ccd1853a39e36969f9e822008 tree 37 48 848
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob 0 9 896
non delta: 10 objects
chain length = 1: 1 object
.git/objects/pack/pack-8d08051a03f3cfca63bf388d0af8be0183bac891.pack: ok
格式
# 原始数据
SHA-1 type size size-in-packfile offset-in-packfile
# 以delta形式存放的
SHA-1 type size size-in-packfile offset-in-packfile depth base-SHA-1
剩下没有pack
的就是没有被commit
直接或间接引用的, 即不在树中;
hash
; 文件object
是快照的叶子结点, tree object
是快照的根结点对应文件夹;index
是某个tree
的拷贝, 专用于修改, 即git add
进行修改;commit
是一个文件, 文件记录了tree object
的值和parent
以及创造者和提交者信息, 提交原因;git ref
: 单独commit,annotated tag
; 简介引用:refs xxx
; 直接记录hash
, commit hash,tree hash,object hash
都可以;pack
即压缩节省空间, base + diff
并压缩;