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-1
hash值。
如果我们将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历史记录所需的一切内容。
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。