什么是Git引用和分支?

一. 引言

什么是Git引用和分支?比如我在 Github 上一个项目的 .git/refs目录下:

复制代码
├─heads
│      dev
│      master
│
├─remotes
│  └─origin
│          master
│
└─tags

refs 目录下包含了 heads、remote、tags 三个子目录,每个子目录下都有对应的文件

打开 heads/master 文件,查看其内容:

复制代码
$ cat heads/master
1b41db435c03fe80fa965dc77442261708deb16d

上述这段编码,其实就是 SHA-1 值,再来看看其类型和内容:

复制代码
$ git cat-file -p 1b41d
tree 03073e441d5360400b758257647c2a0250ee38c7
parent 90157be126d9204779c63aaadf3a95f5fa207fc4
author haohuin <huins...@gmil.com> 1623935829 +0800
committer haohuin <huins...@gmil.com> 1623935829 +0800
系统介绍Readme

这个 SHA-1 值就指向 Git 仓库中的某个提交对象 ID。既然能用 SHA-1 值来区分不同的提交对象,为何要创建一个存放 SHA-1 值的文件呢?因为 SHA-1 值过于长,而且没有规律,如果利用简单的文件名来引用对原来的提交对象,就不用记住复杂且无规律的 SHA-1 值了。

因此在 Git 中,像这种只含有 SHA-1 值的文件,就是 Git 的引用(Reference)。而分支,就是一个指向某一系列提交之首的指针或引用 。如下图所示:

下面就讲解一下 Git 中的引用和分支的具体内容

二. Git引用

在引言中提到过,Git 引用内部存储着 SHA-1 值,来跟踪和引用不同的 Git 对象,从类型上讲,可以分成 HEAD 引用,分支引用(branch),标签引用(tag)和远程引用(remote):

2.1. HEAD引用

.git根目录中,有一个 HEAD 文件,我们先来查看一下 HEAD 文件中的内容:

复制代码
$ cat .git/HEAD
ref: refs/heads/master

发现 HEAD 中的内容指向了一个引用 refs.heads/master,当执行 git checkout dev,将当前版本切换至 dev 分支后,HEAD 文件内容会变成如下引用值:

复制代码
$ git checkout dev
Switched to branch 'dev'

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

此时 HEAD 指向的分支成了 dev,说明 HEAD 的内容就是指向当前 Git 仓库所在的分支。HEAD 文件通常是一个符号引用(symbolic reference),指向目前所在的分支或提交。 所谓符号引用,表示它是一个指向其他引用的指针。让我们首先来看看指向分支的 HEAD:

2.1.1 指向分支

如下图所示,是 HEAD 指向某个分支:

我们可 通过 git checkout <分支名>来使得 HEAD 在不同分支中进行切换。

此外有一种特殊情况,会导致 HEAD 中的内容直接为某个提交对象的 SHA-1 ID 值:

2.1.2 指向某个提交对象

当使用 git checkout检出某个非分支引用指向下的提交对象时:

复制代码
//先查看所有的提交对象
 $ git log
commit 1b41db435c03fe80fa965dc77442261708deb16d (HEAD -> dev, origin/master, master)
Author: haohuin <huins...@gmil.com> 
Date:   Thu Jun 17 21:17:09 2021 +0800

    系统介绍Readme

commit 90157be126d9204779c63aaadf3a95f5fa207fc4
Author: haohuin <huins...@gmil.com>
Date:   Thu Jun 17 21:08:07 2021 +0800

    基本功能完成

commit f7d98432d5bc8c1a67e4c4f75795544cb8a7f4d0
Author: haohuin <huins...@gmil.com>
Date:   Thu Jan 21 21:26:48 2021 +0800

    init repository

//将HEAD指向最早的提交-init repository
$ git checkout f7d98432d5bc
Note: switching to 'f7d98432d5bc'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at f7d9843 init repository

而后出现了一大段的描述,其实现在就处于"detached HEAD"(分离头指针)状态,我们再来看看此时 HEAD 中的内容:

复制代码
$ cat .git/HEAD
f7d98432d5bc8c1a67e4c4f75795544cb8a7f4d0

此时 HEAD 指向了一个具体的提交 ID,而不是一个分支引用。

在这种情况下,如果再次进行提交操作,创建了一个新的提交,但是此时的 master 和 dev 分支引用都不会向前移动。HEAD 也不会指向这个新提交,那么因为没有引用指向该提交,Git 的垃圾回收机制可能最后会清理掉这个提交:

但是分离头指针的这个特性,也有助于创建临时分支的情景。

总结来说,HEAD 引用有以下作用:

  • 当 HEAD 指向当前所处的分支或提交,它表示正在工作的位置。当再执行 Git 操作时,比如提交,检出分支等时,Git 根据 HEAD 的位置确定在哪个分支上进行操作。
  • 当 HEAD 指向某一个具体的提交时,会进入分离头指针状态。在这种状态下,可以在历史提交上工作,创建新的提交,但不会更新任何分支。这在实验性工作、查看旧版本代码或创建临时分支时很有用。
  • 当 HEAD 执行分支时,可以使用 ^ 符号用于引用上一次提交。例如,HEAD^ 表示 HEAD 的父提交,HEAD^^ 表示 HEAD 的父提交的父提交,以此类推。这在查看提交历史、比较版本之间的差异等操作中很有用。
  • 当存在合并冲突时,HEAD 在解决冲突时起到重要作用。HEAD 指向当前分支的最新提交,同时也是合并操作的目标分支。可以通过编辑冲突文件并执行合适的操作,来解决冲突后进行提交。

2.2. 分支引用

在Git中,分支引用是指向特定提交(commit)的可变指针。它们用于跟踪各个分支在代码库中的位置,以便在不同的提交之间进行切换和管理。

2.2.1 本地分支

分支引用存储在 .git/refs/heads 目录下,每个分支引用对应该目录下的一个文件。比如在当前仓库下的 .git/refs/heads目录下有两个分支 dev 和 master,其内部存储的值如下:

复制代码
$ cat .git/refs/heads/master
1b41db435c03fe80fa965dc77442261708deb16d
$ cat .git/refs/heads/dev
1b41db435c03fe80fa965dc77442261708deb16d
$ git cat-file -p 1b41db4
tree 03073e441d5360400b758257647c2a0250ee38c7
parent 90157be126d9204779c63aaadf3a95f5fa207fc4
author haohuin <huins...@gmil.com> 1623935829 +0800
committer haohuin <huins...@gmil.com> 1623935829 +0800

系统介绍Readme

两个分支文件内部的内容就是指向某个提交对象,此处两个分支指向的提交对象一致。

分支引用通常是指向最新提交的指针。当在分支上进行提交时,分支引用会自动更新以指向新的提交。这样,Git就能够跟踪分支的进展和历史记录。

除了本地分支引用,还有远程分支引用。

2.2.2 远程分支

远程分支引用跟踪远程仓库中的分支。这些引用存储在 .git/refs/remotes/ 目录下。

如果你添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存 refs/remotes 目录下。在我的项目中有远程提交的仓库 orgin,可以通过 git remote showw 来看看当前远程仓库:

复制代码
$ git remote show
origin
$ cat .git/refs/remotes/origin/master
1b41db435c03fe80fa965dc77442261708deb16d

此时远程仓库 origin中的分支 master对应的 SHA-1 值就是本地仓库中最新的提交对象。

远程引用和分支(位于 refs/heads 目录下的引用)之间最主要的区别在于,远程引用是只读的。 虽然可以 git checkout 到某个远程引用,但是 Git 并不会将 HEAD 引用指向该远程引用。因此,你永远不能通过 commit 命令来更新远程引用。 Git 将这些远程引用作为记录远程服务器上各分支最后已知位置状态的书签来管理。

在远程引用中,可能会涉及到引用规范的内容,具体可以看这篇文章Git - 引用规范

2.3. 标签引用

除了 HEAD 引用和分支引用,在 Git 对象中还有标签对象,从前面的深入剖析Git对象底层原理我们知道,标签对象有两种:

  • 轻量标签:只有一个固定的指向提交对象的引用值,类似于分支引用
  • 附注标签:会创建一个标签对象,其中记录一个引用来指向该标签对象

标签对象类似于分支引用,但与分支引用不同的是,标签对象是不可变的,一旦创建就不能更改。

标签对象通常用于指定软件版本、发布快照或重要里程碑。它们提供了一个有意义的名称来标识特定的提交或代码状态。

标签对象存储在 .git/refs/tags/ 目录下,每个标签对应该目录下的一个文件。例如,如果有一个名为 firstTag 的标签,对应的标签对象文件就是 .git/refs/tags/firstTag

在我的仓库中分别存储了两种标签,可以看到其对应的标签,以及其引用的对象:

复制代码
$ find .git/refs/tags/
.git/refs/tags/
.git/refs/tags/firstTag
.git/refs/tags/secondTag

//1.查看轻量标签,其内部引用值指向提交对象
$ cat .git/refs/tags/firstTag
2b2af66549827bd6a466fe43081f406c2a12900b

$ git cat-file -p firstTag
tree 2503e9e0c4f774fc5ce298f4972f0e6d3a800d6f
parent 7b34a1e750918570ed610ee1f228e83b43a1192e
author haohuin <huins...@gmil.com> 1705458723 +0800
committer haohuin <huins...@gmil.com> 1705458723 +0800

second commit

//2.查看附注标签,其内部引用值指向标签对象
$ cat .git/refs/tags/secondTag
9cb2c0bd900b37a05f7c531fdd07cece35097045

$ git cat-file -p secondTag
object 8a4678fae181c16c6f4ff0e6a618991128d86da2
type commit
tag secondTag
tagger haohuin <huins...@gmil.com> 1705480524 +0800

secondTag

三. 总结

总体来说,引用机制让Git可以通过简单但唯一的ID来识别和追踪对象,管理项目版本以及支持分布式协作开发模式。Git中的引用主要包括

  1. HEAD引用,指向当前检出的分支或提交对象。HEAD管理 Git 当前操作所在的分支或提交状态。
  2. 分支引用,存储在.git/refs/heads目录下,每个分支对应一个引用文件。指向某个提交对象,跟踪分支历史记录和进度。本地分支和远程分支分别保存在不同目录下。
  3. 标签引用,存储在.git/refs/tags目录下,每个标签对应一个文件或对象。用于标记特定版本,不可变更。分支和标签都是引用的一种,但功能不同。分支指导开发顺序,标签标记具体版本。

技术前沿拓展

前端开发,你的认知不能仅局限于技术内,需要发散思维了解技术圈的前沿知识。细心的人会发现,开发内部工具的过程中,大量的页面、场景、组件等在不断重复,这种重复造轮子的工作,浪费工程师的大量时间。

介绍一款程序员都应该知道的软件JNPF快速开发平台,很多人都尝试用过它,它是功能的集大成者,任何信息化系统都可以基于它开发出来。

这是一个基于 Java Boot/.Net Core 构建的简单、跨平台快速开发框架。前后端封装了上千个常用类,方便扩展;集成了代码生成器,支持前后端业务代码生成,实现快速开发,提升工作效率;框架集成了表单、报表、图表、大屏等各种常用的 Demo 方便直接使用;后端框架支持 Vue2、Vue3。如果你有闲暇时间,可以做个知识拓展。

看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

相关推荐
云云3212 小时前
怎么通过亚矩阵云手机实现营销?
大数据·服务器·安全·智能手机·矩阵
新加坡内哥谈技术2 小时前
苏黎世联邦理工学院与加州大学伯克利分校推出MaxInfoRL:平衡内在与外在探索的全新强化学习框架
大数据·人工智能·语言模型
Data-Miner2 小时前
经典案例PPT | 大型水果连锁集团新零售数字化建设方案
大数据·big data
lovelin+v175030409663 小时前
安全性升级:API接口在零信任架构下的安全防护策略
大数据·数据库·人工智能·爬虫·数据分析
道一云黑板报3 小时前
Flink集群批作业实践:七析BI批作业执行
大数据·分布式·数据分析·flink·kubernetes
节点。csn3 小时前
flink集群搭建 详细教程
大数据·服务器·flink
数据爬坡ing4 小时前
小白考研历程:跌跌撞撞,起起伏伏,五个月备战历程!!!
大数据·笔记·考研·数据分析
云云3214 小时前
云手机方案全解析
大数据·服务器·安全·智能手机·矩阵
武子康5 小时前
大数据-257 离线数仓 - 数据质量监控 监控方法 Griffin架构
java·大数据·数据仓库·hive·hadoop·后端
碳学长5 小时前
2025系统架构师(一考就过):案例题之一:嵌入式架构、大数据架构、ISA
大数据·架构·系统架构