Git 基础操作与核心内部结构:从工作区到 Commit ID
摘要
本文整理 Git 的基础使用流程,并补充 .git 文件夹与 Commit ID 的作用。重点是理解文件如何从工作区进入暂存区,再通过一次提交形成可追踪的项目快照。
关键词:Git、版本控制、工作区、暂存区、.git、Commit ID、分支、对象库
一、为什么需要版本控制
当项目不断迭代时,仅靠复制文件夹或手动修改文件名来保存版本,容易出现版本混乱、修改内容不透明、难以回退和多人协作困难等问题。
版本控制系统会记录项目的变化,使每次重要修改都能被定位、比较和恢复。
Git 是一种分布式版本控制系统,可以管理源代码、文本文件、配置文件以及其他格式的文件。对于图片、视频等二进制文件,Git 可以保存版本,但通常无法像文本文件那样直观地展示每一行发生了什么变化。
二、安装与检查 Git
1. 检查是否已安装
git--version如果已安装,命令会输出 Git 的版本号。命令行参数应使用两个普通短横线:–version。
2. CentOS 7.6
sudoyuminstallgit-y如果确实需要卸载已有版本,可以执行:
sudoyum removegit-y通常不需要为了安装 Git 先卸载已有版本,是否卸载应根据版本要求和系统包管理策略决定。
3. Ubuntu 20.04
sudoapt-getupdatesudoapt-getinstallgit-y如确实需要卸载已有版本:
sudoapt-getremovegit-y三、创建本地仓库
进入项目目录后执行:
cd项目目录gitinit也可以在初始化时指定默认分支名:
gitinit-bmain执行初始化后,当前目录会出现一个隐藏的 .git 文件夹。它保存仓库的元数据、对象数据库、引用和暂存区等内容,是 Git 管理项目的核心。
不要手动修改 .git 内部文件。需要改变仓库状态时,应优先使用 Git 命令,否则可能破坏本地仓库。
需要注意,初始化后的分支不一定叫 master。分支名可能由 Git 版本、init.defaultBranch 配置项或 git init -b 决定,应以当前仓库的实际状态为准。
四、配置提交者信息
每次提交都会记录作者和提交者信息,常见配置项是 user.name 和 user.email。
不带 --global 时,配置只对当前仓库生效;带 --global 时,对当前用户在本机管理的多个仓库生效。仓库级配置通常会覆盖全局配置。
gitconfig user.name"pineapple"gitconfig user.email"123456789@example.com"设置全局配置:
gitconfig--globaluser.name"pineapple"gitconfig--globaluser.email"123456789@example.com"查看配置:
gitconfig--list--localgitconfig--list--globalgitconfig--list--show-origin删除配置:
gitconfig--unsetuser.namegitconfig--global--unsetuser.name五、理解 Git 的基本工作流
Git 的基础工作流可以分为三个主要区域:
| 区域 | 作用 |
|---|---|
| 工作区(working tree) | 当前目录中正在编辑的文件 |
| 暂存区(index / staging area) | 下一次提交准备包含的内容 |
| 提交历史(repository) | 已经提交的项目快照和历史关系 |
完整流程是:在工作区修改文件,使用 git add 选择要提交的内容,再使用 git commit 形成新的提交。
1. 查看状态和差异
gitstatusgitdiffgitdiff--cachedgit diff 默认比较工作区与暂存区,git diff --cached 比较暂存区与当前提交(HEAD)。两者结合使用,可以确认“工作区改了什么”和“下一次准备提交什么”。
2. 添加到暂存区
gitaddREADME.mdgitaddsrc/app.jsgitadd.git add 添加的是命令执行时指定文件的当前内容。之后如果文件再次修改,新的修改不会自动进入暂存区,需要再次执行 git add。
被 .gitignore 忽略的文件默认不会被添加。
3. 创建提交
gitcommit-m"完善 Git 基础操作说明"git commit 默认只提交已经进入暂存区的内容,不会自动包含工作区中尚未暂存的修改。提交成功后,当前分支引用会向前移动,指向新生成的提交。
4. 查看提交历史
gitloggitlog--onelinegitshow<commit-id>git log 默认按时间从近到远展示当前引用可达的提交记录;–oneline 使用较简洁的单行形式展示。
六、.git 文件夹内常见内容
.git 的具体文件和目录可能因 Git 版本、仓库状态、引用存储格式以及执行过的操作而不同。下面是常见内容及其作用。
| 路径 | 作用 |
|---|---|
| HEAD | 表示当前检出位置,通常指向当前分支,也可能直接记录某个提交 ID |
| config | 当前仓库的配置文件,例如仓库级用户名、邮箱和远程仓库信息 |
| index | 暂存区的内部文件,记录下一次提交准备包含的路径、模式和内容对象 |
| objects/ | Git 对象数据库,保存 blob、tree、commit、tag 等对象;对象也可能被压缩到 pack 文件中 |
| refs/heads/ | 本地分支引用,例如 refs/heads/main,通常指向该分支最新提交 |
| refs/tags/ | 标签引用,用于给特定对象起一个稳定、易读的名称 |
| logs/ | 引用日志(reflog)相关记录,用于追踪 HEAD 或分支引用曾经指向过哪里 |
| hooks/ | 仓库钩子脚本目录,可用于提交前检查等自动化操作 |
| info/exclude | 只对当前仓库生效的忽略规则,不会随仓库提交给其他协作者 |
| packed-refs | 某些引用被压缩存储时使用的文件 |
| FETCH_HEAD | 某些获取远程仓库操作产生的临时记录,并非所有时刻都存在 |
HEAD、分支和提交之间的关系
在正常检出状态下,可以把关系理解为:
HEAD -> main -> <commit-id>查看当前分支:
gitbranch --show-current查看 HEAD 当前解析到的提交:
gitrev-parse HEADHEAD 通常指向当前分支,而当前分支引用再指向该分支最新提交。
七、Commit ID 的作用
1. Commit ID 不是简单的流水号
Commit ID 也称 commit hash 或 commit object name,是根据提交对象内容计算出的标识。默认对象格式通常使用 SHA-1,表现为 40 位十六进制字符串;Git 也支持在初始化时选择 SHA-256 对象格式的仓库。
因此,Commit ID 不是“第几个提交”,也不是 Git 自动递增的编号。
2. 一个提交对象记录了什么
一个提交对象通常包含:
- 项目在该时刻的顶层 tree 对象,间接描述目录结构和文件内容;
- 一个或多个 parent 提交,用于连接历史;合并提交可能有多个父提交;
- 作者、提交者、时间戳和提交说明。
由于提交对象引用了目录树和父提交,当提交内容或历史关系发生变化时,Commit ID 也会变化。Git 可以通过对象之间的引用关系组织历史,并对历史内容提供完整性校验。
3. 前两位为什么不是 Commit ID 的含义
在 loose object 存储形式下,一个完整对象 ID 可能被拆成如下路径:
完整对象 ID:abcdef1234567890... 存储路径:.git/objects/ab/cdef1234567890...这里的前两位只是为了把对象分散到子目录中的存储布局,不代表提交类型、分支、时间或版本号。
对象也可能已经被放入 .git/objects/pack 的 pack 文件中,因此不能假定每个 Commit ID 都能在 .git/objects/前两位/ 下找到一个独立文件。
4. Commit ID 的实际用途
定位当前提交:
gitrev-parse HEAD查看某次提交的内容:
gitshow<commit-id>比较两个版本:
gitdiff<commit-id-1><commit-id-2>撤销某次提交:
gitrevert<commit-id>将某次提交复制到当前分支:
gitcherry-pick<commit-id>检查对象类型:
gitcat-file-t<commit-id>gitcat-file-p<commit-id>Git 允许在对象可以被唯一识别时使用缩写 ID,但协作沟通、脚本和审计记录更适合使用完整 ID,或至少确认当前仓库中该缩写没有歧义。
八、一个完整的最小示例
mkdirgit-democdgit-demogitinit-bmaingitconfig user.name"pineapple"gitconfig user.email"123456789@example.com"echo"# Git Demo">README.mdgitstatusgitaddREADME.mdgitdiff--cachedgitcommit-m"初始化项目"gitlog--oneline这组命令对应的状态变化是:
- README.md 先出现在工作区;
- 执行 git add 后,文件内容进入暂存区;
- 执行 git commit 后,Git 形成一个提交对象;
- 当前分支引用和 HEAD 一起指向这个提交;
- 之后每次新的提交都会产生新的 Commit ID。
九、容易混淆的几个点
- git add 不是保存到最终历史,而是选择下一次提交的内容。
- git commit 默认只提交暂存区,不会自动包含所有未暂存修改。
- 分支不是一份独立复制的代码目录,而是指向提交的可移动引用。
- .git 不是普通缓存目录;删除或错误修改它,可能导致本地历史、分支和配置丢失。
- Commit ID 标识的是一个对象;前两位目录拆分属于 loose object 的存储实现,不是 ID 的语义。
- 二进制文件可以被 Git 版本化,但差异查看、合并和仓库体积管理需要额外考虑。
总结
Git 的基础操作可以围绕一条主线理解:
工作区负责编辑,暂存区负责选择,提交负责形成快照,分支和 HEAD 负责指向快照,.git 负责保存这些关系和对象。
Commit ID 是提交对象的内容地址,既用于精确定位一个版本,也用于连接提交历史、检查历史一致性,以及支持回退、比较、撤销和协作。
参考资料
- Git 官方:git-init
- Git 官方:git-add
- Git 官方:git-config
- Pro Git:Git Objects
- Pro Git:Git References
- Pro Git:Viewing the Commit History