Git 分支本质及与 commit、HEAD、tag 的关系

基于:

Git - Git 是什么?

Git - 分支简介

Git - 打标签

本文适合对 Git 有一定使用经验的读者,包括但不限于对分支、分支合并、提交、标签等的了解。

快照

在介绍 Git 分支前,需要知道什么是 commit 对象,介绍 commit 对象前,需要先了解 Git 保存数据的方式。Git 直接记录快照,而非差异比较。

从概念上来说,其它大部分版本控制系统(包括 Subversion 和近似工具)以文件变更列表的方式存储信息,这类系统将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异(它们通常称作基于差异的版本控制)。

存储每个文件与初始版本的差异
存储每个文件与初始版本的差异。

而在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照。为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个快照流

存储项目随时间改变的快照
Git 存储项目随时间改变的快照。

这是 Git 与几乎所有其它版本控制系统的重要区别。

提交对象

在进行提交操作时,Git 会保存一个提交对象(commit object)。提交对象会包含一个指向快照的指针。该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。

分支本质

Git 的分支,本质上是指向提交对象的可变指针。Git 的默认分支名字是master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支。master分支会在每次提交时自动向前移动。

另:Git 的master分支并不是一个特殊分支。它就跟其它分支完全没有区别。之所以几乎每一个仓库都有 master 分支,是因为git init命令默认创建它,并且大多数人都懒得去改动它。

分支及其提交历史
分支及其提交历史。

分支创建

Git 创建新分支其实只是为你创建了一个可以移动的新的指针。比如,创建一个 testing 分支,你需要使用git branch命令:

$ git branch testing

这会在当前所在的提交对象上创建一个指针。

两个指向相同提交历史的分支
两个指向相同提交历史的分支。

Git 是怎么知道当前在哪一个分支上呢?它有一个名为HEAD的特殊指针,指向当前所在的本地分支(可以将HEAD想象为当前分支的别名)。前面创建了testing分支,但当前仍然在master分支上。因为git branch命令仅仅创建一个新分支,并不会自动切换到新分支中去。

HEAD 指向当前所在的分支
HEAD 指向当前所在的分支。

分支切换

要切换到一个已存在的分支,你需要使用git checkout命令。我们现在切换到新创建的testing分支去:

$ git checkout testing

这样HEAD就指向testing分支了。

HEAD 指向当前所在的分支
HEAD 指向当前所在的分支。

多分支开发

现在提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'
HEAD 分支随着提交操作自动向前移动
HEAD 分支随着提交操作自动向前移动。

如图所示,你的testing分支向前移动了,但是master分支却没有,它仍然指向运行git checkout时所指的对象。现在我们切换回master分支:

$ git checkout master
检出时 HEAD 随之移动
检出时 HEAD 随之移动。

这条命令做了两件事。一是使 HEAD 指回master分支,二是将工作目录恢复成master分支所指向的快照内容。也就是说,你现在做修改的话,项目将始于一个较旧的版本。本质上来讲,这就是忽略testing分支所做的修改,以便于向另一个方向进行开发。

我们不妨再稍微做些修改并提交:

$ vim test.rb
$ git commit -a -m 'made other changes'

现在,这个项目的提交历史已经产生了分叉。上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。

项目分叉历史
项目分叉历史。

标签

在上面介绍分支本质的图中,包含了一个v1.0的标签,看上去和master差不多。

Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。

  • 轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。

  • 而附注标签是存储在 Git 数据库中的一个完整对象,其中包含打标签者的名字、电子邮件地址、日期时间,此外还有一个标签信息,所以,它像是一个存档起来的 commit 副本。

posted @ 2024-09-28 01:25  Higurashi-kagome  阅读(1)  评论(0编辑  收藏  举报