git原理 - 底层三区域与三对象问题

88 阅读11分钟

我正在参加「掘金·启航计划」

我们每天都使用git,但是对于git似乎还停留在基本命令上,这显然不够卷。

git命令分为高层命令底层命令,熟练掌握高层命令满足工作需要,了解底层命令增加对git亲切感,不再可怕。

当然这个系列,我也会说明一些难点问题:

  • merge 和 rebase 区别

  • 每次版本迭代,怎么做分支重建

  • 项目运行几年后,每次clone都好几个G,怎么实现不影响提交记录缩小项目

git高层命令

下面会出现HEAD,HEAD是什么呢,就是指向当前分支的指针,理解为当前工作区

我们先来复习下经常使用到的几个命令:

最基本必会

命令描述
git init初始化空git项目
git config --global user.name "xxxx"配置全局账户,也可设置 user.email 邮箱
git clone 项目地址clone项目
git add ./提交暂存区
git commit -m "提交信息"提交到版本库
git push推送到远程服务器
git checkout -b 本地分支名切换到分支,从当前分支建立本地新分支,并切换到新分支,注意并未关联到远程分支
git checkout -b 本地分支名 远程分支名(不带remotes)从远程分支建立新分支,并关联。如git checkout -b xxx origin/xxx
git config --global credential.helper cache保存密码
git config core.ignorecase false大小写敏感,默认是不敏感
git config --system core.longpathswindows解决路径过长问题
git merge test如当前在master分支,是将test分支覆盖到master分支上来合并
git rebase master test不论当前在哪个分支,是将master分支作为基础,把test分支修改内容做暂存,以master变基后,并合并到test分支上

查看

命令描述
git status查看当前状态,文件未跟踪、已修改、已暂存、已提交
git log查看历史记录,未折叠
git log --oneline查看历史记录,折叠简略一行
git reflog查看历史所有操作,包含撤销等,只有HEAD变化都记录
git branch查看所有本地分支
git branch -a查看所有分支,包括远程分支
git checkout 分支名切换到本地分支,如果本地没有这个分支,而远程有这个分支,那么会自动检出远端分支,并建立跟踪
git stash list查看所有存储栈中代码
git diff查看未暂存的修改
git diff --cache查看未提交的暂存
git tag查看本地所有tag
git ls-remote --tags remote查看远程所有tag
git log --oneline --decorate --graph --all查看整个项目的分支图
git remote -v查看所有远程源
git branch -vv查看当前分支与远程分支的关系

添加

命令描述
git branch 分支名新建本地分支
git merge 本地分支名本地分支合并到当前分支上
git fetch 远程仓库名拉取远程仓库数据,注意只是拉取到你本地仓库,不会合并到你工作目录,后续可手动合并
git pull 远程仓库名拉取远程代码,相当于git fetch + git merge
git stash将新代码压入存储栈存起来,一般切分支前用到
git stash pop将最新一次压栈代码弹出来,并移除栈顶
git stash apply stash@2应用某次栈中代码,但不移除栈顶
git stash drop stash@2丢弃某个存储
git tag tag名字打标签
git tag -a tag名字 -m tag注释打标签带注释
git push origin --tags推送所有本地tag到远程
git remote add 仓库别名 仓库地址添加新远程项目地址,一个项目可以添加多个远程项目,git remote add origin xxxx
git push 仓库别名 远程分支推送到一个远程分支
git push -u origin --all推送所有分支到origin服务器
git push -u origin --tags推送所有标签到origin服务器
git branch -u 远程分支本地分支和远程跟踪分支建立关联关系,如git branch -u origin/xxx

修改

命令描述
git commit --amend修改上次提交对象描述信息
git reset --soft HEAD~ 或者 commithash只HEAD和分支名移动到上次提交对象,文件不动,暂存区不动
git reset --mixed HEAD~ 或者 commithashHEAD和分支名移动到上次提交对象,文件不动,暂存区取消
git reset --hard HEAD~ 或者 commithashHEAD和分支名移动到上次提交对象,文件取消,暂存区取消

删除

命令描述
git branch -d 分支名删除分支,如果有未提交的会删除失败
git branch -D 分支名强制删除分支
git tag -d tag名字删除本地tag
git push origin :tag名字删除远程tag
git push origin --delete 远程分支名删除远程分支
git remote prune orinin --dry-run列出远程分支已删除,但是本地仍然在跟踪的分支
git remote prune origin删除上述列出的本地跟踪分支
git branch --unset-upstream删除本分支的跟踪分支关系

底层命令

要理解git就要先知道三区域和三对象:

  • git区域分为工作区暂存区版本库

  • git对象分为git对象tree对象(树对象)、commit对象(提交对象)

三区域

image.png

当我们用git init创建一个git项目时,会生成.git目录,里面内容如上图

  • objects 文件夹内就是版本库,存放实际二进制文件,可以看作是一个键值对的数据库,我们每次提交记录,每个文件修改都会完全加密拷贝进去,所有就算项目运行了N年,你依然可以时光穿梭到任意一次提交上看代码,是完整项目的快照

  • index 二进制文件就是暂存区内容,存放当前项目的每个文件指向objects里面的文件hash值(即objects中文件名)

  • 工作区就是外面项目代码

三对象

刚说到objects版本库里面是实际的二进制文件,里面存放的其实就是三对象文件

  • git对象 二进制文件就是项目每个源代码文件的二进制内容,文件名是hash值,是高层名称git add 时产生,因为git对象只有文件内容,没有文件名等信息,所以需要tree对象来组织git对象

  • tree对象 二进制文件记录项目目录结构,包含git对象的hash值和源文件路径名称等信息,也是git add时产生,因为tree对象虽然有了文件内容和文件信息,但是没有描述信息,没有关联到上次commit,所以需要commit对象来描述

  • commit对象二进制文件就是执行git commit -m时,记录这个tree对象hash值和父commit,描述信息等

虽然说,就是由我们git addgit commit产生,但是实际是调用不同底层命令,做二进制文件和hash值,下面我们就来说下这几个底层命令。

新建

底层新增就四个命令:

  • hash-object生成git对象

  • update-indexwrite-tree生成tree对象

  • commit-tree生成commit对象

命令描述
git hash-object -w 文件名在版本库objects中生成git对象二进制文件和hash值
git hash-object 文件名不生成文件,只是看hash值
echo "test content" | git hash-object -w --stdin从输入流读取生成二进制文件和hash值
git update-index --add 实际文件名将新文件直接加入暂存区,会先到版本库生成hash,再加入暂存区
git update-index --add --cacheinfo 100644 git对象hash 实际文件名将版本库某个文件加入暂存区跟踪,文件已在版本库跟踪的就不用--add参数,100644普通文件,100755可执行文件,12000符号链接
git write-tree将暂存区执行快照,生成tree对象文件
echo "提交信息" | git commit-tree 树hash值 -p 父commit对象hash值生成commit对象信息

查看

既然,已经生成了 git对象tree-对象commit对象,因为都是二进制文件,所以无法直接打开,提供了查看命令:

命令描述
git ls-files -s查看暂存区,即index二进制文件内容
git cat-file -p 文件hash值查看二进制文件内容,文件hash值在objects文件夹内,注意完整hash是带文件夹两位字符
git cat-file -t 文件hash值查看二进制文件类型,git对象,tree对象,commit对象

存储与远程跟踪分支

存储的命令是git stash相关,上面已经说到,这里只是提出来而已。

我主要是说下远程跟踪分支,首先git branch 分支名作用已经知道是从当前分支建立新本地分支,需要知道工作区内容是当前本地分支,而远程跟踪分支的远程分支内容,之间需要建立对应关系,不然pushfetch时不能知道该怎么推/拉代码,git branch -u origin/xxx的作用是就是让本地分支和远程跟踪分支建立关联关系。

否则,像有时git push --set-upstream origin 分支名的报错就会出现,使用git branch -u origin/xxx即可。

也可以在新建分支时写git checkout -b 本地分支名 远程分支名(不带remotes), 像从远程分支建立新分支,并关联。如git checkout -b xxx origin/xxx,直接从远程分支建立本地分支名,并建立跟踪分支关联。也可以写成git checkout --track 本地分支名同样作用。

git branch -vv可以查看当前分支与远程分支关联关系

实际操作命令

新建文件到版本库生成git对象

image.png

上图所有命令:

新建文件test.txt

echo "hello world!" > test.txt

test.txt加入版本库

git hash-object -w test.txt

返回 => a0423896973644771497bdc03eb99d5281615b51

版本库加入暂存区记录

git update-index --add --cacheinfo 100644 a0423896973644771497bdc03eb99d5281615b51 test.txt

查看暂存区

git ls-files -s

返回 => 100644 a0423896973644771497bdc03eb99d5281615b51 0 test.txt

查看版本库二进制文件原始内容

git cat-file -p a0423896973644771497bdc03eb99d5281615b51

返回 => hello world!

查看版本库二进制文件对象类型

$ git cat-file -t a0423896973644771497bdc03eb99d5281615b51

返回 => blob

git对象到tree对象

image.png

上图所有命令:

暂存区执行快照生成tree对象

git write-tree

返回 => 5d56cf9b9843c20d7b29bf6374501f4f48841210

查看版本库中tree对象内容

git cat-file -p 5d56cf9b9843c20d7b29bf6374501f4f48841210

返回 => 100644 blob a0423896973644771497bdc03eb99d5281615b51 test.txt

查看tree对象类型

git cat-file -t 5d56cf9b9843c20d7b29bf6374501f4f48841210

返回 => tree

tree对象到commit对象

image.png

tree对象快照到commit对象

echo "提交信息" | git commit-tree 5d56cf9b9843c20d7b29bf6374501f4f48841210

说明:这里是第一次commit,所有没有父commit,没有加-p参数

返回 => d77e46bacb883271b78a3950c7d1d9f7bc900aaa

查看版本库中commit对象类型

git cat-file -p d77e46bacb883271b78a3950c7d1d9f7bc900aaa

返回 =>

tree 5d56cf9b9843c20d7b29bf6374501f4f48841210

author rootegg 1664618340 +0800

committer rootegg 1664618340 +0800

提交信息

查看版本库commit对象类型

git cat-file -t d77e46bacb883271b78a3950c7d1d9f7bc900aaa

返回 => commit

至此三区域三对象说清楚了,完毕!