文章目录Git 概述Git 的内容追踪原理通过实际操作来理解第一种 object 类型commit第二种 object 类型tree第三种 object 类型blob第二次 commit用 reference 追踪 commitTag总结Git 作为分布式版本管理工具的核心作用支撑多人协同开发云端托管项目。开发者可克隆仓库至本地完成开发完成改动后推送至远端主仓库团队成员同步拉取更新Git 完整记录代码迭代历史支持任意版本回溯。我亦掌握git add、git push等基础 Git 命令操作。日常开发中 95% 的场景仅需复用六七条基础指令。其中两项核心规范其一启动新需求开发时务必基于开发分支新建特性分支并在该分支内完成全部开发工作其二严禁直接向生产分支提交推送任何代码变更这条准则须严格恪守。”对于我来说日常高频使用的指令git pull、git checkout、git add、git commit、git push 与 git merge。在实操过程中逐步吃透了commit的核心含义其等同于版本迭代历程里的独立快照节点同时也厘清了分支branch的底层逻辑 —— 分支是从指定提交节点衍生出的独立开发链路。在长期开发实践中开发者会接触到各类进阶指令例如 git reset、git revert同时掌握 git stash、git cherry-pick 等高阶操作技巧。多数从业者可熟练运用 Git 完成多人员协同开发工作但对工具后台运行机制缺乏认知。为此有必要深入学习 Git 底层数据模型厘清工具内部运行原理。充分掌握 Git 底层运行机制后各类操作逻辑将形成完整闭环。本文旨在跳出机械套用指令的浅层使用模式完整拆解 Git 内部底层运行原理。Git 概述首先参考 Git 官方文档给出的标准定义。补充说明使用 Git 工具前需完成本地环境安装未部署该工具的使用者可前往 Git 官方网站获取安装程序。下文将援引官方文档内容梳理 Git 的标准释义。官方文档首页Git 的自我描述:NAMEgit- the stupid content tracker依据 Git 官方标准定义Git 是一款高效、可拓展的分布式版本控制系统具备完备丰富的指令体系既支持上层封装操作亦开放完整底层接口供使用者调取内部运行机制。剥离上层功能封装Git 的核心本质可概括为内容追踪工具( A tool to track content)。这是理解该工具的关键认知要点。大众普遍存在固有认知即 Git 仅适用于软件开发项目该认知存在局限性。Git 的核心能力为追踪任意类型文件内容适用范围不受软件代码限制。该基础认知看似浅显却能够直观反映使用者普遍存在的知识盲区多数使用者仅掌握表层操作并未真正理解 Git 底层核心逻辑。Git 的内容追踪原理可将 Git 的底层存储机制抽象为一套键值映射体系体系中以唯一键匹配对应数据映射中的值为各类文件转化后的二进制字节流。当一段数据存入 Git 时系统会对其完成持久化存储并借助 SHA-1 哈希算法生成专属哈希键。哈希键是一段固定长度的摘要字符串可唯一标识大块数据Git 依靠该索引区分仓库内所有已存储内容。哈希值的生成结果不受设备、操作系统影响只要两段数据内容完全一致无论在任何环境下计算最终产出的哈希键必然相同。该特性是掌握 Git 底层逻辑的关键认知。通过实际操作来理解我准备创建一个项目用来管理我家人的任务。让我们初始化一个 Git 仓库repository// Initialize an empty Git repository inside tech-lab folder $gitinit tech-lab // Open tech-lab folder $cdtech-lab // Examine .git folder $ls.git HEAD config description hooks/ info/ objects/ refs/执行仓库初始化操作后Git 将自动生成隐藏目录 .git该目录内含多组配套文件与子目录。本节重点说明 objects 目录此目录为 Git 的对象数据库。项目所有版本快照对应的存储对象均统一生成并存放于该目录中。$ls.git/objects info/ pack/objects 目录下内置 info 与 pack 两个子目录二者服务于 Git 底层存储优化逻辑现阶段无需深究。未存入任何内容时objects 目录默认处于空目录状态。接下来向当前项目中新增首个文件# 创建 Plants.txt 文件写入内容 Rose$printfRosePlants.txt#使用cat查看文件内容catPlants.txt Rose#查看新增文件后的仓库当前状态$gitstatus On branch master No commits yet Untracked files:(usegit add file...to includeinwhat will be committed)Plants.txt nothing added to commit but untracked files present(usegit addto track)Git 在这里告诉我们的是我们有一些文件处于未跟踪untracked状态。这些文件存在于 Git 所称的 工作目录Working Directory 中。如果我们希望将这些更改添加到下一次 commit提交 中就必须先将它们添加到一个中间区域intermediary area。将新增或修改的文件添加到暂存区:$gitadd.执行该指令并不会生成提交记录亦不会将变更写入版本历史。该命令的本质意图可表述为告知 Git 对目标文件开启跟踪持续记录其后续改动。这个用于存放已跟踪文件的第二个区域称为 Staging Area暂存区它的技术名称是 index。若确认仅以当前全部变更生成commit并且不包含其他任何更改则暂存区内所有已登记变更均会作为新commit的组成内容。创建一个commit,信息(message)为“create plants file”的新commit$gitcommit-mcreate Plants file1filechanged,1insertion()create mode100644Plants.txt此时产生了何种底层变化其一本次操作生成了第一条 commit。除此之外系统还将在 objects repository 的 objects/ 目录中生成若干内部存储对象。可进入目录进行核查$ ls .git/objects 01/ ab/ cf/ info/ pack/除原有 info/、pack/ 子目录外objects 目录下新增了三类对象文件。在此之前需明确 Git 包含三种基础对象类型blob、tree 与 commit下文将结合当前示例依次阐释各类对象的定义与作用。接下来逐层解析新增对象的内部结构首先从本次生成的 commit 对象入手显示commit log:$gitlog commit 016e263a1b9b430cf0b1044b59d8e66d57ccc58c(HEAD -master)Author: plants-labplants-lablab.comDate: Sat Aug1521:54:5620260700 create Plantsfile本次生成的首个 commit 哈希值为 :016e263a1b9b430cf0b1044b59d8e66d57ccc58c观察 objects/ 目录下新增的子目录能够发现存在名为 f6/ 的文件夹命名取自该哈希值的前两位字符。进入该目录查看内部存储内容:$ls.git/objects/01 6e263a1b9b430cf0b1044b59d8e66d57ccc58c01/ 文件夹中的文件名正好等于构成该 commit 的 hash key 的其余字符。01/6e263a1b9b430cf0b1044b59d8e66d57ccc58c016e263a1b9b430cf0b1044b59d8e66d57ccc58c需留意对象数据库的存储命名规则objects 目录下新建子目录名称取自哈希值前两位字符子目录内文件名称则为哈希键剩余字符。该套命名约定naming convention是 Git 内置的数据存储规范以此结构存储可大幅提升对象objects检索效率。该文件即为对象数据库中与本次 commit 相对应的 object。第一种 object 类型commit该 object 的内部结构与存储内容如何查看可借助底层 Git 命令low-level Git commandcat-file该指令能够输出仓库repository内任意 object 的类型与原始内容。查看object类型$gitcat-file 016e263a1b9b430cf0b1044b59d8e66d57ccc58c-tcommit查看object类容$gitcat-file 016e263a1b9b430cf0b1044b59d8e66d57ccc58c-ptree cf88f5f0c0482b8948fec42bc85bcf23875e12fd author plants-labplants-lablab.com17868056960700 committer plants-labplants-lablab.com17868056960700 create Plantsfile这便是 commit 的内部结构对应前文刚生成的commit记录。commit 本质为纯文本数据。与 Git 内所有存储内容一致该文本会先完成压缩处理再生成专属 hash最终持久化存入 objects repository。commit object 内部存储各类 metadata 元数据涵盖 author、committer、commit date 以及 commit message。除此之外该 commit 中还记录了一条 tree 及其对应的 hash key。沿用检索 commit hash key 的方式查询此 tree 的 hash key能够证实仓库repository内存在匹配该哈希标识的 object。$ls.git/objects/ 01/ ab/ cf/ info/ pack/ $ls.git/objects/cf/ 88f5f0c0482b8948fec42bc85bcf23875e12fdcf/88f5f0c0482b8948fec42bc85bcf23875e12fdcf88f5f0c0482b8948fec42bc85bcf23875e12fd这个 object 就是 object database 中对应于这个 tree 的 object第二种 object 类型treetree 是用于存储项目 directories目录信息的 object。单个 tree 可引用其他 tree以此 build 出完整的文件与子目录 hierarchy层级结构同时也能够指向 blob。每一条 commit 都会关联一个 tree object该 tree object 会以完整 snapshot快照的形式capture 当前 commit 执行瞬间整个 repository 的全部状态。这份 snapshot 便是存入 Git historyGit 历史记录的项目版本。接下来查看 tree 的内部结构$gitcat-file cf88f5f0c0482b8948fec42bc85bcf23875e12fd-ttree $gitcat-file cf88f5f0c0482b8948fec42bc85bcf23875e12fd-p100644blob ab0d218b097a7cb260b99336411779a703a672e1 Plants.txt输出内容解释字段值含义模式100644文件权限模式100644 表示普通文件非可执行即 -rw-r–r–类型blob说明这个条目是一个文件内容对象blob不是子目录tree哈希ab0d218b…这个文件内容对应的 SHA-1可以用 git cat-file -p ab0d218b… 继续查看 Plants.txt 的实际内容文件名Plants.txt该 blob 在这个目录tree里的名字tree object 内部以单行记录单个文件或子目录信息每条记录会依次存储 permissions权限、object type对象类型、object hash 以及 filename文件名。文件名由 tree object 统一管理而非文件自身的 blob 内容决定。下文将对该特性的原理展开说明。当前 tree 中存在一条指向 blob object 的 引用前往 objects 数据库检索即可确认就会发现这个第三个 object 确实存在于其中$ls.git/objects 01/ ab/ cf/ info/ pack/ $ls.git/objects/ab/ 0d218b097a7cb260b99336411779a703a672e1最后我们来查看第三种 object 类型blob 的内部结构$gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-tblob $gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-pRose第三种 object 类型blobBlob 是 binary large object二进制大对象的缩写用于存储文件原始数据不携带任何与文件相关的 metadata其中也不含 filename。文件的每一个版本都由独立 blob 承载。总结Git 包含三类基础 objectblobs、trees、commits。Git 会为每一类 object 计算专属 SHA-1 hash 作为唯一 key随后将对象内容压缩并持久化存入 repository。一条 commit 会关联一个 tree object该 tree 以 snapshot快照形式 捕获repository 在对应时间点的完整状态tree 可引用 blob也可嵌套指向其他 tree以此搭建完整 层级结构blob object 仅单纯存储文件字节内容无额外附属信息。开发流程中生成的每一条 commit 均遵循上述存储逻辑对应 object 会统一存入 Git 的 object database这便是 Git 存储、管理项目内容的底层实现机制。本次首个 commit 对应的对象关系图------------------------------ -------------------------------------- ----------------------------------|commit 016e263...||tree cf88f5...||blob ab0d21...|------------------------------ -------------------------------------- ----------------------------------|tree cf88f5...||modetypeobject name||content(Plants.txt):||author plants-lab|------|--------------------------------------|------|||committer plants-lab||100644blob ab0d21... Plants.txt||Rose||date2026‑08‑1215:30:00||||||||||...||Initial commit||............||(文件内容的二进制数据)|------------------------------ -------------------------------------- ----------------------------------- commit提交对象 tree树对象 blob对象 记录一次提交的信息包含作者、提交者、 记录目录结构。每一行代表一个文件或子目录 保存文件的实际内容二进制数据 时间、提交说明等并指向一个 tree 对象。 包含权限、类型、对象哈希和文件名。 不包含文件名或任何元数据。 可以指向 blob文件或其他 tree子目录。 ---------------------------------------------------------------------------------------------------|字段说明:mode权限type类型(blob文件,tree目录)object对象的 SHA‑1 哈希name文件名/目录名|---------------------------------------------------------------------------------------------------|关系: commit 指向 tree -tree 指向 blob 或其他 tree -blob 保存文件内容|---------------------------------------------------------------------------------------------------我们先前还留有一个悬而未决的问题在探讨 trees 与 blobs 的机制时这个问题至关重要为什么文件名是存放在 tree 之中而不是存放在 blob 里这个问题的答案可以说是理解 Git 过程中最重要、最让人恍然大悟的关键之一。第二次 commit首先我们在项目的根目录下新增一个名为 tasks/ 的文件夹。在其中为每个任务创建一个文件并以负责该任务的人员姓名作为文件名。第一个任务是plants trees而负责这项任务的人是我。$mkdirtasks $printfRose./tasks/plant_trees.txt $gitadd.$gitcommit-mCreate plant the trees task[master ff1080d]Create plant the trees task1filechanged,1insertion()create mode100644tasks/plant_trees.txt我们刚刚新增了一条 commit按照前面学到的知识objects 数据库目录里理应生成了一批新对象。接下来我们就来看看具体新增了哪些内容。和第一次提交时一样我们先查看这个全新的 commit。这次我们使用 Git 的 --oneline 参数将每一条 commit 的信息做精简展示。$gitlog--onelineff1080d(HEAD -master)Create plant the trees task 016e263 create Plantsfile$gitcat-file ff1080dc44bc51d20bde8432ed5f4630b3c2fe5e-tcommit $gitcat-file ff1080dc44bc51d20bde8432ed5f4630b3c2fe5e-ptree c652f10626f677c9452fdfeb4579fec8afced0a0 parent 016e263a1b9b430cf0b1044b59d8e66d57ccc58c author plants-labplants-lablab.com17868875760700 committer plants-labplants-lablab.com17868875760700 Create plant the trees task我们得到了第二个 commit 预期的内容但同时也发现了一个新的要素。这个 commit 里面包含了一个 parent 对象。那么这个 parent 对象到底是什么呢它其实就是我们的第一个 commit。你可以对比两者的 ID以此来验证这一点。除了第一个 commit 以外其余所有 commit 都至少拥有一个 parent commit。现在我们再来看看 tree 容器$gitcat-file c652f10626f677c9452fdfeb4579fec8afced0a0-ttree $gitcat-file c652f10626f677c9452fdfeb4579fec8afced0a0-p100644blob ab0d218b097a7cb260b99336411779a703a672e1 Plants.txt 040000 tree a59dc4bb5f187af5549111d0fee48578bee3ecd7 taskstree 内部包含两组对象引用和上一个 commit 完全相同的 blob 引用对应文件 Plants.txt一个全新的 tree 引用对应目录 Tasks前面我们提到过tree 的职责就是搭建项目的目录层级结构。本次我们向项目新增了一个文件夹出现这个新 tree 对象也就不难理解。指向 Plants.txt 的 blob 引用没有任何改动也就代表该文件对应的实际内容完全没有发生变化。$gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-tblob $gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-pRose让我们一起来查看这个全新的 tree 对象$gitcat-file a59dc4bb5f187af5549111d0fee48578bee3ecd7-ttree $gitcat-file a59dc4bb5f187af5549111d0fee48578bee3ecd7-p100644blob ab0d218b097a7cb260b99336411779a703a672e1 plant_trees.txt正如我们预料的那样这个 tree 中保存着一个 blob 对象的 ID该条记录的引用名称为Plant_trees.txt。最后我们再来查看这个 blob 对象内部的实际内容。$gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-tblob $gitcat-file ab0d218b097a7cb260b99336411779a703a672e1-pRose这里出现了一个很有意思的现象:plant_trees.txt对应的 blob 对象 ID和 Plants.txt 的 blob ID 是一模一样的。两个完全独立的文件对象 ID 按理来说不应当是唯一的吗事实上这并不是简单的重复只是 Git 在对已有数据做高效复用。Git 检测到这两份文件的内容完全一致于是它并不会在数据库中生成两份一模一样的对象而是只创建一个 blob 对象让两处引用同时指向这同一个 blob。Git 的内部逻辑大致是这样“假如对象库中已经存在这份压缩好的内容没必要再保存一份一模一样的对象。直接复用这个对象让不同 tree 里的条目都指向同一个 blob 就可以。”因此只要内容完全一致Git 就会生成完全相同的哈希 key。眼前这个场景正是该规则的实际体现。Git 有一条底层命令 hash‑object接收一段内容返回对应的 hash key。我们可以通过管道传递内容加上 --stdin 参数让命令从标准输入读取字符串。我们示例中的这两个文件内容都是 Rose。现在我们把字符串 Rose 交给 Git看看会得到什么结果$printfRose|githash-object--stdinab0d218b097a7cb260b99336411779a703a672e1我们得到了完全一样的 hash key。意味着如果我们再新建一个内容为 Rose 的 txt 文件新的 commit 同样会指向这同一个 blob。Git 对对象数据库里的对象做了极高效率的管理只要内容一致就会直接复用已有的 blob文件处在哪个目录下并不会对此产生任何影响。这也解释了为什么文件名要存放在 tree 对象当中。正是这样的设计才允许不同文件名共同引用同一个 blob。Git 远比我最初想象的要巧妙。下面就是第二个 commit 的对象关系图┌─────────────────────────────┐ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ commit ff1080..│ │ tree c652f..│ │ blob ab0d21..│ ├─────────────────────────────┤ ├─────────────────────────────┤ ├─────────────────────────────┤ │ tree c652f..│-------│ blob ab0d21..Plants.txt │------│ contentRose│ │ parent 016e26..│ │ tree a59dc4..Tasks │ │ │ │ author plants_lab..│ └─────────────────────────────┘ └─────────────────────────────┘ │ committer plants_lab..│ ↓ ↑ └─────────────────────────────┘ └──────────────────┌───────────────────────────────┐ │ tree a59dc4..│ ├───────────────────────────────┤ │ blob ab0d21..Plant_trees.txt │ └───────────────────────────────┘最后这就是两个 commit 的 对象关系图它们在 objects database 中共享同一个 blob。┌─────────────────────────────┐ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ commit ff1080..│ │ tree c652f1..│ │ tree a59dc4..│ ├─────────────────────────────┤ ├─────────────────────────────┤ ├──────────────────────────────┤ │ tree c652f1..│-------│ blob ab0d21..Plants.txt │ ------│ blob ab0d21..Plant_trees.txt │ │ parent 016e26..│ │ tree a59dc4..tasks │ └──────────────────────────────┘ │ author plants_lab..│ └─────────────────────────────┘ ↓ │ committer plants_lab..│ ↓ ↓ └─────────────────────────────┘ └──────────────────────┌───────────────────────────┐ ↓ │ blob ab0d21..│ ↓ ├───────────────────────────┤ ┌─────────────────────────────┐ ┌──────────────────────────┐ │ contentRose│ │ commit 54725f..│ │ tree cf88f5..│---└───────────────────────────┘ ├─────────────────────────────┤ ├──────────────────────────┤ │ tree cf88f5..│-------│ blob ab0d21..Plants.txt │ │ author plants_lab..│ └──────────────────────────┘ │ committer plants_lab..│ └─────────────────────────────┘2 个 commit、3 个 tree但只有 1 个 blob。用 reference 追踪 commit我们再回到第二个 commit。可以看到这里出现了一个名为 parent 的全新对象它是指向父commit的引用。正是依靠这个引用Git 才得以维护提交的先后历史顺序。Git 通过各个 commit 串联形成一条提交链。只要掌握每个 commit 的hash key我们就可以随时回到项目过去任意一个历史状态。但哈希是一个 40 位的十六进制数字对人来说很难记忆。相比一串随机字母与数字组成的字符串我们更容易记住带有实际语义的单词。为此 Git 提供了可读性更好的引用references帮助我们摆脱冗长哈希的记忆负担。我们打开仓库根目录下的 refs/ 文件夹看看示例环境中这里存放了什么内容。$ls.git/refs/ heads/ tags/在 refs/ 文件夹内部包含 heads/ 和 tags/ 两个子目录。我们先来打开 heads/ 文件夹查看其中的内容$ls.git/refs/heads/ master这就是我们的 master 分支branch。Git 在我们初始化 repository 的那一刻就创建了这个分支。让我们创建一个新的分支$gitcheckout-bbranchA Switched to a new branchbranchA$ls.git/refs/heads/ branchA master分支branches本质上就是引用references所有分支都存放在 refs/ 目录下的 heads/ 文件夹中。一个分支在磁盘上的物理形态究竟是什么?$cat.git/refs/heads/master ff1080dc44bc51d20bde8432ed5f4630b3c2fe5e一个 branch 本质上就是一个 hash。它就是第二个 commit 的 hash。一个 branch 就是一个指向 commit 的指针pointer只是它拥有一个对人类更友好的名称。说得更简单一点一个 branch 本质上就是一个文件文件里面存放着一个字符串而这个字符串就是 commit 的 hash key。借助 Git 的 branch我们可以在 commit 历史中非常快速地前后切换也就是在项目的不同历史状态之间来回移动。$cat.git/refs/heads/branchA ff1080dc44bc51d20bde8432ed5f4630b3c2fe5e如果我们读取 branchA 的内容会发现它的哈希 key 和 master 完全一致。这是因为该分支从 master 分支创建出来之后还没有产生过任何新的 commit。这两个指针此刻同时指向同一个 commit。那么it 又是如何判断我们当前正处于哪一个分支呢Git 会把这个信息保存在仓库根目录下的 HEAD 文件当中。$cat.git/HEAD ref: refs/heads/branchA这个文件里面存放的并不是哈希值正如前面所说只有 object database 中的 objects 才拥有 hash。分支本身并不是对象它们属于引用而 HEAD 指向的恰恰是一个分支。所以HEAD 存储的是对另一个 reference 的引用。简单做个总结HEAD 是一个指向其他指针的指针。我们也可以这样来理解这层关系┌──────┐ ┌────────┐ ┌─────────────────────────────┐ ┌────────┐ │ HEAD │----│branchA │------│ commit ff1080..│ ←------│ master │ └──────┘ └────────┘ ├─────────────────────────────┤ └────────┘ │ tree c652f1..│ │ parent 016e26..│ │ author plants_lab..│ │ committer plants_lab..│ └─────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ commit 016e26..│ ├─────────────────────────────┤ │ tree cf88f5..│ │ author plans_lab..│ │ committer plants_lab..│ └─────────────────────────────┘让我们创建一个新的commit看看会发生什么$gitbranch * branchA master $printfRosetasks/plant_shop.txt $gitadd.$gitcommit-mcreate plant_shop task[branchA 7f1598b]create plant_shop task1filechanged,1insertion()create mode100644tasks/plant_shop.txt让我们检查一下引用的当前值$cat.git/refs/heads/master ff1080dc44bc51d20bde8432ed5f4630b3c2fe5e $cat.git/refs/heads/branchA 7f1598bf66023ef3e20caa9bcbca473edacfc48d $cat.git/HEAD ref: refs/heads/branchA整个过程可以概括为新的 commit 创建后branchA 的值会随之更新因为它现在指向了这个新的 commit。此时branchA 是当前分支current branch。HEAD 文件不会发生变化它仍然指向 branchA。master branch 不会发生变化仍然保持原来的 hash。┌──────┐ ┌────────┐ ┌─────────────────────────────┐ │ HEAD │----│branchA │-------------------│ commit 7f1598..│ └──────┘ └────────┘ ├─────────────────────────────┤ │ tree b544ff..│ │ parent ff1080..│ │ author plants_lab..│ │ committer plants_lab..│ └─────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ ┌────────┐ │ commit ff1080..│ ←----- │ master │ ├─────────────────────────────┤ └────────┘ │ tree c652f1..│ │ parent 016e26..│ │ author plants_lab..│ │ committer plants_lab..│ └─────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ commit 016e26..│ ├─────────────────────────────┤ │ tree cf88f5..│ │ author plants_lab..│ │ committer plants_lab..│ └─────────────────────────────┘Tag我们最后一种 reference 是 tag。tag 是一种特殊的 reference引用用于在 commit 历史中标记某个 commit。它和 branch 有些相似但两者有一个关键区别tag 和 branch 的主要区别在于commit 创建之后tag 不会改变它的值。tag 是一个指向特定 commit 的不可变引用immutable reference。注意tag 主要用于标记代码的发布版本release version。这样无论什么时候通过这个 tag 获取代码都能得到与该版本发布时完全相同的 snapshot。Git Tag vs Branch 对比特性Branch分支Tag标签本质会自动移动的指针固定不变的引用新增 commit 后会怎样自动往前移动指向最新 commit不会移动永远指向打标签那一刻的 commit代表的含义“现在进行到哪了”动态进度“某个特定的历史节点”固定时刻典型用途日常开发、功能迭代如 main、dev、feature/xxx标记版本发布点如 v1.0.0常用命令:git branch 、git checkout git tag 、git tag |简单示例:# 分支会跟着新的 commit 移动main → commitAgitcommit-m新功能main → commitB# main 自动更新指向最新的# tag 一旦打上永远固定gittag v1.0# v1.0 → commitAgitcommit-m新功能v1.0 → commitA# tag 依然指着原来那个不会变main → commitB# 只有 main 分支往前移动了让我们创建一个 tag。它会被保存在 refs/ 目录下的 tags/ 子目录中。$gitbranch * branchA master $gittag v1.0 $cat.git/refs/tags/v1.0 7f1598bf66023ef3e20caa9bcbca473edacfc48d如同我们看到的tag v1.0 本质上只是一个指向某个 commit 的指针——也就是当前这个 commit。即使以后项目不断发展branchA 已经沿着 commit 历史向前推进了很多我们仍然可以通过 tag v1.0 快速回到这个 commit 所对应的项目状态。总结让我们总结一下本文最重要的 5 个要点Git 在 object database 中存储三种类型的 objectcommits、trees 和 blobs。Git database 中的每个 object 都有一个与之关联的 hash key。Git 利用 commits、trees 和 blobs并使用它们的 hash key 作为指针高效地构建项目的数据层级结构同时避免重复存储相同的内容。Hash key 很难被人类记忆因此 Git 提供了一种更友好的方式来引用这些 hashreferences引用。我们有 branches、HEAD 和 tags。branch 是一个指向 commit 的指针。Git 默认的 branch 名称是 master但你可以创建任意数量的 branch。HEAD reference 告诉我们当前正在使用哪个 branch。每当创建一个新的 commit 时branch 指针都会自动向前移动。最后tag reference 是不可变的immutable它始终指向同一个 commit因此可以用来永久标记某个时间点的项目状态。