# 从底层原理开始，把Git彻底讲明白

> 龙虾AGI通用实验室 · 2026-07-16

Git 的教程网上铺天盖地，上来就跟你说分支、commit、合并、PR，每个字都认识，连在一起就不知道在讲什么。

原因很简单：这些概念都是长在底层逻辑上面的，底层没搞懂，上面的东西怎么看怎么糊。

这篇文章反过来，从最底层讲起，一层一层往上搭。每个新概念出现的时候，你已经有了足够的基础，不需要死记，自己就能推出来。

· · ·

## 第一层：Git 到底在帮你解决什么问题

你写了一个文件，保存了。然后改了几行，又保存了。这时候你想看看改之前长什么样——没了，被覆盖了。

这就是最原始的问题：**文件只有一个"当前状态"，你没法回到过去。**

有人会手动解决这个问题，把文件复制一份叫 项目_v1，改完再复制一份叫 项目_v2，再改再复制叫 项目_最终版、项目_最终版2、项目_打死也不改了。你的文件夹很快变成一堆命名混乱的副本，找哪个版本全靠记忆。

Git 就是来解决这个问题的。它帮你**自动记住文件的每个历史版本**，随时可以查看、比较、回退，不需要你手动复制。

· · ·

## 第二层：Git 是怎么记住的

你在项目文件夹里执行 git init，Git 就在这个文件夹里创建了一个叫 .git 的隐藏文件夹。接下来所有的历史记录，都存在这里面。

然后你执行一次 commit（提交），Git 就会做一件事：**给你当前文件夹里的所有文件拍一张快照**，把这张快照存进 .git 里。

这张快照记录了"这一刻，每个文件的完整内容是什么"。

第一次 commit，Git 必须把每个文件的完整内容都存一份。这是全量存储，因为没有任何历史可以参考，只能从零记起。

但从第二次 commit 开始，Git 就聪明了。它会检查：哪些文件变了，哪些没变。没变的文件不会再存一遍，只记一个引用说"跟上次一样"。只有改过的文件才存新版本。时间久了，Git 还会进一步压缩，一万行代码你改了两行，它最终可能只存几个字节的差异信息。

所以 commit 一百次不会占一百倍的空间。一个项目上万次 commit，.git 文件夹通常也就几百 MB。

但不管怎么压缩，有一个逻辑是逃不掉的：**增量必须建立在全量之上**。第一次 commit 存的那个全量，就是所有后续增量的根基。没有它，后面的差异信息就是无根之水。

记住这两点：commit 就是拍快照，快照存在 .git 里。这是 Git 一切功能的地基。

· · ·

## 第三层：Git 不是照相机，是管家

到这里你可能觉得：好，Git 就是个拍照的工具，在旁边默默记录，照片是照片，文件是文件，互不干扰。

**不是这样的。** 这个误解如果不纠正，后面所有概念你都会觉得别扭。

Git 不是一个站在旁边拍照的摄影师。它是一个**有实权的管家**。你执行 git init 之后，这个文件夹就交给它管了，它不只是记录——它有能力**把你的文件改回任何一张快照的样子**。

想象你有一个房间，里面摆着家具。你跟管家说"拍个照记一下"（commit），管家记住了。你把沙发扔了换了张桌子，又说"再拍一张"（commit），管家又记住了。

现在关键来了——你跟管家说"给我恢复成第一张照片的样子"。管家不是给你翻照片看看，它是真的动手，把桌子搬走，把沙发搬回来。房间真的变了。

对应到 Git：你告诉它"切回某个历史版本"，它会真的修改你文件夹里的文件。你打开文件夹去看，文件内容变了，有些文件甚至消失了或者重新出现了。

这不是什么魔法。.git 里存着每个版本的完整信息，Git 只是按照那个版本的记录，把你的文件恢复成对应的样子。

## Git 不是照相机，它是时光机。

记住这一点，后面的东西就全通了。

· · ·

## 第四层：分支到底是什么

有了前面的基础，分支就很好理解了。

你 commit 了三次，Git 存了三张快照，我们叫它 A、B、C。这三张快照按顺序排成一条链：A → B → C。

现在，Git 需要知道"你目前在哪个快照上"。它用一个叫 master 的标记来记录这件事。master 指向快照 C，意思就是"master 这个分支目前的状态是快照 C 的样子"。

注意，master 只指向 C 这一个快照，不是指向"A、B、C 这一整串"。但是每个快照自己记住了"我的上一个快照是谁"——C 知道上一个是 B，B 知道上一个是 A。所以从 C 出发，可以一路追溯回 A，这条链就浮现出来了。链不是 master 持有的，是快照之间自己串起来的。

另外，你每次在 master 上 commit 一次，master 就往前移一格，指向最新的那个快照。commit 了 D，master 就从指向 C 变成指向 D。它永远只指向最新的那一个。

master 不是什么高深的东西。它就是 Git 自动帮你建的第一个标记的名字。没有 Git 就没有 master，它是 Git 的产物，不是代码本身的一部分。你甚至可以把它改名叫 main、叫 production、叫任何你喜欢的名字。

现在你说"我要建一个新分支叫 dev"。Git 做了什么？它只是创建了一个新标记，也指向快照 C。此刻 master 和 dev 都指向同一个快照 C，一模一样。

然后你切换到 dev 分支，改了代码，commit 一次。Git 拍了快照 D。dev 这个标记往前移，指向了 D。但是 master 还指着 C，纹丝不动。

现在你的项目有两条时间线了：

master 的时间线：A → B → C

dev 的时间线：A → B → C → D

你在 dev 上做的所有改动，只存在于快照 D 里。master 指向的快照 C 里面，文件内容从始至终没被碰过。

这就是为什么"在分支上改坏了不影响 master"。不是因为分支是一份复印件，而是因为它们是不同的标记，指向不同的快照，互不干扰。

而且别忘了第三层讲的——Git 是管家，它管着你的文件。你执行 git checkout master，管家把文件夹恢复成快照 C 的样子；你执行 git checkout dev，管家把文件夹恢复成快照 D 的样子。你真的打开文件夹看，文件内容确实不一样。

所以"在哪个分支上 commit"之所以重要，是因为不同的分支指向不同的快照，线上服务器跑的是 master 的快照。你在 dev 上 commit 了一个有 bug 的版本，那只是 dev 的快照里有 bug，master 的快照里没有，用户完全不受影响。

· · ·

## 第五层：剩下的概念，都从上面长出来

到这里，你已经搞懂了 Git 的三个底层逻辑：commit 是拍快照，Git 是管家能改文件，分支是指向不同快照的标记。剩下那些名词，全都是在这个基础上自然长出来的东西。

**合并（Merge）** 就是你在 dev 分支上改好了代码，测试通过了，现在要把 dev 快照里的改动写进 master 的快照里。合并之后，master 的标记往前移，指向一个包含了两边改动的新快照。两条时间线汇成一条。

但合并有时候会遇到一个问题：**冲突（Conflict）**。

两个分支从同一个快照出发，各自往前走。dev 改了文件 A 的第 10 行，master 也改了文件 A 的第 10 行，但改法不一样。合并的时候，Git 发现同一个位置有两个不同的改动，它不知道该听谁的，就会停下来，把两边的改动都标在文件里，等你来决定。

不是所有合并都会冲突。如果两个分支改的是不同的文件，或者同一个文件的不同位置，Git 能自动合好，你甚至感觉不到。只有改了同一个地方又改法不同，才需要你手动处理。

处理方式取决于你用什么工具。用命令行，终端会告诉你哪个文件有冲突，你打开文件手动改好再 commit。用 GitHub 上的 PR，页面会直接显示有冲突，给你一个界面左右对比着解决。用 VS Code 这类编辑器，它会把冲突的地方高亮出来，给你按钮选"保留这边的"还是"保留那边的"，点一下就行。

**推送（Push）** 是你把本地电脑上的快照上传到远程服务器（比如 GitHub）。你在自己电脑上 commit、建分支、合并，这些都只发生在你的 .git 文件夹里。push 之后，远程服务器的 .git 才会同步你的记录，别人才看得到你的工作。

**Pull Request（PR）** 是 GitHub 这类平台提供的功能，不是 Git 本身的命令。它的意思是：我在 dev 上改好了代码，push 到了远程，现在请大家审一下，没问题再合并到 master。PR 提供了一个可以看改动、留评论、批准或打回的流程。你在本地直接执行 merge，没有审核流程，不算 PR。一个人写项目其实用不着 PR，直接本地合并就行，PR 是多人协作时的安全网。

**标签（Tag）** 需要先搞清一件事：每个 commit 都有自己的名字，就是那个哈希值，像 a3f5b2c9e8d1 这种。所以不存在"没名字的快照"，只是这个名字是一串乱码，人记不住，看了也不知道它代表什么。

标签就是你从上千个快照里，挑出几个特别重要的，额外贴一个好记的别名上去。比如你做了一百次 commit，其中第 50 次是正式发布的版本，你给它贴个标签叫 v1.0.0。剩下那 99 个 commit 没有标签，但它们照样有自己的哈希值，照样能找到，只是没有一个好记的别名。

标签的名字你随便起，Git 不管你叫什么，但大家约定用递增版本号，一看就知道新旧。不打标签也不影响功能，只是找版本的时候得翻提交记录去对着哈希值猜。

· · ·

## 最后一件事

讲到这里，退一步想想，其实最神奇的不是 Git 本身，而是一个很容易被忽略的事实：**在电脑上，时光穿梭是真的可以实现的。**

现实世界里，你把一杯水泼了，没法倒流回去。但文件不一样。文件就是一堆数据，数据可以被覆盖。既然可以被覆盖，那只要你提前把内容存好了，你就能随时把文件覆盖回任何一个历史状态。今天的代码、昨天的代码、一个月前的代码，随便跳，想去哪个时刻就去哪个时刻。

这件事听起来理所当然，但你仔细想想，这其实是一个非常强大的能力。现实中没有任何东西能做到这一点。

Git 就是建立在这个能力之上的。它做的事情说到底就是：每个时刻 commit 的代码，都完整地存了下来，并且可以自由切换。分支、合并、回退、标签，所有那些概念，都是这个能力的不同用法而已。

这才是这件事最本质的地方。不是 Git 有多复杂，而是"文件可以被覆盖"这么朴素的一件事，一旦你系统地用起来，效果就是时光机。

我建了一个AI学习研究群，目前几十来人，都是在真正动手搞AI的人。如果你也在自己跑模型、写代码、做项目，欢迎点赞关注转发一键三连，然后加我微信，备注写清"你在做的AI方向"，聊得来的话我拉你进群。纯围观的和小白就不加了，有一定门槛。当然你也可以简单粗暴的甩给我999元，我拉你进群，这个没门槛。

![](images/d3258b/img_001.jpg)
