git的三个区域
-
git clone克隆远程资源到本地目录,作为工作目录; -
本地在新增/修改/删除之后,可以通过
git status查看文件的变动,还可以使用git diff对比文件的变化; -
然后使用
git add添加修改的文件到暂存区; -
在添加之后,可以使用
git commit提交到本地git仓库并生成记录; -
git push将本地的修改推送到远程的git服务器。 -
如果远程修改了,需要同步远程的内容可以使用
git fetch,当然也可以直接git pull同步并更新本地的文件; -
如果发现错误,可以使用
git reset撤回本地的提交记录; -
如果发现远程记录错误,可以使用
git revert生成一个新的提交来撤销远程记录的变更内容 -
你可以使用
git branch来增删查改分支,使用git checkout来切换当前分支 -
可以使用
git merge来合并分支,也可以使用git rebase来进行变基
存储实现原理
在Git中,一切都被视为对象。对象可以是文件内容、目录结构、提交信息、分支信息等。Git有三种主要类型的对象:blob(文件内容),tree(目录结构),commit(提交信息)。
目录结构
我们创建一个新目录,并执行git init初始化为一个git版本库,然后查看生成的隐藏目录.git
$ find .git/ -type d # 查看.git/下的所有目录
.git/
.git/hooks
.git/info
.git/objects
.git/objects/info
.git/objects/pack
.git/refs
.git/refs/heads
.git/refs/tags
git中所有生成的对象都存储在objects目录中
$ find .git/objects/ -type f # 查看.git/objects下的所有文件
1. git add原理
git add 会生成一个blob对象,然后将该对象添加到index区(即暂存区)
$ echo 'first add file'>readme.md
上面命令创建了一个文件readme.md,我们可以执行find .git/objects/ -type f看到 .git/objects 目录下面没有文件, 执行git status 看到 readme.md 文件是红色的,代表未加入index区;
接着我们执行
$ git add readme.md
$ find .git/objects/ -type f
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc
objects目录下多了一个文件,git status发现文件已经变成绿色,代表已添加进index区了
文件路径中的 6a 加上文件名 0b867bdc470c582c15906f264b9fec371cdbfc 可以合成一个 40 字符的hash值,这个字符就是 readme.md 文件在Git 仓库中的 唯一键,一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得到的校验和
值得一提的是只要内容没变那么hash值也不会变,也就是说不会生成新的对象
我们可以使用giit cat-file查看该目录下的对象,-t选项表示查看对象类型,-p表示查看对象内容
$ git cat-file -t 6a0b867bdc470c582c15906f264b9fec371cdbfc #可以只使用部分前缀,只要确能找到唯一对象即可。
blob
$ git cat-file -p 6a0b867bdc470c5
first add file
2. git commit原理
进行代码提交git commit时,需要根据暂存区的内容,先生成tree对象,再生成commit对象,然后会将记录记录到logs文件夹下
$ git commit -m "first commit"
$ find .git/objects/ -type f
.git/objects/37/043b77a8d35b29a7d385d7658d58bd649e4b18
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc
.git/objects/d8/d965c56c04e851e3b47f524c6c52a24c396857
发现objects文件夹下多了两个对象,分别查看一下对象的类型和内容
$ git cat-file -t d8d965
tree
$ git cat-file -p d8d965
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc readme.md
这是一个 tree对象 ,表示一个目录结构,目录下有一个 readme.md 的文件
-
100644表示 readme.md 是文本类型的文件(如果是040000表示文件类型是目录)
-
blob 表示该对象类型
-
6a0b867bdc470c582c15906f264b9fec371cdbfc 表示该文件的 hash值
$ git cat-file -t 37043b
commit
$ git cat-file -p 37043b
tree d8d965c56c04e851e3b47f524c6c52a24c396857
author henry <henry@xxx.com> 1709619995 +0800
committer henry <henry@xxx.com> 1709619995 +0800
first commit
这是一个 commit对象 ,表示一个提交信息
-
提交对象的内容先指定一个顶层tree对象,代表此次提交点的项目快照;然后是可能存在的父提交(即上一次提交);
-
之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个当前时区的时间戳);
-
留空一行,最后是提交的注释。
除了上面两个对象,还发现 .git目录 下还多了一个 logs目录,查看一下里面的文件
$ find .git/logs/ -type f
.git/logs/HEAD
.git/logs/refs/heads/master
.git/logs/HEAD 保存了 HEAD 引用的历史信息
.git/logs/refs/heads 存放本地git仓库所有的分支历史信息文件,例如这里的 master分支文件
每个文件保存了以下信息:
- 每次提交的详细信息: 包括提交的哈希值、作者、时间戳、提交消息等。
- 分支移动的记录: 记录了每次分支移动(包括切换分支、合并分支等)的详细信息。
我们可以直接使用git log来查看记录
3. git branch/git tag原理
## 基于分支创建分支
$ git branch dev master
## 基于分支创建tag
$ git tag 1.0.0 master
$ git branch
dev
* master
$ git tag
1.0.0
此时我们查看.git/refs/目录的文件
$ find .git/refs/ -type f
.git/refs/heads/dev
.git/refs/heads/master
.git/refs/tags/1.0.0
打开其中一个文件,可以发现内容一个 commit对象 的hash值
至此我们明白了branch和tag都是基于commit对象生成的,他们指向了某一个提交对象
4. Head指针
.git文件夹下有个HEAD文件,内容是一个指针,要么指向分支,要么指向一个commit对象
在分支上时
$ cat .git/HEAD
ref: refs/heads/master
在分离HEAD状态时(切换到tag也是分离HEAD状态)
$ git checkout 1.0.0
$ cat .git/HEAD
37043b77a8d35b29a7d385d7658d58bd649e4b18
指向分支其实最终也是指向一个commit对象
补充
当知道了上面这些原理,有时候我们使用git reset --hard不小心丢弃了本地的提交记录,可以使用git log查找commit_hash后,执行git reset --hard <commit_hash>来进行恢复
其他
.git/index:记录暂存区(stage)里文件的状态和内容哈希值,是二进制文件,不适合直接编辑或查看,可使用 git status 查看;
.git/info/exclude:用于配置不希望被 Git 跟踪的文件或文件夹。这个文件类似于 .gitignore 文件,但它是针对该仓库私有的,不会被提交到版本控制中;
.git/config:记录了仓库配置,可查看编辑;
.git/COMMIT_EDITMSG:仅用于临时存储提交信息,方便后续作为提交的一部分存储到提交记录中;
ORIG_HEAD:当你执行像 git reset 或者 git rebase 这样的操作时,Git 会移动 HEAD 到新的位置,并且旧的 HEAD 的位置会被保存在 ORIG_HEAD 中;如果你后悔了这个操作,可以使用 git reset --hard ORIG_HEAD 来恢复到原来的状态;
description:用于存储仓库的描述信息,非必需的文件,不影响git正常使用;