学完了 git 的底层原理后,底气是有了,但是还是不够,毕竟这只是别人讲的寓言故事,这和 git 的真正底层原理差距大不大,自己也不知道。
然后想起了 .git,文件夹,,点进去一看,这不就和刚才讲的一摸一样吗?狂喜。
然后搜了下官方文档,整理了下,将寓言故事中的用词官方化了下,首先创建了个空项目,创建了几个文件,大致如下,
.
├── README.md
├── newFile.js
└── src
└── index.js
创建了两个分支,现在在 dev 分支上,
* dev
master
(END)
.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-merge-commit.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── prepare-commit-msg.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ ├── dev
│ └── master
├── objects
│ ├── 30
│ │ └── d74d258442c7c65512eafab474568dd706c430
│ ├── 38
│ │ └── 01af60c91c7e8c372c690768fdb3fad63f7122
│ ├── 3c
│ │ └── 147b108e14c129e6e2a0c90938c9995b8287c0
│ ├── 43
│ │ └── 920febd360c7c656fb482f4dc7d294447d1b60
│ ├── 75
│ │ └── fa785de707a4ce962f00cfa716e7627f24c0ff
│ ├── 9d
│ │ └── 2d0f1144c1e667277d35210c77d08ff0906d8a
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── e9
│ │ └── 3c50fa7f4c83e145a3a943d9b747df8e4c2dd1
│ ├── info
│ └── pack
└── refs
├── heads
│ ├── dev
│ └── master
└── tags
官方的说法是 commit 或者 commit object。
我们可以通过 git 提供的 git cat-file -p
命令来查看我们在
objects 目录下创建的文件。之前在寓言故事中提到的,temp 文件,blob
文件,tree 文件等,这些都复制到了
objects 目录下了,我们可以根据对应的 hash 值找到对应的文件。
我们再来看下 objects 目录,
├── objects
│ ├── 30
│ │ └── d74d258442c7c65512eafab474568dd706c430
│ ├── 38
│ │ └── 01af60c91c7e8c372c690768fdb3fad63f7122
│ ├── 3c
│ │ └── 147b108e14c129e6e2a0c90938c9995b8287c0
│ ├── 43
│ │ └── 920febd360c7c656fb482f4dc7d294447d1b60
│ ├── 75
│ │ └── fa785de707a4ce962f00cfa716e7627f24c0ff
│ ├── 9d
│ │ └── 2d0f1144c1e667277d35210c77d08ff0906d8a
│ ├── e6
│ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│ ├── e9
│ │ └── 3c50fa7f4c83e145a3a943d9b747df8e4c2dd1
│ ├── info
│ └── pack
我们当前所在的分支是 dev 分支,HEAD(最新的 commit) 是 9d2d0f1144c1e667277d35210c77d08ff0906d8a,我们在 objects 里面看了下,没看到这个名字的文件。。。有点奇怪。。。
原来 objects 下面的文件(object)的 hash 是子目录的名字(sha1 值的头两个字符)加 子目录下的文件的 hash(sha1 的 后38个字符),这样我们就找到了,
│ ├── 9d
│ │ └── 2d0f1144c1e667277d35210c77d08ff0906d8a
来看下内容,
git cat-file -p 9d2d0f1144c1e667277d35210c77d08ff0906d8a
tree 3c147b108e14c129e6e2a0c90938c9995b8287c0
parent e93c50fa7f4c83e145a3a943d9b747df8e4c2dd1
author cary <CaryWill.K@gmail.com> 1632184865 +0800
committer cary <CaryWill.K@gmail.com> 1632184865 +0800
fix: new file
大致的字段都和我们在寓言中学到的是一样的。
上面看到的发现是一个 tree,我们继续打印这个树里面的内容,
git cat-file -p 3c147b108e14c129e6e2a0c90938c9995b8287c0
100644 blob 30d74d258442c7c65512eafab474568dd706c430 README.md
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 newFile.js
040000 tree 43920febd360c7c656fb482f4dc7d294447d1b60 src
原来我们刚才那个 commit 是项目的根目录,你可以看到上面根目录里的内容对应的是这个,
├── README.md
├── newFile.js
└── src
然后 src 对应的是一个 tree 文件,我们继续打印,
git cat-file -p 43920febd360c7c656fb482f4dc7d294447d1b60
100644 blob 75fa785de707a4ce962f00cfa716e7627f24c0ff index.js
这样我们就可以看到整个目录了,想看各个 blob 文件里面的内容,可以继续打印,这个就不继续打印了。
还有一个点,你会发现每一个 commit 都是这个项目在某一个时间点完整的快照,并不是这个 commit 带来的 change。
还有一个点不同的是,寓言故事里面,message 文件是在每个 commit 目录里面的,现在所有的 commit 的 message统一放在 .git 目录下的 logs 目录下去了。
├── logs
│ ├── HEAD
│ └── refs
│ ├── heads
│ │ ├── dev
│ │ └── master
│ └── remotes
│ └── origin
│ ├── dev
├── HEAD
└── refs
├── heads
│ ├── dev
│ └── master
└── tags
分支的话,我们之前是存在根目录下的 branches 文件里面的,
现在是放在 .git 目录下的 refs 目录下的 heads 目录下,顾名思义,里面就是存放了一堆 head(最新的 commit 的 hash),
每个文件对应的是 head 的 hash,文件名是分支名,和我们寓言故事里面讲的大同小异。
里面还有 tags 目录,和我们之前讲的也一样。
不过 .git 目录里还多了个
HEAD 文件,这个就是表示当前所指向的 commit,如果我们是直接
git checkout dev
到 dev 分支上的话, 那么这个就是
refs/heads/dev 的引用,表示其分支上的最新的 commit,这时
HEAD 的具体内容长这样,
ref: refs/heads/dev
但是像是你直接切到某一个 commit 上的话,比如,目前 dev 分支上有两个 commit,
commit 9d2d0f1144c1e667277d35210c77d08ff0906d8a (HEAD -> dev)
Author: cary <CaryWill.K@gmail.com>
Date: Tue Sep 21 08:41:05 2021 +0800
fix: new file
commit e93c50fa7f4c83e145a3a943d9b747df8e4c2dd1 (master)
Author: cary <CaryWill.K@gmail.com>
Date: Mon Sep 20 22:49:41 2021 +0800
first commit
(END)
我们
git checkout 9d2d0f1144c1e667277d35210c77d08ff0906d8a
到第一个 commit 上去的话,你现在就不属于任何一个分支,
git checkout e93c50fa7f4c83e145a3a943d9b747df8e4c2dd1
Note: switching to 'e93c50fa7f4c83e145a3a943d9b747df8e4c2dd1'.
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 switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at e93c50f first commit
来看下这个时候的 HEAD 文件的内容,
e93c50fa7f4c83e145a3a943d9b747df8e4c2dd1
对就是该commit 的 hash。
如果我们在将分支推送到 github 上的仓库里的话,那么在
refs 目录下还会多出一个
remotes 目录,里面是关于远程分支的信息,先基于 dev 分支
git checkout branch main
创建一个
main 分支,然后推送到 github 仓库去,
git remote add origin git@github.com:CaryWill/git-intervals-playground.git
git branch main
git push -u origin main
推送完后,再看下 refs 目录,
└── refs
├── heads
│ ├── main
│ └── master
│ └── dev
├── remotes
│ └── origin
│ └── main
结论,上一篇的寓言故事和真实的 git 底层原理几乎是一摸一样的,平常使用 git 命令的时候,可以用 git 寓言故事作为参考,加深对命令的记忆。
比如,git branch development
命令可以用来创建一个分支,这其实就只是在
refs
目录下多加了个文件而已,用来记录当前新增的分支及其对应的最新的
commit hash。
如果我们使用了
git stash
命令的话,那么这里面还有一个文件,stash
文件,我先现在在根目录下创建一个文件,然后将它 stash 起来,
touch newFile5.js
git stash
然后这给文件就出来了,
.git/refs
├── heads
│ ├── dev
│ ├── main
│ └── master
├── remotes
│ └── origin
│ ├── dev
│ └── main
├── stash
└── tags
这个 stash 文件里面的内容长这样,
209506d2de3c762adb3d24b61058c45dee4ab889
就一个 hash,使用 git cat-files -p
来查看这个
hash,我们发现,这就是一个 commit,来看下它的定义
就很清楚了,
Use git stash when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the HEAD commit.
基于当前 working 目录/ staging状态创建一个 commit,创建完成后再将 working 目录和 staging 回退到当前分支的 HEAD commit 上,起到的就是一个暂存的作用。
大家都知道 stash 可以反复 stash,stash 是一个 list of commits,有没有想过为什么 stash 文件里就存了一个最新 stash 的 commit 的 hash 呢?
感兴趣的小伙伴可以思考下。
这个其实就是 .git 目录下的 index 文件,它就相当于一个 commit 的拷贝,
.git
├── index
可以使用 git ls-files
命令来查看 index 文件的内容,先看下
staging 里有啥,
git ls-files --stage
100644 20030f2cd5c96dc15b95a3b6160be07811032891 0 README.md
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 newFile.js
100644 75fa785de707a4ce962f00cfa716e7627f24c0ff 0 src/index.js
然后我们项目的根目录下新建一个文件,随便取一个名字,叫 newFile2.js 好了,
touch newFile2.js
.
├── README.md
├── newFile.js
├── newFile2.js
└── src
└── index.js
然后将它加入到 staging 去,
git add newFile2.js
然后在查看下 staging(index) 里的东西,
git ls-files --stage
100644 20030f2cd5c96dc15b95a3b6160be07811032891 0 README.md
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 newFile.js
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 newFile2.js
100644 75fa785de707a4ce962f00cfa716e7627f24c0ff 0 src/index.js
可以通过 man 命令查看 git 命令的使用方法,比如,
man git-stash
man git-add
man git-reset