GIT | GIT中指针和引用的理解

前面学习了GIT系统通过三种常用对象存储文本:blob对象存储文本内容tree对象存储blob对象以及子tree对象committer对象存储tree对象以及提交信息。我们只要知道最后一个committer对象的sha-1码就能找到其他所有的对象,GIT中通过一个文件来记录当前分支最后一次提交生成的committer对象的sha-1码;这个文件称为引用。即.git目录下refs/文件夹。.git/refs/文件夹下默然有两个文件夹:heads/和tags/下面学习下具体的含义。

1. refs/heads/和HEAD

既然知道最后的committer对象的sha-1码存储的路径,那么GIT系统只需要读取该路径下的文件内容即可。
在test-git仓库下,查看refs/文件夹下的内容:

$ find refs/
refs/
refs/heads
refs/tags

注意:基于.git目录下的命令。并没有发现存有sha-1码的文件。可以想想为什么没有?

这里通过echo命令创建一个新的引用来记录最后一个committer对象的sha-1码:

$ echo "f40e5e55c2492c5140db9036c688dc74770c63b5" > refs/heads/master

此时,我们可以通过这个引用(文件夹)来查找committer对象,而不是sha-1码:

$ git log --pretty=oneline master
f40e5e55c2492c5140db9036c688dc74770c63b5 (HEAD -> master) third commit
5e76ed431b5f7347b1f683b3a60b3d6d139573b6 second commit
80516b024108ecc079e39f1c0cedc6502f666b12 first commit

我们这里学习的时候都是在一个主分支,即master上,如果有多个分支呢?
很显然,多个分支必然会有多个文件来记录每个分支的最后一个committer对象的sha-1值。
这里通过命令创建一个新的引用作为slave分支,并赋值一个committer对象的sha-1码:

$ echo "5e76ed431b5f7347b1f683b3a60b3d6d139573b6" > refs/heads/slave

注意:以第二次提交的committer对象为新的分支。
通过命令查看当前项目的仓库分支:

$ git branch
* master
  slave

注意:*号标记当前分支。

既然有多个分支,那么GIT系统是如何确定当前分支是master分支还是slave分支或者说分支之间是如何切换的
答案是HEAD文件
HEAD文件是一个指向你当前所在分支的引用标识符。这个标识符不是sha-1码,而是一个指向另外一个引用的指针
通过cat命令查看HEAD文件内容:

$ cat HEAD
ref: refs/heads/master

表明当前分支指向master,这与git branch命令结果一致。

通过git checkout命令切换分支:

$ git checkout slave
Switched to branch 'slave'
D       new.txt
D       test.txt

再次查看HEAD文件内容:

$ cat .git/HEAD
ref: refs/heads/slave

可见HEAD文件内容已经改变。
并查看当前分支状态:

$ git branch
  master
* slave

因此,分支可以简单理解为一个指向某个工作版本的一条HEAD记录的指针或引用
分支结构图
GIT | GIT中指针和引用的理解
当我们执行git commit命令时,git会创建一个committer对象,并且会把HEAD指向引用的sha-1码赋值给committer对象的父级。这个操作在logs/HEAD文件中有体现出来。

注意:

  • 在git系统中,可以通过git symbolic –ref命令查看HEAD文件内容:
    $ git symbolic-ref HEAD
    refs/heads/slave
    
  • 可以通过update-ref更新一个引用:
    $ git update-ref refs/heads/slave f40e5e55c2492c5140db9036c688dc74770c63b5
    

此处把slave分支的引用更新为与master分支指向的引用一样。
此时查看.git/refs/heads/slave内容可以确认修改成功:

$ cat .git/refs/heads/slave
f40e5e55c2492c5140db9036c688dc74770c63b5

至此,已经了解refs/heads/文件夹下的内容和HEAD文件的内容,并知道各文件的具体作用。

2. refs/tags

tag是GIT系统中第四种类型对象。tag有两种类型:annotatedlightweight

  • 通过命令创建lightweight类型的tag对象:
    $ git update-ref refs/tags/v1.0 f40e5e55c2492c5140db9036c688dc74770c63b5
    

可以看出这个命令与更新一个引用没什么区别,唯一的区别在于文件放置在tags文件夹内,所以tags对象与committer对象非常类似。

  • 通过命令创建annotated类型的tag对象:
    $ git tag -a v1.1 5e76ed431b5f7347b1f683b3a60b3d6d139573b6 -m 'test tag'
    
    命令参数含义如下:
    • -a表示这是一个annotated类型的tag对象;
    • -m表示备注;

注意:lightweight类型的命令不会创建tag对象,直接指向commit的reference,可以通过refs/tags/v1.0文件内容体现;而annotated类型的tag会创建独立的tag对象,这个tag对象拥有自己的sha-1码
通过命令查看annotated类型的tag对象的sha-1码:

$ cat .git/refs/tags/v1.1
791ffb93f6ef856d0248241fdf98d6ceea1767d7

注意:生成一个独立的sha-1码。
通过cat-file命令查看sha-1码的信息:

$ git cat-file -p 791ffb93f6ef856d0248241fdf98d6ceea1767d7
object 5e76ed431b5f7347b1f683b3a60b3d6d139573b6
type commit
tag v1.1
tagger XXXX8 < XXXX8@qq.com> 1553527998 +0800

test tag
  • object 5e76ed431b5f7347b1f683b3a60b3d6d139573b6表示tag对象指向sha-1值为5e76ed的committer对象;
  • type commit表示object类型为commit类型;
  • tag v1.1表示该对象的tag为v1.1;
  • tagger …表示创建tag对象的全局作者、邮箱以及时间;
  • test tag表示tag对象的备注信息;

可以看出tag对象本质还是committer对象,只不过再包了一层。

那么,tag对象有什么作用?
tag对象本质是指向某个committer对象的引用,标记某committer对象便于快速查找或者突出该committer对象,因此tag对象基本作用是给committer对象打标签。当有重大版本或者具有里程碑意义提交时,可以在该committer对象上建立tag对象。

3. refs/remotes/origin/

实际在使用过git的项目中,必然会存在这个目录。test-git项目中没有,是因为只有版本库,而没有远程仓库,故没有远程引用。这边对remotes引用简单了解下。
origin/文件夹下包含所有的分支,包括远程分支和本地分支。

4.总结

至此,关于git中对象的引用已经学习完了。不难发现,git系统通过引用或者指针的形式查找committer对象,进而找到其他tree对象和blob对象。另外,也可以知道git branch命令背后的秘密。