Git 的深入理解:工作区、暂存区、本地仓库与 .git目录
本文的内容主要是针对git的本地仓库,着重对.git文件夹中的内容进行阐述
学习 Git 时,最容易混淆的一点是:代码文件在哪里?git commit又提交到了哪里?
可以先记住一句话:
你写的代码在工作区里; git commit 之后的版本数据保存在 .git 目录里。1. Git 仓库的基本结构
假设当前项目目录如下:
git_c_demo/ ├── main.c ├── Makefile ├── .gitignore └── .git/其中可以分成两类:
main.c / Makefile / .gitignore 工作区文件 .git/ Git 本地仓库数据库也就是说:
项目目录 = 工作区 + .git 本地仓库数据库main.c、Makefile是你平时直接编辑的文件,属于工作区。
.git/是 Git 自动维护的隐藏目录,里面保存提交历史、分支信息、暂存区、远端配置等内容。
不要手动删除或随意修改.git/,否则当前目录可能不再是一个正常的 Git 仓库。
2.git commit提交到了哪里?
当执行:
gitcommit-m"修改 main 输出内容"时,Git 并不是把代码上传到了 GitHub,而是把当前暂存区里的内容保存到了本地仓库数据库中。
也就是保存到:
.git/objects/所以:
git commit 是本地操作; git push 才是把本地提交上传到远端仓库,例如 GitHub。可以理解为:
git add 把修改放入暂存区 git commit 把暂存区内容保存到 .git/objects/ git push 把本地 commit 推送到远端仓库3..git目录中有哪些重要内容?
可以使用下面命令查看:
ls-al.git常见内容如下:
| 文件或目录 | 作用 |
|---|---|
.git/HEAD | 记录当前位于哪个分支 |
.git/config | 当前仓库配置,例如远端origin地址 |
.git/index | 暂存区 |
.git/objects/ | 保存 Git 对象,包括 commit、tree、blob |
.git/refs/heads/ | 保存本地分支指针 |
.git/refs/remotes/ | 保存远端追踪分支指针 |
.git/logs/ | 保存 HEAD 和分支移动记录 |
其中最核心的是:
.git/objects/ 保存真正的版本数据 .git/refs/heads/ 保存分支当前指向哪个 commit .git/HEAD 保存当前所在位置 .git/index 保存暂存区4. HEAD、branch 和 commit 的关系
查看当前 HEAD:
cat.git/HEAD可能输出:
ref: refs/heads/main这表示:
HEAD 当前指向 main 分支也就是:
HEAD -> main再查看main分支指向哪个 commit:
cat.git/refs/heads/main可能输出一串完整的 commit ID:
d498f41234567890abcdef...这说明:
main 分支本质上是一个指针,里面保存了最新 commit 的 ID。关系可以理解为:
HEAD -> main -> 某个 commit也就是说:
HEAD 表示你当前在哪里; main 是当前分支; main 指向某个具体的 commit。补充一点:有时 Git 会把引用压缩到.git/packed-refs中,这时.git/refs/heads/main可能不一定直接存在。但概念不变,分支本质上仍然是指向 commit 的引用。
5. Git 底层的三类核心对象
Git 保存版本时,主要涉及三类对象:
commit 提交对象 tree 目录对象 blob 文件内容对象它们的关系是:
commit ↓ tree ↓ blob可以这样理解:
commit:一次提交记录 tree:这次提交对应的目录结构 blob:具体文件内容例如一个项目中有:
main.c Makefile .gitignore一次 commit 大概记录的是:
commit └── tree ├── blob main.c 的内容 ├── blob Makefile 的内容 └── blob .gitignore 的内容所以,commit 并不是简单保存“一个文件”,而是保存了:
当前项目目录快照 父提交 parent 作者 author 提交者 committer 提交说明 message6. 查看当前 commit 的底层信息
查看当前提交的完整 ID:
gitrev-parse HEAD含义:
git rev-parse 解析 Git 引用 HEAD 当前提交查看 HEAD 指向对象的类型:
gitcat-file-tHEAD可能输出:
commit表示 HEAD 指向的是一个 commit 对象。
查看当前 commit 的内容
gitcat-file-pHEAD含义:
git cat-file 查看 Git 对象 -p pretty-print,以友好格式打印 HEAD 当前提交输出中可能包含:
tree xxxxxxx parent xxxxxxx author ... committer ... 提交说明其中:
tree 当前提交对应的目录快照 parent 上一个提交 author 作者 message 提交说明7. 查看某个 commit 中保存了哪些文件
查看当前提交对应的目录树:
gitls-tree HEAD含义:
git ls-tree 查看某个提交中的目录结构 HEAD 当前提交可能输出:
100644 blob xxxxxx .gitignore 100644 blob xxxxxx Makefile 100644 blob xxxxxx main.c这表示当前 commit 中保存了这些文件。
8. 查看 commit 中的某个文件内容
查看当前提交里的main.c:
gitshow HEAD:main.c含义:
git show 查看 Git 对象或提交内容 HEAD:main.c 当前提交中的 main.c 文件查看上一次提交中的main.c:
gitshow HEAD~1:main.c含义:
HEAD 当前提交 HEAD~1 当前提交的上一个提交(以此类推,1可替换) :main.c 该提交中的 main.c 文件这个命令非常适合用来对比历史版本中的文件内容。
9. 工作区文件和 commit 中的文件有什么区别?
执行:
catmain.c看到的是:
工作区当前的 main.c执行:
gitshow HEAD:main.c看到的是:
最近一次 commit 中保存的 main.c当工作区干净时,它们通常一样。
但如果你修改了main.c还没有提交:
cat main.c 显示工作区的新内容 git show HEAD:main.c 显示最近一次 commit 中的旧内容这说明:
工作区文件 ≠ Git 已提交版本Git 管理的不是“当前文件本身”,而是文件在不同提交中的历史快照。
10. 推荐的观察实验
可以通过一个小实验理解工作区和本地仓库的区别。
先查看当前状态:
gitstatus查看工作区文件:
catmain.c查看当前 commit 中保存的文件:
gitshow HEAD:main.c此时如果工作区干净,两者内容应该一致。
然后修改main.c,但不要git add,也不要git commit:
nanomain.c修改后执行:
catmain.cgitshow HEAD:main.cgitdiff你会发现:
cat main.c 显示你刚修改后的工作区内容 git show HEAD:main.c 显示最近一次 commit 中保存的内容 git diff 显示工作区和 Git 记录之间的差异最后撤销修改:
gitrestore main.c含义:
git restore main.c 把 main.c 从最近一次提交状态恢复回来,丢弃工作区修改11. 常用查看命令总结
| 命令 | 作用 |
|---|---|
ls -al .git | 查看.git仓库数据库目录 |
cat .git/HEAD | 查看 HEAD 当前指向哪个分支 |
cat .git/refs/heads/main | 查看 main 分支指向的 commit ID |
git rev-parse HEAD | 查看当前提交的完整 commit ID |
git cat-file -t HEAD | 查看 HEAD 指向对象的类型 |
git cat-file -p HEAD | 查看当前 commit 对象内容 |
git ls-tree HEAD | 查看当前提交保存的目录结构 |
git show HEAD:main.c | 查看当前提交中的main.c |
git show HEAD~1:main.c | 查看上一次提交中的main.c |
git log --oneline --graph --decorate --all | 查看提交历史、分支和 HEAD 关系 |
12. 最终总结
Git 的本质可以概括为:
Git 是一个保存在 .git 目录中的本地版本数据库。你的代码文件在工作区中,例如:
main.c Makefile .gitignoreGit 的版本数据保存在:
.git/执行:
gitadd是把工作区修改放入暂存区。
执行:
gitcommit是把暂存区内容保存为一个新的 commit 对象,存入:
.git/objects/当前分支,例如main,会指向最新的 commit。
HEAD表示你当前所在的位置,通常指向某个分支。
所以完整关系可以理解为:
工作区文件 ↓ git add 暂存区 .git/index ↓ git commit 本地仓库 .git/objects ↑ 分支引用 .git/refs/heads/main ↑ HEAD总结
Git 不是简单保存文件夹副本; Git 是通过 commit、tree、blob 对象保存项目历史快照; 分支只是指向 commit 的轻量级指针; HEAD 表示当前所在位置; git commit 只是提交到本地 .git,并不会自动上传到 GitHub。