Git实战与面试¶
🎯 学习目标¶
- 掌握常见 Git 实战场景的处理方法
- 配置高效的 Git 别名提升工作效率
- 熟记日常 Git 工作流速查表
- 全面准备 Git 面试常见题目
1. 实战场景¶
场景一:紧急修复线上 Bug(Hotfix 流程)¶
背景:你正在 feature/payment 分支开发支付功能,突然接到通知线上有严重 bug。
# 1. 保存当前工作进度
git stash push -m "支付功能开发中"
# 2. 切回 main 分支并拉取最新代码
git switch main
git pull origin main
# 3. 创建 hotfix 分支
git switch -c hotfix/fix-login-crash
# 4. 修复 bug
# 编辑代码...
git add .
git commit -m "fix(auth): 修复登录崩溃问题 (#456)"
# 5. 推送并创建 PR(紧急合并到 main)
git push origin hotfix/fix-login-crash
# 在 GitHub 上创建 PR,紧急 Review 后合并
# 6. 同步修复到 develop(如果使用 Git Flow)
git switch develop
git merge hotfix/fix-login-crash
# 7. 回到功能开发
git switch feature/payment
git stash pop
# 继续开发...
# 8. 清理 hotfix 分支
git branch -d hotfix/fix-login-crash
git push origin --delete hotfix/fix-login-crash
场景二:功能开发被打断(stash + 分支切换)¶
# 正在 feature/user-profile 分支开发
# 突然需要紧急处理另一个任务
# 方案一:使用 stash
git stash -u -m "用户资料页面,完成了头像上传部分"
git switch other-branch
# 处理完后
git switch feature/user-profile
git stash pop
# 方案二:使用 worktree(推荐,不影响当前工作区)
git worktree add ../urgent-task urgent-task-branch
cd ../urgent-task
# 处理完后
cd ../main-project
git worktree remove ../urgent-task
场景三:回滚错误的合并¶
# 情况1:合并还没 push
git reset --hard HEAD~1 # 直接回退(丢弃合并提交)
# 情况2:合并已经 push(不能 reset!)
git revert -m 1 <merge-commit-hash>
# -m 1 表示保留主分支的内容,撤销合并进来的内容
git push origin main
# 注意:revert 合并后,如果还想重新合并那个分支,需要先 revert 那个 revert
git revert <revert-commit-hash>
git merge feature/xxx
场景四:从 commit 历史中删除敏感信息¶
⚠️ 这是一个危险操作,会改写整个仓库历史。
# 方法一:使用 git filter-repo(推荐,需安装)
pip install git-filter-repo
# 从所有历史中删除指定文件
git filter-repo --invert-paths --path secrets.yml
# 从所有历史中替换敏感字符串
git filter-repo --replace-text expressions.txt
# expressions.txt 内容:
# my-api-key==>***REMOVED***
# password123==>***REMOVED***
# 方法二:使用 BFG Repo-Cleaner
# 下载:https://rtyley.github.io/bfg-repo-cleaner/
java -jar bfg.jar --replace-text passwords.txt repo.git
java -jar bfg.jar --delete-files secrets.yml repo.git
# 清理完后
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all
git push --force --tags
⚠️ 重要:即使删除了历史,如果敏感信息已被推送过,认为它已经泄露。应立即更换密码/密钥。
场景五:迁移仓库(保留历史)¶
# 方法一:镜像克隆(保留所有分支、标签、引用)
git clone --mirror https://old-host.com/user/repo.git
cd repo.git
git remote set-url origin https://new-host.com/user/repo.git
git push --mirror
# 方法二:普通迁移
git clone https://old-host.com/user/repo.git
cd repo
git remote rename origin old-origin
git remote add origin https://new-host.com/user/repo.git
git push -u origin --all # 推送所有分支
git push origin --tags # 推送所有标签
2. Git 别名配置¶
推荐的 20 个实用别名¶
# 基础操作
git config --global alias.st "status -sb"
git config --global alias.co "checkout"
git config --global alias.sw "switch"
git config --global alias.br "branch"
git config --global alias.ci "commit"
git config --global alias.cm "commit -m"
# 日志查看
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.ll "log --pretty=format:'%C(yellow)%h%Creset %C(green)%ad%Creset | %s %C(red)%d%Creset %C(blue)[%an]%Creset' --date=short" # 这里的|是格式字符串中的分隔符,不是shell管道
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.today "log --since=midnight --oneline --no-merges"
# 差异与暂存
git config --global alias.df "diff"
git config --global alias.ds "diff --staged"
git config --global alias.unstage "reset HEAD --"
git config --global alias.discard "checkout --"
# 常用操作
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.undo "reset --soft HEAD~1"
git config --global alias.wip "commit -am 'WIP: work in progress'"
git config --global alias.save "stash push"
git config --global alias.pop "stash pop"
# 高级操作
git config --global alias.aliases "config --get-regexp ^alias\\."
使用示例:
git st # = git status -sb
git lg # = git log --oneline --graph --all --decorate
git cm "feat: add login" # = git commit -m "feat: add login"
git undo # = git reset --soft HEAD~1
git amend # = git commit --amend --no-edit
git today # 查看今天的提交
git aliases # 查看所有配置的别名
也可以直接编辑 ~/.gitconfig 文件:
[alias]
st = status -sb
co = checkout
sw = switch
br = branch
ci = commit
cm = commit -m
lg = log --oneline --graph --all --decorate
ll = log --pretty=format:'%C(yellow)%h%Creset %C(green)%ad%Creset | %s %C(red)%d%Creset %C(blue)[%an]%Creset' --date=short
last = log -1 HEAD --stat
today = log --since=midnight --oneline --no-merges
df = diff
ds = diff --staged
unstage = reset HEAD --
discard = checkout --
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
wip = commit -am 'WIP: work in progress'
save = stash push
pop = stash pop
aliases = config --get-regexp ^alias\\.
3. 日常 Git 工作流速查表¶
每日工作流¶
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | git pull --rebase | 拉取最新代码 |
| 2 | git switch -c feature/xxx | 创建功能分支 |
| 3 | 编写代码 | — |
| 4 | git add . | 暂存修改 |
| 5 | git commit -m "type: msg" | 提交(遵循规范) |
| 6 | git push -u origin feature/xxx | 推送到远程 |
| 7 | 创建 PR → Review → 合并 | 在平台操作 |
| 8 | git switch main && git pull | 切回主分支更新 |
| 9 | git branch -d feature/xxx | 删除本地分支 |
常见操作速查¶
| 场景 | 命令 |
|---|---|
| 撤销工作区修改 | git checkout -- file 或 git restore file |
| 取消暂存 | git reset HEAD file 或 git restore --staged file |
| 修改最后一次提交 | git commit --amend |
| 撤销最近一次提交(保留修改) | git reset --soft HEAD~1 |
| 撤销最近一次提交(丢弃修改) | git reset --hard HEAD~1 |
| 撤销已推送的提交 | git revert <hash> |
| 暂存当前工作 | git stash |
| 恢复暂存的工作 | git stash pop |
| 查看某文件的修改历史 | git log --oneline -- file |
| 查看某行代码是谁写的 | git blame file |
| 找回删除的分支 | git reflog → git switch -c name <hash> |
| 合并某个特定提交 | git cherry-pick <hash> |
| 压缩最近 N 个提交 | git rebase -i HEAD~N |
| 清理已合并的本地分支 | git branch --merged \| grep -v main \| xargs git branch -d |
| 查看远程分支 | git branch -r |
| 同步远程已删除的分支 | git fetch --prune |
4. 常见面试题 30 道¶
基础概念(1-10)¶
Q1: Git 是什么?它和 SVN 有什么区别?
Git 是分布式版本控制系统,SVN 是集中式版本控制系统。主要区别:
| 对比 | Git | SVN |
|---|---|---|
| 架构 | 分布式,每个客户端都有完整仓库 | 集中式,依赖中央服务器 |
| 离线操作 | 支持离线提交、查看历史 | 必须联网 |
| 分支 | 轻量级(指针),创建/切换几乎瞬间 | 重量级(目录复制) |
| 速度 | 大部分操作在本地,极快 | 需要网络通信,较慢 |
Q2: 解释 Git 的三棵树模型。
Git 有三个核心区域: - 工作区(Working Directory):实际文件所在的目录,是你编辑代码的地方 - 暂存区(Staging Area / Index):临时存放变更的区域,通过 git add 将修改移入 - 版本库(Repository):存储所有提交历史的地方(.git 目录),通过 git commit 将暂存区内容写入
Q3: git merge 和 git rebase 有什么区别?各自适用场景是什么?
- merge:创建一个新的合并提交,保留完整的分支历史。适用于公共分支的合并。
- rebase:将当前分支的提交"重放"到目标分支之后,历史变成线性。适用于私有分支的更新(如将 feature 分支 rebase 到 main 最新)。
核心原则:不要 rebase 已推送到公共仓库的分支。
Q4: git reset 和 git revert 的区别?
- reset:移动 HEAD 指针,可能改写历史。有
--soft(只移指针)、--mixed(重置暂存区)、--hard(全部重置)三种模式。适用于本地未推送的提交。 - revert:创建一个新的提交来撤销指定提交的效果,不修改历史。适用于已推送的提交。
Q5: git fetch 和 git pull 有什么区别?
git fetch只从远程下载新的数据(分支、标签等),不会自动合并到当前分支。git pull=git fetch+git merge(或git rebase),会自动合并。
推荐用 fetch + merge 的方式,可以先查看远程改动再决定合并。
Q6: 解释 git reset 的三种模式(--soft、--mixed、--hard)。
假设从 C3 回退到 C1: - --soft:HEAD 移到 C1,暂存区和工作区保持 C3 的状态。用于重新组织提交。 - --mixed(默认):HEAD 移到 C1,暂存区重置到 C1,工作区保持 C3。用于取消 add。 - --hard:HEAD、暂存区、工作区全部重置到 C1。⚠️ 未提交的修改会丢失。
Q7: 什么是 Fast-forward 合并?什么时候不会 Fast-forward?
当被合并的分支是当前分支的直接后继(即当前分支没有产生新的提交、没有分叉),Git 只需要将指针前移,这就是 Fast-forward。当两个分支都有新的提交(产生分叉)时,会进行三方合并,生成 merge commit。使用 --no-ff 可以强制不使用 Fast-forward。
Q8: HEAD、HEAD~、HEAD^ 分别是什么?
HEAD:指向当前分支的最新提交HEAD~1或HEAD~:HEAD 的父提交(上一个提交)HEAD~2:HEAD 的祖父提交HEAD^:HEAD 的第一个父提交(同HEAD~1)HEAD^2:HEAD 的第二个父提交(仅在合并提交时有意义,指另一个分支的提交)
Q9: .gitignore 不生效怎么办?
.gitignore 只对未跟踪的文件生效。如果文件已被 Git 跟踪,需要先清除缓存:
git rm --cached filename # 删除单个文件的跟踪
git rm --cached -r directory/ # 删除目录的跟踪
git commit -m "chore: update gitignore"
Q10: Conventional Commits 是什么?为什么要遵循?
一种提交信息规范,格式:<type>(<scope>): <subject>。常见 type 包括 feat、fix、docs、refactor 等。好处:
- 自动生成 CHANGELOG
- 方便团队协作理解提交意图
- 可以配合工具自动确定语义化版本号
- 使
git log输出更有意义
分支与协作(11-20)¶
Q11: 分支的本质是什么?为什么 Git 的分支操作很快?
Git 的分支本质上是一个指向某次 commit 的指针(一个 41 字节的文件,存储 commit 的 SHA-1 哈希值)。创建分支不需要复制任何文件,所以几乎瞬间完成。
Q12: 如何找回删除的分支?
reflog 记录了 HEAD 的每一次变动历史,默认保留 90 天。
Q13: Cherry-pick 是什么?什么时候使用?
Cherry-pick 将指定的一个或多个 commit"摘取"并应用到当前分支。使用场景: - 把 bugfix 从 develop 同步到 main - 从 feature 分支中提取部分提交单独发布 - 误提交到错误分支时的修复
注意:cherry-pick 会创建新的 commit(新 hash),不是移动原来的 commit。
Q14: 解释 Git Flow 和 GitHub Flow 的区别。
Git Flow:有 main、develop、feature、release、hotfix 五类分支,适合有固定发布周期的项目。 GitHub Flow:只有 main + feature 分支,PR 驱动,适合持续部署的 Web 应用。
GitHub Flow 更简单,Git Flow 更适合大型、复杂的项目。
Q15: 什么是 rebase 的黄金法则?
永远不要 rebase 已经推送到公共仓库的分支。 因为 rebase 会改写 commit 历史(hash 变化),如果其他人基于旧的历史工作,他们 pull 后会遇到冲突灾难。
Q16: 如何解决合并冲突?
- 执行
git merge或git rebase触发冲突 - Git 在冲突文件中标记
<<<<<<<、=======、>>>>>>> - 手动编辑文件,选择保留的内容,删除标记
git add标记为已解决git commit(merge)或git rebase --continue(rebase)
放弃:git merge --abort 或 git rebase --abort
Q17: --force 和 --force-with-lease 有什么区别?
--force:无条件覆盖远程分支,可能丢失他人的提交--force-with-lease:在推送前检查远程分支是否有你未见过的新提交,如果有则拒绝推送
在团队协作中应始终使用 --force-with-lease 而非 --force。
Q18: Pull Request 的三种合并方式有什么区别?
- Merge commit:创建一个合并提交,保留所有 feature 分支的提交历史
- Squash and merge:将 feature 分支的所有提交压缩为一个提交合并
- Rebase and merge:将 feature 的提交逐个追加到 base 分支后面,形成线性历史
Q19: 如何回滚一个已经合并的 PR?
-m 1 保留主分支的修改,撤销合并进来的内容。注意:如果之后还想重新合并那个分支,需要先 revert 这个 revert。
Q20: Fork 和 Clone 有什么区别?
- Clone:在本地创建远程仓库的副本
- Fork:在 GitHub/GitLab 上创建仓库的副本到你的账号下,是一个独立的远程仓库
Fork 用于开源贡献:Fork → Clone → 修改 → Push 到你的 Fork → 向原仓库发 PR。
高级操作(21-30)¶
Q21: git reflog 是什么?什么时候用?
reflog 记录了本地仓库中 HEAD 引用的变动历史(commit、reset、checkout 等)。用于: - 找回 reset --hard 丢失的提交 - 恢复误删的分支 - 查看最近的操作历史
默认保留 90 天。注意:reflog 是本地的,不会推送到远程。
Q22: git bisect 怎么用?
二分查找引入 bug 的提交:
git bisect start
git bisect bad # 当前版本有 bug
git bisect good <good-hash> # 某个已知正常的版本
# Git 自动切到中间提交,测试后标记 good/bad
# 重复直到找到第一个 bad commit
git bisect reset
可以用 git bisect run <test-script> 自动化。
Q23: 如何压缩多个提交?
两种主要方式:
# 方式一:交互式变基
git rebase -i HEAD~N # 将 N 个提交压缩
# 编辑器中将 pick 改为 squash 或 fixup
# 方式二:soft reset
git reset --soft HEAD~N
git commit -m "新的提交信息"
Q24: git stash 的工作原理和常用命令?
stash 将工作区和暂存区的修改保存到一个栈结构中。
git stash # 保存
git stash -u # 包含未跟踪文件
git stash list # 查看列表
git stash pop # 恢复并删除
git stash apply # 恢复但保留
git stash drop # 删除某条记录
Q25: Git Hooks 有哪些?常见用途?
pre-commit:提交前检查代码格式、lintcommit-msg:验证提交信息格式(如 Conventional Commits)pre-push:推送前运行测试post-merge:合并后自动安装依赖
工具:JavaScript 用 husky + lint-staged,Python 用 pre-commit 框架。
Q26: submodule 和 subtree 有什么区别?
- submodule:在仓库中引用另一个仓库(存指针),需要
--recursive克隆。适合大型依赖。 - subtree:将另一个仓库的代码直接放入子目录,无特殊克隆要求。适合小型库。
Q27: Git LFS 是什么?为什么需要它?
Git LFS(Large File Storage)用于管理大文件(图片、视频、模型等)。Git 不擅长处理大二进制文件,每次修改都会存储完整副本。LFS 将大文件存储在专门的服务器上,仓库中只保存指针。
Q28: 如何从 Git 历史中彻底删除敏感信息?
使用 git filter-repo(推荐)或 BFG Repo-Cleaner。处理后需要 git push --force --all。但要注意:只要敏感信息曾经推送过,就应视为已泄露,立即更换。
Q29: 什么是 Git worktree?使用场景?
worktree 允许同一个仓库同时在多个目录中检出不同分支。场景: - 正在开发功能时需要紧急修复 bug,不用 stash - 需要同时运行两个分支的代码进行对比 - 长期维护多个版本
Q30: 如何编写好的 commit message?
遵循 Conventional Commits 规范: 1. 使用明确的 type(feat/fix/docs/refactor 等) 2. scope 指明影响范围 3. subject 用祈使语气,简洁描述(不超过 72 字符) 4. body 解释"是什么"和"为什么"(可选) 5. footer 引用 Issue(可选)
好的例子:feat(auth): 添加OAuth2.0第三方登录支持 差的例子:update code、fix bug、修改了一些东西
✏️ 综合练习¶
- 模拟完整开发流程:
- 初始化仓库,配置
.gitignore和远程仓库 - 使用 Git Flow 分支策略开发两个功能
- 制造并解决一个合并冲突
- 紧急修复一个 hotfix
- 使用
rebase -i整理提交历史 -
创建 Release Tag
-
Git 灾难恢复:
- 误
reset --hard后用reflog找回 - 误删分支后恢复
-
回滚错误的合并
-
配置自动化:
- 配置 Git 别名
- 编写一个 pre-commit hook
- 配置 GitHub Actions CI
📋 面试要点总结¶
| 主题 | 核心知识点 |
|---|---|
| 基础概念 | 三棵树模型、四种文件状态、集中式vs分布式 |
| 提交操作 | reset三种模式、revert、amend、stash |
| 分支管理 | 分支是指针、merge vs rebase、黄金法则 |
| 远程协作 | fetch vs pull、force vs force-with-lease、PR流程 |
| 高级技巧 | reflog、bisect、cherry-pick、rebase -i |
| 工程实践 | Conventional Commits、Git Hooks、Git LFS |
| 分支策略 | Git Flow、GitHub Flow、Trunk Based |