关注CodingTechWork
引言
Git 是现代软件开发不可或缺的版本控制工具。然而,很多开发者虽然每天都在使用 Git,却对其底层原理缺乏理解,导致遇到冲突时手足无措。本文将深入剖析六大核心命令的工作原理,帮助你建立清晰的 Git 心智模型。
Git 的底层数据模型
在理解具体命令之前,我们先来建立 Git 的核心认知。
三个区域
工作目录 (Working Directory)
↓ git add
暂存区 (Staging Area / Index)
↓ git commit
本地仓库 (Local Repository)
↓ git push
远程仓库 (Remote Repository)
四个对象
Git 本质上是一个 内容寻址文件系统,核心对象包括:
| 对象类型 | 作用 | 示例 |
|---|---|---|
| Blob | 存储文件内容 | 文件快照 |
| Tree | 存储目录结构 | 文件名→Blob 的映射 |
| Commit | 存储版本信息 | 作者、时间、Tree 哈希、父提交 |
| Tag | 存储标签信息 | 指向特定提交的指针 |
提交链结构
commit A ← commit B ← commit C (HEAD)
↓ ↓ ↓
tree tree tree
↓ ↓ ↓
blobs blobs blobs
每个提交都指向其父提交,形成一条不可变的链。
六大核心命令详解
git commit - 记录变更
技术原理

git commit 做的事情:
- 将暂存区内容打包成 Tree 对象
- 创建 Commit 对象(包含 Tree、父提交、作者等信息)
- 将当前分支指针移动到新提交
常用场景
bash
# 基本提交
git commit -m "feat: 添加用户登录功能"
# 跳过暂存区(仅对已跟踪文件)
git commit -am "fix: 修复登录bug"
# 修改最后一次提交(补交遗漏文件)
git add forgotten.class
git commit --amend --no-edit
# 修改提交信息
git commit --amend -m "新的提交信息"
原理图示
提交前:
main → ① → ② → ③ (HEAD)
执行 git add file.txt + git commit
提交后:
main → ① → ② → ③ → ④ (HEAD)
↑
新提交包含:
- 父提交: ③
- Tree: 新快照
git push - 同步到远程
技术原理

git push 做的事情:
- 计算本地与远程的提交差异
- 打包缺失的对象(使用 packfile 压缩)
- 传输到远程服务器
- 远程更新对应的分支指针
常用场景
bash
# 首次推送并设置上游
git push -u origin main
# 推送到指定远程分支
git push origin feature/login
# 删除远程分支
git push origin --delete old-branch
# 强制推送(谨慎使用!)
git push --force-with-lease # 更安全的强制推送
原理图示
推送前:
本地 main → ① → ② → ③
远程 main → ① → ②
推送后:
本地 main → ① → ② → ③
远程 main → ① → ② → ③ (同步完成)
git fetch - 获取远程变更
技术原理

git fetch 做的事情:
- 连接远程仓库
- 下载本地没有的新对象(commits、trees、blobs)
- 更新远程跟踪分支(如
origin/main) - 不修改本地分支和工作目录
常用场景
bash
# 获取所有远程分支更新
git fetch origin
# 获取并清理已删除的远程分支
git fetch --prune
# 获取特定分支
git fetch origin feature/login
# 查看远程更新(但不合并)
git fetch origin
git log HEAD..origin/main # 查看远程比本地多哪些提交
fetch vs pull 原理对比
git fetch: git pull:
远程 → 远程跟踪分支 远程 → 远程跟踪分支 → 合并到本地分支
(安全,不改变工作区) (fetch + merge,可能冲突)
git merge - 分支合并
技术原理
三种合并场景:
1. Fast-forward (快进合并)
- 条件: 当前分支(main)是目标分支(feature)的直接祖先。
- 合并前:main 停在提交 B,而 feature 已经走到了 C。两者的历史在一条直线上,没有分叉。
- 合并后:main 指针直接"快进"到 C 的位置,两者的历史完全重合。

2. Three-way Merge (三方合并)
- 条件: 自共同祖先分叉后,目标分支(feature)和当前分支(main)都有新的提交,且修改的文件/行没有发生冲突。
- 合并前:main 分支在分叉后有了新提交 D,feature 有了新提交 C。两者的共同祖先是 B。此时无法直接"快进"指针。
- 合并后:Git 会自动寻找三方(共同祖先 B、main 的最新提交 D、feature 的最新提交 C)进行合并,并自动创建一个新的合并提交(Merge Commit)M。最后,main 指针指向 M。

3. 冲突解决
当两个分支修改同一文件的同一区域时发生。
- 条件: 目标分支(feature)和当前分支(main)在分叉后,修改了同一个文件的同一部分代码。
- 合并中(发生冲突):当你执行 git merge 时,Git 尝试进行三方合并,但发现 D 和 C 冲突了。此时合并过程会中断,Git 不会自动生成新的提交,而是留下一堆冲突标记(如 <<<<<<< 和 >>>>>>>),等待人工介入。
- 解决冲突后:开发人员打开文件,手动决定保留哪一部分代码,然后执行 git add 和 git commit。这时才会人工生成合并提交 M,流程完成。

常用场景
bash
# 快进合并(默认允许)
git merge feature
# 禁止快进(保留分支历史)
git merge --no-ff feature
# 压缩合并(将多个提交合并为一个)
git merge --squash feature
# 取消合并
git merge --abort
原理图示:三方合并
合并前:
D---E feature
/
A---B---C main
合并过程:
1. 找到共同祖先:B
2. 计算两个分支相对于B的差异
3. 合并差异,创建新提交F
合并后:
D---E feature
/ \
A---B---C---F main (F包含C和E的变更)
git pull = git fetch + git merge
技术原理

git pull 的本质是 git fetch + 第二个操作(默认 merge)。
常用场景
bash
# 默认行为(merge)
git pull origin main
# 使用 rebase 而非 merge
git pull --rebase
# 明确使用 merge(覆盖配置)
git pull --no-rebase
# 仅获取,不合并
git pull --no-ff
配置默认行为
bash
# 全局设置默认使用 rebase
git config --global pull.rebase true
# 仅当前仓库
git config pull.rebase true
# 推荐:仅对当前分支设置
git config branch.main.rebase true
git rebase - 重新提交
技术原理

git rebase 做的事情:
- 找到当前分支与目标分支的共同祖先
- 计算当前分支从祖先之后的所有提交(生成 patch)
- 将当前分支指针移动到目标分支的最新提交
- 逐个应用之前的 patch(生成全新的 commit)
常用场景
bash
# 基本变基
git checkout feature
git rebase main
# 交互式变基(整理提交历史)
git rebase -i HEAD~3
# 继续变基(解决冲突后)
git add .
git rebase --continue
# 跳过有问题的提交
git rebase --skip
# 放弃变基
git rebase --abort
merge vs rebase 对比
| 场景 | 使用 Merge | 使用 Rebase |
|---|---|---|
| 公共分支 | 安全 | 危险(重写历史) |
| 个人分支 | 可接受 | 保持整洁 |
| 整合上游更新 | 产生合并提交 | 线性历史 |
| PR前整理 | - | 交互式rebase |
原理图示:Rebase
初始:
D---E feature
/
A---B---C main
执行 git rebase main (在feature分支):
步骤1:生成patches
patch1 = D相对于B的变更
patch2 = E相对于D的变更
步骤2:移动feature到C
D---E (旧,将被回收)
/
A---B---C main
\
feature (指向C)
步骤3:应用patches到C
C' = C + patch1 (新提交 D')
D' = C' + patch2 (新提交 E')
结果:
D'---E' feature
/
A---B---C main
实践
日常工作流
bash
# 1. 开始新功能前,同步主分支
git checkout main
git pull --rebase # 保持线性历史
# 2. 创建功能分支
git checkout -b feature/user-profile
# 3. 开发过程中多次提交
git add .
git commit -m "wip: 添加用户资料页面"
# 4. 定期同步上游(保持分支最新)
git fetch origin
git rebase main # 在个人分支上使用rebase
# 5. 整理提交历史(PR前)
git rebase -i HEAD~3 # 合并squash、修改信息
# 6. 推送并创建PR
git push -u origin feature/user-profile
常见问题解决
冲突解决流程
bash
# 场景:git pull 或 git merge 时发生冲突
# 1. 查看冲突文件
git status
# 2. 手动编辑冲突文件(删除 <<<<<<<, =======, >>>>>>>)
# 3. 标记为已解决
git add conflicted-file.class
# 4. 继续操作
git merge --continue # 或 git rebase --continue
撤回操作
bash
# 撤回最后一次提交(保留修改)
git reset --soft HEAD~1
# 撤回提交并放弃修改
git reset --hard HEAD~1
# 撤回已 push 的提交
git revert <commit-hash> # 创建反向提交,安全
黄金法则
| 场景 | 推荐命令 | 原因 |
|---|---|---|
| 公共分支(main/develop) | merge |
保持真实历史 |
| 个人功能分支 | rebase |
保持历史整洁 |
| 同步上游到个人分支 | git pull --rebase |
避免无意义合并 |
| 代码审查前 | rebase -i |
整理提交结构 |
| 紧急修复 | merge --no-ff |
标记修复来源 |
总结
命令关系图

核心记忆点
- Commit:创建不可变的版本快照
- Push:将本地提交同步到远程
- Fetch:安全下载远程更新(不合并)
- Pull:Fetch + Merge/Rebase
- Merge:创建合并提交,保留真实历史
- Rebase:重写提交链,创造线性历史
实践建议
公共分支用 Merge,私有分支用 Rebase。不要在公共分支上使用 rebase!一旦某个分支已经推送到远程(如 main 或 develop),并且别人也在基于它开发,绝对不要去 rebase 它。因为 rebase 会重写历史,这会导致其他协作者的本地仓库与远程彻底脱节,引发代码覆盖或混乱。