3. 新增一个文件
现在,我们已经了解了.git目录中初始文件的情况,让我们执行第一个将内容添加到.git目录的操作。我们将创建一个文件并将其添加到暂存区(但还没有提交)。
echo 'hello,789' > file git add file
我们继续使用erd -y inverted .git来查看文件变化。
git add file这会引起两个主要的更改。
- 首先,它新增了索引文件(
index)。Index用于存储有关当前暂存区的信息,用于表示名为file的文件已被添加到暂存区。 - 第二个更为重要的更改是添加了一个新文件夹
objects/c3,其中包含一个名为dc8e6cf3e1117a8d9731ddde9916da644296aa的文件。这是Git中存储对象的部分。
窥探objects中信息
我们使用file来查看一下内容是何方神圣。
file .git/objects/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa .git/objects/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa: zlib compressed data
结果显示这是经过zlib压缩的数据。这就很让人抓马。你有张良计,我有过墙梯,我们可以使用zlib库对其解压处理。
zlib-flate -uncompress < .git/objects/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa blob 10hello,789
结果显示它包含了文件名为file的文件的类型、大小和数据。
也就是说,/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa表示它是一个大小为10的blob,内容是hello,789的数据。(只不过是被zlib处理了)
上面提到的/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa这是Git对象的一部分,用于存储文件内容。
注意,此时我们用到了
zlib库,我们可以通过brew install zlib下载该库。(我是Mac环境,其他环境大家自行寻找解决方案)
文件名的由来
文件名来自内容的
SHA-1hash值。
如果我们将zlib压缩的数据通过sha1sum命令处理,我们会得到文件名。
$ zlib-flate -uncompress <.git/objects/c3/dc8e6cf3e1117a8d9731ddde9916da644296aa | sha1sum c3dc8e6cf3e1117a8d9731ddde9916da644296aa
Git使用内容的SHA-1散列值,取前两个字符(在这种情况下是c3),创建一个文件夹,然后将剩余部分用作文件名。Git从前两个字符创建文件夹,以确保我们不会在单个objects文件夹下有太多文件。
在
Mac环境下,我们需要通过brew install md5sha1sum
git cat-file
由于objects的内容在Git中比较重要,Git还特意提供了一个名为git cat-file的命令,用于查看git对象的内容。 使用git cat-file命令
- 带有
-t选项查看类型(type) - 带有
-s选项查看大小(size) - 带有
-p选项查看内容(pretty-print)
- 这个选项用于显示 Git 对象的内容,以更易读的方式呈现,通常用于查看提交、树或标签对象的内容
git cat-file -t c3dc8e6cf3e1117a8d9731ddde9916da644296aa blob git cat-file -s c3dc8e6cf3e1117a8d9731ddde9916da644296aa 10 git cat-file -p c3dc8e6cf3e1117a8d9731ddde9916da644296aa hello,789
4. git commit
既然,已经将内容通过git add 添加到Index(暂存区),接下来我们就需要将内容commit到local repository:(本地仓库)
前面讲过,下面的ci等同于commit。
git ci -m '首次提交'
继续使用erd -y inverted .git 来查看目录结构
嚯,一下多了很多文件。让我们来解读一下。
首先是一个新文件COMMIT_EDITMSG,它包含了(最新的)提交消息。
如果我们运行git ci命令而没有使用-m标志,那么Git获取提交消息的方式是打开一个文本编辑器,使用COMMIT_EDITMSG文件来让用户编辑提交消息。一旦用户更新了消息并退出编辑器,Git就会使用该文件的内容作为提交消息。
它还添加了一个全新的logs文件夹。这是Git用来记录仓库中所有提交更改的一种方式。我们将能够在这里看到所有refs和HEAD的提交更改。
在refs/heads目录,其中新增了一个名为master的文件。这是对主分支(master)的引用。
使用cat查看对于的内容信息。
cat .git/refs/heads/master fe010b33df5078cdbd96f2397aad60ec5f42a967
看起来它指向了其中一个新对象。我们用内置命令cat-file查看内容。
$ git cat-file -t fe010b33df5078cdbd96f2397aad60ec5f42a967 commit $ git cat-file -p fe010b33df5078cdbd96f2397aad60ec5f42a967 tree 658524b859ae78d902597253a3b68b4da3b70466 author xxx <xxx@simple> 1697178492 +0800 committer xxx <xxx@xxx> 1697178492 +0800 首次提交
我们也可以使用以下命令查看该引用的类型:
git cat-file -t refs/heads/master
看起来这是一种新的对象类型,似乎是一个提交对象(commit object)。提交对象的内容告诉我们,它包含一个哈希为658524b859ae78d902597253a3b68b4da3b70466的树对象(tree object),这看起来就像我们在提交时添加的另一个对象。提交对象还包含了作者和提交者的信息。最后,它还显示了这个提交的提交消息是什么。
我们继续来看看树对象包含了什么内容。
git cat-file -t 658524b859ae78d902597253a3b68b4da3b70466 tree git cat-file -p 658524b859ae78d902597253a3b68b4da3b70466 100644 blob c3dc8e6cf3e1117a8d9731ddde9916da644296aa file
我们会发现该文件指向了在我们执行git add file时添加的原始对象(c3dc8e6cf3e1117a8d9731ddde9916da644296aa)。
树对象内部使用更多的树对象来表示文件夹,这些树对象与提交对象相连,用于表示目录结构。
5. 新增修改
让我们对文件进行更改并查看它是如何工作的。
echo 'git,hello,789' > file git ci -am "修改文件内容"
还是利用erd查看文档目录
- 创建了3个新的对象。
- 一个包含文件新内容的
blob对象 - 一个是一个
树对象 - 最后一个是一个
提交对象
让我们再次从HEAD或refs/heads/master开始跟踪它们。
git cat-file -p refs/heads/master tree 02185c57f2040abcaa0c67dfd7026464d916da2b parent fe010b33df5078cdbd96f2397aad60ec5f42a967 author 789 <789@xx.net> 1697179597 +0800 committer 789 <789@xxx.net> 1697179597 +0800 修改文件内容 git cat-file -p 02185c57f2040abcaa0c67dfd7026464d916da2b 100644 blob 1f9224976e282aa9e32398a5bca0cec08041f1dc file git cat-file -p 1f9224976e282aa9e32398a5bca0cec08041f1dc git,hello,789
提交对象现在有一个额外的属性,名为parent,它链接到前一个提交,因为这个提交是建立在前一个提交之上的。
这是Git中的提交历史的关键概念,
每个提交都有一个或多个父提交,形成一个提交链。
6. 创建分支
是时候创建一个分支了。让我们使用git br fix-text命令创建一个名为fix-text的分支。
前面讲过,下面的br等同于branch。
这将在refs/heads文件夹下创建一个新文件,文件名为分支名称,文件内容为最新提交的ID。
我们首先用git log查看提交记录
发现最新的提交记录为efa223e697c6452a393963887f9926ea0662c923
cat .git/refs/heads/fix-text efa223e697c6452a393963887f9926ea0662c923
在Git中,分支是非常轻量级的。标签(Tags)的行为也类似,只不过它们是创建在refs/tags下的。
还会在logs目录下添加一个文件,用于存储与主分支类似的提交历史数据。这有助于跟踪各个分支的提交历史。Git的分支和标签是非常有用的版本控制工具,可以帮助我们管理项目的不同状态和版本。
7. 分支切换
在Git中,检出(checkout)操作是获取提交的树对象,并将working tree中的文件更新为与树对象记录的状态相匹配。
在这种情况下,因为我们从master切换到fix-text,而这两个分支都指向相同的提交和底层树对象,Git在working tree中没有任何事情要处理。
前面讲过,下面的co等同于checkout。
git co fix-text
在.git目录内执行co操作时,唯一的变化是.git/HEAD文件现在将指向fix-text。
cat .git/HEAD ref: refs/heads/fix-text
另外,让我进行一次提交。
echo 'hello,git' > file git ci -am "更换文本内容"
这将在fix-text分支上创建一个新的commit,将文件file中的内容更改为hello,git。
8. 分支合并
合并(merging)有主要三种方式。
- 最简单和最容易的方式是快进合并(
fast forward merge)
- 在这种情况下,我们只需将一个分支指向另一个分支指向的
commit object。 - 这实际上涉及将
refs/heads/fix-text中的hash复制到refs/heads/master。
- 第二种方式是变基合并(
rebase merge)
- 在这种情况下,我们首先逐个将我们的更改应用到主分支(main或master)当前指向的每个提交,然后执行类似于快进合并的操作。
- 最后一种方式是通过创建一个独立的合并提交来合并两个分支。
- 这在于它将在其提交对象中有两个父节点(parent entries)。
首先,让我们看看在合并之前图形是什么样子。(先将分支切换回master(git co master))
git log --graph --oneline --all * 4359ab4 (fix-text) 更换文本内容 * efa223e (HEAD -> master) 修改文件内容 * fe010b3 首次提交
执行合并操作
git merge fix-text 更新 efa223e..4359ab4 Fast-forward file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
上面的操作,将一个master分支的引用(指向的哈希值)更新为fix-text分支的引用指向的哈希值。
git log --graph --oneline --all * 4359ab4 (HEAD -> master, fix-text) 更换文本内容 * efa223e 修改文件内容 * fe010b3 首次提交
9. 远程提交
为了演示这一点,首先让我创建另一个Git仓库,它可以作为这个仓库的远程仓库。
新建一个裸仓库
$ mkdir fake_git_remote $ cd fake_git_remote && git init --bare
切换到我们dot_git项目中,为仓库设置remote
git remote add origin ../fake_git_remote
顺便说一下,添加一个新的远程仓库是一项配置更改,我们可以在.git/config文件中查看这个更改。我会让我们自己去查看这个更改是什么。
现在让我们进行推送操作。
git push origin master
让我们看看我们的本地仓库中发生了什么变化。
它添加了一个新的refs/remotes,用于存储有关不同远程仓库中的所有可用内容的信息。
但是发送到另一个Git仓库的是什么呢?实际上,
发送的内容就是
.git/objects目录中的所有对象,以及我们显式推送的refs下的所有分支和标签。
这就是另一个Git仓库需要获取我们的完整Git历史记录所需的一切内容。
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。









