理解Git核心:Commit的本质与增量思维

🔍 Commit的本质:增量而非快照

多数教程将Git的commit描述为文件快照,但更本质的理解是:每次commit都是基于上一次提交的增量更新。这种视角让Git提交树的理解更加清晰:

分支的本质就是基于某个commit的不同增量更新路径

📖 文档演进的增量示例

想象一个空白Word文档的版本演进:

  1. 小A的线性提交
  • 写了第一段 → commit 1
  • 添加第二段 → commit 2
  • 完成第三段 → commit 3
  1. 分支的增量分离
    小美和小帅基于commit 3创建分支,小美加了封面,小帅加了目录:
  • 小美分支 = v3 + 封面Δ
  • 小帅分支 = v3 + 目录Δ

🔀 合并的本质:增量融合

合并操作 = 将两个增量(封面Δ + 目录Δ)叠加到基础版本(v3)

⚠ 冲突的本质:增量重叠

当两人同时修改同一区域时:

小美的增量:认为第二段没有存在的必要,删除第一面的第2段
小帅的增量:重写了第2段,优化了文字逻辑

Git无法自动融合冲突的增量Δ,需要手动决策

1
2
3
4
5
<<<<<<< HEAD
这里的变化会是小美删除了第一面的第2段
=======
这里的变化会是小帅重写了第2段,优化了文字逻辑
>>>>>>> feature-小帅

🛠 强烈建议自己动手构建提交树,尝试一下这个过程

关键学习路径

  1. 在空仓库中构建3-4次提交
  2. 创建分支并制造差异修改
  3. 故意设计重叠修改引发冲突
  4. 自己尝试解决冲突
    没有实操的Git学习如同纸上谈兵!

理解了 commit 之后,实际上对于所有的命令,都能够在任何其他教程中快速获得正确认知。接下来我介绍一些非常常用的命令,仅提供最核心的用法和注意事项。

🚀 从 git clone 说起

我们都知道 git clone 是克隆了一个远程仓库到本地。实际上它最核心的意义除了从远程获取一份代码之外,最重要的是我们建立了自己的代码与远程代码仓库的关联。

这意味着我们在远程维护了一个公共的版本,每个人自由的在这个版本上进行增量更新。

我们最简单而常见的方式是:

  • 远程维护着一个主分支 main(稳定的版本)
  • 每个人在本地分支上进行开发
  • 推送你的本地分支到远程仓库
  • 通过发起一个合并(Pull Request)将你的修改合并到主分支

    本质上就是申请把你基于主分支的增量更新合并到主分支上

💡 实际开发示例:添加按钮功能

以增加一个按钮为例,实际上进行开发时,你只需要如下考虑:

开发流程

  1. 确定基础分支

我该基于哪个分支进行增量更新?

一般都是稳定的 main 分支,那么我们就把 main 分支拉取到本地(pull),然后你基于本地的 main 分支,创建一个新的按钮开发分支(不妨叫 feature-addButton)。

1
2
git pull origin main
git checkout -b feature-addButton

然后我们在本地的这个分支上完成开发,提交(commit)后得到的提交记录里就是这个按钮的修改增量。

  1. 合并到稳定版本

我该如何合并到最新的稳定版本?

我们只需要把你完成开发的那个按钮分支推送(push)到远程仓库,然后在远程仓库上从你的远程特性分支发起一个合并请求(Pull Request)。

这个请求在通过后会把你基于 main 分支的增量更新合并到 main 分支上。

  1. 处理冲突

如果有冲突怎么办?

例如你和其他人同时修改了同一区域,且其他人比你先完成了一次远程的 PR 合并,此时 Git 会提示你的 PR 出现了冲突。

你需要手动解决冲突,选择保留哪个增量或者融合两个增量。

  1. 冲突解决方案

冲突到底如何解决呢?

这里也是最吊诡的地方,大多数教程总是相当含糊,或者说堆砌术语,没有讲清楚解决冲突的本质。

⚠️ 解决冲突的本质

冲突的本质:增量重叠

冲突的本质是增量重叠,那如果你能保证你的增量是基于最新的远程稳定分支(也即基于与你产生冲突者的增量更新叠加后的版本),那么再次合并时就不会出现冲突。

解决冲突的关键

保持你的分支基于最新的远程稳定分支。

🔧 两种冲突解决场景

📋 场景一:推送前预先解决冲突

最佳实践:在推送分支到远程前,主动更新到最新版本

操作步骤:

  1. 更新本地 main 分支到最新版本
  2. 将特性分支 rebase 到最新的 main 分支
1
2
3
4
git checkout main
git pull origin main
git checkout feature-yours
git rebase main

🚨 场景二:PR发现冲突后解决

如果你已经推送了分支并发起了PR,但发现有冲突,需要更新远程分支

完整操作流程:

1
2
3
4
5
6
7
git checkout main
git pull origin main # 更新本地main到最新
git checkout feature-yours
git rebase main # 将你的分支rebase到最新main
# 如果有冲突,手动解决后继续
git rebase --continue
git push --force-with-lease origin feature-yours # 安全地强制推送更新后的分支

通过这种方式,你的PR依然存在,但此时就能够无冲突地合并到main分支了。

📊 Rebase 过程可视化

🔍 Rebase 的本质

将你的增量更新重新应用到最新的base上

  • 原来:你的分支 = C2 + F1Δ + F2Δ
  • rebase后:你的分支 = C4 + F1'Δ + F2'Δ
  • 其中 F1'F2' 是基于新base(C4)重新计算的增量

实用 Git 命令补充

git stash - 暂存工作区

这个命令非常常用!当你开发到一半需要切换分支修 bug,但又不想提交未完成的代码时:

1
2
3
git stash          # 暂存当前修改
git stash pop # 恢复并删除暂存
git stash apply # 恢复但保留暂存

对于经常需要切换的配置文件,可以创建带标识的暂存:

1
2
3
git stash save "配置文件修改"
git stash list
git stash apply stash@{0}

git cherry-pick - 复制提交

注意:复制的是修改增量,可能会产生冲突

用于将某个分支的提交复制到当前分支:

1
2
git checkout dev
git cherry-pick <commit-id>

使用场景示例:

  • 新功能在 feature 分支开发完成
  • 需要在 dev 分支测试
  • 使用 cherry-pick 将功能提交复制过去

git push --force-with-lease - 安全强推

相比 --force 更安全的强制推送方式:

为什么更安全?
如果其他人在你的提交之后进行了修改,此操作会失败,避免覆盖他人的工作

1
git push --force-with-lease

适用场景:处理 GitHub PR comments 或 merge conflict


git reset - 重置提交

1
git reset --soft HEAD~n

将最新的 n 个提交退回到工作区,常用于修改上次提交

1
git reset --hard HEAD~n

危险操作:会直接丢弃这些修改,请谨慎使用!


git rebase -i - 交互式变基

进阶功能:建议有一定经验后使用

功能包括:

  • 🔄 修改 commit 提交顺序
  • 🔗 合并多个提交
  • ✏️ 修改历史提交信息
  • 🗑️ 删除某次历史提交

    使用注意事项:
    由于会改变 Git 历史,在协作开发中需要谨慎使用:

    • ✅ 自己的特性分支且独立开发:可以使用
    • ❌ 多人协作的共享分支:避免使用
    • ⚠️ 使用后需要强制推送到远程仓库
1
git rebase -i HEAD~n

🎯 总结:Git多人开发的核心思维

💡 关键要点

  1. 增量思维:每个commit都是基于前一个commit的增量更新
  2. 分支本质:不同的增量更新路径
  3. 冲突根源:增量重叠导致的冲突
  4. 解决方案:保持分支基于最新的稳定版本

⚠️ 重要提醒

  • 没有实操的Git学习如同纸上谈兵
  • 理解增量思维比记忆命令更重要
  • 冲突解决的关键是理解其本质,而非死记硬背操作步骤