近期需要给 git 仓库制作一个 commit-msg
钩子,进入 .git/hooks
文件夹正准备干活,突然想知道其它 git hooks 都是干啥的?.git
文件夹里面那么多文件,又都是干什么的呢?于是产生了这篇文章。
另外,想要 git
进阶,了解 .git
文件夹也是最佳切入点,关于 git
运作机制的线索都可以在这里找到。
.git
文件夹创建
任意文件夹中,用 git init
命令初始化仓库,即可在此文件夹下创建 .git
文件夹(.
打头为隐藏文件夹,所以平时可能看不到)。这个文件夹之外的部分叫做工作区(Working Directory),.git
文件夹我们称做 Git仓库 (Git Repository)。
如果出于某种原因,想要重新来过,rm -rf .git && git init
,此仓库的git记录会归零! (提醒:慎用!!!)
.git
结构
随便初始化一个仓库,git init temp
,运行 cd temp && ls -F1 .git
,可以看到基本的 .git
目录结构:
HEAD
config
description
hooks/
info/
objects/
refs/
但这里面没有实质性内容,研究意义不大。我们找一个有过几次提交的仓库,运行 ls -F1 .git
可以看到更丰富的 .git
目录结构:
COMMIT_EDITMSG
HEAD
ORIG_HEAD
FETCH_HEAD
config
description
index
hooks/
info/
logs/
objects/
refs/
通常会有7个文件5个目录,分别如下:
重要:动手之前,请做好整个仓库的备份!!!
重要:动手之前,请做好整个仓库的备份!!!
重要:动手之前,请做好整个仓库的备份!!!
1. 文件 COMMIT_EDITMSG
此文件是一个临时文件,存储最后一次提交的信息内容,git commit
命令之后打开的编辑器就是在编辑此文件,而你退出编辑器后,git
会把此文件内容写入 commit 记录。
实际应用: git pull
远程仓库后,新增了很多提交,淹没了本地提交记录,直接 cat .git/COMMIT_EDITMSG
就可以弄清楚最后工作的位置了,是不是很实用?
2. 文件 HEAD
此文件永远存储当前位置指针,就像 linux 中的 $PWD
变量和命令提示符的箭头一样,永远指向当前位置,表明当前的工作位置。在 git
中 HEAD
永远指向当前正在工作的那个 commit
。
分支HEAD
HEAD 存储一个分支的 ref,运行:cat .git/HEAD
通常会显示:
ref: refs/heads/master
这说明你目前正在 master
分支工作。此时你的任何 commit,默认自动附加到 master
分支之上。
git cat-file -p HEAD
, 显示详细的提交信息:
tree 0551ac8f8238aca193bafea5c1dabe94a3f61909
parent 12459bcc65db0f20e533078b313e8dc6747e4ea3
author jamesyang.yjm <jamesyang.yjm@alibaba-inc.com> 1561509340 +0800
committer jamesyang.yjm <jamesyang.yjm@alibaba-inc.com> 1561509340 +0800
docs: fix typo in README.md
孤立HEAD
HEAD 不关联任何分支,只指向某个 commit,运行 git checkout 12459b
,你会看到如下信息:
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
相信很多人一开始使用 git 都会对这段信息头大,其实它只是告诉你 HEAD
这个文件中存储的信息已不再是一个分支信息,运行:cat .git/HEAD
,看到:
12459bcc65db0f20e533078b313e8dc6747e4ea3
看到区别了吗?HEAD 指向一个40字符的 SHA-1 提交记录,git 已经不知道你在哪个分支工作了,所以你如果生成新的 commit,git 不知道往哪里 push
,你只能做些实验性代码自嗨一把,无法影响到任何分支,也无法与人协同。这就是所谓的 'detached HEAD' state
。
(由分支HEAD 变为 孤立HEAD)
关于 HEAD
的用法示例:
git push origin HEAD
git checkout HEAD~1
3. 文件 ORIG_HEAD
正因为 HEAD
比较重要,此文件会在你进行危险操作时备份 HEAD
,如以下操作时会触发备份:
git reset
git merge
git rebase
git pull
此文件的应用示例:
# 回滚到上一次的状态(慎用!!!)
git reset --hard ORIG_HEAD
4. 文件 FETCH_HEAD
这个文件作用在于追踪远程分支的拉取与合并,与其相关的命令有 git pull/fetch/merge
,
而git pull
命令相当于执行以下两条命令:
$ git fetch
$ git merge FETCH_HEAD
# 显示如下>>>
From https://github.com/xxx/xxxx
* branch master -> FETCH_HEAD
Updating f785638..59db1b2
并且,此时会默默备份 HEAD
到 ORIG_HEAD
。
看看 FETCH_HEAD
里面有什么内容:
$ cat .git/FETCH_HEAD
848d7701250d5fee1449c5355158f629f6564484 branch 'master' of https://github.com/xxxx/xxx
最前面是 hash值,最后面是需要 fetch
的分支信息。
此文件可能不止一行,比如:
$ cat .git/FETCH_HEAD
848d7701250d5fee1449c5355158f629f6564484 branch 'master' of https://github.com/xxxx/xxx
81d84ed74fc2b29c73d6ac82d681e5819b4d35d3 branch 'next' of https://github.com/xxxx/xxx
a25f5f1615a479e717a82bc4a10d816a44de6cd1 not-for-merge branch 'add-i18n' of https://github.com/xxxx/xxx
065c1b268386d533be65f4ae34742b2f1780d589 not-for-merge branch 'add-sche-catch' of https://github.com/xxxx/xxx
其中会有关键字 not-for-merge
,由于 git pull
其实就是 fetch + merge
,有这个标志就表明 git pull
时只 fetch
,不 merge
。
此特性在 2015年 git 2.5 之后被加入,可以看看 源码,当 git pull
时,not-for-merge
会做为 magic string 来判定是否要从远程合并到本地分支。上面这段源码的注释写得恰到好处:
"Appends merge candidates from FETCH_HEAD that are not marked not-for-merge into merge_heads."
5. 文件 config
此文件存储项目本地的 git 设置,典型内容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = git@gitlab.xxxx.com/xxx.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "v2.6.0"]
remote = origin
merge = refs/heads/v2.6.0
[branch "v2.8.0"]
remote = origin
merge = refs/heads/v2.8.0
这是典型的 INI
配置文件,每个 section
可包含多个 variable = value
,其中 [core]
字段包含各种 git 的参数设置,如 ignorecase = true
表示忽略文件名大小写。
[core]
段的内容跟git config
命令对应
执行以下命令:
git config user.name abc
git config user.email abc@abc.com
会在 config
文件中追加以下内容:
... ...
[user]
name = abc
email = abc@abc.com
git config --global
影响的则是全局配置文件 ~/.gitconfig
。
[remote]
段表示远程仓库配置
详见 Git Internals - The Refspec,注意这里的 +
与 *
的含义。
[branch]
段表示分支同步设置
假设当前在 master
分支,执行 git pull
若出现以下提示:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
就说明 .git/config
文件缺少对应的 [branch "master"]
字段。
解决方案为:
git branch -u origin/master master
# 或者执行一次 push
git push -u origin master
会出现提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其实就是生成以下内容在 .git/config
中:
[branch "master"]
remote = origin
merge = refs/heads/master
你去手动编辑 .git/config
,效果一样。这就是 upstream
的真正含义,即生成 config
中的这段配置。
6. 文件 description
看到文档中有如下一段描述:
The description file is used only by the GitWeb program, so don’t worry about it.
说明这个文件主要用于 GitWeb
的描述,如果我们要启动 GitWeb
可用如下命令:
# 确保lighttpd已安装: brew install lighttpd
$ git instaweb --start
默认会启动 lighttpd
服务并打开浏览器 http://127.0.0.1:1234
(试着改成对外IP并分享给别人?)
以下显示当前的 git 仓库名称以及描述,默认的描述如下:
Unnamed repository; edit this file 'description' to name the repository.
上面这段话就是默认的 description
文件的内容,编辑这个文件来让你 GitWeb
描述更友好。除此之外没发现其它用处。
7. 文件夹 hooks/
存放 git hooks
,用于在 git 命令前后做检查或做些自定义动作。运行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,编辑器启动之前触发,传入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,编辑器退出后触发,传入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通过后触发,譬如校验文件名是否含中文
pre-push.sample # git push 之前触发
pre-receive.sample # git push 之后,服务端更新 ref 前触发
update.sample # git push 之后,服务端更新每一个 ref 时触发,用于针对每个 ref 作校验等
post-update.sample # git push 之后,服务端更新 ref 后触发
pre-rebase.sample # git rebase 之前触发,传入 rebase 分支作参数
applypatch-msg.sample # 用于 git am 命令提交信息校验
pre-applypatch.sample # 用于 git am 命令执行前动作
fsmonitor-watchman.sample # 配合 core.fsmonitor 设置来更好监测文件变化
如果要启用某个 hook,只需把 .sample
删除即可,然后编辑其内容来实现相应的逻辑。
比如我们要校验每个 commit message 至少要包含两个单词,否则就提示并拒绝提交,将 commit-msg.sample
改为 commit-msg
后,编辑如下:
#!/bin/sh
grep -q '\S\s\+\S' $1 || { echo '提交信息至少为两个单词' && exit 1; }
这样当提交一个 commit 时,会执行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG
,退出值不为 0
,就拒绝提交。
8. 文件夹 info/
此文件夹基本就有两个文件:
- 文件
info/exclude
用于排除规则,与.gitignore
功能类似。 - 可能会包含文件
info/refs
,用于跟踪各分支的信息。此文件一般通过命令 git update-server-info 生成,里面的内容:
94e1a0d952f577fe1348d828d145507d3709e11e refs/heads/master
# object hash # branch reference
这表示 master 分支所指向的文件对象 hash 值为:94e1a0d952f577fe1348d828d145507d3709e11e
,
运行 git cat-file -p 94e1a0d952f577fe1348d828d145507d3709e11e
,可以看到 master 分支最后提交的记录信息。
同时:cat .git/objects/94/e1a0d952f577fe1348d828d145507d3709e11e
可以看到最后提交文件的二进制内容表示。
文件 info/refs
对于 搭建 git 服务器 来说至关重要。
9. 文件夹 logs/
记录了操作信息,git reflog
命令以及像 HEAD@{1}
形式的路径会用到。如果删除此文件夹(危险!),那么依赖于 reflog 的命令就会报错。
mv .git/logs .git/logs_bak
git checkout HEAD@{1}
报错信息如下:
error: pathspec 'HEAD@{1}' did not match any file(s) known to git
10. 文件夹 objects/
此文件夹简单说,就是 git的数据库
,运行 tree .git/objects
,可以看到目录结构:
.git/objects/
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这些文件分两种形式:pack压缩包 形式放在 pack/
目录下,除此之外都是 hash文件
形式,被叫做 loost objects
。
这个文件夹以及相应的算法,我没找到独立的名称,就叫它 hash-object
体系吧。因为确实有个 git hash-object
命令存在,是一个底层的负责生成这些 loost objects
文件,如果要看到这些文件各自的含义,执行以下命令:
git cat-file --batch-check --batch-all-objects
可以看到
04c87c65f142f33945f2f5951cf7801a32dfa240 commit 194
098217953a6ca169bed33d2be8a07d584fcdaf30 tree 31
0cd370696b581c38ee01e62b148a759f80facc2d commit 245
2a810017bfc85d7db2627f4aabdaa1583212bda3 blob 19
3920a07c1d5694df6b8658592b0939241d70e9e5 tree 93
593d5b490556791212acd5a516a37bbfa05d44dd tag 148
61be44eedde61d723e5761577a2b420ba0fc2794 tree 154
... ...
但你会发现这个列表里有些值在文件夹中并不存在,因为除了 loost objects
它还汇总了 pack
文件中的内容。
hash文件
又称为 loose object
,文件名称共由40字符的 SHA-1 hash 值组成,其中前两个字符为文件夹分桶,后38个字符为文件名称。
按文件内容可分为四种类型:commit, tree, blob, tag,若执行以下命令会生成所有四种类型:
echo -en 'xx\n' > xx # 共 3 个字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
经过以上操作后,对比一下文件树,发现多了四个 hash文件
:
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 18
| `-- 143661f96845f11e0b4ab7312bdc0f356834ce
|-- 30
| `-- 20feea86d222d83218eb3eb5aa9f58f73df04d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- ad
| `-- f4c9afac7afae3ff3e95e6c4eefe009d547f00
|-- cc
| `-- c9bd67dc5c467859102d53d54c5ce851273bdd
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
这四个 hash文件
分别是:
cc/c9bd67dc5c467859102d53d54c5ce851273bdd # blob
30/20feea86d222d83218eb3eb5aa9f58f73df04d # commit
ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00 # tree
18/143661f96845f11e0b4ab7312bdc0f356834ce # tag
我们想看下里面到底存的什么?其实这些文件都经过了压缩,压缩形式为 zlib。先安装一下解压工具 macOS 版 brew install pigz
或 windows 版 pigz,后执行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB类型,显示结果为>>>>(注意xx后有个\n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT类型,显示结果为>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm <jamesyang.yjm@alibaba-inc.com> 1562044880 +0800
committer jamesyang.yjm <jamesyang.yjm@alibaba-inc.com> 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE类型,显示结果为>>>>
tree 154100644 abc*???]}?bJ?ڡX2??100644 asdf???CK?)?wZ???S?100644 iou???CK?)?wZ???S?100644 xx?ɽg?\FxY-S?L\?Q';?100644 yy???CK?)?wZ???S?
$ pigz -d < .git/objects/18/143661f96845f11e0b4ab7312bdc0f356834ce
# TAG类型,显示结果为>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm <jamesyang.yjm@alibaba-inc.com> 1562045942 +0800
release: 1.0.0
会发现,显示结果都是 type size+内容
形式,这就是 object 文件的存储格式:
[type] [size][NULL][content]
type
可选值:commit, tree, blob, tag,NULL
就是C语言里的字符结束符:\0
,size
就是 NULL
后内容的字节长度。
type
的几种类型可以使用 git cat-file -t hash
看到,内容可以用 git cat-file -p hash
看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 显示结果为>>>>
xx
所以 blob
文件就是对原文件内容的全量拷贝,同时前面加了 blob size\0
,而文件名称的 hash
值计算是计算整体字符的 SHA-1
值:
echo -en 'blob 3\0xx\n' | shasum
# 显示结果为>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它类型格式请自行参考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,当我们 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
时,会发生些什么?
- 找到
3020feea86d222d83218eb3eb5aa9f58f73df04d
这个commit
,显示出来 - 找到此
commit
关联的tree object
:adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相应的blob
文件,并与当前工作区内的文件做diff
,然后显示出来
这就是 objects/
文件夹作为 git数据库
被使用的真实例子。
pack文件
为什么会有 .pack
文件?
由于每次 commit
都会生成许多 hash文件
,并且由于 blob
文件都是全量存储的,导致 git 效率下降,于是有了 pack-format,优势:
- 对于大仓库存储效率高
- 利于网络传输,便于备份
- 增量存储,优化磁盘空间
将 .git/objects
下的部分文件打包成 pack格式
$ tree .git/objects/ | wc -l
311
$ git gc
Enumerating objects: 288, done.
Counting objects: 100% (288/288), done.
Delta compression using up to 4 threads
Compressing objects: 100% (287/287), done.
Writing objects: 100% (288/288), done.
Total 288 (delta 131), reused 90 (delta 0)
$ tree .git/objects/ | wc -l
12
可以看到文件数量减小了不少,其中大部分文件被打到一个 .pack
包中,并且是增量存储,有部分变更的文件只存储 基础hash + 变更内容,磁盘空间优化很明显。
git gc
其实运行了两条命令:git repack
用来打包 和 git prune-packed
用来移除已打包的 hash文件
- 如果你想打包所有文件,并不推荐,但可以用以下命令:
$ git repack -a -d -f --depth=250 --window=250
具体可见:此问题
- 如果想看一下包里有啥,运行:
git verify-pack -v .git/objects/pack/pack-5963b552193021791c1a0ab9136c272f07124c98.pack
显示如下:
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 commit 245 153 12
2305588a632214f266462260428c4395f936b5b0 commit 252 156 165
1fa9735670eb952b6468d17b418525717c8e3527 commit 248 156 321
3ffb7fb9830e232669c95b3b65f0f8f3fc7a6027 commit 248 155 477
86a5912f97d7dd8f90a28cab6bffc8ee78997e2c commit 244 151 632
94e1a0d952f577fe1348d828d145507d3709e11e commit 249 156 783
86903f8f5024485afa8480020a04cc00f228d23c commit 243 150 939
6efdffad4fb725aa8d0f4d7d29feb5aee7ea5dff commit 242 151 1089
04c87c65f142f33945f2f5951cf7801a32dfa240 commit 73 85 1240 1 6efdffad4fb725aa8d0f4d7d29feb5aee7ea5dff
2a810017bfc85d7db2627f4aabdaa1583212bda3 blob 19 27 1325
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob 0 9 1352
b5e810691433cf8a2960c27c1b33546fa96e2bef blob 16 26 1361
2f36e957afc2b3bcda988cb29a86e3a1490e8cc2 tree 153 106 1387
2ed6130bd33afa26817418308e29c4081ea056ec tree 5 15 1493 1 2f36e957afc2b3bcda988cb29a86e3a1490e8cc2
9df301ad27294a62ba1ae65aaed489072d778c79 tree 123 103 1508
7d48a14b9ca1dca2f6a593eef19633ce45f81bee blob 12 21 1611
a448b4d6450de854dcc6fe658bdb72e22c726cbb tree 123 102 1632
9e56fd51f52d8b9d242c50c24a4cae586d76ec7e blob 7 16 1734
bde15b851f135327ada02c9deac0fb1ee01cf343 tree 123 102 1750
58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c blob 4 13 1852
3920a07c1d5694df6b8658592b0939241d70e9e5 tree 7 17 1865 1 bde15b851f135327ada02c9deac0fb1ee01cf343
16729e3b94f19bc95cb6f563f776bfb4694a6e5b tree 4 14 1882 2 3920a07c1d5694df6b8658592b0939241d70e9e5
b72c74792528892694c395b2c9a3d6af740f3fb2 tree 63 50 1896
098217953a6ca169bed33d2be8a07d584fcdaf30 tree 31 42 1946
non delta: 20 objects
chain length = 1: 3 objects
chain length = 2: 1 object
.git/objects/pack/pack-5963b552193021791c1a0ab9136c272f07124c98.pack: ok
后面那串数字说明文档里很详细:
When specifying the -v option the format used is:
SHA-1 type size size-in-packfile offset-in-packfile
for objects that are not deltified in the pack, and
SHA-1 type size size-in-packfile offset-in-packfile depth base-SHA-1
for objects that are deltified.
以上最后有 hash 的条目,说明是增量存储的 基础hash,其前是增量深度。
11. 文件夹 refs/
refs
可以理解成文件系统中的 symbol link
,看下结构:
$ tree .git/refs/
.git/refs
|-- heads
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ cat .git/refs/tags/v1.0
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ git cat-file -t 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
commit
可以看到 master
和 v1.0
都指向 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
这个 commit
。
refs/heads/
文件夹内的 ref
一般通过 git branch
生成。git show-ref --heads
可以查看。
refs/tags/
文件夹内的 ref
一般通过 git tag
生成。git show-ref --tags
可以查看。
如下:
$ git branch abc
$ tree .git/refs/
.git/refs/
|-- heads
| |-- abc
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
说明新建分支其实就是生成了一个指向某个 commit
的 symbol link
,当然在这里叫做 ref
。
而 git tag
命令本质与 git branch
相同,只生成一个 ref
放在 tags
目录下,所以被称为 lightweight tag
。
而 git tag -a xx
命令会首先生成一个类型为 tag
的 hash文件
放到 objects/
目录,然后生成 ref
放到 tags
目录下指向那个文件。这就叫做 annotated tag
,好处是可包含一些元信息如 tagger
和 message
,被 git 的 hash-object
算法管理,可被 GPG 签名等,所以更稳定,更安全。
使用以下命令来拿到 refs
文件夹存储的信息:
$ git show-ref --head --dereference
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
我们来看这些信息如何变化的:
$ touch new_file && git add . && git commit -m 'add new_file'
[master 44b0d05] add new_file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 new_file
$ git show-ref --head --dereference
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
diff 一下可以看到:
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
这两行发生了变化。也就是每次 commit 时,HEAD 与 heads
都会自动更新。
12. 文件 index
细心的读者发现,没有讲 index
文件?
原因在于,index
文件是整个 git 除 hash-object
体系最核心的部分,值得用单独一篇来讲。
可以先参考以下文章:
阮一峰老师写的 Git 原理入门 中 暂存区
的部分
Use of index and Racy Git problem
简要说一下,index
是一个微型的 linux文件系统,用最经济的方式实现了 inode,这并不是偶然,因为创造这个想法的人同时也是 linux 的创造者 Linus Torvalds。
这个文件也叫做 git
的暂存区(Staging Area
),git add
就是把工作区内的某些文件取部分 stat
抓取的内容并写入 .git/index
文件并存为相应的一条 index entry
,多条 index entry
形成一个 tree
。
git commit
是把上一步形成的 tree
结构及相应的 blob
存储到 objects/
文件夹下并同时生成一条 commit
记录。
git reset
是将刚写入 index
文件的 tree
丢弃,并从 HEAD
中恢复一个 tree
。
git status
是拿 index
文件中存储的 tree
与工作区内的文件在 stat
层面做对比,并输出变更。
以上,这几个文件夹咱们用一张图做总结:
参考资料:
https://git-scm.com/docs/githooks
https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain
http://www-cs-students.stanford.edu/~blynn/gitmagic/ch08.html#_the_object_database
https://git-scm.com/docs/git-show-ref
https://stackoverflow.com/questions/28720151/git-gc-aggressive-vs-git-repack