Git 命令很多,但最值得先理解的是它的对象模型。知道 .git 目录里保存了什么,HEAD 指向哪里,reset 改了哪一层,误删提交后为什么还能从 reflog 找回来,很多排障就不再靠猜。
Git 可以先理解成一个内容寻址的文件系统,再叠加分支、暂存区和工作区。
.git 目录里有什么
一个仓库初始化后,会出现 .git 目录。里面比较关键的是:
objects:保存 Git 对象。refs:保存分支和标签引用。HEAD:当前工作区指向的提交或分支。config:仓库配置。hooks:钩子脚本。
真正的历史内容主要在 objects 里。Git 会根据内容计算哈希,把对象存成文件。对象内容不变,哈希就不变。
Git 有几类核心对象
常见 Git 对象有三类:
- blob:文件内容。
- tree:目录结构,记录文件名、权限和 blob。
- commit:一次提交,指向 tree,并记录作者、提交信息和父提交。
可以用:
git cat-file -t <hash>
git cat-file -p <hash>
查看对象类型和内容。
一次 commit 并不是直接保存“改了哪些文件”的文字描述,而是指向一个 tree。tree 再指向一批 blob 或子 tree。提交之间通过父提交串成历史。
HEAD 是当前指针
.git/HEAD 通常内容类似:
ref: refs/heads/main
这表示 HEAD 当前指向 main 分支。main 分支文件里保存了一个 commit hash。
如果你 checkout 到某个具体 commit,而不是分支,HEAD 会直接指向 commit hash,这就是 detached HEAD。
分支本质上只是一个可移动指针。提交一次,当前分支指针往前移动到新 commit。
工作区、暂存区和版本库
Git 日常操作可以分三层:
- 工作区:你正在编辑的文件。
- 暂存区:
git add后准备提交的快照。 - 版本库:
git commit后形成的历史对象。
对应关系:
git add # 工作区 -> 暂存区
git commit # 暂存区 -> 版本库
很多命令的区别,其实就是它们改了哪一层。
reset 的三种模式
git reset 最容易让人紧张,但理解三层后就清楚了。
git reset --soft HEAD~1
只移动分支指针,不改暂存区和工作区。撤销提交但保留已暂存内容。
git reset --mixed HEAD~1
移动分支指针,并重置暂存区,工作区保留改动。默认就是 mixed。
git reset --hard HEAD~1
移动分支指针,重置暂存区,也重置工作区。这个会丢掉未保存改动,最危险。
所以日常不确定时,优先别用 --hard。先看 git status 和 git diff,确认哪些内容还需要保留。
reflog 是误操作救命线
Git 会记录 HEAD 和分支指针移动历史,这就是 reflog。
git reflog
即使你 reset 掉了一个提交,只要对象还没被垃圾回收,通常还能在 reflog 里看到旧 commit。
恢复方式可以是:
git checkout <old-commit>
或者新建分支:
git branch recover <old-commit>
如果确认要回到那个提交:
git reset --hard <old-commit>
最后这条仍然要谨慎,因为它会改工作区。
常见排障怎么想
提交不见了
先看:
git reflog
找到旧 commit,再新建恢复分支。
add 错了
如果只是暂存错文件:
git restore --staged <file>
这只从暂存区拿下来,不删除工作区文件。
改错文件想撤销
如果确认工作区改动不要了:
git restore <file>
执行前先 git diff <file> 看一眼,避免撤掉有用内容。
分支乱了
先看图:
git log --oneline --graph --decorate --all
不要急着 reset。先搞清楚当前分支、远端分支和目标提交的位置。
最后记忆
Git 的底层不是黑盒。blob 存文件内容,tree 存目录结构,commit 指向 tree 和父提交,branch 是可移动指针,HEAD 指向当前分支或提交。
命令出问题时,先问:它改的是工作区、暂存区,还是分支指针?这个问题答清楚,大多数 Git 排障就有方向了。




