Git
英语 ▾ 主题 ▾ 最新版本 ▾ gittutorial-2 最后更新于 2.23.0

名称

gittutorial-2 - Git 入门教程:第二部分

概要

git *

描述

您应该在阅读本教程之前先学习gittutorial[7]

本教程的目标是介绍 Git 架构的两个基本部分——对象数据库和索引文件,并为读者提供理解其余 Git 文档所需的一切知识。

Git 对象数据库

让我们开始一个新项目并创建少量历史记录

$ mkdir test-project
$ cd test-project
$ git init
Initialized empty Git repository in .git/
$ echo 'hello world' > file.txt
$ git add .
$ git commit -a -m "initial commit"
[master (root-commit) 54196cc] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt
$ echo 'hello world!' >file.txt
$ git commit -a -m "add emphasis"
[master c4d59f3] add emphasis
 1 file changed, 1 insertion(+), 1 deletion(-)

Git 响应提交返回的 7 位十六进制数字是什么?

我们在教程的第一部分看到过像这样的提交名称。事实证明,Git 历史中的每个对象都存储在一个 40 位十六进制名称下。该名称是对象内容的 SHA-1 哈希;其中,这确保了 Git 永远不会存储相同的数据两次(因为相同的数据会获得相同的 SHA-1 名称),并且 Git 对象的内容永远不会改变(因为这也会改变对象的名称)。这里的 7 个字符的十六进制字符串只是这些 40 个字符长字符串的缩写。只要不含糊不清,缩写可以在任何可以使用 40 个字符字符串的地方使用。

预期您在遵循上述示例时创建的提交对象的内容会生成与上面显示的不同 SHA-1 哈希,因为提交对象记录了它创建的时间以及执行提交的人员的姓名。

我们可以使用 cat-file 命令询问 Git 关于这个特定对象的信息。不要复制此示例中的 40 个十六进制数字,而要使用您自己版本中的数字。请注意,您可以将其缩短为仅几个字符,以节省您输入所有 40 个十六进制数字的麻烦

$ git cat-file -t 54196cc2
commit
$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <[email protected]> 1143414668 -0500
committer J. Bruce Fields <[email protected]> 1143414668 -0500

initial commit

树可以引用一个或多个“blob”对象,每个对象对应一个文件。此外,树也可以引用其他树对象,从而创建目录层次结构。您可以使用 ls-tree 检查任何树的内容(请记住,SHA-1 的足够长的初始部分也可以工作)

$ git ls-tree 92b8b694
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt

因此,我们看到这棵树中有一个文件。SHA-1 哈希是该文件数据的引用

$ git cat-file -t 3b18e512
blob

“blob”只是文件数据,我们也可以使用 cat-file 检查它

$ git cat-file blob 3b18e512
hello world

请注意,这是旧的文件数据;因此,Git 在其对初始树的响应中命名的对象是包含第一个提交记录的目录状态快照的树。

所有这些对象都存储在 Git 目录中,其 SHA-1 名称位于其下

$ find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/92
.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
.git/objects/54
.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
.git/objects/a0
.git/objects/a0/423896973644771497bdc03eb99d5281615b51
.git/objects/d0
.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
.git/objects/c4
.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241

这些文件的内容只是压缩数据加上一个标头,用于标识其长度和类型。类型可以是 blob、tree、commit 或 tag。

最简单的提交是 HEAD 提交,我们可以从 .git/HEAD 找到它

$ cat .git/HEAD
ref: refs/heads/master

如您所见,这告诉我们我们目前位于哪个分支,它通过命名 .git 目录下的一个文件来告诉我们这一点,该文件本身包含一个指向提交对象的 SHA-1 名称,我们可以使用 cat-file 检查它

$ cat .git/refs/heads/master
c4d59f390b9cfd4318117afde11d601c1085f241
$ git cat-file -t c4d59f39
commit
$ git cat-file commit c4d59f39
tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
author J. Bruce Fields <[email protected]> 1143418702 -0500
committer J. Bruce Fields <[email protected]> 1143418702 -0500

add emphasis

此处的“tree”对象引用树的新状态

$ git ls-tree d0492b36
100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
$ git cat-file blob a0423896
hello world!

而“parent”对象引用先前的提交

$ git cat-file commit 54196cc2
tree 92b8b694ffb1675e5975148e1121810081dbdffe
author J. Bruce Fields <[email protected]> 1143414668 -0500
committer J. Bruce Fields <[email protected]> 1143414668 -0500

initial commit

树对象是我们首先检查的树,这个提交很不寻常,因为它没有任何父级。

大多数提交只有一个父级,但提交拥有多个父级也很常见。在这种情况下,提交表示合并,其中父级引用指向已合并分支的头部。

除了 blob、tree 和 commit 之外,剩下的唯一一种对象类型是“tag”,我们这里不讨论;有关详细信息,请参阅git-tag[1]

所以现在我们知道了 Git 如何使用对象数据库来表示项目的历史记录

  • “commit”对象引用“tree”对象,该对象表示历史中某个特定时间点的目录树的快照,并引用“parent”提交以显示它们如何在项目历史记录中连接在一起。

  • “tree”对象表示单个目录的状态,将目录名称与包含文件数据的“blob”对象和包含子目录信息的“tree”对象相关联。

  • “blob”对象包含文件数据,没有任何其他结构。

  • 每个分支头部的提交对象的引用存储在 .git/refs/heads/ 下的文件中。

  • 当前分支的名称存储在 .git/HEAD 中。

顺便说一句,请注意,许多命令都接受树作为参数。但如上所述,树可以通过多种方式进行引用——通过该树的 SHA-1 名称、通过引用该树的提交的名称、通过其头部引用该树的分支的名称等等——大多数这样的命令都可以接受任何这些名称。

在命令概要中,有时使用“tree-ish”一词来表示这样的参数。

索引文件

我们一直在使用的创建提交的主要工具是 git-commit -a,它创建一个包含您对工作树所做的所有更改的提交。但如果您只想提交对某些文件的更改怎么办?或者只提交对某些文件的某些更改怎么办?

如果我们查看提交创建的底层方式,我们会发现有更灵活的方式来创建提交。

继续我们的测试项目,让我们再次修改 file.txt

$ echo "hello world, again" >>file.txt

但这次不要立即进行提交,让我们采取一个中间步骤,并要求在此过程中提供差异,以便跟踪发生的事情

$ git diff
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again
$ git add file.txt
$ git diff

最后一个差异为空,但没有创建新的提交,头部仍然不包含新行

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

所以git diff与除头部以外的其他内容进行比较。它正在比较的内容实际上是索引文件,该文件存储在 .git/index 中,以二进制格式存储,但其内容可以使用 ls-files 检查

$ git ls-files --stage
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
$ git cat-file -t 513feba2
blob
$ git cat-file blob 513feba2
hello world!
hello world, again

因此,我们的git add所做的是存储一个新的 blob,然后将对它的引用放到索引文件中。如果我们再次修改文件,我们会看到新的修改反映在git diff输出中

$ echo 'again?' >>file.txt
$ git diff
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

使用正确的参数,git diff还可以显示工作目录和最后提交之间的差异,或者索引和最后提交之间的差异

$ git diff HEAD
diff --git a/file.txt b/file.txt
index a042389..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,3 @@
 hello world!
+hello world, again
+again?
$ git diff --cached
diff --git a/file.txt b/file.txt
index a042389..513feba 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world!
+hello world, again

在任何时候,我们都可以使用git commit(不使用“-a”选项)创建新的提交,并验证提交的状态仅包含存储在索引文件中的更改,而不包含仅在我们工作树中存在的额外更改

$ git commit -m "repeat"
$ git diff HEAD
diff --git a/file.txt b/file.txt
index 513feba..ba3da7b 100644
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,3 @@
 hello world!
 hello world, again
+again?

所以,默认情况下,git commit使用索引来创建提交,而不是工作树;提交的“-a”选项告诉它首先使用工作树中的所有更改更新索引。

最后,值得看看git add对索引文件的影响

$ echo "goodbye, world" >closing.txt
$ git add closing.txt

git add的影响是在索引文件中添加一个条目

$ git ls-files --stage
100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt

并且,如您使用 cat-file 所见,这个新条目引用了文件的当前内容

$ git cat-file blob 8b9743b2
goodbye, world

“status”命令是获取当前状况的快速摘要的有用方法

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

	new file:   closing.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:   file.txt

由于 closing.txt 的当前状态缓存在索引文件中,因此它被列为“待提交的更改”。由于 file.txt 在工作目录中存在未反映在索引中的更改,因此它被标记为“已更改但未更新”。此时,运行“git commit”将创建一个添加 closing.txt(及其新内容)但未修改 file.txt 的提交。

另外,请注意,一个简单的 `git diff` 命令会显示 file.txt 的更改,但不会显示 closing.txt 的添加,因为索引文件中的 closing.txt 版本与工作目录中的版本相同。

除了作为新提交的暂存区域外,索引文件还会在检出分支时从对象数据库中填充,并用于保存合并操作中涉及的树。有关详细信息,请参阅 gitcore-tutorial[7] 和相关手册页。

下一步?

此时,您应该了解阅读任何 git 命令手册页所需的一切;一个好的起点是 giteveryday[7] 中提到的命令。您应该能够在 gitglossary[7] 中找到任何未知的术语。

The Git 用户手册 提供了更全面的 Git 简介。

gitcvs-migration[7] 解释了如何将 CVS 存储库导入 Git,以及如何在类似 CVS 的方式中使用 Git。

有关 Git 使用的一些有趣示例,请参阅 howtos

对于 Git 开发人员,gitcore-tutorial[7] 详细介绍了 Git 中涉及的低级机制,例如创建新的提交。

GIT

git[1] 套件的一部分

scroll-to-top