Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
7270 字
36 分钟
Git 进阶教程:分支、PR 工作流与实用技巧

Git 进阶教程 —— 分支、PR 工作流与实用技巧#

接续《Git使用教程.md》的进阶内容 基于 D:\desktop\double_car\ 项目的实战记录 远程仓库:git@github.com:forest-wang-sunshine/double_car.git 整理时间:2026-05-04


📑 目录#


阶段 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-detection
fix/<bug描述> 修 bug,如 fix/pid-saturation
hotfix/<紧急bug> 生产紧急修复,如 hotfix/voltage-crash
refactor/<重构内容> 重构,如 refactor/extract-pid-interface
docs/<文档主题> 改文档,如 docs/update-readme
test/<测试内容> 加测试,如 test/imu-filter

斜杠不是路径,只是命名习惯。GitHub 上会自动按斜杠分组显示,看起来很整齐。


5.4 实战:创建并切换分支#

我们用一个真实场景演示。流程是:

  1. 创建一个 docs/git-tutorial 分支
  2. 在分支上把某个文件提交
  3. 切回 main,看看 main 上没有这个改动
  4. 把分支合并到 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使用教程.md
git 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-tutorial
ls 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 main
git 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(大写),但除非明确知道自己在做什么。

推到远程#

forest-wang-sunshine/double_car.git
git push
# c313303..ec4f055 main -> main

5.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 会:

  1. 暂停合并,输出报错:

    CONFLICT (content): Merge conflict in PID.c
    Automatic merge failed; fix conflicts and then commit the result.
  2. 把两边的内容都塞进文件,加上特殊标记让你看:

    <<<<<<< HEAD
    float kp = 1.5;
    =======
    float kp = 2.0;
    >>>>>>> feature/tune
    • <<<<<<< HEAD======= 之间是**当前分支(main)**的版本
    • =======>>>>>>> feature/tune 之间是要合进来的分支的版本
  3. 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.c
git commit # 不带 -m,Git 自动打开编辑器,用预设的"Merge branch"消息

冲突中的两条逃生通道#

命令作用
git merge --abort取消合并,回到 merge 之前的干净状态。卡住时的后悔药
git status随时查看哪些文件还有冲突未解决

⚠️ 新手最怕的两个错

  1. <<<<<<< 这些标记 commit 进代码——别人看到会笑话你(而且代码会编译失败)
  2. 不知道自己在 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,但所有正经团队都不允许。原因:

  1. 质量保障:合并前必须有人 review,避免引入 bug
  2. CI 检查:GitHub Actions 等会自动跑测试,通过才能合并
  3. 审计追踪:每次合并都有 PR 页面,记录讨论、修改、审核全过程
  4. 保护 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.md
git 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-readme
branch '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 # 切回 main
git pull # 拉取合并后的最新 main
git 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 logs
touch logs/.gitkeep
git add logs/.gitkeep
git 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,文件改动也丢了” → 彻底清除

⚠️ 三大铁律:

  1. --hard 是核武器,操作前一定要 git status 确认你不在乎当前的改动
  2. 绝对不要 reset 已 push 到 GitHub 的 commit!会让别人 pull 时一团乱
  3. 只对纯本地、还没 push 的 commit 用 reset

HEAD~N 怎么读?

  • HEAD = 当前 commit
  • HEAD~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 的 commitgit 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~1
def5678 HEAD@{1}: commit: 你以为没了的提交
...

然后跳回去:

git reset --hard def5678 # 跳回到那个被丢弃的 commit

reflog 默认保留 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.md
git 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 # 看分支+最后一次 commit
git 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 # 抵消最后一次 commit
git 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 → 合并 → 清理”完整流程整理。

Git 进阶教程:分支、PR 工作流与实用技巧
https://mizuki.mysqil.com/posts/git使用/git进阶教程/
作者
风过无痕
发布于
2026-05-04
许可协议
CC BY 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00