git 数据结构探究之index文件

上一篇探究了blob类型,我们知道了它本质就是一个压缩文件。并且这个压缩文件还用哈希值重新取名了。
在git的数据结构中,有个很重要的知识点:文件名和内容是分开存储的。
这样我们的文件名就不会强绑定内容了。

这次我们接着上次的实验,来探究index文件。

我们将上次构造的写有“version 1”的blob文件写入暂存区。

$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 temp.txt
  1. git update-index:这是 Git 操作暂存区(index)的底层命令,用于修改暂存区中文件的元数据(如哈希值、权限、状态等)。
  2. --add:表示将指定的文件添加到暂存区(如果文件原本不在暂存区中)。
  3. --cacheinfo:关键参数,用于通过文件模式、哈希值和文件名直接向暂存区添加记录,无需依赖工作区文件。
  4. 100644:文件模式(mode),表示这是一个普通文件(非 executable、非符号链接等)。
  5. test.txt:文件名可以随便取。
    注:Git 中常见模式:100644(普通文件)、100755(可执行文件)、120000(符号链接)。

查看目录结构:

$ tree -a
.
|-- .git
|   |-- HEAD
|   |-- config
|   |-- description
|   |-- index
|   |-- info
|   |   `-- exclude
|   |-- objects
|   |   |-- 1f
|   |   |   `-- 7a7a472abf3dd9643fd615f6da379c4acb3e3a
|   |   |-- 7f
|   |   |   |-- 8f011eb73d6043d2e6db9d2c101195ae2801f2
|   |   |   `-- acc89938bbc5635e3d36ffa56b4c85e9b07db8
|   |   |-- 83
|   |   |   `-- baae61804e65cc73a7201a7252750c76066a30
|   |   |-- d6
|   |   |   `-- 70460b4b4aece5915caf5c68d12f560a9fe3e4
|   |   |-- info
|   |   `-- pack
|   `-- refs
|       |-- heads
|       `-- tags
|-- test.txt
`-- test2.txt

12 directories, 12 files

目录没有变化,但是多了一个index文件。
似乎index就代表着暂存区,或者说他们两个是强相关的。

这个index文件是一个二进制文件,可以把他理解为一个c语言中的结构体,不同字段代表不同内容,具体如何划分,可以参看最后的引用资料。

我们可以使用如下命令查看他包含的主要内容:

$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0       temp.txt
  1. 100644:表示普通文件
  2. 83baae61804e65cc73a7201a7252750c76066a30:哈希值,对应特定blob。
  3. 0:冲突状态,在合并文件时会用到。
  4. temp.txt:文件名。

index文件的主要作用就是把一个哈希值指定的blob和文件名关联起来。
相当于给解压缩后的blob取名。
index就像一个对照表,记录着不同名字对应的blob。

在blob关联文件名的过程中,文件名是可以让我们随意指定的。
此时会出现3种情况:

  1. 没有同名文件:这个文件名还没有其他文件使用过。
  2. 有同名文件,但内容不同
  3. 有同名文件,且内容相同

我们接下来分类讨论:

1.没有同名文件

$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   temp.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    temp.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test.txt
        test2.txt

Changes to be committed:
new file: temp.txt
这段表示有个新文件名和blob关联起来了,可以进行最终存储。

Changes not staged for commit:
deleted: temp.txt
这段表示temp.txt只有blob文件,没有对应的源文件,源文件被删除掉了。

Untracked files:
test.txt
test2.txt
这段表示还有两个文件名没有指定blob。也就是说index文件还没有记录他们,所以叫:未追踪状态。

看到这里,用过git的人已经明白,原来暂存区就是index这个对照表。
那删除index是不是就表示暂存区没有了呢?
尝试一下,手动删除index

$ git ls-files --stage
$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test.txt
        test2.txt

nothing added to commit but untracked files present (use "git add" to track)

删除掉index文件后,暂存区的信息的确没有了。

2.有同名文件,但内容不同

先查看blob中的内容,避免与现有文件重复。

$ git cat-file -p 83baa
version 1

test.txt中存储的是version 2.
接下来给blob指定文件名

$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt

查看index信息

$ git ls-files --stage
100644 83baae61804e65cc73a7201a7252750c76066a30 0       test.txt
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   test.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   test.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test2.txt

区别主要在下面这段:
Changes not staged for commit:
modified: test.txt
其实就是提示你test.txt这个文件可能有改动,你要不要重新计算一个blob。

3.文件名相同,内容相同

删除掉index
查看内容

$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
version 2

写入index

$ git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt

查看index

$ git ls-files --stage
100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a 0       test.txt
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   test.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test2.txt

很明显,没有被追踪的文件就只剩下一个了。


探究blob是否可以还原为源文件

显然,文件可以变为blob,
那么blob也可以根据index这份对照表变为文件。
手动删除之前的index文件和temp.txt文件
然后查看可用的blob

$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/7f/8f011eb73d6043d2e6db9d2c101195ae2801f2
.git/objects/7f/acc89938bbc5635e3d36ffa56b4c85e9b07db8
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

将其中一个写入index,也可以都写,然后赋予不同的文件名

$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 temp.txt

注意,输入的是完整哈希值。

下面的代码就可以实现由blob转回源文件:

$ git restore temp.txt

可以查看目录树:

$ tree
.
|-- temp.txt
|-- test.txt
`-- test2.txt

0 directories, 3 files

发现我们多了一个temp.txt文件。
如果是同名文件,那么就会直接替换掉你的源文件,所以使用要谨慎。

参考资料:
深入Git索引(一):索引文件结构篇
Git 内部结构:体系结构和索引文件

posted @ 2025-08-23 19:58  leader4  阅读(21)  评论(0)    收藏  举报