Git 由 Linux 创始人 Linus Torvalds 于 2005 年创建。当时 Linux 内核开发团队使用的商业版本控制系统 BitKeeper 收回了免费使用授权,Linus 决定开发一个全新的版本控制系统。他用了大约 10 天时间完成了 Git 的第一个版本,并在两周内开始将其用于 Linux 内核的日常开发。Git 的设计目标非常明确:速度快、设计简单、支持非线性开发(大量并行分支)、完全分布式、能够高效处理超大规模项目(如 Linux 内核)。
为什么 Git 如此成功?Git 的设计哲学是"信任"而非"控制"——每个开发者本地都有完整的仓库副本,不依赖中央服务器。这让 Git 比 SVN、CVS 等集中式版本控制系统更灵活、更快速。Git 中的数据存储方式(内容寻址的文件系统)保证了数据的完整性,任何数据损坏都能被立刻检测出来。
| 特性 | Git | SVN | Mercurial |
|---|---|---|---|
| 架构 | 分布式 | 集中式 | 分布式 |
| 离线操作 | 完全支持 | 有限 | 完全支持 |
| 分支成本 | 极低(指针) | 高(目录复制) | 低 |
| 数据完整性 | SHA-1 校验 | 无内置校验 | SHA-1 校验 |
| 暂存区 | 有 | 无 | 无 |
| 学习曲线 | 较陡 | 平缓 | 中等 |
Git 的分支成本极低(只是一个 41 字节的指针文件),这使其分支操作几乎是瞬时完成的,这是 Git 相对于其他 VCS 的最显著优势。这也催生了 Git Flow、GitHub Flow 等基于分支的协作模式,从根本上改变了现代软件开发的工作方式。
Git 的核心模型可以用"三个区域"和"三种状态"来概括。理解这个模型是掌握 Git 的钥匙。
工作区(Working Directory):你电脑上实际看到的文件目录。你在编辑器中打开、修改的文件都在工作区中。工作区中的文件不一定是 Git 关心的对象。
暂存区(Staging Area / Index):一个位于 .git/index 的文件,记录了下次提交将要包含的文件信息。你可以把它理解为"提交预检区"——把准备要提交的修改先放在这里,确认无误后再一次性提交。
仓库(Repository / .git 目录):Git 用来存储项目元数据和对象数据库的地方。当你执行 git commit 时,暂存区中的内容就被永久保存到了仓库中。仓库中存储的是不可修改的"快照"。
已修改(Modified):文件在工作区中被修改了,但还没有添加到暂存区。此时 Git 知道"文件变了",但不会将其纳入提交范围。
已暂存(Staged):修改后的文件被添加到了暂存区,准备在下一次提交时被保存到仓库中。这是你对"我要提交这些修改"的明确声明。
已提交(Committed):暂存区的数据已经被安全地保存到了 Git 仓库中。一旦提交,如果你不主动删除,这个版本几乎永久存在。
这三个区域和三种状态对应着 Git 的核心工作流程:在工作区修改文件 → 将修改暂存到暂存区 → 将暂存区的内容提交到仓库。每次 git add 就是从"已修改"到"已暂存",每次 git commit 就是从"已暂存"到"已提交"。理解了这个流程,你就理解了 Git 80% 的操作。
Git 本质上是一个 内容寻址的文件系统。Git 的核心是一个键值对数据库:键是内容的 SHA-1 哈希值,值就是内容本身。Git 中有四种类型的对象:
理解 Git 对象模型的意义:当你理解了 Commit → Tree → Blob 的层级关系,你就明白为什么 Git 能如此高效。每次提交只是创建一个新的 Commit 对象和变化文件的 Tree/Blob 对象,未变化的文件直接复用已有对象。这就是为什么 Git 切换分支、查看历史、计算差异都极其快速的原因。
引用(refs)是 Git 中用来指代特定提交的"友好名称"。它们存放在 .git/refs/ 目录下。主要的引用类型包括:
.git/refs/heads/ 下。.git/refs/tags/ 下。ref: refs/heads/main。.git/refs/remotes/ 下。例如 origin/main 记录了远程 main 分支在你最后一次 git fetch 时的位置。Git 别名可以大幅提升日常操作效率。建议设置 git lg 别名(上面的 log --oneline --graph --all),它用图形方式展示提交历史和分支关系,非常直观。使用后你会爱上这个命令。
Git 的撤销操作是初学者最容易混淆的部分。关键在于明确你要撤销的内容在哪个区域:工作区、暂存区还是仓库。
git reset 会重写提交历史,适用于尚未推送到远程的本地分支。使用 --hard 时要极其谨慎,它会丢弃工作区的所有修改,无法恢复。
git revert 会创建新的提交来撤销旧提交,不会重写历史,适用于已推送到远程的公共分支(如 main)。团队协作中,永远不要对公共分支使用 git reset,而应使用 git revert。
当你需要使用强制推送时,始终优先使用 --force-with-lease 而不是 --force。后者会无条件覆盖远程分支,可能导致他人的工作丢失。前者会检查远程分支是否已被他人更新,如果是则拒绝推送,给你一个检查的机会。这层"安全带"在团队协作中至关重要。
在 Git 中,分支本质上只是一个指向某个提交的可移动指针。当你执行 git branch feature-x 时,Git 只是在 .git/refs/heads/ 下创建了一个名为 feature-x 的文件,里面写着当前提交的哈希值。创建分支不会创建任何新的文件或目录,也不会复制任何数据。这就是 Git 分支操作"瞬间完成"的原因。
Git 提供了多种合并策略,选择合适的策略对于维护清晰的项目历史至关重要。
| 策略 | 历史特征 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 标准合并 | 保留分支拓扑 | 真实反映开发过程 | 历史可能混乱 | 公共分支、长期分支 |
| 快进合并 | 线性历史 | 历史简洁 | 丢失分支信息 | 短期分支、个人项目 |
| Squash 合并 | 单次提交 | 极致简洁 | 丢失所有中间提交 | 功能开发完成时 |
| Rebase 合并 | 线性、整洁 | 历史干净且保留粒度 | 改变提交哈希,不可用于公共分支 | 本地分支整理 |
Git Flow 是 2010 年由 Vincent Driessen 提出的经典分支模型,适用于有固定发布周期的项目。它定义了五种分支类型:
Git Flow 的核心原则:每种分支都有明确的"生命周期"——从哪里来、做什么、回哪里去。main 代表"可发布"的状态,develop 代表"最新开发"的状态,feature 是"开发中"的状态,release 是"即将发布"的状态,hotfix 是"紧急修复"的状态。严格遵循这些约定,项目维护者看到分支名就知道它的用途和当前阶段。
GitHub Flow 是 Git Flow 的简化版本,更适合持续部署的现代开发模式。它的规则非常简单:
对于大多数中小团队和持续部署的项目,GitHub Flow 是最佳选择——简单、灵活、高效。如果你的项目有严格的版本发布周期(如移动 App、企业软件),Git Flow 更合适。Trunk-Based Development 则适用于 CI/CD 极度成熟、追求极致效率的团队。不建议初学者一上来就照搬 Git Flow,建议从 GitHub Flow 开始,逐步根据团队需求调整。
Rebase(变基)是 Git 最强大的功能之一,也是最容易引起困惑的操作。Rebase 的核心思想是:将一个分支上的提交"移植"到另一个分支的最新提交之上,从而形成一条线性的提交历史。
绝不要对已经推送到公共仓库的提交执行 rebase!Rebase 会改写提交的哈希值,如果别人已经基于你的旧提交工作了,rebase 会导致他们的仓库与远程仓库严重不一致。这条规则只有在你独自使用的分支上才可以打破。简单记忆:公共分支用 merge,私有分支用 rebase。
Cherry-pick 允许你将某个分支上的特定提交复制到当前分支。这在不合并整个分支却需要某个特定功能或修复的场景下非常有用。
Stash 用于临时保存工作区的修改,让你在不提交的情况下切换到其他分支。
Bisect 是一个非常强大的排错工具,它通过二分查找法快速定位引入 bug 的提交。
Bisect 实战场景:生产环境中突然出现了一个 bug,但不知道是哪个改动引起的。已知一周前版本是正常的。使用 git bisect,假设有一周的提交记录(约 100 次提交),你只需要测试大约 7 次就能找出罪魁祸首。配合自动化测试脚本,整个过程可以完全自动化。
子模块允许你在一个 Git 仓库中嵌入另一个 Git 仓库作为子目录。适用于依赖第三方库或共享模块的场景。
Reflog 记录了 Git 中所有引用(包括 HEAD、分支等)的移动历史。即使你丢失了提交(如误操作 reset),只要操作还在 reflog 中,就能恢复。
Reflog 是 本地 的操作日志,不会被推送到远程仓库。默认情况下,reflog 中的记录在 90 天后会被 Git 自动清理。如果你想要恢复一个更早的提交,需要查看 reflog 或使用 git fsck 查找悬挂对象。
每个 Git 仓库的根目录下都有一个 .git 隐藏文件夹。理解这个目录的结构,就是理解 Git 的底层实现。
Git 在存储大量对象时会进行压缩。初始时,每个对象都是一个单独的文件(松散对象,loose object)。当松散对象数量达到阈值时,Git 会自动执行 GC(垃圾回收),将松散对象打包成 Pack 文件并进行增量压缩。
Git 在执行合并时会根据情况自动选择合适的策略:
git merge branch1 branch2 branch3 时使用。大多数时候你不需要指定策略,Git 会自动选择。但了解这些策略有助于理解为什么某些合并会产生冲突,以及如何通过选择不同的策略来解决问题。
良好的提交信息是项目文档的重要组成部分。一份清晰的提交信息应该:
推荐使用规范化前缀:feat:(新功能)、fix:(bug 修复)、docs:(文档)、style:(格式)、refactor:(重构)、test:(测试)、chore:(杂项)。这符合 Conventional Commits 规范,可以自动生成 CHANGELOG。
.gitignore 文件告诉 Git 哪些文件不应该被跟踪。每个项目都应该有恰当的 .gitignore 配置。
.gitignore 核心原则:只忽略"生成的文件"和"敏感文件",不忽略"项目本身的源代码"。不应忽略的有:配置文件模板(如 .env.example)、项目依赖清单(如 package.json)、构建脚本。通常你可以在 GitHub 的 gitignore 模板仓库中找到适合你项目的参考配置。
.gitignore。仅仅从仓库中删除文件是不够的,因为历史中仍然存在。git gc 优化仓库性能,删除不需要的本地分支和标签。feature/xxx、bugfix/xxx、hotfix/xxx、release/xxx。命名要能反映分支的用途。Git 不适合管理大文件(二进制文件、媒体资源等)。如果你需要在 Git 仓库中管理大文件,推荐使用 Git LFS(Large File Storage)。
| 场景 | 解决方案 | 命令 |
|---|---|---|
| 误删了未提交的文件 | 从 Git 仓库恢复 | git restore 文件名 |
| add 了不该 add 的文件 | 取消暂存 | git restore --staged 文件名 |
| 提交信息写错了 | 修改最近一次提交信息 | git commit --amend -m "新信息" |
| 漏提交了文件 | 补充到最近一次提交 | git add 文件 && git commit --amend --no-edit |
| 想要撤销某次提交 | 创建反向提交 | git revert 提交哈希 |
| 误 reset --hard 丢失了提交 | 从 reflog 恢复 | git reset --hard HEAD@{n} |
| 误删了分支 | 从 reflog 重建 | git branch 分支名 提交哈希 |
| 想撤销某文件的修改 | 恢复到指定版本 | git checkout 提交哈希 -- 文件名 |
遇到合并冲突时,按以下步骤处理:
git status 会列出所有冲突文件。<<<<<<< 标记,找到冲突区域。git add 文件名。git commit 完成合并。原因:未配置用户名和邮箱。
解决:执行 git config --global user.name "你的名字" 和 git config --global user.email "你的邮箱"。
原因:本地和远程分支出现了分叉。
解决:执行 git pull --rebase 或 git pull --no-rebase 来合并远程更改。
原因:远程仓库有本地没有的提交。
解决:先 git pull 获取远程更新,解决冲突后再推送。或者,如果你确定要覆盖远程,使用 git push --force-with-lease(但不推荐对公共分支这样做)。
原因:直接签出了某个提交而不是分支(如 git checkout a1b2c3d)。
解决:如果你只是想查看,直接切换回分支即可(git switch main)。如果你想基于此提交开始工作,创建一个新分支(git switch -c 新分支名)。
解决:使用 git filter-branch 或 BFG Repo-Cleaner 从整个历史中移除敏感文件。但更好的做法是:立即到对应平台(如 GitHub)撤销泄露的密钥/密码,然后清理仓库,最后强制推送。
Git 钩子(Hook)是在特定 Git 事件发生时自动触发的脚本。它们存放在 .git/hooks/ 目录下。钩子可以用来强制执行代码规范、运行测试、检查提交信息格式等。
| 钩子名称 | 触发时机 | 常见用途 |
|---|---|---|
| pre-commit | 执行 git commit 前 |
代码格式检查、运行单元测试、防止提交大文件 |
| prepare-commit-msg | 提交信息编辑器打开前 | 自动生成提交信息模板 |
| commit-msg | 提交信息编辑完成后 | 检查提交信息格式是否符合规范 |
| post-commit | 提交完成后 | 发送通知、更新 CI 状态 |
| pre-push | 执行 git push 前 |
运行完整测试套件、检查分支命名 |
| post-merge | 合并完成后 | 自动安装新依赖、迁移数据库 |
手动管理 .git/hooks/ 中的脚本不太方便(这些文件不被版本控制)。推荐使用 Husky(Node.js 项目)或 pre-commit(Python 项目)来管理钩子。这些工具让你可以在项目源码中定义钩子,并与其他开发者共享。
第一:Git 本质是内容寻址的文件系统。四个核心对象类型(Blob、Tree、Commit、Tag)构成了 Git 的数据模型。理解 Git 对象模型是掌握 Git 底层原理的关键。
第二:三个区域(工作区、暂存区、仓库)和三种状态(已修改、已暂存、已提交)是 Git 的核心工作模型。所有 Git 操作都围绕这个模型展开。
第三:分支本质是 41 字节的指针文件。Git 分支成本极低,因此分支操作是 Git 的核心优势。学会灵活使用分支和合并策略,是高效协作的基础。
第四:公共分支用 merge 或 revert,私有分支用 rebase。不会改写公共历史是 Git 协作的第一准则。--force-with-lease 应该永远优先于 --force。
第五:高级技巧(rebase -i、cherry-pick、stash、bisect、reflog)是 Git 高手的必备技能。这些技巧解决的是真实开发中的"痛点问题"——整理历史、选择性合并、临时切换、快速排错、误操作恢复。
第六:最好的学习方式是实践。创建一个沙盒仓库,反复练习本文的所有命令。故意制造冲突、误操作,然后用学到的技术来修复。只有在"犯错-修复"的循环中,才能真正理解 Git 的工作原理。
本文是《使用 Claude Code 必备的前置基础知识》中 Git 章节的专题延伸。建议先阅读前置基础知识中的 Git 入门内容,再深入本文的专题详解。两者结合,可以建立从入门到精通的完整 Git 知识体系。