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 的关系查看当前 HEADcat.git/HEAD可能输出ref: refs/heads/main这表示HEAD 当前指向 main 分支也就是HEAD - main再查看main分支指向哪个 commitcat.git/refs/heads/main可能输出一串完整的 commit IDd498f41234567890abcdef...这说明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 的底层信息查看当前提交的完整 IDgitrev-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.cgitshow HEAD:main.c含义git show 查看 Git 对象或提交内容 HEAD:main.c 当前提交中的 main.c 文件查看上一次提交中的main.cgitshow 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 commitnanomain.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 IDgit rev-parse HEAD查看当前提交的完整 commit IDgit cat-file -t HEAD查看 HEAD 指向对象的类型git cat-file -p HEAD查看当前 commit 对象内容git ls-tree HEAD查看当前提交保存的目录结构git show HEAD:main.c查看当前提交中的main.cgit show HEAD~1:main.c查看上一次提交中的main.cgit 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。