Git 引用 (Git Reference)
OID, 即40位的16进制字符串表示(SHA-256则为64位), 毫无疑问让我们非常晦涩难记。 Git 引用 (References 或 Refs) 就是为此而生的, 引用是一个逻辑概念, 其并非Git所独创, 很多的VCS工具都有类似的概念和设计, 但是基本通过不同的物理方式实现。
Git 引用的存储方式
Git 引用存储的文件可以在我们仓库中轻松找到, 并且其存储的内容结构也非常简易。 Git refs通常会 松散存储 在仓库的 .git/refs/ 目录下 (实际上也可以“打包”), 我们可以在此前的仓库中执行 find 命令:
➜ find .git/refs
.git/refs
.git/refs/heads
.git/refs/heads/master
.git/refs/tags
.git/refs/tags/v2.0
.git/refs/tags/v1.0
其中 .git/refs/heads 和 .git/refs/tags 等目录是用来让 Git 区分不同引用类型, 其中包括:
常见仓库内建引用目录标识
· .git/refs/heads/ : 分支引用(Branches);
· .git/refs/tags/ : 标签引用(Tags);
· .git/refs/remotes/<origin>/<ref> : 远端引用(Remotes Refs);
· .git/refs/stash/ : 暂存引用(保存还未提交的对象信息);
· .git : 符号引用(例如: HEAD等);
· .git/refs/meta/config : Gerrit 用于保存仓库信息和用户权限等用到的特殊引用;
分支和标签引用
分支(Branches) 是 Git refs 最常见的引用之一, 我们可以通过 cat 命令直接查看我们在 test.git 仓库中已经默认创建的 master 分支:
➜ cat .git/refs/heads/master
6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f
➜
➜ git cat-file -p 6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f
tree 2e6cdc032db0ecfa4e3e898b8f8551acce10db11
parent 3de31e208ae26c6b44d9e6c4a3f0adb32d0c68b6
author Dyrone Teng <tenglong.tl@alibaba-inc.com> 1633681481 +0800
committer Dyrone Teng <tenglong.tl@alibaba-inc.com> 1633681627 +0800
COMMIT B
Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
可以看到, 在引用文件 .git/refs/heads/master 中保存了 6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f , 这个 OID 就是我们刚才创建的 COMMIT B 的对象。 同理, 我们可以查看 .git/refs/tags/v1.0 的引用存储的信息, 我们可以执行:
➜ cat .git/refs/tags/v1.0 | xargs git cat-file -p
tree 2e6cdc032db0ecfa4e3e898b8f8551acce10db11
parent 3de31e208ae26c6b44d9e6c4a3f0adb32d0c68b6
author Dyrone Teng <tenglong.tl@alibaba-inc.com> 1633681481 +0800
committer Dyrone Teng <tenglong.tl@alibaba-inc.com> 1633681627 +0800
COMMIT B
Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
符号引用
了解 符号引用(Symbolic Reference) 之前, 我们先在 test.git 目录中执行 git log 子命令:
➜ git log
commit 6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f (HEAD -> master, tag: v2.0, tag: v1.0)
Author: Dyrone Teng <tenglong.tl@alibaba-inc.com>
Date: Fri Oct 8 16:24:41 2021 +0800
COMMIT B
Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
commit 3de31e208ae26c6b44d9e6c4a3f0adb32d0c68b6
Author: Dyrone Teng <tenglong.tl@alibaba-inc.com>
Date: Fri Oct 8 15:45:01 2021 +0800
COMMIT A
Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
git log 子命令用于查看 提交历史, 这里我们没有指定任何一个选项或者参数, 这中情况下子命令会为我们展示 当前所在引用(the branch currently on) 分支 的最新提交历史, 那么Git是如何知晓我们当前引用是哪个呢? 原因就在于符号引用。
Git使用 .git/HEAD 文件来保存你当前所在分支引用的信息, 与分支引用和标签引用相比不同的是, 后者存储的是引用对象的信息, 而前者保存的是 引用的引用 , 我们执行 :
➜ cat .git/HEAD
ref: refs/heads/master
除了 HEAD 之外, 实际上我们可以 symbolic-ref 子命令更新和获取, 自定义名称的符号引用:
➜ git symbolic-ref MARK refs/heads/master
➜ test.git git:(master) cat .git/MARK
ref: refs/heads/master
Remotes引用
Remotes引用, 通常用来存储 已经存在于远程存储库中的对象 信息, 例如在执行 git push 子命令后进行维护. 我们此前介绍的内容都是基于本地仓库, 并不涉及与远端仓库的协作. 如果我们添加一个远端仓库 (remote) 的地址并将新的提交执行推送 (push) ,Git 便会将上次推送的对象的值存储在 refs/remotes 目录中的对应分支中。例如,您可以添加一个名为 origin 的远程信息并将master推送上去:
➜ git remote add origin https://codeup.aliyun.com/rdc2020/dyrone.git
➜
➜ git push origin master
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (9/9), 754 bytes | 754.00 KiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
To https://codeup.aliyun.com/rdc2020/dyrone.git
* [new branch] master -> master
➜
➜ cat .git/refs/remotes/origin/master
6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f
现在我们可以看到, 在 .git/refs/remotes/origin/master 引用中记录了我们上一次 push 到名为 origin 的远端仓库上的对象信息。
引用维护
了解了引用的存储方式以后, 我们可以通过直接创建文件的方式, 创建一个仓库中的引用:
➜ git log --graph
* commit 6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f (HEAD -> master, tag: v2.0, tag: v1.0, origin/master)
| Author: Dyrone Teng <tenglong.tl@alibaba-inc.com>
| Date: Fri Oct 8 16:24:41 2021 +0800
|
| COMMIT B
|
| Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
|
* commit 3de31e208ae26c6b44d9e6c4a3f0adb32d0c68b6 (topic_aliyun)
Author: Dyrone Teng <tenglong.tl@alibaba-inc.com>
Date: Fri Oct 8 15:45:01 2021 +0800
COMMIT A
Signed-off-by: Dyrone Teng <tenglong.tl@alibaba-inc.com>
➜
➜ echo 3de31e23de31e208ae26c6b44d9e6c4a3f0adb32d0c68b6 > .git/refs/heads/topic_aliyun
➜
➜ git log --oneline refs/heads/topic_aliyun
3de31e2 (topic_aliyun) COMMIT A
首先我们找到仓库中的第一个提交, 随后基于第一个提交的OID创建一个名为 topic_aliyun 的分支引用, 随后执行 git log 子命令查看新分支的提交记录, 其中 --graph 选项可以在提交历史中附带输出文本形式的简易图形信息, --oneline 可以简化我们的输出信息, 只关注提交的 标题 和 简短形式的OID。
直接使用 echo 的方式去修改引用其实不是非常安全, 所以Git设计了 git update-ref 子命令专门用来维护引用的数据。 例如, 我们可以尝试将分支应用 refs/heads/topic_aliyun 更新为 COMMIT B 的提交对象:
➜ git update-ref refs/heads/topic_aliyun 6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f
➜ cat .git/refs/heads/topic_aliyun
6ae8bbeeb4dba091d3c6295d0b3d0f9a3863d32f
最后
本文花了一部分的篇幅用来介绍了Git的设计思路, 引出了Git是如何通过对象来完成版本控制数据的存储, 以及如何通过引用来方便的管理和使用这些对象, 希望这些内容对不太了解Git的同学能够小有帮助, 同时这也是继续学习Git的重要基础。
本文没有对所有深入的内容都深入介绍, 例如Git存储对象和引用的方式不光光包含松散存储的方式, 而这种方式往往方便演示, 感兴趣的同学可以边阅读边执行。
在视频中, 除了对象和引用之外, 还介绍了一些简单的运维仓库的方式, 这些内容将在后面的系列视频和文章中由我其他同事们继续深入介绍, 敬请期待。
关于我们
欢迎加入我们!
如果你是一个懂代码,爱Git,有技术梦想的工程师,并想要和我们一起打造世界NO.1的代码服务和产品!
请联系我吧!C/C++/Golang/Java 我们都要 ღ( ´・ᴗ・` )!
If not now, when? If not me, who?
— 简历投递: tenglong.tl@alibaba-inc.com
Last updated 2021-10-11 19:05:43 CST