《Git版本控制管理(第2版)》——4.3 Git在工作时的概念

4.3 Git在工作时的概念

带着一些原则,来看看所有这些概念和组件是如何在版本库里结合在一起的。让我们创建一个新的版本库,并更详细地检查内部文件和对象库。

4.3.1 进入.git目录
首先,使用git init来初始化一个空的版本库,然后运行find来看看都创建了什么文件。

$ mkdir /tmp/hello
$ cd /tmp/hello
$ git init
Initialized empty Git repository in /tmp/hello/.git/

# 列出当前目录中的所有文件
$ find .
.
./.git
./.git/hooks
./.git/hooks/commit-msg.sample
./.git/hooks/applypatch-msg.sample
./.git/hooks/pre-applypatch.sample
./.git/hooks/post-commit.sample
./.git/hooks/pre-rebase.sample
./.git/hooks/post-receive.sample
./.git/hooks/prepare-commit-msg.sample
./.git/hooks/post-update.sample
./.git/hooks/pre-commit.sample
./.git/hooks/update.sample
./.git/refs
./.git/refs/heads
./.git/refs/tags
./.git/config
./.git/objects
./.git/objects/pack
./.git/objects/info
./.git/description
./.git/HEAD
./.git/branches
./.git/info
./.git/info/exclude

可以看到,.git目录包含很多内容。这些文件是基于模板目录显示的,根据需要可以进行调整。根据使用的Git的版本,实际列表可能看起来会有一点不同。例如,旧版本的Git不对.git/hooks文件使用.sample后缀。

在一般情况下,不需要查看或者操作.git目录下的文件。认为这些“隐藏”的文件是Git底层(plumbing)或者配置的一部分。Git有一小部分底层命令来处理这些隐藏的文件,但是你很少会用到它们。

最初,除了几个占位符之外,.git/objects目录(用来存放所有Git对象的目录)是空的。

$ find .git/objects

.git/objects
.git/objects/pack
.git/objects/info

现在,让我们来小心地创建一个简单的对象。

$ echo "hello world" > hello.txt
$ git add hello.txt

如果输入的“hello world”跟这里一样(没有改变间距和大小写),那么objects目录应该如下所示:

$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info

所有这一切看起来很神秘。其实不然,下面各节会慢慢解释原因。

4.3.2 对象、散列和blob
当为hello.txt创建一个对象的时候,Git并不关心hello.txt的文件名。Git只关心文件里面的内容:表示“hello world”的12个字节和换行符(跟之前创建的blob一样)。Git对这个blob执行一些操作,计算它的SHA1散列值,把散列值的十六进制表示作为文件名它放进对象库中。

如何知道一个SHA1散列值是唯一的?

两个不同blob产生相同SHA1散列值的机会十分渺茫。当这种情况发生的时候,称为一次碰撞。然而,一次SHA1碰撞的可能性太低,你可以放心地认为它不会干扰我们对Git的使用。

SHA1是“安全散列加密”算法。直到现在,没有任何已知的方法(除了运气之外)可以让一个用户刻意造成一次碰撞。但是碰撞会随机发生吗?让我们来看看。

对于160位数,你有2160或者大约1048(1后面跟48个0)种可能的SHA1散列值。这个数是极其巨大的。即使你雇用一万亿人来每秒产生一万亿个新的唯一blob对象,持续一万亿年,你也只有1043个blob对象。

如果你散列了280个随机blob,可能会发生一次碰撞。

不相信我们的话,就去读读Bruce Schneier的书吧②。
在这种情况下散列值是3b18e512dba79e4c8300dd08aeb37f8e728b8dad。160位的SHA1散列值对应20个字节,这需要40个字节的十六进制来显示,因此这内容另存为.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad。Git在前两个数字后面插入一个“/”以提高文件系统效率(如果你把太多的文件放在同一个目录中,一些文件系统会变慢;使SHA1的第一个字节成为一个目录是一个很简单的办法,可以为所有均匀分布的可能对象创建一个固定的、256路分区的命名空间)。

为了展示Git真的没有对文件的内容做很多事情(它还是同样的内容“hello world”),可以在任何时间使用散列值把它从对象库里提取出来。

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world

Git也知道手动输入40个字符是很不切实际的,因此它提供了一个命令通过对象的唯一前缀来查找对象的散列值。

$ git rev-parse 3b18e512d
3b18e512dba79e4c8300dd08aeb37f8e728b8dad

4.3.3 文件和树
既然“hello world”那个blob已经安置在对象库里了,那么它的文件名又发生了什么事呢?如果不能通过文件名找到文件Git就太没用了。

正如前面提到的,Git通过另一种叫做目录树(tree)的对象来跟踪文件的路径名。当使用git add命令时,Git会给添加的每个文件的内容创建一个对象,但它并不会马上为树创建一个对象。相反,索引更新了。索引位于.git/index中,它跟踪文件的路径名和相应的blob。每次执行命令(比如,git add、git rm或者git mv)的时候,Git会用新的路径名和blob信息来更新索引。

任何时候,都可以从当前索引创建一个树对象,只要通过底层的git write-tree命令来捕获索引当前信息的快照就可以了。

目前,该索引只包含一个文件,hello.txt.

$ git ls-files -s
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0   hello.txt

在这里你可以看到文件的关联,hello.txt与3b18e4...的blob。

接下来,让我们捕获索引状态并把它保存到一个树对象里。

$ git write-tree
68aba62e560c0ebc3396e8ae9335232cd93a3f60

$ find .git/objects
.git/objects
.git/objects/68
.git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60
.git/objects/pack
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info

现在有两个对象:3b18e5的“hello world”对象和一个新的68aba6树对象。可以看到,SHA1对象名完全对应.git/objects下的子目录和文件名。

但是树是什么样子的呢?因为它是一个对象,就像blob一样,所以可以用底层命令来查看它。

$ git cat-file -p 68aba6
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad   hello.txt

对象的内容应该很容易解释。第一个数100644,是对象的文件属性的八进制表示,用过UNIX的chmod命令的人应该对这个很熟悉了。这里,3b18e5是hello world的blob的对象名,hello.txt是与该blob关联的名字。

当执行git ls-file -s的时候,很容易就可以看到树对象已经捕获了索引中的信息。

4.3.4 对Git使用SHA1的一点说明
在更详细地讲解树对象的内容之前,让我们先来看看SHA1散列的一个重要特性。

$ git write-tree
68aba62e560c0ebc3396e8ae9335232cd93a3f60

$ git write-tree
68aba62e560c0ebc3396e8ae9335232cd93a3f60

$ git write-tree
68aba62e560c0ebc3396e8ae9335232cd93a3f60

每次对相同的索引计算一个树对象,它们的SHA1散列值仍是完全一样的。Git并不需要重新创建一个新的树对象。如果你在计算机前按照这些步骤操作,你应该看到完全一样的SHA1散列值,跟本书所刊印的一样。

这样看来,散列函数在数学意义上是一个真正的函数:对于一个给定的输入,它总产生相同的输出。这样的散列函数有时也称为摘要,用来强调它就像散列对象的摘要一样。当然,任何散列函数(即使是低级的奇偶校验位)也有这个属性。

这是非常重要的。例如,如果你创建了跟其他开发人员相同的内容,无论你俩在何时何地工作,相同的散列值就足以证明全部内容是一致的。事实上,Git确实将它们视为一致的。

但是等一下——SHA1散列是唯一的吗?难道万亿人每秒产生的万亿个blob永远不会产生一次碰撞吗?这在Git新手中是一个常见的疑惑。因此,请仔细阅读,因为如果你能理解这种区别,那么本章的其他内容就很简单了。

在这种情况下,相同的SHA1散列值并不算碰撞。只有两个不同的对象产生一个相同的散列值时才算碰撞。在这里,你创建了相同内容的两个单独实例,相同的内容始终有相同的散列值。

Git依赖于SHA1散列函数的另一个后果:你是如何得到称为68aba62e560c0ebc3396 e8ae9335232cd93a3f60的树的并不重要。如果你得到了它,你就可以非常有信心地说,它跟本书的另一个读者的树对象是一样的。Bob通过合并Jennie的提交A、提交B和Sergey的提交C来创建这个树,而你是从Sue得到提交A,然后从Lakshmi那里更新提交B和提交C的合并。结果都是一样的,这有利于分布式开发。

如果要求你查看对象68aba62e560c0ebc3396e8ae9335232cd93a3f60,并且你能找到这样的一个对象,同时因为SHA1是一个加密散列算法,因此你就可以确信你找的对象跟散列创建时的那个对象的数据是相同的。

反过来也是如此:如果你在你的对象库里没找到具有特定散列值的对象,那么你就可以肯定你没有持有那个对象的副本。总之,你可以判断你的对象库是否有一个特定的的对象,即使你对它(可能非常大)的内容一无所知。因此,散列就好似对象的可靠标签或名称。

但是Git也依赖于比那个结论更强的东西。考虑最近的一次提交(或者它关联的树对象)。因为它包含其父提交以及树的散列,反过来又通过递归整个数据结构包含其所有子树和blob的散列,因此可归结为它通过原始提交的散列值唯一标识整个数据结构在提交时的状态。

最后,我们在上一段中的声明可以推出散列函数的强大应用:它提供了一种有效的方法来比较两个对象,甚至是两个非常大而复杂的数据结构③,而且并不需要完全传输。

4.3.5 树层次结构
只有单个文件的信息是很好管理的,就像上一节所讲的一样,但项目包含复杂而且深层嵌套的目录结构,并且会随着时间的推移而重构和移动。通过创建一个新的子目录,该目录包含hello.txt的一个完全相同的副本,让我们看看Git是如何处理这个问题的。

$ pwd
/tmp/hello
$ mkdir subdir
$ cp hello.txt subdir/
$ git add subdir/hello.txt
$ git write-tree
492413269336d21fac079d4a4672e55d5d2147ac

$ git cat-file -p 4924132693
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad  hello.txt
040000 tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60  subdir

新的顶级树包含两个条目:原始的hello.txt以及新的 子目录 ,子目录是 树 而不是blob。

注意到不寻常之处了吗?仔细看subdir的对象名。是你的老朋友,68aba62e560c0 ebc3396e8ae9335232cd93a3f60!

刚刚发生了什么?subdir的新树只包含一个文件hello.txt,该文件跟旧的“hello world”内容相同。所以subdir树跟以前的顶级树是完全一样的!当然它就有跟之前一样的SHA1对象名了。

让我们来看看.git/objects目录,看看最近的更改有哪些影响。

$ find .git/objects
.git/objects
.git/objects/49
.git/objects/49/2413269336d21fac079d4a4672e55d5d2147ac
.git/objects/68
.git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60
.git/objects/pack
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info

这只有三个唯一的对象:一个包含“hello world”的blob;一棵包含hello.txt的树,文件里是“hello world”加一个换行;还有第一棵树旁边包含hello.txt的另一个索引的另一棵树。

4.3.6 提交
讨论的下一主题是提交(commit)。现在hello.txt已经通过git add命令添加了,树对象也通过git write-tree命令生成了,可以像这样用底层命令那样创建提交对象。

$ echo -n "Commit a file that says hello\n" \
  | git commit-tree 492413269336d21fac079d4a4672e55d5d2147ac
3ede4622cc241bcb09683af36360e7413b9ddf6c

结果如下所示。

$ git cat-file -p 3ede462
tree 492413269336d21fac079d4a4672e55d5d2147ac
author Jon Loeliger <jdl@example.com> 1220233277 -0500
committer Jon Loeliger <jdl@example.com> 1220233277 -0500

Commit a file that says hello

如果你在计算机上按步骤操作,你可能会发现你生成的提交对象跟书上的名字不一样。如果你已经理解了目前为止的一切内容,那原因就很明显了:这是不同的提交。提交包含你的名字和创建提交的时间,尽管这区别很微小,但依然是不同的。另一方面,你的提交确实有相同的树。这就是提交对象与它们的树对象分开的原因:不同的提交经常指向同一棵树。当这种情况发生时,Git能足够聪明地只传输新的提交对象,这是非常小的,而不是很可能很大的树和blob对象。

在实际生活中,你可以(并且应该)跳过底层的git write-tree和git commit-tree步骤,并只使用git commit命令。成为一个完全快乐的Git用户,你不需要记住那些底层命令。

一个基本的提交对象是相当简单的,这是成为一个真正的RCS需要的最后组成部分。提交对象可能是最简单的一个,包含:

标识关联文件的树对象的名称;
创作新版本的人(作者)的名字和创作的时间;
把新版本放到版本库的人(提交者)的名字和提交的时间;
对本次修订原因的说明(提交消息)。
默认情况下,作者和提交者是同一个人,也有一些情况下,他们是不同的。

可以使用git show --pretty=fuller命令来查看给定提交的其他细节。
尽管提交对象跟树对象用的结构是完全不同的,但是它也存储在图结构中。当你做一个新提交时,你可以给它一个或多个父提交。通过继承链来回溯,可以查看项目历史。第6章会给出关于提交和提交图的更详细描述。

4.3.7 标签
最后,Git还管理的一个对象就是标签。尽管Git只实现了一种标签对象,但是有两种基本的标签类型,通常称为轻量级的(lightweight)和带附注的(annotated)。

轻量级标签只是一个提交对象的引用,通常被版本库视为是私有的。这些标签并不在版本库里创建永久对象。带标注的标签则更加充实,并且会创建一个对象。它包含你提供的一条消息,并且可以根据RFC 4880来使用GnuPG密钥进行数字签名。

Git在命名一个提交的时候对轻量级的标签和带标注的标签同等对待。不过,默认情况下,很多Git命令只对带标注的标签起作用,因为它们被认为是“永久”的对象。

可以通过git tag命令来创建一个带有提交信息、带附注且未签名的标签:

$ git tag -m "Tag version 1.0" V1.0 3ede462
可以通过git cat-file -p命令来查看标签对象,但是标签对象的SHA1值是什么呢?为了找到它,使用4.3.2节的提示。

$ git rev-parse V1.0
6b608c1093943939ae78348117dd18b1ba151c6a

$ git cat-file -p 6b608c
object 3ede4622cc241bcb09683af36360e7413b9ddf6c
type commit
tag V1.0
tagger Jon Loeliger <jdl@example.com> Sun Oct 26 17:07:15 2008 -0500

Tag version 1.0

除了日志消息和作者信息之外,标签指向提交对象3ede462。通常情况下,Git通过某些分支来给特定的提交命名标签。请注意,这种行为跟其他VCS有明显的不同。

Git通常给指向树对象的提交对象打标签,这个树对象包含版本库中文件和目录的整个层次结构的总状态。

回想一下图4-1,V1.0标签指向提交1492——依次指向跨越多个文件的树(8675309)。因此,这个标签同时适用于该树的所有文件。

这跟CVS不同,例如,对每个单独的文件应用标签,然后依赖所有打过标签的文件来重建一个完整的标记修订。并且CVS允许你移动单独文件的标签,而Git则需要在标签移动到的地方做一个新的提交,囊括该文件的状态变化。

时间: 2024-09-10 03:45:07

《Git版本控制管理(第2版)》——4.3 Git在工作时的概念的相关文章

《Git版本控制管理(第2版)》——导读

前言 本书是按照一系列渐进式主题进行组织编排的,每一个主题都建立在之前介绍的概念之上.本书前11章讲解的是与一个版本库相关的概念和操作,这些内容是在多个版本库上进行复杂操作(将在本书后10章涉及)的基础. 如果你已经安装了Git,甚至曾经简单使用过,那么你可能用不到前两章中Git相关的介绍性知识和安装信息,第3章的知识对你来说也是可有可无. 第4章介绍的概念是深入掌握Git对象模型的基础,读者可以通过第4章清楚理解Git更为复杂的操作. 第5章-第11章更为详细地讲解了Git的各个主题.第5章讲

《Git版本控制管理(第2版)》——第4章 基本的Git概念 4.1基本概念

第4章 基本的Git概念 4.1 基本概念 前一章介绍了Git的一个典型应用,并且可能引发了相当多的问题.Git是否在每次提交时存储整个文件?.git目录的目的是什么?为什么一个提交ID像乱码?我应该注意它吗? 如果你用过其他VCS,比如SVN或者CVS,那么对最后一章的命令可能会很熟悉.事实上,你对一个现代VCS期望的所有操作和功能,Git都能提供.然而,在一些基本的和意想不到的方面,Git会有所不同. 本章会通过讨论Git的关键架构组成和一些重要概念来探讨Git的不同之处和原因.这里注重基础

《Git版本控制管理(第2版)》——1.4 时间线

1.4 时间线 有了应用场景,有了一点额外的动力,再加上对新VCS的需求迫在眉睫,Git于2005年4月诞生了. 4月7日,Git从以下提交起,正式成为自托管项目. commit e83c5163316f89bfbde7d9ab23ca2e25604af29 Author: Linus Torvalds <torvalds@ppc970.osdl.org> Date: Thu Apr 7 15:13:13 2005 –0700 Initial revision of "git&quo

《Git版本控制管理(第2版)》——第1章 介绍 1.1背景

第1章 介绍 1.1 背景 现如今,难以想象有创意的人会在没有备份策略的情况下启动一个项目.数据是短暂的,且容易丢失--例如,通过一次错误的代码变更或者一次灾难性的磁盘崩溃.所以说,在整个工作中持续性地备份和存档是非常明智的. 对于文本和代码项目,备份策略通常包括版本控制,或者叫"对变更进行追踪管理".每个开发人员每天都会进行若干个变更.这些持续增长的变更,加在一起可以构成一个版本库,用于项目描述,团队沟通和产品管理.版本控制具有举足轻重的作用,只要定制好工作流和项目目标,版本控制是最

《Git版本控制管理(第2版)》——1.2 Git的诞生

1.2 Git的诞生 通常来说,当工具跟不上项目需求时,开发人员就会开发一个新的工具.实际上,在软件领域里,创造新工具经常看似简单和诱人.然而,鉴于市面上已经有了相当多的VCS,决定再创造一个却应该是要深思熟虑的.不过,如果有着充分的需求.理性的洞察以及良好的动机,则完全可以创造一个新的VCS. Git就是这样一个VCS.它被它的创造者(Linus,一个脾气急躁又经常爆出冷幽默的人)称作"从地狱来的信息管理工具".尽管Linux社区内部政治性的争论已经淹没了关于Git诞生的情形和时机的

《Git版本控制管理(第2版)》——1.5 名字有何含义

1.5 名字有何含义 据Linus宣称,命名为Git,是因为"我是一个自私的混蛋,我照着自己命名我所有的项目,先是Linux,现在是git"⑤.倘若,Linux这个名字是Linus和Minix的某种结合.那么反用一个表示愚蠢无用之人的英语词汇也不是没可能. 那之后,也有人曾建议,使用一些其他也许更让人舒服的解释.其中最受欢迎的一个就是:全局信息追踪器(Global Information Tracker).

《Git版本控制管理(第2版)》——1.3 先例

1.3 先例 VCS的完整历史已经超出了本书的讨论范围.然而,有一些具有里程碑.革新意义的系统值得一提.这些系统对Git的开发或者有重要的铺垫意义,或者有引导意义.(本节为可选章节,希望能够记录那些新特性出现的时间,以及在自由软件社区变得流行的时间.) 源代码控制系统(Source Code Control System, SCCS)是UNIX②上最初的几个系统之一,由M. J. Rochkind于20世纪70年代早期开发.["The Source Code Control System,&qu

《Git版本控制管理(第2版)》——4.2 对象库图示

4.2 对象库图示 让我们看看Git的对象之间是如何协作来形成完整系统的. blob对象是数据结构的"底端":它什么也不引用而且只被树对象引用.在接下来的图里,每个blob由一个矩形表示. 树对象指向若干blob对象,也可能指向其他树对象.许多不同的提交对象可能指向任何给定的树对象.每个树对象由一个三角形表示. 一个圆圈表示一个提交对象.一个提交对象指向一个特定的树对象,并且这个树对象是由提交对象引入版本库的. 每个标签由一个平行四边形表示.每个标签可以指向最多一个提交对象. 分支不是

分布式版本控制Git分支管理策略及使用规范流程

Git分支管理策略 目前最流行的"版本管理系统",非Git莫属. 相比同类软件,Git有很多优点.其中很显著的一点,就是版本的分支(branch)和合并(merge)十分方便.有些传统的版本管理软件,分支操作实际上会生成一份现有代码的物理拷贝,而Git只生成一个指向当前版本(又称"快照")的指针,因此非常快捷易用. 但是,太方便了也会产生副作用.如果你不加注意,很可能会留下一个枝节蔓生.四处开放的版本库,到处都是分支,完全看不出主干发展的脉络. Vincent Dr