设置和配置
获取和创建项目
基本快照
分支与合并
共享和更新项目
检查和比较
打补丁
调试
电子邮件
外部系统
服务器管理
指南
管理
底层命令
- 2.23.1 → 2.53.0 无更改
-
2.23.0
2019-08-16
- 2.13.7 → 2.22.5 无更改
-
2.12.5
2017-09-22
- 2.3.10 → 2.11.4 无更改
-
2.2.3
2015-09-04
- 2.1.4 无更改
-
2.0.5
2014-12-17
描述
在阅读本教程之前,您应该先学习 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 <bfields@puzzle.fieldses.org> 1143414668 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 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 在响应初始树时命名的对象是一个树,其中包含第一个提交记录的目录状态快照。
所有这些对象都以其 SHA-1 名称存储在 Git 目录中
$ 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
如您所见,这告诉我们当前所在的 branch,它通过 .git 目录下的一个文件来告诉我们,该文件本身包含一个指向 commit 对象的 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 <bfields@puzzle.fieldses.org> 1143418702 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 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 <bfields@puzzle.fieldses.org> 1143414668 -0500 committer J. Bruce Fields <bfields@puzzle.fieldses.org> 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
但这次我们不立即提交,而是采取一个中间步骤,并一路要求 diff 来跟踪正在发生的事情
$ git diff --- a/file.txt +++ b/file.txt @@ -1 +1,2 @@ hello world! +hello world, again $ git add file.txt $ git diff
最后的 diff 为空,但没有新的提交,head 仍然不包含新行
$ 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 正在与 head 以外的东西进行比较。它正在比较的实际上是索引文件,它以二进制格式存储在 .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] 中找到任何不熟悉的术语。
Git 用户手册提供了对 Git 更全面的介绍。
gitcvs-migration[7] 解释了如何将 CVS 仓库导入 Git,并展示了如何以类似 CVS 的方式使用 Git。
有关 Git 使用的一些有趣示例,请参阅 howtos。
对于 Git 开发者,gitcore-tutorial[7] 详细介绍了 Git 低级机制,例如,创建新提交。