[!quote] 如果是兴趣的角度推荐学一下,但是功利的角度就算了

基本说明

  1. git 和svn的方式不同,主要使用文件快照的形式储存,更加快速索引
  2. git 使用sha1生成hash,使用zlib的deflate算法压缩文件
  3. 具有相同hash值的文件,git认为为同一个文件,只生成一份obj

目录分析

.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-merge-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           ├── feat
│           │   └── example
│           └── master
├── objects
│   ├── 18
│   │   └── 605c522c56ea4ebbf2ee17ae702c38e89f37e2
│   ├── 21
│   │   └── c8f9899198b747f5a44bc5f837a15dbe8de639
│   ├── 30
│   │   └── 532454c17b108960baaa779156d4ed06f4700a
│   ├── af
│   │   └── 81445cd6401f394045651a876e9196db61e912
│   ├── e5
│   │   └── 3be081fdea9000576336d0d0dee7b326915ff2
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   ├── feat
    │   │   └── example
    │   └── master
    ├── remotes
    │   └── origin
    │       ├── HEAD
    │       └── main
    └── tags
        └── v0.0.1

description

  • 暂时没发现有什么用(似乎和gitweb有关)

branches

  • 暂时没发现有什么用

COMMIT_EDITMSG

  • 最近的commit 的msg信息,没什么用

info/exclude

  • 和.gitignore类似,用于忽略文件

区别在于这个文件不会被提交,一般用于本地忽略文件

hooks

  • 一些git的脚本,可以自己进行编写,在对应的时机调用,默认不使用,需要使用去除文件后缀

config

  • 配置文件,有关项目git的配置都在这里
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = git@github.com:chenxuan520/gitlearn.git
	fetch = +refs/heads/*:refs/remotes/origin/*
  • 用于指示目前所在的分支以及位置
ref: refs/heads/master

logs

HEAD

  • HEAD 记录所有更改记录,包括切换分支
  • reflog 查看的位置
0000000000000000000000000000000000000000 21c8f9899198b747f5a44bc5f837a15dbe8de639 chenxuan <1607772321@qq.com> 1668685183 +0800commit (initial): init
21c8f9899198b747f5a44bc5f837a15dbe8de639 21c8f9899198b747f5a44bc5f837a15dbe8de639 chenxuan <1607772321@qq.com> 1668685269 +0800checkout: moving from master to feat/example
21c8f9899198b747f5a44bc5f837a15dbe8de639 21c8f9899198b747f5a44bc5f837a15dbe8de639 chenxuan <1607772321@qq.com> 1668686611 +0800checkout: moving from feat/example to master

refs

  • 对该分支的变化操作记录
0000000000000000000000000000000000000000 21c8f9899198b747f5a44bc5f837a15dbe8de639 chenxuan <1607772321@qq.com> 1668685183 +0800commit (initial): init
21c8f9899198b747f5a44bc5f837a15dbe8de639 2f161ef1b5de6a71321f508f91fcb9c95c4e42f7 chenxuan <1607772321@qq.com> 1668687323 +0800commit: 2th
2f161ef1b5de6a71321f508f91fcb9c95c4e42f7 21c8f9899198b747f5a44bc5f837a15dbe8de639 chenxuan <1607772321@qq.com> 1668687356 +0800reset: moving to 21c8f9899198b747f5a44bc5f837a15dbe8de639

objects

info pack

  • 用于gc打包文件
  • 似乎使用了增量储存的形式(不确定)
使用gc 后

├── objects
│   ├── info
│   │   ├── commit-graph
│   │   └── packs
│   └── pack
│       ├── pack-87f50f248e000319b69ffc78501ce7c4f2e53999.idx
│       └── pack-87f50f248e000319b69ffc78501ce7c4f2e53999.pack

其他

  • git 的obj 分有三种
    1. blob 文件类型
    2. tree 树类型(文件夹)
    3. commit 提交类型
  • 全都是通过sha1 计算hash值再进行zlib压缩得到

文件权限存储在tree中,这样改动文件权限改动较少

refs

  • 里面内容都是引用类型(都是hash指针)

head

  • 所有的分支指针位置

remote

  • remote指针的位置以及远程分支内容

tags

  • tags 所指向提交内容

index

  • git 暂存区信息

git过程分析

add

before
├── objects
│   ├── info
│   └── pack

after
├── objects
│   ├── 9c
│   │   └── 595a6fb7692405a5c4a10e1caf93d7a5bd9c37
│   ├── af
│   │   └── 81445cd6401f394045651a876e9196db61e912
│   ├── e5
│   │   └── 3be081fdea9000576336d0d0dee7b326915ff2
│   ├── info
│   └── pack

git reset
├── objects
│   ├── 9c
│   │   └── 595a6fb7692405a5c4a10e1caf93d7a5bd9c37
│   ├── af
│   │   └── 81445cd6401f394045651a876e9196db61e912
│   ├── e5
│   │   └── 3be081fdea9000576336d0d0dee7b326915ff2
│   ├── info
│   └── pack

git reset 不会删除生成的内容 add 似乎只计算文件的hash,不会递归构建tree的obj

  1. 计算文件hash值,并递归构建tree,生成多个obj
  2. 更新index的内容

commit

after commit
├── objects
│   ├── 13
│   │   └── d298626d6cad66fd224fe25106a6def17ca9d1
│   ├── 18
│   │   └── 605c522c56ea4ebbf2ee17ae702c38e89f37e2
│   ├── 30
│   │   └── 532454c17b108960baaa779156d4ed06f4700a
│   ├── 9c
│   │   └── 595a6fb7692405a5c4a10e1caf93d7a5bd9c37
│   ├── af
│   │   └── 81445cd6401f394045651a876e9196db61e912
│   ├── e5
│   │   └── 3be081fdea9000576336d0d0dee7b326915ff2
│   ├── info
│   └── pack
  1. 类似add,生成commit的obj,指针前移
  2. 更新对应值

过程总结

commit

  • 保存提交的msg根目录所有文件和目录的hash

tree

  • 保存目录所有文件和目录的hash

blob

  • 保存文件压缩后内容

index

  • 保存暂存区的文件hash列表

计算过程

  1. 递归寻找根目录下所有文件
  2. 计算文件的hash,压缩文件并生成obj,向上传递hash
blob
$ git cat-file -p af81
## this is a log for lean git
  1. 目录将文件的hash包含,生成类似目录列表,压缩文件并生成obj,并对自己内容生成hash,向上传递
tree
$ git cat-file -p 3053
100644 blob af81445cd6401f394045651a876e9196db61e912	README.md
040000 tree 18605c522c56ea4ebbf2ee17ae702c38e89f37e2	example
  1. commit 收集hash,像tree一样生成列表,再加上提交信息和上一次提交的hash,压缩文件并生成obj,继续向上传递
commit
$ git cat-file -p 13d2
tree 30532454c17b108960baaa779156d4ed06f4700a
author chenxuan <1607772321@qq.com> 1668689153 +0800
committer chenxuan <1607772321@qq.com> 1668689153 +0800

init
  1. 修改HEAD的值,并将提交信息记录

QA

如何避免碰撞

  • 基本无需避免,两个不同文件产生同一个hash几率太小了.而且git有自动gc功能,进一步避免冲突

恶意碰撞

  • git 不会发现文件的不同,因此不会处理hash相同的文件,不会生成obj

为什么空文件夹不会被识别

  • tree obj存在意义在于记录包含的文件和文件夹,如果不包含任何文件和文件夹,那么这个tree完全为空就无法计算hash值

参考列表

https://git-scm.com/docs https://git-scm.com/book/zh/v2 https://zhuanlan.zhihu.com/p/106243588 https://developer.aliyun.com/article/761663