跳转至

Git实战与面试

🎯 学习目标

  • 掌握常见 Git 实战场景的处理方法
  • 配置高效的 Git 别名提升工作效率
  • 熟记日常 Git 工作流速查表
  • 全面准备 Git 面试常见题目

1. 实战场景

场景一:紧急修复线上 Bug(Hotfix 流程)

背景:你正在 feature/payment 分支开发支付功能,突然接到通知线上有严重 bug。

Bash
# 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 + 分支切换)

Bash
# 正在 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

场景三:回滚错误的合并

Bash
# 情况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 历史中删除敏感信息

⚠️ 这是一个危险操作,会改写整个仓库历史。

Bash
# 方法一:使用 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

⚠️ 重要:即使删除了历史,如果敏感信息已被推送过,认为它已经泄露。应立即更换密码/密钥。

场景五:迁移仓库(保留历史)

Bash
# 方法一:镜像克隆(保留所有分支、标签、引用)
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 个实用别名

Bash
# 基础操作
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\\."

使用示例:

Bash
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 文件:

INI
[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 -- filegit restore file
取消暂存 git reset HEAD filegit 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 refloggit 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~1HEAD~:HEAD 的父提交(上一个提交)
  • HEAD~2:HEAD 的祖父提交
  • HEAD^:HEAD 的第一个父提交(同 HEAD~1
  • HEAD^2:HEAD 的第二个父提交(仅在合并提交时有意义,指另一个分支的提交)

Q9: .gitignore 不生效怎么办?

.gitignore 只对未跟踪的文件生效。如果文件已被 Git 跟踪,需要先清除缓存:

Bash
git rm --cached filename          # 删除单个文件的跟踪
git rm --cached -r directory/     # 删除目录的跟踪
git commit -m "chore: update gitignore"

Q10: Conventional Commits 是什么?为什么要遵循?

一种提交信息规范,格式:<type>(<scope>): <subject>。常见 type 包括 featfixdocsrefactor 等。好处:

  • 自动生成 CHANGELOG
  • 方便团队协作理解提交意图
  • 可以配合工具自动确定语义化版本号
  • 使 git log 输出更有意义

分支与协作(11-20)

Q11: 分支的本质是什么?为什么 Git 的分支操作很快?

Git 的分支本质上是一个指向某次 commit 的指针(一个 41 字节的文件,存储 commit 的 SHA-1 哈希值)。创建分支不需要复制任何文件,所以几乎瞬间完成。


Q12: 如何找回删除的分支?

Bash
git reflog    # 找到该分支最后一次 commit 的 hash
git switch -c recovered-branch <commit-hash>

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: 如何解决合并冲突?

  1. 执行 git mergegit rebase 触发冲突
  2. Git 在冲突文件中标记 <<<<<<<=======>>>>>>>
  3. 手动编辑文件,选择保留的内容,删除标记
  4. git add 标记为已解决
  5. git commit(merge)或 git rebase --continue(rebase)

放弃:git merge --abortgit 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?

Bash
git revert -m 1 <merge-commit-hash>

-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 的提交:

Bash
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: 如何压缩多个提交?

两种主要方式:

Bash
# 方式一:交互式变基
git rebase -i HEAD~N    # 将 N 个提交压缩
# 编辑器中将 pick 改为 squash 或 fixup

# 方式二:soft reset
git reset --soft HEAD~N
git commit -m "新的提交信息"

Q24: git stash 的工作原理和常用命令?

stash 将工作区和暂存区的修改保存到一个栈结构中。

Bash
git stash           # 保存
git stash -u        # 包含未跟踪文件
git stash list      # 查看列表
git stash pop       # 恢复并删除
git stash apply     # 恢复但保留
git stash drop      # 删除某条记录

Q25: Git Hooks 有哪些?常见用途?

  • pre-commit:提交前检查代码格式、lint
  • commit-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 codefix bug修改了一些东西


✏️ 综合练习

  1. 模拟完整开发流程
  2. 初始化仓库,配置 .gitignore 和远程仓库
  3. 使用 Git Flow 分支策略开发两个功能
  4. 制造并解决一个合并冲突
  5. 紧急修复一个 hotfix
  6. 使用 rebase -i 整理提交历史
  7. 创建 Release Tag

  8. Git 灾难恢复

  9. reset --hard 后用 reflog 找回
  10. 误删分支后恢复
  11. 回滚错误的合并

  12. 配置自动化

  13. 配置 Git 别名
  14. 编写一个 pre-commit hook
  15. 配置 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

← 上一章:高级技巧 | 返回目录 →