Git 进阶教程 —— 分支、PR 工作流与实用技巧
接续《Git使用教程.md》的进阶内容 基于
D:\desktop\double_car\项目的实战记录 远程仓库:git@github.com:forest-wang-sunshine/double_car.git整理时间:2026-05-04
📑 目录
- 阶段 5:分支(branch)与合并(merge)
- 阶段 6:Pull Request 工作流
- 阶段 7:日常实用技巧
- 实战记录:double_car 项目 PR #1 全过程
- 进阶速查表
- 一页纸总结
阶段 5:分支(branch)与合并(merge)
这是 Git 真正强大的地方,也是从”个人备份工具”晋升到”团队协作工具”的分水岭。
5.1 为什么需要分支?
真实痛点场景
假设你正在 main 分支上开发”环岛识别优化”,改到一半还没完成。这时候:
- 突然发现正式比赛代码有个 bug 必须立刻修
- 老师要看一个演示版本,但你正在改的代码还跑不起来
- 你想试一个新算法,但不确定能不能行
如果只有 main 一个分支,你会陷入两难:
- ❌ 要么强行 commit 半成品(仓库乱七八糟)
- ❌ 要么放弃当前改动(前功尽弃)
分支的解决方案
分支 = 平行宇宙。可以同时维护多个版本的代码:
main (稳定主线) ●────────●────────●────────●────────●─────────● 首次提交 修bug 完善PID 版本A 版本B 版本C \ / \ / ●────●────●────●● feature/ring (开发中的功能分支) 环岛识别优化核心思想:
- main 永远保持”能跑”的状态(让别人随时能用)
- 新功能/试验/bug 修复都在独立分支上做
- 做好了再合并回 main,没做好可以丢弃,互不影响
类比:分支 = 不同的”草稿本”
┌─────────────────────────────────────────────────┐│ main 分支 "正式发表的论文" ││ feature/ring "草稿1:写第3章" ││ feature/exposure "草稿2:改第5章插图" ││ hotfix/voltage-bug "应急:修第1章一个错字" │└─────────────────────────────────────────────────┘每个草稿本独立改写,最后挑成熟的合并到正式论文。
5.2 分支的本质:一个轻量级的指针
这是大多数教程不讲清楚但至关重要的概念。
提交是这样的链表(每个 ● 是一次 commit,里面存着完整快照):
●────●────●────●────● c1 c2 c3 c4 c5
分支只是一个"指针",指向某个 commit:
●────●────●────●────● c1 c2 c3 c4 c5 ← main(指针) └── HEAD("我现在在这"的指针)
创建新分支 = 多一个指针,指向同一个 commit:
●────●────●────●────● ← main └── feature/ring(新建的分支) └── HEAD(切到 feature/ring 后,HEAD 跟它走)
在 feature/ring 上 commit,feature 指针前进,main 不动:
●────●────●────●────● ← main(停在这) \ ●────● ← feature/ring(前进了) └── HEAD所以创建分支几乎是零成本的——只是新建一个指针,不是复制整个项目。这就是为什么 Git 鼓励”用完即弃的小分支”,跟 SVN 等老工具完全不一样。
5.3 分支基础命令
查看分支
git branch # 看本地分支(当前分支前面有 *)git branch -a # 看本地+远程所有分支git branch -v # 看分支 + 每个分支最后一次 commit创建 / 切换 / 删除
| 命令 | 作用 |
|---|---|
git branch <分支名> | 创建分支,但不切过去 |
git checkout <分支名> | 切换到指定分支(老命令) |
git switch <分支名> | 切换到指定分支(新命令,推荐) |
git checkout -b <分支名> | 创建并切换(一步到位,老写法) |
git switch -c <分支名> | 创建并切换(一步到位,新写法,推荐) |
git branch -d <分支名> | 删除分支(仅在已合并时) |
git branch -D <分支名> | 强制删除(没合并也删,慎用) |
git branch -m 旧名 新名 | 重命名分支 |
⚠️ 老命令 git checkout 太”全能”了——既能切分支,又能恢复文件,容易误用。新版 Git(2.23+)拆成两个:
git switch专门切分支git restore专门恢复文件
推荐用新命令,更清晰、更安全。
分支命名约定(行业惯例)
feature/<功能名> 新功能开发,如 feature/ring-detectionfix/<bug描述> 修 bug,如 fix/pid-saturationhotfix/<紧急bug> 生产紧急修复,如 hotfix/voltage-crashrefactor/<重构内容> 重构,如 refactor/extract-pid-interfacedocs/<文档主题> 改文档,如 docs/update-readmetest/<测试内容> 加测试,如 test/imu-filter斜杠不是路径,只是命名习惯。GitHub 上会自动按斜杠分组显示,看起来很整齐。
5.4 实战:创建并切换分支
我们用一个真实场景演示。流程是:
- 创建一个
docs/git-tutorial分支 - 在分支上把某个文件提交
- 切回 main,看看 main 上没有这个改动
- 把分支合并到 main
第 1 步:创建并切换到新分支
git switch -c docs/git-tutorial# Switched to a new branch 'docs/git-tutorial'
git branch -v# * docs/git-tutorial c313303 docs:测试提交流程# main c313303 docs:测试提交流程关键观察:刚创建时两个分支指向同一个 commit (c313303),因为分支只是个指针。
第 2 步:在新分支上提交
git add Git使用教程.mdgit commit -m "docs: 添加 Git 使用教程笔记"# [docs/git-tutorial ec4f055] docs: 添加 Git 使用教程笔记# 1 file changed, 674 insertions(+)
git branch -v# * docs/git-tutorial ec4f055 docs: 添加 Git 使用教程笔记 ← 前进了一步# main c313303 docs:测试提交流程 ← 还在原位两个分支分道扬镳了:
c313303 ●────●────●────● ← main(停在这) \ ● ← docs/git-tutorial(前进了) ec4f055 │ └── HEAD(你现在在这)第 3 步:切回 main 验证”分支隔离”
git switch main
ls Git使用教程.md# ls: cannot access 'Git使用教程.md': No such file or directory文件真的”消失”了!这不是 Bug:
- 文件并没有被删除,它还在
docs/git-tutorial分支的快照里 - 但 main 分支的快照里没有这个文件
- Git 切换分支时,工作区被换成了 main 的快照
切回 docs/git-tutorial 验证文件还在:
git switch docs/git-tutorialls Git使用教程.md# Git使用教程.md ← 文件回来了5.5 合并分支(git merge)
现在分支上的改动已经 OK 了,要把它整合回 main。这就是 merge。
合并的两种方式
方式 A:Fast-forward(快进合并)
当 main 自分叉后没有新提交,Git 直接把 main 指针”快进”到分支的最新 commit:
合并前: ●────●────● ← main \ ● ← docs/git-tutorial
合并后(fast-forward): ●────●────●────● ← main, docs/git-tutorial(都指向同一个 commit)方式 B:Three-way merge(三方合并)
当 main 也有新提交,两个分支真的分叉了:
合并前: ●────●────●────● ← main(main 也前进了) \ ●────● ← docs/git-tutorial
合并后(产生一个"合并提交"M): ●────●────●────●─────M ← main \ / ●────●──/ ← docs/git-tutorial合并提交 M 有两个父亲,记录了”这里把两条历史合到一起”。
操作流程
合并的口诀:先切到要”接收”的分支(main),再 merge 别的分支进来。
git switch main # 1. 切到接收方git merge docs/git-tutorial # 2. 把目标分支合进来注意方向:git merge X 是把 X 合进当前分支。容易记反——合并不是”我去 X 那里”,而是”把 X 拉到我这里”。
实战合并
git switch maingit merge docs/git-tutorial# Updating c313303..ec4f055# Fast-forward# Git使用教程.md | 674 +++++++++++++++++# 1 file changed, 674 insertions(+)
git log --oneline --graph --all# * ec4f055 docs: 添加 Git 使用教程笔记# * c313303 docs:测试提交流程# * 95ac857 chore: initial commit ...Fast-forward 关键字说明是快进合并(最干净的情况)。
删除已合并的分支
git branch -d docs/git-tutorial# Deleted branch docs/git-tutorial (was ec4f055).-d(小写)是安全删除——只在分支已经合并到 main 时才允许删除。如果没合并就用 -d 会报错保护你(别误删未保存的工作)。强制删除用 -D(大写),但除非明确知道自己在做什么。
推到远程
git push# c313303..ec4f055 main -> main5.6 合并冲突(merge conflict)—— 必须懂的”灾难现场”
什么时候会冲突?
当两个分支改了同一个文件的同一行,Git 不知道该听谁的,就会停下来让你手动决定。
冲突产生的场景示例
假设 PID.c 第 42 行原本是:
float kp = 1.0;你做了两个分支:
- main 分支上改成:
float kp = 1.5; - feature/tune 分支上改成:
float kp = 2.0;
合并时 Git 懵了:到底 1.5 还是 2.0?
冲突时 Git 做了什么
执行 git merge feature/tune 时,看到冲突,Git 会:
-
暂停合并,输出报错:
CONFLICT (content): Merge conflict in PID.cAutomatic merge failed; fix conflicts and then commit the result. -
把两边的内容都塞进文件,加上特殊标记让你看:
<<<<<<< HEADfloat kp = 1.5;=======float kp = 2.0;>>>>>>> feature/tune<<<<<<< HEAD到=======之间是**当前分支(main)**的版本=======到>>>>>>> feature/tune之间是要合进来的分支的版本
-
git status会显示Unmerged paths: PID.c,提醒你处理。
冲突解决流程(4 步)
┌─────────────────────────────────────────────────────────────┐│ 1. 打开冲突文件,找到 <<<<<<< / ======= / >>>>>>> 标记 ││ ↓ ││ 2. 决定保留哪个版本(或二者结合),把标记和不要的内容删掉 ││ ↓ ││ 3. git add <冲突文件> 告诉 Git:我处理好了 ││ ↓ ││ 4. git commit 完成合并(不需要 -m,Git 会 ││ 自动用预设的合并消息) │└─────────────────────────────────────────────────────────────┘解决冲突的实操示例
继续上面的例子,你决定取折中值 1.7:
步骤 1+2:手动编辑 PID.c,把冲突区域改成:
float kp = 1.7;(删除所有的 <<<<<<<、=======、>>>>>>> 标记和不要的版本)
步骤 3+4:
git add PID.cgit commit # 不带 -m,Git 自动打开编辑器,用预设的"Merge branch"消息冲突中的两条逃生通道
| 命令 | 作用 |
|---|---|
git merge --abort | 取消合并,回到 merge 之前的干净状态。卡住时的后悔药 |
git status | 随时查看哪些文件还有冲突未解决 |
⚠️ 新手最怕的两个错:
- 把
<<<<<<<这些标记 commit 进代码——别人看到会笑话你(而且代码会编译失败) - 不知道自己在 merge 中,乱改其他文件后 commit——把不相干的改动卷进合并提交
⛑️ 万一彻底搞乱了,git merge --abort 就是核按钮——回到刚 merge 之前的样子。
推荐工具
VSCode、Cursor、AURIX Studio 这些 IDE 都内置了冲突解决面板:会高亮显示冲突区域,提供 “Accept Current”(用 main 的)/ “Accept Incoming”(用对方的)/ “Accept Both”(都要)三个按钮,比手撕标记好用太多。
阶段 6:Pull Request 工作流
学到这一步,你已经掌握了单人 Git 的全部核心技能。下面进入多人协作的核心:PR 工作流。
6.1 为什么不直接 push 到 main?
技术上你可以一直 git push origin main,但所有正经团队都不允许。原因:
- 质量保障:合并前必须有人 review,避免引入 bug
- CI 检查:GitHub Actions 等会自动跑测试,通过才能合并
- 审计追踪:每次合并都有 PR 页面,记录讨论、修改、审核全过程
- 保护 main:可以在 GitHub 设置”main 分支保护”,禁止直接 push,只能通过 PR 进入
简单说:main 是大家共同的稳定主线,所有改动必须先在自己的分支上做,再”申请”合并进来。
6.2 PR 的本质
PR 不是一种 Git 操作,而是 GitHub 提供的一种”申请 + 讨论 + 合并”的网页流程。
底层的 Git 操作还是 git merge,但这次发生在 GitHub 服务器上,由 GitHub 帮你执行。
6.3 标准 PR 工作流(背下来!)
本地操作 网页操作───────── ─────────
1. git switch main ─────────────────────2. git pull │ 同步最新代码 │ ─────────────────────
3. git switch -c feature/xxx ─────────────────────4. 改代码 │ │5. git add . && git commit -m "feat: xxx" │ 在分支上开发 │6. (重复 4-5 直到完成) │ │7. git push -u origin feature/xxx ─────────────────────
───────────────────── 8. 在 GitHub 网页发起 PR │ 9. 别人 review、提建议 │ 10. (根据建议改代码 │ → push → 自动更新PR)│ 11. 通过审核,点 Merge │ ─────────────────────
12. git switch main ─────────────────────13. git pull │ 同步合并后的 main │14. git branch -d feature/xxx │ 删除已合并分支 │ ─────────────────────记住关键:
- 每个 PR 一个分支,分支名要描述性强
- 小 PR 易 review,别一个 PR 改 300 个文件
- PR 描述要写清楚:做了什么、为什么、怎么测试
6.4 完整 PR 流程实战
步骤 1:先同步 main 分支(养成好习惯)
git switch main # 确保在 main 上git pull # 拉取远程最新代码为什么每次开新分支前都要 pull?
- 多人项目里,别人可能已经 push 了新提交
- 在过时的 main 上开分支,做完合并时大概率冲突
- 这是写代码前的”洗手”动作,习惯成自然
步骤 2:创建并切换到功能分支
git switch -c feat/add-readme# Switched to a new branch 'feat/add-readme'步骤 3:改代码 / 写文件
(在编辑器里完成实际工作)
步骤 4:add + commit
git add README.mdgit commit -m "docs: 添加项目根目录 README
简要介绍项目结构、硬件平台、构建方法、系统架构要点与提交规范,作为 GitHub 仓库主页的入口文档。"多行 commit 信息技巧:
- 第一行:简短标题(72 字符内)
- 空一行
- 后续:详细说明(正文)
这是 Git 提交信息的标准格式:标题简短能让 git log --oneline 看着清爽,正文详细能让别人理解动机。
步骤 5:把分支 push 到远程(关键!)
git push -u origin feat/add-readme⚠️ 新分支首次推送也要加 -u(和 main 首次推送一样)。这次推的是分支 feat/add-readme,要在远程也建一个同名分支并跟踪。
push 成功后,GitHub 会”贴心地”返回 PR 创建链接:
remote: Create a pull request for 'feat/add-readme' on GitHub by visiting:remote: https://github.com/<用户名>/<仓库>/pull/new/feat/add-readmebranch 'feat/add-readme' set up to track 'origin/feat/add-readme'. * [new branch] feat/add-readme -> feat/add-readme步骤 6:在 GitHub 网页发起 PR
A. 打开 PR 创建页
直接点 push 输出里的 URL,或者去 GitHub 仓库主页,会有一个黄色提示条:“feat/add-readme had recent pushes... [Compare & pull request]”。
B. 填写 PR 信息
页面顶部会显示:
base: main ← compare: feat/add-readme这是说”把 feat/add-readme 合到 main”。方向是对的,不用动。
| 字段 | 怎么填 |
|---|---|
| Title | 默认会带过来你的 commit 标题,可改 |
| Description | 解释这次 PR 做了什么、为什么、要不要测 |
Description 推荐模板:
## 改动内容- 列出主要改动点- 每条一行
## 动机说明为什么要做这个改动(解决什么问题、补充什么不足)
## 测试方法- [x] 已完成的验证项- [ ] 待验证项
## 影响范围说明改动影响哪些模块,会不会破坏现有功能C. 检查 “Files changed” 标签页
PR 页面上有几个 tab:
Conversation(讨论区)Commits(这次 PR 包含的提交)Files changed(改动的文件 — 必看!)
点 Files changed,逐行检查改动是否都对,避免有错字、多余空行、调试代码遗留。
D. 点 Create pull request 按钮提交
PR 创建后,会得到一个有编号的 PR 页面(比如 #1),这个页面就是这次合并的”档案”——讨论、commit、review、合并都记录在里面。
E. 合并 PR
正常团队是别人 review 完批准后再 merge。但你是仓库主人 + 单人项目,可以直接:
- 滑到页面下方
- 点
Merge pull request按钮 - 看到 3 个合并方式选项(详见下表),新手选
Create a merge commit(默认) - 再点
Confirm merge确认 - 出现
Pull request successfully merged and closed即成功 - ⚠️ 建议立刻点
Delete branch按钮 删除已合并的远程分支,保持仓库整洁
GitHub 的 3 种合并方式(重要!)
┌──────────────────────────────────────────────────────────────────┐│ Create a merge commit ← 默认,最完整 ││ 产生一个新的"合并提交"M。保留所有分支历史 ││ 历史看起来:●─●─●─M ││ \ / ││ ●● │├──────────────────────────────────────────────────────────────────┤│ Squash and merge ← 推荐用于"小 PR 多次小 commit" ││ 把分支上所有 commit 合并为 1 个,然后接到 main ││ 历史看起来:●─●─●─●(一条直线) ││ 好处:main 历史超干净 ││ 坏处:丢失分支上的细节提交记录 │├──────────────────────────────────────────────────────────────────┤│ Rebase and merge ← 进阶选项 ││ 把分支上的 commit 一个个"嫁接"到 main 末尾,无合并提交 ││ 历史看起来:●─●─●─●(一条直线,保留每个 commit) ││ 好处:直线历史 + 保留细节 ││ 坏处:会改写 commit 哈希,多人协作时要小心 │└──────────────────────────────────────────────────────────────────┘新手默认用 Create a merge commit,等熟悉了再选别的。
步骤 7:合并完成后,本地清理
PR 合并后,本地状态是”过时”的:
- GitHub 上 main 已经合入了改动
- 但本地 main 还不知道这件事
git switch main # 切回 maingit pull # 拉取合并后的最新 maingit branch -d feat/add-readme # 删除本地已合并的功能分支git log --oneline --graph -5 # 验证看到合并提交如果合并 PR 时没在网页上点 “Delete branch”,远程分支会留着。需要手动删:
git fetch --prune # 同步并清理过时引用git push origin --delete feat/add-readme # 删除远程分支git push origin --delete <分支名> 解读:
git push origin是推送--delete <分支名>是”推送一个删除操作”——告诉远程”删掉那个分支”- 也有简写
git push origin :<分支名>(前面冒号),但--delete更直观
阶段 7:日常实用技巧
7.1 git stash —— “我改到一半要切分支救火!“
痛点场景
正在 feat/ring-detection 分支上改环岛识别,已经改了 7 个文件,还没改完不能 commit。
突然老师让立刻去 main 分支上修一个 bug。
直接 git switch main 会报错:
error: Your local changes to the following files would be overwritten by checkout: ...Please commit your changes or stash them before you switch branches.Git 在保护你——切分支会让工作区变成另一个分支的快照,没保存的改动会被覆盖丢失。
stash 的解决方案
git stash = “临时栈”:把工作区和暂存区的改动打包压栈,工作区瞬间变干净,可以放心切分支。事情办完再 pop 出来恢复。
工作区有改动 工作区干净 ●────●────● ← main ●────●────● ← main │ │ PID.c (改了) ──────► │ .gitignore (改了) git stash │ └─→ stash@{0}(藏起来了)核心命令
git stash # 把改动压栈,工作区变干净git stash push -m "说明" # 压栈并加描述(推荐)git stash list # 查看栈里有哪些"暂存包"git stash pop # 弹出最上面那个,恢复改动(用一次少一个)git stash apply # 恢复但不删除栈里的(可重复使用)git stash drop stash@{0} # 删除栈里某个暂存包git stash clear # 清空整个栈实战演示
# 假设你在 README.md 末尾随手加了一行测试改动echo '<!-- 临时改动 -->' >> README.md
git status# Changes not staged for commit:# modified: README.md
git stash push -m "WIP: README 测试改动"# Saved working directory and index state On main: WIP: README 测试改动
git status# nothing to commit, working tree clean ← 工作区瞬间变干净!
git stash list# stash@{0}: On main: WIP: README 测试改动 ← 改动安全藏在栈里
# 现在可以放心切分支去做别的事 ...
git stash pop# Changes not staged for commit:# modified: README.md# Dropped refs/stash@{0} ← 改动恢复,栈里删除行话补充:
WIP= Work In Progress(进行中),程序员常用在临时 commit 或 stash 描述里,自我提醒”这是半成品”。
进阶用法
git stash -u # 也保存未追踪文件(默认只保存已追踪文件的改动)git stash show -p # 看最新 stash 里到底改了什么git stash apply stash@{2} # 恢复指定的 stash(不删除)git stash branch <分支名> # 用 stash 创建一个新分支并恢复改动到上面7.2 .gitkeep —— 让 Git 追踪空文件夹
痛点
Git 只追踪文件,不追踪文件夹。新建一个空目录 logs/,git add . 会无视它。但有时项目结构需要这个空文件夹(比如运行时会往里写日志)。
解决方案
在空文件夹里放一个名叫 .gitkeep 的空文件:
mkdir logstouch logs/.gitkeepgit add logs/.gitkeepgit commit -m "chore: 创建 logs 目录"⚠️ .gitkeep 不是 Git 的官方文件,只是社区约定俗成的命名(以点开头表示”配置类隐藏文件”)。叫 .placeholder 也行,叫 keep_me.txt 也行——本质就是”占位文件让 Git 不忽略这个文件夹”。
7.3 后悔药全集 —— restore / reset / revert / reflog
这是 Git 最容易用错的几个命令。讲清楚它们的边界,能避免 90% 的”误删代码”事故。
概念图:它们各自影响哪个区域
┌────────────┐ git add ┌────────────┐ git commit ┌────────────┐ git push ┌────────────┐│ 工作区 │ ─────────► │ 暂存区 │ ───────────► │ 本地仓库 │ ─────────► │ GitHub │└────────────┘ └────────────┘ └────────────┘ └────────────┘ ▲ ▲ ▲ ▲ │ │ │ │ git restore git restore git reset git revert (撤销工作区) --staged (移动 HEAD 指针) (产生反向 commit) (拿出暂存区)7.3.1 git restore —— 撤销工作区/暂存区的改动
| 命令 | 作用 | 影响范围 |
|---|---|---|
git restore <文件> | 用最后一次 commit 的内容覆盖工作区文件 | 工作区 |
git restore --staged <文件> | 把已 add 的文件从暂存区拿回来(变回 modified) | 暂存区 |
git restore --source=HEAD~2 <文件> | 用 2 个 commit 之前的版本恢复文件 | 工作区 |
适用场景:
- 改坏了某文件想丢弃 →
git restore 文件名 - 误 add 了某文件不想 commit →
git restore --staged 文件名 - ✅ 不影响 commit 历史,最安全
7.3.2 git reset —— 移动 HEAD 指针(小心!)
reset 的本质:把分支指针往后移。看起来像”撤销 commit”,但实际是”装作那些 commit 没发生过”。
它有 3 种模式,影响范围递增:
工作区 暂存区 本地仓库 ───── ───── ───────── git reset --soft 不变 不变 回退(commit 没了) git reset --mixed 不变 回退 回退(默认,等于 git reset) git reset --hard 回退 回退 回退(彻底!文件都恢复)| 命令 | 通俗解释 |
|---|---|
git reset --soft HEAD~1 | ”撤销最后 1 次 commit,改动还在暂存区” → 重新写 commit message |
git reset --mixed HEAD~1 | ”撤销最后 1 次 commit,改动回到工作区”(默认) → 重新决定怎么 add |
git reset --hard HEAD~1 | ”撤销最后 1 次 commit,文件改动也丢了” → 彻底清除 |
⚠️ 三大铁律:
--hard是核武器,操作前一定要git status确认你不在乎当前的改动- 绝对不要 reset 已 push 到 GitHub 的 commit!会让别人 pull 时一团乱
- 只对纯本地、还没 push 的 commit 用 reset
HEAD~N 怎么读?
HEAD= 当前 commitHEAD~1= 倒数第 2 个HEAD~3= 倒数第 4 个
7.3.3 git revert —— 安全的”反向 commit”
revert 的本质:不删除历史,而是新加一个 commit,内容是”把目标 commit 的改动反过来做一遍”。
原历史: ●────●────●────● (要撤掉中间这个 ❌) c1 c2 c3 c4 ❌
revert c2 后: ●────●────●────●────●' (新增 c2',内容是 c2 的"反操作") c1 c2 c3 c4 c2'
c2 没消失,c2 的影响被 c2' 抵消了| 命令 | 作用 |
|---|---|
git revert <提交哈希> | 产生一个反向 commit 抵消目标 commit |
git revert HEAD | 抵消最近一次 commit |
git revert --no-commit HEAD~3..HEAD | 抵消最近 3 个 commit,但不自动 commit(让你手动整理) |
适用场景:
- ✅ 已 push 到 GitHub 的 commit 想撤销,必须用 revert
- ✅ 团队协作中标准做法
- ✅ 不会改写历史,所有人 pull 后看到的是”撤销 commit”,干净又安全
三兄弟对比一览
| 场景 | 用哪个? | 例子 |
|---|---|---|
| 改坏了某文件想恢复 | git restore 文件 | git restore PID.c |
| 误 add 了某文件 | git restore --staged 文件 | git restore --staged debug.log |
| 想改最后一次 commit 信息 | git commit --amend 或 --soft reset | (前者更直接) |
| 想撤销最后 1 次本地 commit(保留改动) | git reset HEAD~1(默认 mixed) | |
| 想彻底丢弃最近的本地改动 | git reset --hard HEAD | ⚠️ 用前再三确认 |
| 想撤销已 push 的 commit | git revert(不要 reset!) | git revert abc1234 |
7.3.4 git reflog —— 终极后悔药(90 天保险)
如果你不小心 git reset --hard 把工作丢了,别慌:
git reflog # 看 HEAD 的移动历史(包括被你"撤销"掉的提交)每个动作 Git 都记着,你能看到:
abc1234 HEAD@{0}: reset: moving to HEAD~1def5678 HEAD@{1}: commit: 你以为没了的提交...然后跳回去:
git reset --hard def5678 # 跳回到那个被丢弃的 commitreflog 默认保留 90 天,几乎是 Git 唯一不能用 reset 抹掉的东西。这是 Git 给你的最后一道安全网。
实战记录:double_car 项目 PR #1 全过程
下面是 D:\desktop\double_car\ 项目从”创建分支 → 合并 PR”的真实命令序列。
创建功能分支并提交
git switch main && git pull# Already on 'main'# Already up to date.
git switch -c feat/add-readme# Switched to a new branch 'feat/add-readme'
# 创建 README.md ...
git add README.mdgit commit -m "docs: 添加项目根目录 README
简要介绍项目结构、硬件平台、构建方法、系统架构要点与提交规范,作为 GitHub 仓库主页的入口文档。"# [feat/add-readme 6dcbc31] docs: 添加项目根目录 README# 1 file changed, 71 insertions(+)
git push -u origin feat/add-readme# remote: Create a pull request for 'feat/add-readme' on GitHub by visiting:# remote: https://github.com/forest-wang-sunshine/double_car/pull/new/feat/add-readme# branch 'feat/add-readme' set up to track 'origin/feat/add-readme'.# * [new branch] feat/add-readme -> feat/add-readme网页操作发起并合并 PR #1
(在浏览器里完成,不在命令行)
合并方式:Create a merge commit,产生 merge commit 36ff410。
合并后历史:
* 36ff410 Merge pull request #1 from forest-wang-sunshine/feat/add-readme|\| * 6dcbc31 docs: 添加项目根目录 README|/* ec4f055 docs: 添加 Git 使用教程笔记* c313303 docs:测试提交流程* 95ac857 chore: initial commit ...|\ 和 |/ 描绘了”分叉”和”汇合”——这就是分支结构的可视化。
本地清理
git switch main && git pull# Updating ec4f055..36ff410# Fast-forward# README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
git branch -d feat/add-readme# Deleted branch feat/add-readme (was 6dcbc31).
git fetch --prune# (远程 GitHub 上分支没删,本地也保留远程引用)
git push origin --delete feat/add-readme# To github.com:forest-wang-sunshine/double_car.git# - [deleted] feat/add-readme
git branch -a# * main# remotes/origin/main完美收尾——本地、远程都只剩 main,仓库干净。
进阶速查表
分支操作
git branch # 看本地分支git branch -a # 看本地+远程git branch -v # 看分支+最后一次 commitgit switch <分支名> # 切换分支git switch -c <分支名> # 创建并切换git switch - # 切回上一个分支(类似 cd -)git branch -d <分支名> # 安全删除(已合并)git branch -D <分支名> # 强制删除git branch -m 旧名 新名 # 重命名分支合并
git merge <分支名> # 把指定分支合到当前分支git merge --abort # 取消正在进行的合并git merge --no-ff <分支名> # 强制产生合并提交(即使能 fast-forward)远程分支
git push -u origin <分支名> # 首次推送新分支git push origin --delete <分支名> # 删除远程分支git fetch --prune # 拉取并清理过时引用git remote prune origin # 只清理过时引用,不拉取stash
git stash # 压栈git stash push -m "说明" # 压栈带描述git stash -u # 包括未追踪文件git stash list # 看栈git stash pop # 弹出最新(删除栈条目)git stash apply # 恢复但不删除git stash drop stash@{0} # 删指定栈条目git stash clear # 清空栈git stash branch <分支名> # 从 stash 创建分支后悔药
git restore <文件> # 撤销工作区改动git restore --staged <文件> # 拿出暂存区git restore --source=HEAD~2 <文件> # 用历史版本恢复git reset HEAD~1 # 撤销最后 1 次 commit(mixed)git reset --soft HEAD~1 # 撤销 commit,改动留暂存区git reset --hard HEAD~1 # 彻底撤销,文件也恢复(危险!)git revert <哈希> # 反向 commit(已 push 用这个)git revert HEAD # 抵消最后一次 commitgit reflog # 看所有 HEAD 移动历史(终极救命)git commit --amend -m "新说明" # 改最后一次 commit 信息(仅 push 前)历史查看
git log --oneline # 简洁git log --oneline --graph --all # 带分支图,所有分支git log --oneline -5 # 最近 5 条git log --grep "PID" # 按提交信息搜git log --author "forest" # 按作者搜git log --since "2 weeks ago" # 按时间筛git log -- <文件> # 看某文件的修改历史git show <哈希> # 看某次提交的详细改动一页纸总结
┌──────────────────────────────────────────────────────────────────┐│ Pull Request 标准工作流 │├──────────────────────────────────────────────────────────────────┤│ ││ 1. git switch main && git pull 同步主线 ││ 2. git switch -c feat/xxx 开新分支 ││ 3. 改代码 → git add → git commit 开发 ││ 4. git push -u origin feat/xxx 推到远程 ││ 5. GitHub 网页发 PR、merge 协作合并 ││ 6. git switch main && git pull 同步合并结果 ││ 7. git branch -d feat/xxx 清理本地分支 ││ │├──────────────────────────────────────────────────────────────────┤│ ││ "改一半要切分支" → git stash ││ "改坏了想丢弃" → git restore <文件> ││ "本地误 commit" → git reset HEAD~1 ││ "已 push 想撤销" → git revert <哈希> ││ "彻底丢了找回来" → git reflog ││ │└──────────────────────────────────────────────────────────────────┘
合并冲突标记: <<<<<<< HEAD ← 当前分支的版本 ... ======= ← 分隔线 ... >>>>>>> feature/xxx ← 要合并进来的分支版本
冲突处理 4 步: 1. 编辑文件,删除标记,保留正确内容 2. git add <冲突文件> 3. git commit 4. (可选)git merge --abort 取消合并回到原状本教程基于 2026-05-04 在 double_car 项目实操”分支 → PR → 合并 → 清理”完整流程整理。
部分信息可能已经过时














