今日目标: 精通变基 (Rebase),特别是交互式变基,学会像艺术家一样雕琢提交历史。
变基是 Git 中与 merge 并列的、最重要的分支集成策略之一。但它遵循着完全不同的哲学。
-
我遇到的问题: “
merge也能合并代码,为什么还需要rebase?它们到底有什么不同?” -
你的解答 (绳结 vs. 嫁接):
merge(合并) 是“系绳结”: 它会保留两条分支所有的原始提交,然后用一个全新的“合并提交”作为绳结,将两条历史线绑在一起。- 优点: 忠实地记录了所有发生过的事情,历史是完全真实的。
- 缺点: 当分支众多时,
git log --graph会看起来像一碗意大利面,充满了各种分叉和绳结,难以追溯。
rebase(变基) 是“嫁接”: 它不会创造一个绳结。它会先把你当前分支上的所有提交“拔下来”,然后把你的分支起点移动到目标分支的最新末端,最后再把你那些提交一个个重新种上去。- 优点: 创造了一个完全线性的历史记录。整条历史是一条干净的直线,非常易于阅读和理解。
- 缺点: 它重写了历史。你种上去的那些提交,虽然内容一样,但已经是拥有全新哈希值的“克隆体”了。
变基非常强大,但也因此很危险。所有使用者都必须遵守这条用血泪换来的黄金法则:
永远不要在一个已经被推送到远程、并且可能被团队共享的分支上执行
rebase!
- 为什么?
因为
rebase会重写历史。如果你变基了一个共享分支,你的本地历史就和远程历史“分道扬镳”了。你的队友拉取了旧的历史,而你强行推送了新的历史,这会导致灾难性的后果:大量的重复提交、混乱的合并、以及队友对你无尽的抱怨。结论:
rebase只应该用在你自己的、还未推送到远程共享的本地功能分支上,用来在合并前“打扫房间”。
这是 rebase 最强大的模式,也是你未来会最常用的功能。它让你能精细地控制一连串的提交。
想象一下,你开发一个功能,可能会有5、6次提交,比如:"feat: add button", "style: change color", "fix: oops, fix typo", "refactor: clean code"。直接把这样凌乱的历史给同事审查是不专业的。交互式变基能让你把这6次提交“浓缩”成一次完美的提交。
目标: 我们来亲手制造一段凌乱的历史,然后用 rebase -i 将它清理干净。
第一步:营造“犯罪现场”
- 确保你在
learn-git项目的main分支上,并且它是干净的。git switch main git pull # 确保和远程同步 - 创建一个新的功能分支,开始你的“凌乱”开发。
git switch -c feature/newsletter-signup
- 现在,我们模拟几次开发过程中的提交。
# 第一次提交:添加基础表单 echo "Newsletter Signup Form" > newsletter.html git add . git commit -m "feat: Add initial newsletter form" # 第二次提交:添加一个样式 echo "Form Style" > style.css git add . git commit -m "style: Add basic form styling" # 第三次提交:修复一个拼写错误 echo "Newsletter Sign-up Form" > newsletter.html git add . git commit -m "fix: Fix typo in newsletter form title" # 第四次提交:添加提交按钮 echo "Submit Button" >> newsletter.html git add . git commit -m "feat: Add submit button"
- 查看你凌乱的历史。
你会看到类似这样的4次提交。
git log --oneline
第二步:启动“时间机器”
我们想把这4次提交清理成一个。所以我们需要对从 HEAD 往前的4次提交进行操作。
git rebase -i HEAD~4第三步:编辑你的“任务清单”
Git 会在 Neovim 中打开一个任务清单。你的目标是保留第一次提交,然后把后续的所有提交都合并进去。
把文件从这样:
pick 1a1b1c1 feat: Add initial newsletter form
pick 2d2e2f2 style: Add basic form styling
pick 3g3h3i3 fix: Fix typo in newsletter form title
pick 4j4k4l4 feat: Add submit button
修改成这样:
pick 1a1b1c1 feat: Add initial newsletter form
squash 2d2e2f2 style: Add basic form styling
squash 3g3h3i3 fix: Fix typo in newsletter form title
squash 4j4k4l4 feat: Add submit button
pick(p): 保留这个提交,它的提交信息将作为基础。squash(s): 将这个提交“压扁”,合并到它前一个提交中。
第四步:撰写最终的“历史决议”
保存并关闭 (:wq) 任务清单后,Git 会开始工作。因为它遇到了 squash,所以它会再次打开 Neovim,让你为这次“四合一”的超级提交撰写一个全新的、最终的提交信息。
打开的文件会包含之前所有的提交信息,像这样:
# This is a combination of 4 commits.
# The first commit's message is:
feat: Add initial newsletter form
# This is the 2nd commit message:
style: Add basic form styling
# This is the 3rd commit message:
fix: Fix typo in newsletter form title
# This is the 4th commit message:
feat: Add submit button
你的任务是:删除所有这些内容,只留下一行最能概括这次所有工作的、清晰的提交信息。
例如,把它修改成:
feat: Add complete newsletter signup feature
现在,再次保存并退出 (:wq)。
第五步:验收成果
变基完成了!现在,再次运行 git log --oneline。
你会惊喜地发现,之前那4条凌乱的提交历史消失了,取而代之的是一条干净、清晰、专业的提交记录。
至此,你的 feature/newsletter-signup 分支已经打扫干净,可以随时发起一个高质量的“拉取请求”了。
在你掌握了 rebase 这门“手艺”之后,Pull Request (PR) 就是你展示手艺、并与团队高效协作的“舞台”。它不是 Git 的一个命令,而是 GitHub/GitLab 等平台提供的核心功能。
如果说 git merge 解决了技术上的合并问题,那么 PR 就是解决了团队协作和质量保障的问题。
-
我遇到的问题: “既然我可以直接合并分支,为什么还需要费劲地创建一个 PR?”
-
你的解答 (代码的“质量安检门”):
把 PR 想象成机场的安检流程。你的代码就是你的“行李”,
main分支就是待飞的“航班”。在你的行李被装上飞机之前,必须经过层层检查,确保它安全、合规。-
代码审查 (Code Review) - “人工安检”: 你的同事会像安检员一样,逐行检查你的代码,发现潜在的 bug、不好的设计或拼写错误。这是保障代码质量最重要的一环。
-
自动化检查 (CI/CD) - “X光机”: 当你提交 PR 时,会自动触发一系列“机器人”检查:代码风格是否统一?单元测试是否全部通过?项目是否能成功构建?这台“X光机”能过滤掉大部分低级错误。
-
保护核心分支 - “登机口管制”: 在专业项目中,你无法直接把代码推送到
main分支。main分支就像一个有专人看守的登机口,只允许通过了所有安检流程的 PR 进入。 -
讨论与知识共享 - “安检记录”: PR 下的所有评论和讨论都会被记录下来。这不仅解决了当前的问题,更成为了后来者学习的宝贵资料,本身就是一份活的文档。
-
目标: 将我们之前用 rebase -i 清理干净的 feature/newsletter-signup 分支,通过一个完整的 PR 流程,最终安全地合并到 main 分支。
第 0 步:【本地】打扫房间
- 在你的
feature/newsletter-signup分支上,使用git rebase -i将凌乱的提交历史清理成一次或几次逻辑清晰的提交。(我们在上一节已经完成了这一步!)
第 1 步:【本地】推送你的功能分支
- 将你打扫干净的本地分支推送到远程仓库 (
origin)。git push origin feature/newsletter-signup
第 2 步:【GitHub】创建 PR
- 在浏览器中打开你的 GitHub 仓库页面,通常会有一个黄色的提示条,引导你为刚推送的分支创建 PR。点击 “Compare & pull request” 按钮。
- 进入 PR 创建页面,填写关键信息:
- 标题 (Title): 写一个清晰、概括性的标题,例如
feat: Add newsletter signup feature。 - 描述 (Description): 这是最重要的部分! 详细描述你“做了什么”、“为什么这么做”、“如何测试”。一个好的描述能让审查者事半功倍。
- 标题 (Title): 写一个清晰、概括性的标题,例如
- 点击 “Create pull request” 按钮,你的 PR 就正式诞生了。
第 3 步:【GitHub & 本地】审查与迭代的循环
- 审查: 你的同事会收到通知,在 GitHub 页面上逐行审查你的代码,并留下评论和修改建议。
- 修改: 你收到了反馈。你不需要关闭或新建 PR!
- 更新:
- 在本地的
feature/newsletter-signup分支上,根据反馈做出修改。 - 完成修改后,正常地
add和commit。# 比如,根据反馈修改了表单的提示文字 echo "Subscribe to our awesome newsletter" > newsletter.html git add newsletter.html git commit -m "refactor: Update form prompt based on feedback"
- 然后,再次执行
git push。git push
- 你刚刚的
push会被自动追加到那个已经存在的 PR 中。这个“修改-推送-再审查”的循环会一直持续,直到所有人都满意。
- 在本地的
第 4 步:【GitHub】批准与合并
- 当你的代码得到足够多的“Approve”(批准),并且所有自动化检查都显示为绿色对勾时,“Merge pull request”按钮就会被点亮。
- 项目维护者点击此按钮,选择一个合并策略(例如
Squash and merge来保持主干整洁),然后确认合并。
第 5 步:【GitHub & 本地】清理战场
- PR 合并后,
feature/newsletter-signup分支已经完成了它的历史使命。
- 在 GitHub 上: 点击 PR 页面上出现的 “Delete branch” 按钮,删除远程的功能分支。
- 在本地终端: 清理你本地的功能分支。
# 1. 切换回主分支 git switch main # 2. 从远程拉取最新的 main 分支代码(包含了你刚刚合并的 PR) git pull # 3. 安全地删除你本地的、已经完成使命的分支 git branch -d feature/newsletter-signup
至此,一次专业、完整的 PR 流程就全部走完了。你不仅贡献了代码,还通过了团队的质量保证体系,并且清理了所有痕迹,让仓库保持整洁如新。