简单来说,很多同学用了好几年 Git,其实一直是在“盲操作”,只知道 git add 和 git commit,但真要问起“代码现在到底在哪儿”,大概率说不清楚。这一节咱们就掰开了揉碎了聊聊 Git 的三个核心区域,这是你玩转 Git 高级用法的地基。
在 Git 2.45.0(2024年4月刚发布的版本,性能优化杠杠的)里,这三个概念依然是最底层的逻辑。咱们从最底层往上说:
这就是你肉眼能看到的文件夹。你在 VS Code 里改代码,删文件,新建目录,这些都是在操作工作目录。可以这么理解,这就是你当前正在折腾的“现场”。在这个区域里的文件,Git 管这叫 Untracked(未跟踪)或者 Modified(已修改)。
这是 Git 最精髓也最容易被忽视的地方。你可以把它理解成一个“购物车”或者“快照准备区”。当你执行 git add 的时候,并不是直接把代码扔进了仓库,而是把工作目录里的改动“拍个快照”,放到了这个 Index 里。
Index 里存的是你下一次提交(Commit)想要包含的内容。这也是为什么有时候你改了文件 A 和 B,结果提交的时候发现只有 A 进去了,大概率是你忘了 git add B。
HEAD 就是个指针,它指向你当前所在的“位置”。通常情况下,它指向某个分支(比如 main),而那个分支指向最新的 Commit。
main 分支,HEAD 指向 main。git checkout 或者 git switch 到了 dev 分支,HEAD 就跟着指向 dev。这三个东西是怎么流转的呢?看这个流程:
git add . -> 改动被打包进 Index。git commit -m "fix" -> Index 里的内容被永久保存成一个 Commit,HEAD 向前移动一步,指向这个新的 Commit。来个实战例子,咱们看看这三个区域是怎么互动的。假设你刚克隆了一个仓库,或者刚提交完代码,状态是干净的。
📖 学习建议:养成随时 git status 的好习惯。不要盲目操作。另外,如果你发现 git status 太慢(在巨型仓库里很容易遇到),可以关注 Git 2.45 引入的 Commit Graph 特性,它能通过位图文件加速查询,或者配置 core.untrackedCache 来缓存未跟踪文件的状态,这比傻傻地等命令执行要强得多。
---
老程序员肯定对 git checkout 这个命令又爱又恨。爱它是因为它功能强大,恨它是因为它太“精神分裂”了——它既能切换分支,又能恢复文件,参数稍微写错,代码就没了。
从 Git 2.23 开始,社区终于听进去了,引入了 git switch 和 git restore 来把这两个功能拆开。到了 Git 2.45.0,这两个命令已经非常成熟稳定,可以这么理解,除非你要兼容老得掉牙的脚本,否则请彻底放弃 git checkout。
git switch 专门负责切换分支。它的语义非常清晰,不容易误操作。
比如你想从 main 切到 dev:
如果你想基于当前分支新建一个分支并切换过去,switch 也有专门的参数:
实战经验提醒:如果你当前有未提交的改动,且这些改动会和你要切换过去的分支冲突,Git 会拒绝切换。git switch 在这方面比 checkout 更严格,其实这是在保护你,防止代码丢失。
git restore 专门负责恢复文件。这是重头戏,因为恢复文件是高频操作,也是最容易出事故的。
假设你改了 src/index.js,现在想把这个文件恢复到最近一次提交的样子(也就是丢弃工作区的改动):
再比如,你 git add 了一个文件到了暂存区(Index),突然反悔了,不想提交这个文件了,想把它从暂存区撤回来,但保留工作区的改动:
如果你既想把它从暂存区撤下来,又想直接把文件内容也恢复到上次提交的状态(相当于彻底放弃这个文件的改动):
假设你正在 feature/user 分支上开发,手贱改了三个文件,现在想全部丢弃,回到和远程分支一致的状态:
💡 经验总结:强制自己使用 switch 和 restore。如果你还在用老版本的 Git(低于 2.23),赶紧升级到 Git 2.45.0。新版本不仅这两个命令更好用,而且在 restore 的时候,如果检测到可能的误操作,会有更明确的提示。另外,配合 git switch 时,如果分支名不存在,它会提示你是否要创建,这比 checkout 直接报错要友好得多。
---
现在的大厂项目,动不动就是 Monorepo(单体巨仓库),几个 G 的代码量。你如果老老实实 git clone,估计能喝三杯咖啡。这时候,Git 2.45.0 里强调的 Sparse Checkout(稀疏检出) 和 Partial Clone(部分克隆) 就是你的救命稻草。
简单来说,这两个功能的核心思想就是:别全下,按需加载。
传统的 git clone 是把所有的历史提交、所有的文件内容(Blob)一股脑全拉下来。
Partial Clone 允许你只下载提交的历史(Commits 和 Trees),不下载具体的文件内容。当你真正需要查看某个文件时,Git 再去服务器上“按需抓取”。
这特别适合那种历史包袱重、大文件多的仓库。
如果说 Partial Clone 是控制“下载多少历史”,那 Sparse Checkout 就是控制“检出哪些目录”。
比如你的 Monorepo 结构是这样的:
你只是个前端开发,根本不需要看 backend 和 mobile 的代码。那你就可以只检出 frontend 目录。
在 Git 2.45 里,Sparse Checkout 默认使用的是 Cone Mode(锥形模式),性能比老模式好太多。
实战步骤:
实战经验记录:Sparse Checkout 开启后,你虽然在文件系统里看不到其他目录,但它们在 Git 的历史里还是存在的。如果你在 frontend 目录下执行 git log,它依然能看到涉及 backend 的提交历史,这很正常。另外,如果你在 Sparse 模式下切换分支,而那个分支没有 frontend 目录,Git 会报错,这时候需要调整 Sparse 规则或者切换回全量模式。
⚡ 效率提示:core.hooksPath 配置。在大型团队里,大家都在用 Sparse Checkout,但新人可能不知道怎么配。你可以在仓库里放一个 hooks 目录,然后通过 git config core.hooksPath .githooks 强制指向它,配合 Husky 或者自定义的脚本,在 post-checkout 钩子里自动检测并提示开发者设置 Sparse Checkout,这样能保证团队规范的一致性。参考 Git 2.45 的优化,这种配置在大型仓库里运行钩子速度也很快。
---
这一节咱们聊聊“多人怎么一起干活不打架”。Git 2.45 虽然在性能上做了很多优化,但协作规范主要还是靠人。最经典的莫过于 Feature Branch Workflow(特性分支工作流)以及让人头大的 Rebase vs Merge 争论。
简单来说,就是“别在 main 分支上瞎改”。
main 分支永远保持可发布状态。feature/xxx。main。这是目前 GitHub/GitLab 最主流的玩法。
很多新手分不清这俩,或者只知道 git merge。
实战演示:Rebase 的优雅
假设你从 main 拉了一个 feature/login 分支,你在开发的时候,main 已经被同事更新了。这时候你的分支落后了。
为什么推荐 --force-with-lease?
--force 是强制覆盖远程分支,不管别人有没有提交新的东西。
--force-with-lease 是“带租约的强制推送”,它会检查远程分支是不是你刚才拉取时的那个版本,如果中间有人推了代码,它会拒绝推送,防止你把同事的代码给覆盖了。这是 Git 2.45 环境下依然强烈推荐的安全操作。
在 GitHub 的 PR 界面,你经常会看到 "Squash and merge" 按钮。
它的作用是:把你 feature 分支上的 10 个 commit(可能包含 "fix typo", "wip", "test" 这种垃圾提交)合并成一个干净的提交,放到 main 上。
main 的历史非常干净,每个 PR 对应一个 Commit。git blame 只能看到那个巨大的 Squash Commit。📌 要点提醒:
feature 分支上,多用 git rebase -i HEAD~3 来整理自己的提交(比如把几个 fixup 合并成一个 feat),保持提交原子性(一个提交只做一件事)。- 如果是开源项目或者非常规范的团队,推荐 Squash Merge,保持主干整洁。
- 如果是内部项目,且大家水平都不错,推荐 Rebase + Merge(非 Squash),保留完整历史。
git push -f:在团队规范里明文规定,除非使用 --force-with-lease,否则禁止强制推送。main 还是 master:现在都 2024 年了,赶紧把默认分支从 master 改成 main 吧。Git 2.45 对此没有强制,但社区趋势就是这样。改名命令很简单:git branch -M main。可以这么理解,每个程序员都有过这种心跳骤停的时刻:手一抖执行了 git reset --hard,或者刚切完分支发现刚才写了半天的代码没提交也没保存,瞬间感觉天都要塌了。别慌,这时候千万别乱动当前目录,也别急着去重写代码,Git 里有两个“时光机”级别的命令能救你狗命:git reflog 和 git cherry-pick。
很多新手以为 git log 就是看历史的全部了,其实 git log 只能看到当前分支的提交链。而 git reflog(Reference Log)记录的是你本地仓库里 HEAD 指针每一次移动的记录。哪怕你做了 reset 把提交“删”了,或者切错了分支,只要你的本地仓库还在,Reflog 里就有记录。
举个例子,你正在 feature/login 分支上开发,突然脑子短路执行了:
这时候你用 git log 看,最近的提交全没了。别怕,赶紧敲这个:
你会看到类似这样的输出:
看到了吗?def5678 那个提交就是你的代码!现在只需要把 HEAD 指回去:
瞬间,你的代码就全回来了。值得留意的是,Reflog 默认只保留 90 天,所以出事了赶紧救,别等几个月后再想起来。
另一个场景也很常见:你在 dev 分支上修了一个紧急 Bug,提交哈希是 a1b2c3d,这时候你发现 main 分支也有这个 Bug,需要同步过去。你总不能把 dev 分支整个合并过来吧?那会把一堆还在测试的代码也带过去。这时候 cherry-pick 就派上用场了,它就像个“摘樱桃”的工人,只把你指定的那个提交“摘”过来应用到当前分支。
假设你在 main 分支,想把 dev 分支的那个修复拿过来:
如果没冲突,Git 会自动提交。如果有冲突,解决完冲突后执行:
📖 学习建议:cherry-pick 虽然好用,但别滥用。如果你经常需要把同一个提交到处“搬运”,说明你的分支管理策略可能有问题,或者这个改动应该被抽象成公共模块。另外,在 Git 2.45.0 这种新版本里,虽然核心逻辑没变,但性能优化让这种操作在大型仓库里也更丝滑了。
现在的 Git 早就不是那个只负责存代码的工具了,随着仓库越来越大(尤其是那些动不动几个 G 的 Monorepo),以及 AI 的介入,玩法也变了。作为一个踩过无数坑的工程师,我得跟你们聊聊 2024 年最火的两个趋势:AI 辅助提交和 Monorepo 的高效管理。
打个比方,写 Commit Message 是程序员最烦的事之一。这就导致很多人提交信息全是“update”、“fix”、“改了个bug”。这在团队协作里简直是灾难,出了问题你都不知道当初改了啥。
2024 年的趋势就是利用 AI(比如 GitHub Copilot 或者一些本地 LLM 工具)来自动生成规范的提交信息。现在很多插件能直接分析你的 git diff 内容,然后生成符合 Conventional Commits 规范的 Message。
比如你改了一堆代码,准备提交时,不用自己憋字:
AI 可能会帮你生成这样的提交信息:
这看起来是不是专业多了?面试官看到这种提交记录都会高看你一眼。
另一个大趋势就是 Monorepo(单体仓库)。前端、后端、基础设施全放一个仓库里,克隆一次几十个 G,网速慢点一下午都干不了活。这时候就得用上 Git 的新特性了。
Git 2.45.0 继续在性能上发力,特别是针对大型仓库的 Partial Clone(部分克隆)和 Sparse Checkout(稀疏检出)。
🔧 实战技巧:如果你入职的公司仓库巨大,千万别直接 git clone。试试这个组合拳:
`bash
# 使用 --filter=blob:none 进行部分克隆
git clone --filter=blob:none https://github.com/your-org/huge-monorepo.git
cd huge-monorepo
`
frontend 目录。`bash
# 开启稀疏检出功能
git sparse-checkout init --cone
# 设置只检出 frontend 目录
git sparse-checkout set frontend
`
这时候你 ls 一下,仓库里只有 frontend 文件夹,其他的目录对你来说就像不存在一样,速度飞快。
另外,微软贡献的 Scalar 技术也在逐渐普及,它能自动配置这些优化参数。如果你维护的是像 Windows 源码那种级别的仓库,一定要关注这个。还有那个 Reftable 后端,如果你发现仓库里分支多到爆炸导致操作变慢,这就是未来的解决方案。
面试的时候,面试官问 Git 可不只是问你怎么 add 和 commit。他们喜欢问一些“刁钻”的场景,考你对 Git 原理的理解。这里我挑几个最近社区里吵得比较热的话题和常见面试问题,帮你们理理思路。
这是面试必问,也是社区里争论不休的话题。其实,merge 是“合并”,它会生成一个新的合并提交,保留完整的历史时间线,看起来像是一棵树;rebase 是“变基”,它会把你的提交“挪”到目标分支的最新节点上,让历史变成一条直线。
面试怎么答?
rebase 保持提交历史的整洁。实战场景:你在 feature 分支开发,这时候 main 分支更新了,你需要同步一下。
如果在 rebase 过程中遇到冲突,解决后:
很多新手在 rebase 之后发现推不上去,就直接 git push --force,这是大忌!这会直接覆盖远程仓库,别人的代码可能就没了。
💡 经验总结:永远使用 --force-with-lease。这个参数会检查远程分支是否有你不知道的更新,如果有(说明别人提交了代码),它就会拒绝强制推送,保护队友的代码。
最近社区里有个热门话题:提交原子性。就是说“一个提交只做一件事”。面试官可能会问你:“你修 Bug 的时候顺便格式化了代码,该怎么提交?”
正确做法:不要混在一起。
这样,你的历史记录就非常清晰。虽然麻烦一点,但在回滚或者 cherry-pick 的时候,你会感谢当初这么做的自己。
还有个面试常考的点是 HEAD、Index 和 Working Directory 的关系。简单来说,Working Directory 就是你电脑里看到的文件;Index(暂存区)是你 git add 后准备提交的地方;HEAD 是指向当前分支最新提交的指针。理解了这三层,什么 checkout、reset、restore 就都通了。特别是 Git 2.45.0 里推荐的 git restore 命令,它就是专门用来操作 Working Directory 和 Index 的,比老旧的 checkout 清晰多了。