gittutorial 2

gittutorial-2

命名

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 响应提交的十六进制数字是什么?

我们在教程的第一部分看到了提交这样的名字。事实证明,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 在对初始树的响应中命名的对象是一棵树,其中包含第一次提交记录的目录状态的快照。

所有这些对象都存储在 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 ,树,提交或标记。

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

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

正如你所看到的,这告诉我们我们当前正在使用哪个分支,并且它通过命名 .git 目录下的一个文件告诉我们这个文件,它本身包含引用一个提交对象的 SHA-1 名称,我们可以用它来检查猫文件:

$ 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

这里的“树”对象指的是树的新状态:

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

而“父”对象引用了之前的提交:

$ 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

树对象是我们首先检查的树,而这个提交是不寻常的,因为它缺少任何父对象。

大多数提交只有一个父,但是承诺有多个父也很常见。在这种情况下,提交表示合并,并且父引用指向合并分支的 HEAD 。

除了斑点,树和提交之外,唯一剩下的对象就是一个“tag”,我们不在这里讨论; 有关详细信息,请参阅 git-tag [1] 。

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

  • “commit”对象指的是表示历史中特定点上的目录树快照的“树”对象,并参考“父”提交来显示它们如何连接到项目历史中。

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

  • “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

最后的差异是空的,但没有新的提交已经完成,并且 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比较头部以外的东西。它比较的东西实际上是索引文件,它以二进制格式存储在 .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 reset HEAD <file>..." to unstage) new file: closing.txt Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: file.txt

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

此外,请注意,bare 会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 机制。