git 笔记

简介

内容介绍

介绍git怎么管理和实现的

核心概念

  • 文件名-hash-文件内容: 可以通过文件路径定位位置, 也可以通过hash定位位置;
  • 快照: 所谓一个快照其实就是一棵树, 叶子结点是一个hash,对应一个文件, 根节点对应文件夹; 一棵树就是一个快照;
  • committree, 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-objecthash计算结果, -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查询, indexfilename => objecthash, 即使删除也可以使用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

自行创建treeindex

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这个hashpath强行配对, 就相当于修改了文件映射, 即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
  • 内部底层指令创建;
    https://git-scm.com/docs/git-update-ref
$ 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并压缩;

你可能感兴趣的:(git,git,github)