来源:gitee官网操作文档以及deepseek

作用:方便个人学习理解

一:相关基础

1.1关于版本控制

      什么是版本控制:版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统

本地版本控制系统

       许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单。不过坏处也不少:有时候会混淆所在的工作目录,一旦弄错文件丢了数据就没法撤销恢复。

      为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异(见图示本地版本控制系统)

      其中最流行的一种叫做 rcs,现今许多计算机系统上都还看得到它的踪影。甚至在流行的 Mac OS X 系统上安装了开发者工具包之后,也可以使用 rcs 命令。它的工作原理基本上就是保存并管理文件补丁(patch)。文件补丁是一种特定格式的文本文件,记录着对应文件修订前后的内容变化。所以,根据每次修订后的补丁,rcs 可以通过不断打补丁,计算出各个版本的文件内容。

集中化的版本控制系统

      接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作?于是,集中化的版本控制系统( Centralized Version Control Systems,简称 CVCS )应运而生。这类系统,诸如 CVS,Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法

       这种做法带来了许多好处,特别是相较于老式的本地 VCS 来说。现在,每个人都可以在一定程度上看到项目中的其他人正在做些什么。而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。

       事分两面,有好有坏。这么做最显而易见的缺点是中央服务器的单点故障。如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。要是中央服务器的磁盘发生故障,碰巧没做备份,或者备份不够及时,就会有丢失数据的风险。最坏的情况是彻底丢失整个项目的所有历史更改记录,而被客户端偶然提取出来的保存在本地的某些快照数据就成了恢复数据的希望。但这样的话依然是个问题,你不能保证所有的数据都已经有人事先完整提取出来过。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。

分布式版本控制系统

        于是分布式版本控制系统( Distributed Version Control System,简称 DVCS )面世了。在这类系统中,像 Git,Mercurial,Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作,实际上都是一次对代码仓库的完整备份

      更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。

1.2理解git的思想和基本工作原理

      开始学习 Git 的时候,请不要尝试把各种概念和其他版本控制系统(诸如 Subversion 和 Perforce 等)相比拟,否则容易混淆每个操作的实际意义。Git 在保存和处理各种信息的时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同

1 :git是直接记录快照,并不是差异比较

      Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容

        Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。Git 的工作方式就像图 所示。

       这是 Git 同其他系统的重要区别。它完全颠覆了传统版本控制的套路,并对各个环节的实现方式作了新的设计。Git 更像是个小型的文件系统,但它同时还提供了许多以此为基础的超强工具,而不只是一个简单的 VCS。稍后在第三章讨论 Git 分支管理的时候,我们会再看看这样的设计究竟会带来哪些好处。

2:近乎所有操作都是本地执行

        在 Git 中的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用 CVCS 的话,差不多所有操作都需要连接网络。因为 Git 在本地磁盘上就保存着所有当前项目的历史更新,所以处理起来速度飞快。

       举个例子,如果要浏览项目的历史更新摘要,Git 不用跑到外面的服务器上去取数据回来,而直接从本地数据库读取后展示给你看。所以任何时候你都可以马上翻阅,无需等待。如果想要看当前版本的文件和一个月前的版本之间有何差异,Git 会取出一个月前的快照和当前文件作一次差异运算,而不用请求远程服务器来做这件事,或是把老版本的文件拉到本地来作比较。

       用 CVCS 的话,没有网络或者断开 VPN 你就无法做任何事情。但用 Git 的话,就算你在飞机或者火车上,都可以非常愉快地频繁提交更新,等到了有网络的时候再上传到远程仓库。同样,在回家的路上,不用连接 VPN 你也可以继续工作。换作其他版本控制系统,这么做几乎不可能,抑或非常麻烦。比如 Perforce,如果不连到服务器,几乎什么都做不了(译注:默认无法发出命令 p4 edit file 开始编辑文件,因为 Perforce 需要联网通知系统声明该文件正在被谁修订。但实际上手工修改文件权限可以绕过这个限制,只是完成后还是无法提交更新。);如果是 Subversion 或 CVS,虽然可以编辑文件,但无法提交更新,因为数据库在网络上。看上去好像这些都不是什么大问题,但实际体验过之后,你就会惊喜地发现,这其实是会带来很大不同的。

3:时刻保持数据完整性

       在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。换句话说,不可能在你修改了文件或目录之后,Git 一无所知。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。

        Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。该字串由 40 个十六进制字符(0-9 及 a-f)组成,看起来就像是:

24b9da6552252987aa493b52f8696cd6d3b00373

         Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。实际上,所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不是靠文件名。

4:多数操作仅添加数据

      常用的 Git 操作大多仅仅是把数据添加到数据库。因为任何一种不可逆的操作,比如删除数据,都会使回退或重现历史版本变得困难重重。在别的 VCS 中,若还未提交更新,就有可能丢失或者混淆一些修改的内容,但在 Git 里,一旦提交快照之后就完全不用担心丢失数据,特别是养成定期推送到其他仓库的习惯的话。

      这种高可靠性令我们的开发工作安心不少,尽管去做各种试验性的尝试好了,再怎样也不会弄丢数据。至于 Git 内部究竟是如何保存和恢复数据的,我们会在第九章讨论 Git 内部原理时再作详述。

1.3 文件的三种状态

        对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。

由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库。

        每个项目都有一个 Git 目录(译注:如果 git clone 出来的话,就是其中 .git 的目录;如果 git clone --bare 的话,新建的目录本身就是 Git 目录。),它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。

      从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。这些文件实际上都是从 Git 目录中的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。

        所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。

基本的 Git 工作流程如下:

  1. 在工作目录中修改某些文件。
  2. 对修改后的文件进行快照,然后保存到暂存区域。
  3. 提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。

       所以,我们可以从文件所处的位置来判断状态:如果是 Git 目录中保存着的特定版本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。到第二章的时候,我们会进一步了解其中细节,并学会如何根据文件状态实施后续操作,以及怎样跳过暂存直接提交。

二:对于git的理解

      大多数传统的版本控制系统(例如 Subversion、Perforce、CVS)采用基于文件的变化跟踪。它们会将每个文件视为独立的历史单元,仓库中保存着文件的初始版本,然后每次提交只记录该文件相对于上一次提交的变化内容(也称为差异或 delta)。

    而git是项目级别的完整快照(通过树对象组织)

  • Git 的方式:你不是真的“拍照”,而是列一个清单(目录树),上面写着房间里每个物品的唯一编号,并有一个仓库专门存储每个物品的实物照片。

    • 第一次记录时,你把所有物品都拍成一张张独立的照片(blob 对象),并给每张照片一个唯一的编号(比如根据内容生成的 SHA-1 哈希)。然后你写一个清单(tree 对象),上面列出物品名称和对应的照片编号。最后,你把这个清单装订成一个笔记本(commit 对象),记录下当天的日期。

    • 第二天,如果你只是移动了桌子的位置,但桌子本身没变,你就不需要重新给桌子拍照。你只需要创建一个新的清单,其中桌子的照片编号还是原来的那张,而其他没变的物品也引用原来的照片编号。只有那些真正改变的物品(比如你换了本书),才需要拍新照片。

    • 这样,无论你记录了多少天的状态,仓库里每件物品只保存了一份照片,所有天的清单都只是引用这些照片。当你想看某天的房间状态时,拿出那天的清单,按编号取出照片,就能还原出完整的房间。

Git 的工作方式正是如此:相同内容的文件在 Git 仓库中永远只存储一份,所有提交中只要该文件内容没变,就都指向同一个 blob 对象。

Git 内部的实际机制

1. 内容寻址与 SHA-1 哈希

Git 使用 SHA-1 哈希算法为每个文件的内容计算一个 40 位的十六进制字符串(例如 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391)。这个哈希值就是文件内容的“指纹”:

  • 如果两个文件的内容完全相同,它们的哈希值也完全相同。

  • Git 以哈希值作为文件名,将文件内容存储在对象数据库(.git/objects 目录)中。

因此,文件内容相同 → 哈希值相同 → 只存储一次。这实现了自动的去重。

2. 树对象(Tree Object)记录目录结构

树对象类似于一个目录清单,它记录了该目录下每个文件的:

  • 文件名

  • 文件类型(文件、目录、符号链接等)

  • 对应的 blob 对象的哈希值(或子树的哈希值)

树对象本身也有自己的哈希值,由其包含的所有条目(文件名+权限+子对象哈希)计算得出。所以:

  • 如果某个目录下的所有文件及其内容都未变,那么该目录的树对象也不会变,哈希值相同,Git 只会存储一份树对象。

  • 如果只修改了一个文件,那么包含该文件的目录树就会生成新的树对象(因为其条目中的 blob 哈希变了),但其他未变的子目录仍然指向旧的树对象。

3. 提交对象(Commit Object)指向根树

每次提交时,Git 会:

  • 根据当前暂存区的内容,递归地创建或复用树对象,构建出整个项目的根树对象。

  • 创建一个提交对象,它包含:

    • 根树对象的哈希值

    • 父提交的哈希值(如果是第一次提交则为空)

    • 作者、提交者信息、提交说明等

提交对象本身也有哈希值,作为该次提交的唯一标识。

4. 共享机制示例

假设项目初始只有一个文件 a.txt,内容为 hello。提交一次后,Git 仓库中会有:

  • 一个 blob 对象,内容为 hello,哈希假设为 aaa

  • 一个树对象,记录 a.txt → aaa

  • 一个提交对象,指向该树对象

现在你修改 a.txt,内容改为 world,并再次提交。Git 会:

  • 创建新的 blob 对象,内容为 world,哈希为 bbb(因为内容变了,哈希不同)

  • 创建新的树对象,记录 a.txt → bbb

  • 创建新的提交对象,指向新树对象,父提交指向上一次提交

此时仓库中有了两个 blob(aaa 和 bbb)、两个树对象、两个提交对象。hello 和 world 各存一份,没有冗余。

如果你再增加一个新文件 b.txt,内容也是 hello(与 aaa 相同),那么 Git 会直接引用已有的 blob aaa,不会重复存储。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐