← 全部材料 Git
GIT · 10 个高频考点

Git 里很少真的丢东西

Git 题考的是心智模型清不清楚:三个区域、一个指针、以及"哪些操作会改写历史"。理解这一层,reset、rebase、cherry-pick 的取舍都不用背。

01先看动不动历史改历史的操作别用在共享分支上,这条能避开八成事故。
02记住 reflogreset --hard 和删分支都能救回来,前提是操作后别再乱跑命令。
03选模型看发布节奏有版本号的客户端用 Git Flow,持续部署的 Web 用短分支。
1

三个区域与四种状态★

所有 git 命令都在挪一个指针
Git 的心智模型很窄:工作区、暂存区、本地仓库三个区域,以及 HEAD 指针。所有命令都是在"哪些文件、进哪个区、指针动不动"。想清楚这一层,reset / checkout / restore 的区别就不用背了。
工作区你编辑的文件,git status 显示 modified
→
暂存区 indexgit add 之后,下次 commit 会包含的内容
→
本地仓库git commit 之后,HEAD 指向的新提交
→
远程仓库git push 之后,别人能看到的
命令拆解:谁动、动哪个区git add <file> 工作区 → 暂存区 git restore <file> 暂存区 → 覆盖工作区(丢弃修改) git restore --staged 暂存区 → 撤回,变回未暂存(工作区不变) git commit 暂存区 → 本地仓库,HEAD 前进一格 git reset --soft H HEAD 回退,暂存区和工作区不变 git reset --mixed H HEAD 回退,暂存区也回退,工作区不变(默认) git reset --hard H HEAD 回退,暂存区和工作区都回退(丢数据) git revert H 新建一个"反向提交",历史保留 记忆:--soft 只动 HEAD;--mixed 再动暂存区;--hard 三样全动
面试怎么答Git 只有三个区域加一个指针:工作区、暂存区、本地仓库,以及指向当前分支的 HEAD。所有命令都是在挪文件或者挪指针。reset 的三个模式就是回退范围不同,soft 只动 HEAD,mixed 再动暂存区,hard 三样全动所以会丢数据。restore 是"用暂存区覆盖工作区",加 --staged 则是把暂存区退回未暂存。
2

merge 与 rebase★

本质是"历史要不要保留分叉"
唯一区别:merge 保留分叉,生成一个合并提交;rebase 把提交"复制"到新基底上,历史变成一条直线。所以判断标准是这个分支有没有别人在用——共享分支用 merge,自己的本地分支用 rebase。
◐ 可交互:同一个分支,merge 和 rebase 各留下什么历史
main
feature
merge 保留分叉:feature 上的 D、E 还是原来的提交,多出一个合并提交 M。
同一个场景,两种结果初始:A - B - C (main) \ D - E (feature) 【git merge main】在 feature 上执行 A - B - C ------- M (feature) \ / D - E --- M 是合并提交,两个父节点 历史保留了"这两条线什么时候汇合" 【git rebase main】在 feature 上执行 A - B - C - D' - E' (feature) D 和 E 被重新应用,换了新的 hash 历史变成直线,好像一直在 main 上开发 注意:D' 和 E' 不是原来的提交, hash 变了,所以共享分支上 rebase 会让别人的历史错乱
面试怎么答merge 和 rebase 的区别是历史要不要保留分叉。merge 生成一个有两个父节点的合并提交,完整保留了分支什么时候创建、什么时候汇合;rebase 把提交复制到新基底上,hash 会变,历史变成一条直线。判断标准是这个分支有没有别人在用,共享分支必须用 merge,因为改写历史会让别人 pull 时产生重复提交和冲突;自己的本地分支可以 rebase 保持整洁。
3

reset / revert / restore

名字像,用途完全不重叠
三个词对应三种意图:reset 是"把分支指针挪回去"(改历史)、revert 是"新建一个反向提交"(不改历史,适合已推送)、restore 是"用某处内容覆盖文件"(不动提交)。
按意图选命令我想撤掉还没提交的修改 git restore file 丢弃工作区修改 git restore --staged file 从暂存区撤出,修改还在 我想撤掉最近一次提交,但保留改动 git reset --soft HEAD~1 我想撤掉最近一次提交,连改动也不要 git reset --hard HEAD~1 ⚠️ 工作区数据丢失 我想撤掉已经推送的提交 git revert <hash> 生成反向提交,安全 ❌ 不要用 reset + push -f 会改写别人看到的历史 我想把某个文件恢复到某个提交的样子 git restore --source=<hash> file git checkout <hash> -- file 老写法,等价
面试怎么答三个命令对应三种意图。reset 是挪分支指针,会改写历史,只适合本地未推送的提交;revert 是新建一个反向提交,历史保留,是撤销已推送提交的唯一安全方式;restore 是用暂存区或某个提交的内容覆盖文件,不动提交。要注意 revert 合并提交必须带 -m 指定主父节点,reset --hard 会真的丢掉未提交的修改。
4

cherry-pick:复制而不是移动

挑一个提交过来的代价
cherry-pick 是把某个提交的改动"复制"到当前分支,生成一个新的提交(新 hash)。所以原提交还在原分支上,两边 hash 不同。这带来一个长期麻烦:以后 merge 时同一个改动可能出现两次冲突。
典型场景与它的后患场景:hotfix 分支修了 bug,要合到 main 和 release git switch release git cherry-pick <hotfix-hash> 结果: hotfix 上的原提交还在 release 上多了一个内容相同但 hash 不同的提交 后患: 以后 hotfix 合进 release 时, Git 不知道这两个提交是"同一件事", 可能报冲突,或者产生一个空的合并提交 缓解: 合并时用 git merge 而不是再 cherry-pick 或者记录来源,在合并时用 -s ours 标记已合入
面试怎么答cherry-pick 是把某个提交的改动复制到当前分支,生成一个新的提交,所以 hash 和原提交不同,原提交还在原分支上。它适合热修复同步到多个版本分支、或者把误提到错误分支的提交挪走。后患是以后两个分支合并时 Git 不知道这两个提交是同一件事,可能重复冲突。整条功能分支合并应该用 merge 或 rebase,不要用 cherry-pick,那会把连续的历史打散。
5

冲突处理:rebase 冲突为什么难缠

因为你可能要解好几遍
冲突本身不难,难的是rebase 过程中同一个冲突要重复解决多次——因为它是逐个提交重新应用,每个提交都可能撞上同一处改动。git rerere 就是为解决这个而生的。
两种冲突的流程差异【merge 冲突】只解一次 git merge main → 冲突标记写进文件 → 改完 git add <file> → git merge --continue(或 git commit) 【rebase 冲突】每个提交解一次 git rebase main → 停在某个提交,报冲突 → 改完 git add <file> → git rebase --continue → 下一个提交可能又冲突,再来一遍 → 想跳过这个提交:git rebase --skip → 整个放弃回原状:git rebase --abort 【推荐开启】 git config --global rerere.enabled true 解决过的同类冲突会被记住,下次自动套用
面试怎么答冲突本身不难,rebase 难在同一个冲突要解好几遍,因为它逐个提交重新应用,每个提交都可能撞上同一处改动。解决办法是开启 git rerere,让它记住解决过的冲突自动套用。解冲突时不要草率地"接受一边",因为很容易丢掉对方的功能。Git 只保证文本层面合并成功,语义是否正确它不知道,所以解完一定要跑构建和测试。冲突太多就直接 rebase --abort 改用 merge。
6

分支模型:三种主流做法

选模型看发布节奏
三种主流:Git Flow(多分支、适合有明确版本的客户端产品)、GitHub Flow(只有 main + 短分支,适合持续部署的 Web)、Trunk Based(几乎都在 main 上,靠特性开关控制发布)。Web 服务绝大多数用后两种。
三种模型的差别【Git Flow】 main / develop / feature/* / release/* / hotfix/* 优点:版本发布可控,hotfix 路径清晰 缺点:分支多,合并路径复杂,不适合一天多次发布 适合:客户端、SDK、有版本号的产品 【GitHub Flow】 main + feature/* PR 合并到 main 即部署 优点:简单,一条规则 缺点:需要完善的 CI 和回滚能力 适合:持续部署的 Web 服务 【Trunk Based】 所有人直接提交到 main,未完成的用特性开关关掉 优点:没有长命分支,没有大合并 缺点:要求测试和开关治理到位 适合:成熟的工程团队、高频发布
面试怎么答分支模型要看发布节奏。Git Flow 有 develop、release、hotfix 多条分支,适合有明确版本的客户端产品;GitHub Flow 只有 main 加短分支,PR 合并即部署,适合持续部署的 Web 服务;Trunk Based 几乎都在 main 上开发,未完成功能用特性开关关掉,适合成熟团队高频发布。关键指标是分支存活时间,超过两三天就该合并,长命分支的合并一定痛苦。
7

reflog:误删之后怎么救★

Git 几乎不会真的丢东西
git reflog 记录了HEAD 的每一次移动,包括 reset、rebase、checkout、commit。所以只要提交过,即使 reset --hard 或者删了分支,都能在 reflog 里找到那个 hash 救回来。这是最该记住的救火技能。
三个救火场景【reset --hard 之后反悔】 git reflog a1b2c3d HEAD@{0}: reset: moving to HEAD~1 e4f5g6h HEAD@{1}: commit: 加了导出功能 ← 就是这个 git reset --hard e4f5g6h 【删了还没合并的分支】 git reflog 找到分支最后一次提交的 hash git switch -c feature-lost e4f5g6h 【rebase 解到一半发现搞错方向】 git reflog 找到 rebase 开始前的 hash git reset --hard <那个 hash> (或者直接 git rebase --abort,如果还在 rebase 过程中) 【reflog 也有期限】 默认 90 天,gc 会清理。 已 add 但没 commit 的内容不在 reflog 里, 要用 git fsck --lost-found 找 dangling blob
面试怎么答reflog 记录 HEAD 的每一次移动,包括 reset、rebase、checkout,所以只要提交过,即使 reset --hard 或者删了分支都能从里面找到 hash 救回来。这是最实用的救火技能。但要注意 reflog 是本地记录,别人的机器上看不到;而且没 commit 过的东西它管不了,add 过的还能用 fsck --lost-found 找 dangling blob,完全没 add 的就真丢了。
8

stash 与临时切换

别用 stash 当长期存储
stash 是"把工作区和暂存区的改动收起来,回到干净状态"。适合临时切分支修急事。不适合长期存放——它不是备份,容易忘,而且容易和后来的改动冲突。
面试怎么答stash 适合临时切分支修急事,把工作区和暂存区的改动收起来。要注意它默认不包含未追踪的新文件,要加 -u。它不适合长期存放,因为没有版本历史,冲突时看不到 base 很难解,而且很容易忘。如果只是临时切分支,更可靠的做法是直接建一个 wip 提交,回来再 reset --soft,这样有记录可查。
9

提交信息与 PR 规范

让历史能被人读
提交信息不是格式洁癖,它决定了能不能用 git log 追问题。约定式提交(Conventional Commits)的价值在于能被机器解析:自动生成 changelog、自动决定版本号、以及让人一眼看出这个提交改了什么性质的东西。
提交信息的结构<type>(<scope>): <subject> ← 一行摘要,不超过 50 字,不加句号 <正文> ← 为什么改,不是改了什么(代码已经说明) 每行不超过 72 字 <footer> ← BREAKING CHANGE / Closes #123 常见 type: feat 新功能(进 MINOR 版本) fix bug 修复(进 PATCH 版本) refactor 重构,不改行为 perf 性能优化 docs 文档 test 测试 chore 构建、依赖、工具 ci CI 配置
面试怎么答提交信息决定能不能用 log 追问题。约定式提交的价值是能被机器解析,自动生成 changelog、决定版本号。结构是一行 type 加 scope 加摘要,正文写为什么改而不是改了什么,因为 diff 已经说明了。footer 里写 BREAKING CHANGE 和关联 issue,漏写会导致版本号跳错。另外要保证一个提交只做一件事,用 git add -p 拆开,PR 描述重点写怎么验证和怎么回滚。
10

大仓库的几件实事

clone 太慢、status 太卡
仓库变大后有三个具体痛点:clone 慢、status 卡、log 慢。对应的解法是浅克隆 / 稀疏检出、文件系统监控、以及 commit-graph。这些都是配置层面的,不用改工作方式。
四个实用配置【浅克隆】只要最近的历史 git clone --depth=1 <url> 优点:clone 快几十倍 缺点:看不到历史、不能切老分支 适合:CI 机器、只需要构建的场景 【部分克隆】按需拉对象 git clone --filter=blob:none <url> 只拉提交和树,文件内容用到才拉 适合:大仓库日常开发 【稀疏检出】只要部分目录 git sparse-checkout set apps/web packages/ui 适合:monorepo 里只关心自己那部分 【加速 status】 git config core.fsmonitor true git config core.untrackedCache true 大仓库里 git status 从几秒降到几百毫秒
面试怎么答大仓库的三个痛点是 clone 慢、status 卡、log 慢。clone 用浅克隆或部分克隆,只拉需要的对象;status 慢开 core.fsmonitor 让文件系统主动通知变更,不用全量扫;log 慢靠 commit-graph 预计算父子关系。另外二进制大文件不要进 Git,进了历史就很难清,要用 LFS 或对象存储。monorepo 里构建慢是另一件事,要靠 Turborepo 这类工具的远程缓存。
11

速答清单

考前十分钟只看这一页
01Git 的三个区域是什么?
工作区、暂存区、本地仓库,加上指向当前分支的 HEAD 指针。所有命令都是在挪文件或挪指针。
02reset 三个模式的区别?
soft 只动 HEAD;mixed 再回退暂存区,工作区不变;hard 三样全动,未提交的修改会丢。
03reset 和 revert 怎么选?
本地未推送用 reset 改历史;已推送共享分支必须用 revert 生成反向提交,reset + push -f 会影响别人。
04revert 合并提交为什么要带 -m?
合并提交有两个父节点,-m 1 表示以第一个父节点为主去反向应用,不带会报错。
05restore 和 checkout 的关系?
Git 2.23 把 checkout 的职责拆成 restore 和 switch,还原文件用 restore,切分支用 switch。
06merge 和 rebase 的取舍?
merge 保留分叉生成合并提交;rebase 把提交复制到新基底,hash 会变、历史变直线。共享分支用 merge。
07rebase 的黄金法则?
不要在已推送、别人可能基于它开发的提交上 rebase。改写历史会让别人 pull 时产生重复提交和冲突。
08rebase 时同一冲突要解多次怎么办?
开启 git config --global rerere.enabled true,它会记住解决过的冲突自动套用。
09cherry-pick 有什么后患?
它复制改动生成新 hash,原提交还在。以后两分支合并时 Git 不知道是同一件事,可能重复冲突。
10解完冲突就可以提交了吗?
不行。Git 只保证文本层面合并成功,语义是否正确它不知道,必须跑构建和测试。
11Git Flow 适合什么场景?
有明确版本号的客户端、SDK 产品。Web 服务更适合 GitHub Flow 或 Trunk Based,分支少、发布快。
12分支该活多久?
两三天内合并,或者拆小。长命分支的最终合并一定痛苦,这是必然的。
13reset --hard 之后还能救回来吗?
能,只要提交过。git reflog 找到那个 hash,再 reset --hard 回去。但没 commit 过的东西 reflog 管不了。
14reflog 能跨机器用吗?
不能。它是本地记录,只在你的机器上。别人看到的只有推上去的提交。
15没 add 过的文件误删能恢复吗?
不能。add 过的还有救,用 git fsck --lost-found 找 dangling blob;完全没 add 过的 Git 里没有记录。
16stash 为什么容易出问题?
默认不含未追踪文件要加 -u;没有版本历史所以冲突时看不到 base;容易忘。临时切分支更推荐建 wip 提交。
17提交信息该写什么?
写为什么改,不写改了什么,因为 diff 已经说明。BREAKING CHANGE 必须写在 footer,否则版本号会跳错。
18clone 大仓库怎么加速?
浅克隆 --depth=1 或部分克隆 --filter=blob:none;日常开发还可以用 sparse-checkout 只检出自己关心的目录。