Git 八股不靠死记:跟着小周救回一次“消失的代码”

hello 我是逆境

第一次参加实习面试前,小周背过不少 Git 命令。

addcommitpush,他念得很顺。可面试官换了个问法:“你刚提交的代码还没推送,现在想撤销提交,但代码得留下,怎么办?”

小周脑子里一下冒出五六条命令。reset 好像可以,revert 好像也行,后面到底该接 softmixed 还是 hard?他不敢赌。

这种卡壳很常见。Git 的命令不少,真正难的却不是记命令,而是弄清楚:现在的代码在哪一层,你希望哪一层发生变化。

这篇文章从一个普通开发者的工作日讲起。小周会切错分支、遇到冲突、误删提交,也会一点点把代码救回来。每道高频题最后都有一段“面试回答”,理解之后再读两遍,基本就能复述。


第一站:早上九点,先看懂 Git 这座“版本仓库”

小周入职第一天,组长没有急着讲命令,而是在白板上画了四个地方:

工作区          暂存区          本地仓库          远程仓库
正在修改的文件   下次要提交的内容   已提交的历史       团队共享的历史
    │ git add        │ git commit      │ git push
    └──────────────→ └───────────────→ └──────────────→

后面的绝大多数 Git 问题,都能回到这张图上。

在这里插入图片描述

图 1:代码依次经过工作区、暂存区、本地仓库和远程仓库。

1. Git 和 SVN 有什么区别?

可以把 SVN 想成公司的中央档案室。完整档案主要放在服务器上,员工拿到的是一份工作副本。服务器出问题或者网络断开,很多操作会受影响。

Git 更像每位员工都拿到了一整套档案副本。本地仓库里有完整的提交历史,所以断网时仍然可以提交、创建分支、查看记录。等网络恢复,再把结果同步给 GitHub、GitLab 之类的远程仓库。

面试回答:Git 是分布式版本控制系统,每个开发者本地都有完整仓库,可以离线提交和查看历史;SVN 是集中式版本控制系统,版本历史主要由中央服务器管理。Git 的分支更轻量,分布式协作也更灵活。

2. Git 的工作区、暂存区和本地仓库分别是什么?

小周正在编辑的 UserService.java 位于工作区。执行 git add 后,Git 把文件当前内容放进暂存区,表示“下次提交准备带上它”。执行 git commit 后,暂存区里的内容才进入本地仓库,成为一段正式历史。

把它想成寄快递就很好记:

工作区:桌上正在整理的物品
暂存区:已经装进箱子的物品
本地仓库:已经封箱并登记的包裹

箱子装好后,你仍然可以继续修改桌上的文件。此时工作区和暂存区保存的就不是同一个版本。

面试回答:工作区是实际编辑文件的地方;暂存区保存下一次准备提交的内容;本地仓库保存已经提交的版本历史。典型流程是修改文件后执行 git add 进入暂存区,再通过 git commit 写入本地仓库。

3. git add 到底做了什么?

很多初学者把 git add 理解为“开始跟踪文件”。这只说对了一部分。它真正做的事,是把文件此刻的内容写入暂存区。

例如,小周写完第一版代码后执行:

git add UserService.java

随后他又改了两行。如果现在直接提交,提交的是第一次 add 时的版本,后改的两行还留在工作区。想把最新内容也交上去,需要再次执行 git add

面试回答:git add 会把文件当前版本写入暂存区,作为下一次提交的内容。文件在 add 之后又被修改,需要再次 add,否则新修改不会进入本次提交。

4. git commit 做了什么?

git commit 会根据暂存区创建一个新的提交对象,并让当前分支指向它。

一次提交并不是只有代码。它还记录根目录快照、父提交、作者、提交者、时间和提交说明。正因为这些信息都参与提交对象的计算,哪怕只改一句 Commit Message,提交哈希也会变化。

面试回答:git commit 根据暂存区生成新的提交对象。提交中包含项目快照、父提交、作者和提交者信息、时间以及提交说明。提交成功后,当前分支会指向这个新提交。

5. Git 保存的是文件差异,还是文件快照?

从使用者的角度看,Git 每次提交保存的是整个项目在那个时刻的快照。

这不代表每次都把所有文件复制一遍。没有变化的文件会复用已有对象;发生变化的文件才会形成新的内容对象。Git 在存储和传输时还会做压缩,但它的核心模型仍然是快照。

面试回答:Git 在逻辑上按提交保存项目快照,而不是把每次提交简单理解成一组文件差异。未变化的内容会复用已有对象,因此不会机械地重复保存全部文件。

6. Git 中有哪些重要对象?

小周打开 .git 目录时,看见一堆认不出的文件。组长告诉他,先记住四种对象:

  • Blob 保存文件内容,但不保存文件名。
  • Tree 保存目录结构、文件名以及它们指向的 Blob 或子 Tree。
  • Commit 指向一个根 Tree,同时记录父提交、作者和提交说明。
  • Tag 可以指向某个对象,通常用来给发布提交起一个固定名字。

分支不属于这四类对象。它只是一个引用,保存着某个提交的哈希。

面试回答:Git 的核心对象有 Blob、Tree、Commit 和 Tag。Blob 保存文件内容,Tree 组织目录结构,Commit 记录项目快照和父提交等信息,Tag 通常用于固定标记某个提交。分支和 HEAD 属于引用。

7. Git 的提交哈希是怎么来的?

Git 会根据对象类型、长度和内容计算哈希。传统仓库主要使用 SHA-1,新格式仓库也支持 SHA-256。

提交对象里不只有代码快照,还有父提交、作者、时间和说明。因此修改一次历史提交,会得到新的提交哈希;它后面的子提交因为记录了父提交哈希,也会跟着变化。这正是 rebase 会“改写历史”的原因。

面试回答:Git 根据对象类型、长度和内容计算哈希。提交对象包含根 Tree、父提交、作者、提交者、时间和提交说明,这些内容发生变化都会产生新的提交哈希。修改较早的提交还可能导致后续提交哈希连锁变化。

在这里插入图片描述

图 2:正常情况下 HEAD 跟着分支走;分离状态下,HEAD 直接指向某次提交。

8. HEAD 是什么?

HEAD 可以理解成“你现在站在哪里”的路标。

正常情况下,它先指向当前分支,分支再指向最新提交:

HEAD → main → C3

小周在 main 上提交一次后,会变成:

HEAD → main → C4

HEAD 和 main 看起来一起向前走,其实是当前分支移动到了新提交,HEAD 仍指着这个分支。

面试回答:HEAD 表示当前检出的提交。正常情况下,HEAD 指向当前分支,分支再指向某个提交。产生新提交后,当前分支向前移动,HEAD 仍跟随这个分支。

9. 什么是 Detached HEAD?

午休时,小周想看看三个月前的代码,于是直接检出了某个提交。此时 HEAD 没有指向分支,而是直接指向提交:

HEAD → C2
main → C8

这叫 Detached HEAD,也就是“分离头指针”。在这里可以修改和提交,但新提交没有正式分支长期指着它。切走之后,它可能变得难找。

如果这次修改需要保留,及时创建分支:

git switch -c repair-from-old-version

面试回答:Detached HEAD 表示 HEAD 直接指向某个提交,而不是指向分支。此时仍能提交,但新提交不属于正式分支。若需要保留,应及时用 git switch -c 创建分支。

10. Git 分支的本质是什么?

新建分支并不会复制一份项目。Git 只是新建一个引用,让它指向当前提交:

main    → C3
feature → C3

小周切到 feature 提交后,只有 feature 向前移动:

main    → C3
feature → C4

这也是 Git 创建和切换分支很快的原因。

面试回答:Git 分支本质上是一个指向提交的可移动指针。创建分支只是创建一个新引用,不需要复制整个项目,因此操作很轻量。


第二站:上午十点,弄清每天都在用的命令

小周已经知道代码存在哪里了。接下来再看命令,感觉会完全不同:每条命令只是让某一层的数据发生变化。

在这里插入图片描述

图 3:fetch 把更新取到远程跟踪分支,pull 还会继续合入当前分支。

11. git fetchgit pull 有什么区别?

远程同事提交了新代码。git fetch 会把远程数据下载到本地,并更新 origin/main 这类远程跟踪分支,却不直接改动小周当前的 main

git fetch origin

这像快递先送到小区驿站,你可以看一眼是什么,再决定是否拿回家。

git pull 则更像直接取件并拆包。它通常先 fetch,再把远程变化合入当前分支:

git pull ≈ git fetch + git merge

如果使用 git pull --rebase,第二步会改成 rebase。

面试回答:git fetch 只下载远程更新并刷新远程跟踪分支,不会自动修改当前本地分支;git pull 会在 fetch 之后继续执行 merge,或按配置执行 rebase。fetch 更便于先检查再合并,pull 更方便但动作更多。

12. origin 是什么?

origin 只是远程仓库的一个名字。执行 git clone 时,Git 通常把克隆来源默认命名为 origin,但它并不是不可更改的关键字。

git remote -v
git remote rename origin company

一个本地仓库也能配置多个远程,例如把自己 Fork 的仓库叫 origin,把原项目仓库叫 upstream

面试回答:origin 是克隆仓库时默认创建的远程仓库名称,本质上只是一个可修改的别名。一个本地仓库可以同时配置多个远程。

13. 什么是远程跟踪分支?

origin/main 不是远程服务器上的实时分支,而是本地保存的一份“上次同步时,远程 main 在哪里”的记录。

本地分支:main
远程跟踪分支:origin/main
远程服务器分支:远程仓库中的 main

别人刚推送完代码,小周本地的 origin/main 不会凭空更新。执行 git fetch 后,它才会移动到最新位置。

面试回答:远程跟踪分支是本地对远程分支状态的记录,例如 origin/main。它由 fetch 或 pull 更新,并不等于服务器上的实时状态,也不能像普通本地分支那样直接在上面持续提交。

14. checkoutswitchrestore 有什么区别?

老教程里到处都是 git checkout。它既能切分支,又能恢复文件,还能检出某次提交,职责太多,很容易把人绕晕。

现代 Git 提供了两个更清楚的命令:

git switch feature       # 切换分支
git switch -c feature    # 创建并切换分支
git restore app.java     # 恢复文件

checkout 仍然可用,维护老项目时也经常见到。面试时说清楚职责拆分即可。

面试回答:git checkout 是历史较久的多用途命令,可以切换分支或恢复文件。较新的 Git 将常用职责拆成 git switchgit restore:前者管理分支切换,后者恢复文件,语义更明确。

15. git stash applygit stash pop 有什么区别?

小周的功能写到一半,线上突然有 Bug。他又不想为了半成品创建一次提交,于是先把修改临时收起来:

git stash push -m "unfinished login page"

修完 Bug 回来后,两种恢复方式都能取出修改:

git stash apply stash@{0}
git stash pop

apply 恢复后保留 stash 记录;pop 在成功恢复后通常会删除对应记录。担心冲突或想重复使用时,先用 apply 更稳妥。

面试回答:stash applystash pop 都会恢复临时保存的修改。apply 恢复后保留 stash 记录,pop 恢复成功后会删除对应记录。存在冲突风险时,可以先使用 apply。

16. git commit --amend 有什么用?

小周刚提交就发现漏了一个配置文件。他可以把文件加入暂存区,再修补最近一次提交:

git add config.example
git commit --amend

amend 不是打开旧提交原地涂改,而是创建一个新提交来替换它。因此提交哈希会变化。若旧提交已经推送并被同事使用,随意 amend 会给别人制造麻烦。

面试回答:git commit --amend 用于修改最近一次提交,可以调整提交说明或补充遗漏文件。它会创建新提交替换原提交,所以哈希会变化,通常只用于尚未共享的本地提交。

17. git cherry-pick 有什么作用?

测试分支里有十个提交,小周只想把其中一个 Bug 修复带到发布分支。这时不必合并整个分支,可以执行:

git switch release
git cherry-pick <commit-id>

Git 会把指定提交造成的修改重新应用到当前分支。内容相似,但由于父提交和环境不同,通常会产生一个新哈希。

面试回答:git cherry-pick 会把指定提交的修改应用到当前分支。它适合只同步部分提交,例如将修复带到发布分支。cherry-pick 生成的是新提交,提交哈希通常不同。


第三站:午后合代码,merge 和 rebase 终于打起来了

小周开发功能时,main 也在继续前进:

      F1---F2  feature
     /
C1---C2---M1  main

现在要让 feature 获得 main 的最新代码,可以 merge,也可以 rebase。两者都能把代码放到一起,但整理历史的方法不同。

在这里插入图片描述

图 4:Merge 保留分叉并产生汇合点,Rebase 把功能提交重新排到主线后面。

18. git mergegit rebase 有什么区别?

merge 会把两条历史接起来。发生分叉时,通常创建一个新的合并提交:

      F1---F2
     /       \
C1---C2---M1---M2

rebase 则把 F1F2 暂时取下,重新放到 M1 后面:

C1---C2---M1---F1'---F2'

看起来像一条直线,但 F1'F2' 是重新生成的提交,哈希已经改变。

面试回答:merge 通过合并提交连接两条分支历史,一般不改写原提交;rebase 会把当前分支的提交重新应用到新的基点上,使历史更线性,但会生成新的提交哈希。公共分支适合 merge,尚未共享的个人分支可以用 rebase 整理历史。

19. 什么是 Fast-forward 合并?

如果 main 停在 C2 后再也没动,而 feature 从它后面继续提交:

main
 ↓
C1---C2---F1---F2
              ↑
           feature

合并时不需要创建额外提交,Git 直接把 main 指针移动到 F2。这叫 Fast-forward,也就是快进合并。

如果团队希望明确保留“这个功能分支被合并过”的记录,可以执行:

git merge --no-ff feature

面试回答:当目标分支是待合并分支的直接祖先时,Git 可以只移动目标分支指针,不创建 merge commit,这叫 Fast-forward。使用 --no-ff 可以强制生成合并提交,保留功能分支的合并痕迹。

20. 代码冲突为什么会出现?怎么解决?

Git 擅长合并明确的修改。小周改了第 20 行,同事改了第 80 行,Git 通常能自动处理。可两个人都改了同一段逻辑,而且结果不一致,Git 就不知道业务上该听谁的。

冲突文件中会出现标记:

<<<<<<< HEAD
当前分支的内容
=======
另一个分支的内容
>>>>>>> feature

正确做法不是随手点“全部采用本地”,而是读懂两边意图,手动整理最终代码,然后执行:

git add conflicted-file
git commit                 # merge 场景

如果冲突发生在 rebase 中,则解决后执行:

git add conflicted-file
git rebase --continue

最后要运行测试。冲突标记消失,只能说明文本合上了,不代表业务逻辑一定正确。

面试回答:当不同分支对同一区域做出无法自动协调的修改时,Git 会产生冲突。开发者需要理解双方修改,手动得到最终内容,执行 git add 标记冲突已解决,再继续 commit 或 rebase,并通过测试验证结果。

21. 如何中止 merge 或 rebase?

小周处理了半天,发现自己合错了目标分支。与其继续硬解冲突,不如先退回操作前:

git merge --abort
git rebase --abort

两条命令分别用于中止进行中的 merge 和 rebase,并尝试恢复开始操作前的状态。

面试回答:进行中的 merge 可以用 git merge --abort 中止,进行中的 rebase 可以用 git rebase --abort 中止。它们会尽量把仓库恢复到操作开始前。

22. 为什么公共分支不建议随意 rebase?

假设小周和同事都基于提交 F2 开发。小周把共享分支 rebase 后,远程变成了 F1'F2'。虽然代码看起来相似,Git 眼里它们却是另一批提交。

同事本地还保留旧历史,之后拉取和推送时,旧提交与新提交会重新碰面,可能造成重复记录和复杂冲突。

面试回答:rebase 会改写提交并改变哈希。如果其他人已经基于旧提交开发,改写后的历史会与他们的本地历史分叉,容易产生重复提交和冲突。因此不要对已经共享并被他人依赖的提交随意 rebase。

23. 如何整理多个零散提交?

功能分支上可能有这样的提交:

完成登录页
修一下
又漏了一个空指针
真的修好了

合并前可以用交互式 rebase 整理:

git rebase -i HEAD~4

常见操作有 pickrewordsquashfixupdrop。例如把后三个修补提交合并进第一个,最终留下一个容易阅读、容易回滚的功能提交。

这仍然属于改写历史,只适合整理自己的未共享分支。

面试回答:可以使用 git rebase -i 交互式整理提交。reword 修改说明,squashfixup 合并提交,drop 删除提交。因为操作会改写历史,通常只在个人分支尚未共享时使用。

24. Git Flow、GitHub Flow 和主干开发有什么区别?

这三种模式没有绝对高下,只是适合不同团队。

Git Flow 设计了 maindevelopfeaturereleasehotfix 等分支,适合版本发布节奏明显、需要维护多个阶段的项目,但流程偏重。

GitHub Flow 简单一些:从主分支拉功能分支,提交 Pull Request,审查和测试通过后合并,常见于持续交付团队。

主干开发要求开发者频繁向主干集成。为了不把半成品直接暴露给用户,团队通常配合自动化测试和特性开关。

面试回答:Git Flow 分支角色完整,适合发布流程较重的项目;GitHub Flow 以短期功能分支和 Pull Request 为中心,适合持续交付;主干开发强调小批量、频繁集成,通常依赖完善的自动化测试和特性开关。选择取决于团队规模和发布方式。


第四站:下午四点,撤销命令不是“后悔药大礼包”

小周终于遇到了开头那道面试题:刚提交的代码想撤销,但文件内容必须留下。

组长没有直接告诉他命令,只问了三句话:

要移动分支指针吗?
要重置暂存区吗?
要覆盖工作区吗?

只要能回答这三句,reset 的三个模式就不会再混。

在这里插入图片描述

图 5:先判断修改处于哪一层、是否已经共享,再选择对应的撤销或恢复命令。

25. git resetgit revert 有什么区别?

把提交历史想成一本已经编好页码的账本。

reset 是把当前分支的书签移回旧页。后面的提交可能不再属于当前分支,历史看起来像被改写了,适合处理尚未共享的本地提交。

revert 不撕旧账,而是在最后新增一页“冲销记录”,用反向修改抵消目标提交。旧提交还在,所以更适合公共分支。

面试回答:git reset 通过移动分支指针回到指定提交,可能改写可见历史,适合处理未共享的本地提交;git revert 创建一个新的反向提交来抵消原修改,不删除历史,适合已经推送的公共分支。

26. git reset --soft--mixed--hard 有什么区别?

先看三层变化:

模式移动 HEAD/分支重置暂存区重置工作区
--soft
--mixed
--hard

撤销最近一次提交但保留暂存状态:

git reset --soft HEAD~1

撤销提交,保留文件修改但取消暂存:

git reset --mixed HEAD~1

--mixed 是默认模式,所以也常写成 git reset HEAD~1

--hard 会同时覆盖暂存区和工作区。未提交的修改可能直接丢失,不能把它当普通清理命令随手执行。

面试回答:soft 只移动 HEAD,暂存区和工作区不变;mixed 移动 HEAD 并重置暂存区,但保留工作区,它也是默认模式;hard 会同时重置 HEAD、暂存区和工作区,可能丢失未提交修改。

27. 已经 git add,但还没提交,如何取消暂存?

文件已经装进“快递箱”,现在只想拿回桌上继续改,不想扔掉内容:

git restore --staged UserService.java

它会取消暂存,工作区修改仍然保留。老写法是:

git reset HEAD UserService.java

面试回答:可以使用 git restore --staged <file> 将文件移出暂存区,同时保留工作区修改。旧版本中也常使用 git reset HEAD <file>

28. 如何丢弃工作区中尚未暂存的修改?

如果确认这些修改不要了,可以执行:

git restore UserService.java

它会用暂存区中的版本覆盖工作区文件。因为这些修改还没有进入 Git 对象库,丢弃后未必能找回,执行前最好先看 git diff

面试回答:可以使用 git restore <file> 恢复工作区文件,它通常会用暂存区版本覆盖当前修改。未提交内容可能无法恢复,因此操作前应先确认差异或临时 stash。

29. 如何撤销最近一次提交,但保留代码?

这就是小周面试时卡住的题。

如果想让代码继续留在暂存区:

git reset --soft HEAD~1

如果想保留工作区修改,同时取消暂存:

git reset HEAD~1

如果提交已经推送到团队公共分支,通常不再移动公共历史,而是:

git revert HEAD

面试回答:未推送时,可以用 git reset --soft HEAD~1 撤销提交并保留暂存状态,或用默认 mixed reset 保留工作区代码但取消暂存。若提交已经共享,应优先使用 git revert HEAD 新增反向提交。

30. git loggit reflog 有什么区别?

git log 像项目正式出版的历史书,只显示从当前分支等引用能够到达的提交。

git reflog 更像本地监控录像。commit、reset、rebase、分支切换都会让引用移动,这些动作会被 reflog 记录一段时间。即使某个提交已经不在正常分支历史里,也可能从 reflog 中找到。

git reflog

reflog 主要是本地记录,不会作为普通提交历史推送给别人。

面试回答:git log 展示当前引用可达的提交历史;git reflog 记录本地 HEAD 和分支引用的移动过程。误执行 reset、rebase 或删除分支后,可以通过 reflog 找到原提交。reflog 通常不会推送到远程。

31. 如何恢复误删的提交或分支?

小周误删分支后,先用 reflog 找到了原来的提交:

git reflog

假设目标哈希是 a1b2c3d,重新创建分支即可:

git switch -c recovered-feature a1b2c3d

如果提交已经存在于远程分支、Tag 或其他开发者仓库中,也可以从这些引用恢复。真正危险的是未提交的工作区内容,因为 Git 可能从未保存过它。

面试回答:先通过 git reflog、远程分支或 Tag 找到目标提交哈希,再使用 git switch -c <new-branch> <commit-id> 创建分支恢复。已提交内容通常有机会找回,未进入 Git 对象库的修改则不一定能恢复。

32. 代码提交到了错误分支怎么办?

假设提交本来属于 feature,却落到了 main

先记住错误提交的哈希,然后在正确分支接收它:

git switch feature
git cherry-pick <commit-id>

再处理错误分支。如果 main 上的提交没有推送,可以谨慎 reset;如果已经共享,就用 revert 产生一条明确的撤销记录。

面试回答:可以先切到正确分支,用 git cherry-pick 接收错误提交。随后处理原分支:未推送的本地提交可以 reset,已经推送到公共分支的提交应优先 revert。


第五站:准备推送,团队协作最怕“我本地明明是好的”

个人仓库里,许多操作只是自己收拾房间。公共分支则像共享办公室:移动一把椅子之前,得先看看上面有没有坐着同事。

33. 为什么不建议直接 git push --force

普通 push 被拒绝,往往说明远程分支出现了本地没有的新提交。--force 会无视这个情况,用本地历史覆盖远程历史,同事刚推送的工作可能因此从分支上消失。

相对安全的方式是:

git push --force-with-lease

它会检查远程分支是否仍处于本地预期的位置。如果别人已经更新远程分支,推送就会被拒绝。

不过,“相对安全”不等于“可以随便用”。在受保护的主分支上,团队通常直接禁止强制推送。

面试回答:git push --force 可能覆盖远程分支上其他人的新提交。--force-with-lease 会先检查远程引用是否符合本地预期,若远程已经变化则拒绝推送,因此更安全,但公共主分支仍应避免强推。

34. .gitignore 为什么对已提交文件无效?

.gitignore 像仓库门口的“以后不要收这类文件”告示。文件如果早已登记入库,贴告示不会让它自动消失。

想让 Git 停止跟踪,但保留本地文件,可以执行:

git rm --cached application-local.yml

随后把路径写入 .gitignore 并提交。若一个目录下已有大量被跟踪文件,处理前应先确认范围,避免误删索引内容。

面试回答:.gitignore 只影响尚未被 Git 跟踪的文件,对已经提交或进入索引的文件无效。可以用 git rm --cached 取消跟踪,再提交 .gitignore 规则。

35. 密码已经提交到 Git,加入 .gitignore 能解决吗?

不能。密码已经出现在历史提交中,后来忽略文件只会影响未来的跟踪行为。

处理顺序很重要。第一件事不是清理 Git 历史,而是让泄露的凭证立即失效:撤销 Token、轮换密码或更换密钥。之后再删除当前代码中的秘密,必要时使用专门工具清理历史,并通知协作者重新同步。

以后把配置改为读取环境变量或密钥管理服务,不要把真实凭证写进仓库。

面试回答:不能。.gitignore 无法删除历史中的敏感信息。发现泄露后应先吊销或轮换凭证,再从当前代码和必要的历史记录中清除秘密,通知协作者重新同步,并改用环境变量或密钥管理服务。

36. 标签和分支有什么区别?

分支会随着新提交向前移动,像阅读进度书签;标签通常固定指向某个提交,像印在书页上的版本章。

发布版本时常创建附注标签:

git tag -a v1.0.0 -m "release v1.0.0"
git push origin v1.0.0

Lightweight Tag 只是一个简单引用。Annotated Tag 还包含标签作者、时间和说明,也可以进行签名,更适合正式发布。

面试回答:分支是会随提交移动的引用,主要用于持续开发;标签通常固定指向某个提交,用于标记发布版本。Annotated Tag 包含作者、时间和说明,Lightweight Tag 则只是简单引用。

37. 拉取代码时发生冲突,应该怎么处理?

先执行:

git status

如果本地还有未完成修改,先提交或 stash,别让“旧工作没收好”和“新代码拉不下来”混成一个问题。然后 fetch,确认远程变化,再选择 merge 或 rebase。

冲突发生后,逐个理解双方代码、解决标记、加入暂存区并继续操作。最后跑测试。

面试回答:先用 git status 检查本地状态,未完成修改应先提交或 stash。获取远程变化后,手动解决冲突并执行 git add,再继续 merge 或 rebase。解决文本冲突后还要运行测试,确认业务逻辑正确。


第六站:线上出 Bug,Git 也能帮你找“凶手”

Git 不只负责保存代码。历史足够清楚时,它还能帮团队定位问题、自动检查提交,并管理一些特殊文件。

38. git bisect 有什么作用?

线上 Bug 在今天被发现,但团队只知道“两周前还是好的”。从几百个提交里逐个排查太慢,git bisect 会用二分查找不断缩小范围。

git bisect start
git bisect bad
git bisect good <某个正常提交>

Git 会检出中间提交。测试后告诉它当前版本是 good 还是 bad,它再选择下一半。若项目有可靠的自动化测试,还能让 bisect 自动运行脚本。

面试回答:git bisect 使用二分查找定位首次引入问题的提交。先标记一个 bad 提交和一个 good 提交,再不断判断中间版本,查找次数大约是对数级;它也可以配合测试脚本自动执行。

39. Git 为什么不适合直接保存大型二进制文件?

文本代码修改一行,Git 很容易压缩和比较。视频、压缩包、模型权重这类二进制文件稍微变化,就可能产生一个新的大对象。即使后来删除文件,旧版本仍在历史里,仓库体积不会自动缩回去。

Git LFS 的做法是:普通 Git 仓库保存一个小型指针文件,真实大文件放在单独的 LFS 存储中。

面试回答:大型二进制文件不利于差异比较和版本压缩,多次修改会让仓库历史持续膨胀。Git LFS 在仓库中保存指针,把真实大文件放到独立存储,更适合模型、视频和大型设计资源。

40. 什么是 Git Hook?

Git Hook 是在特定 Git 事件发生时运行的脚本。

例如,pre-commit 可以在提交前检查格式,commit-msg 可以验证提交说明,pre-push 可以在推送前运行测试。服务端的 pre-receivepost-receive 则能参与接收代码后的检查和部署流程。

.git/hooks 下的客户端脚本默认不会跟着仓库提交。团队若想统一规则,通常会借助 Husky、pre-commit 等工具,或者在 CI 和服务端再做一层强制校验。仅靠本地 Hook 防不住主动绕过。

面试回答:Git Hook 是在 commit、push、receive 等事件前后执行的脚本,可用于格式检查、测试和提交信息校验。客户端 Hook 默认不会随仓库分发,也可能被跳过,因此重要规则还应放在 CI 或服务端执行。

41. 什么是浅克隆?

一个项目有十年历史,但构建服务器只需要最新代码。此时可以限制克隆深度:

git clone --depth 1 <repository-url>

这样下载更快、占用更少,但本地历史不完整,某些 log、bisect、历史比较和分支操作会受限制。需要时可以继续拉深,或转换为完整克隆。

面试回答:浅克隆通过 --depth 只获取有限深度的提交历史,可以减少下载时间和磁盘占用。代价是历史不完整,部分日志、比较和排错操作会受限,适合只需要当前代码的构建环境。

42. Submodule 解决什么问题?有什么代价?

公司项目依赖另一个独立维护的仓库,又希望主项目明确锁定它的某个版本,可以使用 Git Submodule。

主仓库不会直接保存子仓库全部内容,只记录子模块路径、远程地址配置以及一个特定提交。克隆后通常还要初始化和更新:

git submodule update --init --recursive

它的麻烦也很真实:切换主项目分支后,子模块可能仍停在旧状态;修改子模块后还要分别在子仓库和主仓库提交。团队需要统一操作流程。

面试回答:Git Submodule 用于让一个仓库引用另一个独立仓库的特定提交,适合依赖需要独立版本管理的场景。它能精确锁定版本,但克隆、切换、更新和提交都更复杂,团队必须管理好主仓库指针与子仓库状态。


面试前十分钟:把命令按“影响哪一层”再背一遍

命令很多,死记名字很容易串。按影响范围复习会轻松得多:

你的目的常用命令主要影响
把当前修改放进下次提交git add暂存区
取消暂存,保留代码git restore --staged暂存区
丢弃未暂存修改git restore工作区
临时收起未完成修改git stash工作区、暂存区
修改最近一次提交git commit --amend本地提交历史
撤销本地提交,代码留在暂存区git reset --softHEAD/分支
撤销本地提交,代码留在工作区git reset --mixedHEAD/分支、暂存区
连工作区一起回退git reset --hardHEAD/分支、暂存区、工作区
安全撤销公共提交git revert新增反向提交
只下载远程更新git fetch远程跟踪分支
下载并合入当前分支git pull远程跟踪分支、当前分支

再记住四组最容易混的:

add 是准备提交,commit 才是生成历史。
fetch 只取回,pull 还会继续合入。
reset 移动旧历史,revert 新增撤销历史。
merge 保留分叉,rebase 重排成直线。

最后做一次模拟追问

面试官问:“你本地有一次错误提交,还没有 push,希望撤销提交但保留代码,怎么办?”

你可以先答:

如果希望代码仍在暂存区,我会使用 git reset --soft HEAD~1;如果只想保留工作区修改并取消暂存,就使用默认的 mixed reset。因为提交还没有共享,所以可以移动本地历史。

面试官接着问:“如果已经 push 了呢?”

如果是团队公共分支,我会优先使用 git revert 创建反向提交,不随意 reset 后强推,避免改写别人已经依赖的历史。

他再追问:“要是你真的 reset 错了?”

我会先停止继续改动,通过 git reflog 找到 reset 之前的提交,再创建恢复分支。只要提交对象还没被清理,通常有机会找回来。

这三问其实只考一件事:你是否知道当前变化发生在工作区、暂存区还是提交历史,以及这段历史有没有被别人使用。

小周后来又参加了一次面试。面试官仍然问 reset、revert、rebase,他没有逐字背定义,而是先在脑子里画出三层区域和几根分支指针。命令终于不再是一堆长得很像的咒语。

Git 的八股,大多就是这么回事:先判断代码在哪,再决定要把哪根指针、哪一层内容,移到哪里。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值