文章目录
- 1. 创建git本地仓库
- 2. 配置git
- 3. 认识工作区、暂存区、版本库
- 重要结论
- 4. 对象库
- 5. add和commit命令
- 6. git log命令与commit id
- 7. 查看.git目录
- 8. 底层逻辑链路总结
1. 创建git本地仓库
想用 Git 管理文件版本,第一步通常是先创建一个本地仓库
在项目目录里执行 git init,Git 就会把这个目录纳入版本控制;之后用 git add 和 git commit 把文件变更记录进仓库,就能查看历史、回滚版本、开分支了。
后面的我们讲到再详谈,第一步,我们先来创建git本地仓库:
首先我们来创建一个项目目录
比如我们就叫做git
创建一个 Git 本地仓库对应的命令为 git init ,注意命令要在项目目录下执行:
这样就创建好了,我们发现,当前目录下多了一个 .git 的隐藏文件
这个.git 目录就是 Git 本地仓库的核心(仓库本体/元数据目录)。
Git 通过它来保存版本历史、分支、配置和索引等信息,从而实现对项目文件的版本控制。不要手动修改这个目录里面的文件,不然改乱了,就把 Git 仓库给破坏了。
2. 配置git
那创建完git本地仓库后首先要做的事情是设置你的 用户名称 和 e-mail 地址,这是非常重要的。配置命令为:
git config --global user.name "你的用户名称"git config --global user.email "你的邮箱地址"
其中 --global 是一个可选项。
如果使用了该选项,表示这台机器上所有的 Git 仓库都会使用这个配置。如果你希望在不同仓库中使用不同的 name 或 e-mail ,可以不要 --global 选项,但要注意的是,执行命令时必须要在仓库里。
是否配置好了呢,我们可以使用git config -l命令,它会列出当前生效的所有 Git 配置项
删除对应的配置命令为:git config [--global] --unset user.namegit config [--global] --unset user.email
3. 认识工作区、暂存区、版本库
那现在我们已经创建好了git本地仓库,也进行了配置,那下面是不是就可以写一些文件让它进行管理了
比如我们可以先来创建一个readme文件,如果大家之前用过Gitee/GitHub,相信肯定见过readme文件:
README 文件是项目的说明文档,通常放在仓库根目录,用 Markdown 编写。它告诉别人这个项目是什么、怎么安装、怎么运行、如何配置和参与贡献。Gitee/GitHub平台会自动把 README 渲染到仓库首页,所以它相当于项目的“门面”和“快速上手手册”。README 本身也受 Git 版本控制,修改后同样需要提交。
那问一个问题:现在这个Readme文件是否可以被git管理了呢?
答案是还不行!
因为当前所在的这个git目录并不是本地的git仓库,上面我们也提到,.git 目录才是 Git 本地仓库的核心(仓库本体/元数据目录),也可以叫做版本库
那需要把文件放到.git目录里面嘛?
也不是的!
并且我们上面强调了,不要手动对.git目录进行任何修改!
这需要我们再来了解几个概念
先来了解第二个概念叫做工作区:
.git所在的目录,我们把它叫做工作区(当然.git 目录本身保存版本控制数据,不属于工作区),即我们写代码或文件的地方,所以当前Readme文件所在的这个git目录就是工作区!
也就是说,我们写的代码或者其它各种文件,要想后面能被git成功管理,放在这里就是对的!
那想真正用git管理工作区里面的文件,还需要怎么做呢?
来看这张图:
首先工作区我们已经了解了,在这里我们的工作区就是git目录(不是.git目录)
图中左侧为工作区,右侧为版本库。Git 的版本库里存了很多东西,我们先来了解暂存区。
暂存区(Staging Area),英文通常叫 stage 或 index。它物理上一般存放在 .git 目录下的 index 文件中(即 .git/index)。因为叫 index,所以我们在平时交流或阅读官方文档时,也会把暂存区叫作索引(Index)。
可以把暂存区想象成“购物车”或者“快递打包箱”:你在工作区(超市货架)挑好了要买的商品(修改的文件,这里的“修改”包括:新增,修改,删除),不能直接带走,得先放进购物车(git add 到暂存区),等你确认没问题了,再去收银台结账(执行提交操作 git commit ,对应分支会做相应的更新,可以简单理解为暂存区的目录树才会被真正写到版本库中)。
这些命令我们后面还会介绍和演示。
为什么要有暂存区?
很多新手会问:为什么改了文件不直接 commit,非要先 add 一下?
暂存区的存在是 Git 区别于其他版本控制工具(如 SVN)的一个重要优势,它有三大作用:
先简单了解,有些概念需要在后续的学习中才能深入理解。
继续看上面那张图,再往右边我们还看到什么head和master又是什么?
master:Git 为你自动创建的第一个分支,也是默认分支。(关于分支我们后面会详细介绍)
HEAD:
图中的指针。它指向当前所在的版本或分支(图中指向了 master 分支)。它告诉 Git 你当前处于哪个分支或提交节点。
重要结论
虽然上面还有些概念我们不能很好的理解,但至少目前可以得到一个结论:
新建或粘贴进项目目录的文件,并不能称之为向仓库中新增文件,而只是在工作区新增了文件。
必须要通过使用 git add 和 git commit 命令才能将文件添加到git仓库中进行管理!!!
4. 对象库
objects 为 Git 的对象库
当执行 git add 命令时,暂存区的目录树被更新,同时工作区修改的文件被写入到对象库中的一个新的对象中,就位于 “.git/objects” 目录下。
图中的 stage(暂存区)和 master(分支),它们里面画的文件夹和文件,其实并不是真正的文件实体,而是对象的索引/指针。
真正的内容去哪了?全部被压缩存放在底部的 objects(对象库) 中。
5. add和commit命令
上面我们得出结论:
新建或粘贴进项目目录的文件,并不能称之为向仓库中新增文件,而只是在工作区新增了文件。
必须要通过使用 git add 和 git commit 命令才能将文件添加到git仓库中进行管理!!
那下面我们就使用我们一开始创建的那个readme文件,进行一个演示,来熟悉一下这两个命令:
首先可以给Readme文件里面随便写点内容
第一步:addgit add 文件名(可以跟多个文件,空格隔开,也可以是目录名)
将文件添加到暂存区(staging area)
当然直接git add .,就可以把当前目录中所有没添加到暂存区里面的修改文件添加进去
那么此时,readme这个文件就已经从工作区被放到暂存区里面了。
第二步:commitgit commit -m "提交的内容信息或修改信息"(不要随便写)
注意 git commit 后面的 -m 选项,要跟上描述本次提交的 message,由用户自己完成,这部分内容绝对不能省略,并要好好描述,是用来记录你的提交细节,是给我们人看的。git commit会将暂存区的文件更改提交到本地版本库。
成功执行,并且我们看到git给我们打印了对应的日志信息,一个文件被改变,增加了一行内容!
我们还可以多次 add 不同的文件,而只 commit 一次便可以提交所有文件,是因为需要提交的文件是通通被 add 到暂存区中,然后一次性 commit 暂存区的所有修改。
6. git log命令与commit id
截至目前为止,我们已经能够将文件直接提交至本地仓库了。我们可以使用 git log 命令,来查看下历史提交记录日志信息
我们看到就列出来我们历史提交的所有信息(因为目前这个仓库只提交了一次),然后commit 后面有一长串内容,这个是这次提交对应的commit id。
这是一串由 40 个十六进制字符组成的字符串。它是 Git 根据你提交的内容、作者信息、提交时间、提交说明以及父节点等信息,通过密码学算法(SHA-1)计算出来的。
这个 ID 有什么用?
在后续的学习中,这个 ID 会非常有用。比如:
如果嫌git log输出信息太多,看得眼花缭乱的,可以试试加上 --pretty=oneline 参数:
7. 查看.git目录
我们查看.git目录的目录结构,其实上面图中的部分都可以找到:
然后我们说master是Git 为你自动创建的第一个分支,也是默认分支。
那它里面存的是啥呢?
能够看到,master里面存的其实是最近一次提交的commit id!
这意味着,Git 里的分支极其轻量。它本质上就是一个包含提交 ID 的文本文件。每次你产生新的提交(commit),Git 就会把新的 ID 覆盖写入到这个文件里,分支指针也就自然而然地“向前移动”了一步。
HEAD 则是一个指向当前分支的指针(它里面存着 ref: refs/heads/master)。
所以链条是:HEAD 指向 master,master 指向最新的 Commit ID。
前面我们还提到:
图中的 stage(暂存区)和 master(分支),它们里面画的文件夹和文件,其实并不是真正的文件实体,而是对象的索引/指针。
真正的内容去哪了?全部被压缩存放在底部的 objects(对象库) 中。
现在我们已经看到了master分支里面的内容,是一个commit id,所以commit id 就是commit对象的索引!
那如何通过commit id这个索引找到其对应的提交对象呢?
查找 object 时要将 commit id 分成2部分,其前2位是文件夹名称,后38位是文件名称。
那这里所说的这个Commit 对象(提交对象)又是什么呢?
找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过 sha (安全哈希算法)加密过的文件,我们可以使用 git cat-file 命令来查看版本库提交对象的内容:
如果你想查看这个 Commit 对象的真实内容(包括作者、时间、对应哪棵树等),请使用 -p(pretty-print)参数:git cat-file -p 40位哈希值
如果你想看这个对象到底是个啥类型(是 commit 还是 tree 还是 blob),用 -t 参数
之前我们只提交了一次,如果多提交几次:
然后再查看分支里面存的最后一次提交对应的commit 对象的内容
那这些内容分别是什么意思?
第一行:tree 7724b3e84d6476cc2e31f3a5603bfc7c6a02038b
它说明当前这个 Commit 包含一棵目录树(Tree 对象,记录本次提交后的目录结构,同样也存在objects中)。这个 Tree 的哈希值是7724…
那tree对象又是什么呢?我们也可以来看一下它的内容:
可以看到这里面能找到我们之前提交的所有文件,以及它们对应的文件内容对象索引(objects中也能找到对应的对象),这里又出来一个文件内容对象!
是什么呢?
我们也可以来看一下:
🆗,这里面就包含了文件的内容。(如果修改了,这里面的内容也会随之改变)
前面说暂存区存的也是对象的索引,其实就是文件内容对象的索引!
第二行:parent 80a6a4d8dd4b10f7c8b9cb6db5ae839e5aeb7b42
这是父节点指针。它指向了你上一次的提交
为什么第一次提交没有这行? 因为第一次提交是“始祖”,它没有前任。而从第二次提交开始,每次 Commit 都会用 parent 记录上一次是谁,这就形成了一条单向链表,也就是 Git 的提交历史线!
后三行:
8. 底层逻辑链路总结
现在,我们已经在 Git 的底层建立了一个完美的时间线闭环:
第一步:git add 时发生了什么?
当我们执行 git add 时,Git 会把工作区里的文件内容压缩打包,生成一个 Blob 对象 存放在 objects 库中。同时,暂存区(index)更新,记录下文件的名称、权限,以及指向这个 Blob 内容对象的索引(哈希值)。
第二步:git commit 时又发生了什么?
当我们敲下 git commit,Git 会基于暂存区的结构生成一棵 Tree 对象(目录树),并生成一个 Commit 对象,这些对象都存放进objects库中。对应分支中存储最后一次提交的commit对象的commit id(索引)。
完整的底层链路:
你敲 git commit,生成了 Commit 对象(358f44a…)。
Commit 对象里存了一个指向 Tree 对象 的指针(7724b3e…)。
Tree 对象就像一张清单,列出了当时项目里有 Readme 和 test.txt。
顺着清单里的哈希值,Git 就能在 objects 库中精准找到这两个 Blob 对象,也就是真正的代码文本内容。
最后,master 分支的指针指向最新的 Commit ID,HEAD 指向 master。
所以,依靠这套完美的底层数据结构,Git 就可以轻松实现版本的追踪与回溯,并找到不同版本对应的内容对象:
如何追踪?
因为每个 Commit 对象都记录了 parent(父节点指针),这就形成了一条单向链表。顺着这条链表,Git 可以毫无遗漏地追溯从第一次提交到现在的每一次修改。
如何回溯?
因为所有的历史快照(Commit、Tree、Blob)都完好无损地存放在 .git/objects 对象库里。当我们需要回退到某个版本时,Git 根本不需要去修改或覆盖旧的代码,它只需要把 master 分支的指针,重新指向历史中的某一个 Commit ID 即可。
如何找到内容?
一旦指针指向了那个过去的 Commit,Git 就能顺藤摸瓜:通过 Commit 找到 Tree,再通过 Tree 找到一个个具体的 Blob,瞬间把当时的“文件目录结构”和“代码文本内容”完完整整地还原到你的工作区。