1.git概念
我们的项目一般由文件夹和文件组成,在文件系统中,基本都是树形结构, 在git中,文件夹称为 “tree” ,文件称为 “blob” ,顶层文件夹称为 “top-level tree” 。下方的目录结构是个例子而已:
. (top-level tree)
├── humx.txt (blob,内容为“111”)
└── test (tree)
└── humx1.txt (blob,内容为“222”)
└── humx2.txt (blob,内容为“222”)
整个树形结构我们称为"commit", 在git 系统中, 就是众多commit构成的
11316@DESKTOP-SMV4E4J MINGW64 /f/idea
$ mkdir test
11316@DESKTOP-SMV4E4J MINGW64 /f/idea
$ cd test
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test
$ git init
Initialized empty Git repository in F:/idea/test/.git/
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ cd .git/
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test/.git (GIT_DIR!)
$ tree
.
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags
.git目录下结构功能:
- HEAD 文件:指向当前所在分支。
- config文件:包含了一些git的配置。
- description文件:只有在GitWeb项目中才会用到,所以不用关注这个文件。
- hooks文件夹:包含了一些钩子脚本, 触发某些特定事件触发
- info文件夹:包含了.gitignore 文件中的信息, 忽略某些文件用的
- objects文件夹:存放object的数据库,存放整个项目的所有数据。
- refs文件夹:存放了指向objects的指针(如branches,tags,remotes等)
首先,我们先创建一个文件humx.txt 并写入111的内容,并执行git add ,得出以下:
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ echo '111' > humx.txt
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ git add .
warning: LF will be replaced by CRLF in humx.txt.
The file will have its original line endings in your working directory
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ tree .git/
.git/
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- objects
| |-- 58
| | `-- c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags
可以看到,在objects文件夹下多了一个58文件夹和一个名叫c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c文件,把两者看成一个整体就是一个git object,而且我们将它们的名字拼接起来就能得到一个40位的hashId,这个hashId是这个object的唯一标志符,git通过这个hashId来查找这个object。不过不能直接cat,因为文件是经过压缩的,不能直接阅读。通过 git cat-file命令查看可得到 111 的内容 和blob 的类型。并且多了index文件,这是我们git 的暂存区存放的文件,内容为(mode) (object) (stage) (file).
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ git commit -m"first commit"
[master (root-commit) a4f7afb] first commit
1 file changed, 1 insertion(+)
create mode 100644 humx.txt
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ tree .git/
.git/
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| `-- master
|-- objects
| |-- 56
| | `-- 63e48c7b2bc80c2cdc2990d11ba8594a87ba0f
| |-- 58
| | `-- c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
| |-- a4
| | `-- f7afb5eda5f66b249171a744feebf9a754a7ea
| |-- info
| `-- pack
`-- refs
|-- heads
| `-- master
`-- tags
接下来提交文件,我们发现又多了两个object,其中:
56:(tree)
a4:(commit)
仔细观察,commit指向tree, tree指向blob,可以得出:
然后在创建一个test文件夹,文件夹下新建humx1.txt humx2.txt 内容分别为222 333
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test/text (master)
$ echo '222' > humx1.txt
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test/text (master)
$ echo '333' > humx2.txt
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test/text (master)
$ cd ..
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ git add .
warning: LF will be replaced by CRLF in text/humx1.txt.
The file will have its original line endings in your working directory
warning: LF will be replaced by CRLF in text/humx2.txt.
The file will have its original line endings in your working directory
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ git commit -m"second commit"
[master 8b2580a] second commit
2 files changed, 2 insertions(+)
create mode 100644 text/humx1.txt
create mode 100644 text/humx2.txt
11316@DESKTOP-SMV4E4J MINGW64 /f/idea/test (master)
$ tree .git/
.git/
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| `-- master
|-- objects
| |-- 30
| | `-- ed92ac0fa69377e0f25cb339e1e5676a300c5a
| |-- 55
| | `-- bd0ac4c42e46cd751eb7405e12a35e61425550
| |-- 56
| | `-- 63e48c7b2bc80c2cdc2990d11ba8594a87ba0f
| |-- 58
| | `-- c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
| |-- 8b
| | `-- 2580a781611d1e7be39e42ab8bbf88181103bf
| |-- a4
| | `-- f7afb5eda5f66b249171a744feebf9a754a7ea
| |-- c2
| | `-- 00906efd24ec5e783bee7f23b5d7c941b0c12c
| |-- df
| | `-- 15a9f7eb285f1384886435bf4055f12d1bb93a
| |-- info
| `-- pack
`-- refs
|-- heads
| `-- master
`-- tags
30:(tree)
55:(blob)
8b:(commit)
c2:(blob)
df:(tree)
我们可以发现:hashId为df的tree是新生成的,是因为根目录下的文件/文件夹列表发生了变化。git并没有直接在56 的tree中直接修改,是因为如果我们需要跳回第一次commit的内容时,直接使用56的tree就可以了,这样就很方便。
还有值得注意的是:由于humx.txt文件内容未改变,df tree直接引用了58这个object,这样的策略可以为.git目录节省很大的空间,如下图:
2.Reference
git是通过hashId来查找某个commit的,这对于计算机来说是件非常简单的事,但对于人来说,要记住这么长的hashId,真不是件容易的事。如果能给这些hashId取一些“简单的名字”(比如master、dev、HEAD等),那就非常容易记忆了。在git术语中,这些“简单的名字”被叫做 “reference” 。这些reference主要保存在.git/refs文件夹中
2.1 Branch
这里我们新建一个名为test1的仓库,并创建humx3.txt文件,进行commit后,cd 到.git/refs/head路径下,会有一个master文件,直接查看文件可以得到这个commit的hashId,可以直接找到这个提交,下面清楚的标记了这次提交的 tree ,作者信息,并且类型为 commit。git管理的项目原本没有master分支,当我们提交第一个commit后,git会自动给我们创建master分支,并将它指向第一个commit。
.
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| `-- master
|-- objects
| |-- ce
| | `-- 013625030ba8dba906f756967f9e9ca394464a
| |-- e2
| | `-- 244575c8f723ffb6de45fac2b34391cc1b074d
| |-- fb
| | `-- e2b124907eb30c097cec796f60dfb9c18df8ff
| |-- info
| `-- pack
`-- refs
|-- heads
| `-- master
`-- tags
ce:
e2:
fb:
这里我们修改一下开始文件的内容,追加“world”进去,发现新的提交指向了新的地址,但文件名还是之前的文件名。
.git/
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| `-- master
|-- objects
| |-- 47
| | `-- 0b84f11fafd7e2a29a54d9cefd9fa199b9f3d3
| |-- 94
| | `-- 954abda49de8615a048f8d2e64b5de848e27a1
| |-- b7
| | `-- e48a1342abdc6c13ff4c10993625d6a60d836e
| |-- ce
| | `-- 013625030ba8dba906f756967f9e9ca394464a
| |-- e2
| | `-- 244575c8f723ffb6de45fac2b34391cc1b074d
| |-- fb
| | `-- e2b124907eb30c097cec796f60dfb9c18df8ff
| |-- info
| `-- pack
`-- refs
|-- heads
| `-- master
`-- tags
47:
94:
b7:
接下来,我们在切换分支,并新增humx4.txt文件写入内容hahaha
.git/
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- index
|-- info
| `-- exclude
|-- logs
| |-- HEAD
| `-- refs
| `-- heads
| |-- master
| `-- text
|-- objects
| |-- 44
| | `-- 5a69c00e48288ac420a2ead9ae5a1cb4cd36d4
| |-- 47
| | `-- 0b84f11fafd7e2a29a54d9cefd9fa199b9f3d3
| |-- 94
| | `-- 954abda49de8615a048f8d2e64b5de848e27a1
| |-- b7
| | `-- e48a1342abdc6c13ff4c10993625d6a60d836e
| |-- bf
| | `-- 06cff14be1e67be65b50f333cbf1d13270662e
| |-- ce
| | `-- 013625030ba8dba906f756967f9e9ca394464a
| |-- dc
| | `-- 55dcba685e66992a5379e393a4fb2de1738d92
| |-- e2
| | `-- 244575c8f723ffb6de45fac2b34391cc1b074d
| |-- fb
| | `-- e2b124907eb30c097cec796f60dfb9c18df8ff
| |-- info
| `-- pack
`-- refs
|-- heads
| |-- master
| `-- text
`-- tags
44:
bf:
dc:
HEAD文件保存着当前所在分支的路径,git通过这个路径访问到对应分支文件,然后通过这个分支文件保存的hashId就能获取到对应commit,这样git就能构建出当前commit对应的目录结构,如图:
3.总结
- objects文件夹是git最重要的数据库,所有的文件内容,及各个版本的内容,都保存在objects文件夹中。
- objects文件夹中主要保存三类object:commit、tree、blob,它们都由一个文件夹和文件组成,文件夹和文件的名字拼接成40位的hashId,这个hashId就是这个object的唯一标识符,git通过这个hashId来查找某个object。另外,这些文件都是经过压缩的,不能直接供人阅读,需要通过git cat-file指令查看。
- 每一个commit都对应一个top-level tree,以top-level tree为根节点,可以构造出当前版本的目录结构,通过访问blob类的object,就能读取到对应文件的具体内容。另外,多个版本之间的文件如果是完全相同的,git只会生成一个blob对象,多个版本引用同一个blob对象,这可以大大节省磁盘空间。
- referrence都是基于commit来实现的。