在上一篇文章Git工作原理(一)中,我们介绍了简单的
git 版本控制,上一篇文章中并没有牵涉到分支 branch
相关的内容,这篇文章将会介绍:当我们创建不同的分支时,git
做了些什么?
[TOC]
1. master
分支
上一篇文章结束后,我们查看仓库的历史
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| $ git ls-files --stage 100644 445a69c00e48288ac420a2ead9ae5a1cb4cd36d4 0 a.txt 100644 c200906efd24ec5e783bee7f23b5d7c941b0c12c 0 dir/b.txt
$ git cat-file --batch-check --batch-all-objects 0ed6427de6990a17351bf0e0fd648b642e15f967 tree 63 38f74e0a07955212bdb02699f6d73cd7420cd823 commit 175 445a69c00e48288ac420a2ead9ae5a1cb4cd36d4 blob 7 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c blob 4 aef06e3d27cc6b17730daf473499ab58b68e772d tree 33 b00f88d6e09fd9e767fc3246c971bf0d14f0621e commit 223 b02b1164ed5a571b723cb25d978780b15d826d62 tree 63 c200906efd24ec5e783bee7f23b5d7c941b0c12c blob 4
$ git log commit b00f88d6e09fd9e767fc3246c971bf0d14f0621e (HEAD -> master) Author: Glooow1024 <glooow1024@gmail.com> Date: Sun Dec 29 16:07:08 2019 +0800
commit 2
commit 38f74e0a07955212bdb02699f6d73cd7420cd823 Author: Glooow1024 <glooow1024@gmail.com> Date: Sun Dec 29 15:42:55 2019 +0800
commit 1 $ git cat-file -p b00f tree 0ed6427de6990a17351bf0e0fd648b642e15f967 parent 38f74e0a07955212bdb02699f6d73cd7420cd823 author Glooow1024 <glooow1024@gmail.com> 1577606828 +0800 committer Glooow1024 <glooow1024@gmail.com> 1577606828 +0800
commit 2
|
当前 .git/
文件夹下我们主要关注以下内容
1 2 3 4 5 6 7 8 9 10 11 12
| .git ├── HEAD ├── refs │ └── tags │ └── heads │ └── master ├── logs │ └── HEAD │ └── refs │ └── heads │ └── master ...
|
1.1 .git/HEAD
如果查看当前的 HEAD
就可以看到他指向了仓库
master
最后一次 commit
1 2 3 4 5
| $ cat .git/HEAD ref: refs/heads/master
$ cat .git/refs/heads/master b00f88d6e09fd9e767fc3246c971bf0d14f0621e
|
1.2 .git/logs
如果再查看 .git/logs/
中的文件,可以看到这里也保存一个
HEAD
,但是内容跟之前差很多
1 2 3 4
| $ cat ./.git/logs/HEAD 0000000000000000000000000000000000000000 38f74e0a07955212bdb02699f6d73cd7420cd823 Glooow1024 <glooow1024@gmail.com> 1577605375 +0800 commit (initial): commit 1 38f74e0a07955212bdb02699f6d73cd7420cd823 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577606828 +0800 commit: commit 2
|
第一列是上一次提交的 commit
哈希值,第二列是本次
commit
哈希值,后面是用户信息,最后一列则是每次
commit
的附加的消息 message。
再看 logs
中的 master
,可以看到跟前面
HEAD
中的内容一样
1 2 3
| $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 38f74e0a07955212bdb02699f6d73cd7420cd823 Glooow1024 <glooow1024@gmail.com> 1577605375 +0800 commit (initial): commit 1 38f74e0a07955212bdb02699f6d73cd7420cd823 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577606828 +0800 commit: commit 2
|
2. Git 的分支管理
2.1 git branch
现在让我们新建一个分支
1 2 3
| $ git branch br1 $ cat ./.git/HEAD ref: refs/heads/master
|
HEAD
的内容当然没有改变,因为我们没有转移到新的分支。但是 .git/
发生了哪些变化呢?他变成了这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .git ├── HEAD ├── refs │ └── tags │ └── heads │ └── br1 │ └── master ├── logs │ └── HEAD │ └── refs │ └── heads │ └── br1 │ └── master ...
|
来看看内容
1 2 3 4 5
| $ cat ./.git/refs/heads/br1 b00f88d6e09fd9e767fc3246c971bf0d14f0621e
$ cat .git/logs/refs/heads/br1 0000000000000000000000000000000000000000 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577615247 +0800 branch: Created from master
|
refs/
中的 br1
只是记录了产生分支的最近一次
commit
的哈希值,而 logs/
中的信息第一列变成了空,也就是说分支后不能向更早的版本回退,最多回退到分支时的那个版本,同时最后一项信息记录了这个分支信息。
敲黑板!重点
.git/refs/master
只记录了当前分支最后一个
commit
的哈希值;
.git/logs/refs/master
记录了当前分支所有
commit
的哈希值,构成一个可以向旧版本回溯的单向链表;
.git/HEAD
只记录了当前工作区所在分支对应的
refs/
中的文件;
.git/logs/HEAD
记录了 .git/HEAD
的变化历程;
2.2 git checkout
现在让我们转移到新的分支
1 2 3 4 5 6
| $ git checkout br1 Switched to branch 'br1'
$ cat ./.git/HEAD ref: refs/heads/br1
|
只是修改了当前的 HEAD
就表示我们转移到了新的分支,原来的主分支 master
还在
ref: refs/heads/master
中保存,所以不会有信息丢失。
假如我们在分支上修改了内容呢?
1 2 3 4 5
| $ echo 'hello' >> a.txt $ cat a.txt hahaha hello
|
然后查看暂存区 index
文件,可以看到 a.txt
的哈希值已经改变了
1 2 3 4
| $ git ls-files --stage 100644 63db897b879aac027311451ea6d8158daab3ac39 0 a.txt 100644 c200906efd24ec5e783bee7f23b5d7c941b0c12c 0 dir/b.txt
|
2.3 git commit
然后我们提交一哈,不出意外的话用
$ git cat-file --batch-check --batch-all-objects
会发现多了一个 commit
和一个 tree
文件,这是我们在上一篇文章中所讲的,忘记的可以再回顾一下。
再看当前 br1
已经被修改了
1 2 3 4 5 6 7
| $ cat .git/refs/heads/br1 4b60ac6ea7ebab972920f84bd07de3d20d7d5804
$ cat .git/logs/refs/heads/br1 0000000000000000000000000000000000000000 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577615247 +0800 branch: Created from master b00f88d6e09fd9e767fc3246c971bf0d14f0621e 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 Glooow1024 <glooow1024@gmail.com> 1577616495 +0800 commit: commit br 1
|
重要的是 logs/HEAD
1 2 3 4 5 6
| $ cat .git/logs/HEAD 0000000000000000000000000000000000000000 38f74e0a07955212bdb02699f6d73cd7420cd823 Glooow1024 <glooow1024@gmail.com> 1577605375 +0800 commit (initial): commit 1 38f74e0a07955212bdb02699f6d73cd7420cd823 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577606828 +0800 commit: commit 2 b00f88d6e09fd9e767fc3246c971bf0d14f0621e b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577615776 +0800 checkout: moving from master to br1 b00f88d6e09fd9e767fc3246c971bf0d14f0621e 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 Glooow1024 <glooow1024@gmail.com> 1577616495 +0800 commit: commit br 1
|
可以看到这里记录了我们转移分支并提交的记录。
小结
敲黑板!重点
- 实质上我们的每个 branch 都相当于维护了一个
commit
文件的单项链表;
- 命令
git branch
实际上就是在
.git/refs/heads
和 .git/logs/refs/heads
分别创建一个对应的文件,文件名就是分支名,文件中保存了这个分支对应的
commit
链表的各项;
- 我们通过
git checkout
实际上就是修改
.git/HEAD
使其指向对应的分支在 .git/refs/
中的文件;
2.4 git merge
现在让我们合并一下分支
1 2 3 4 5
| $ git merge br1 Updating b00f88d..4b60ac6 Fast-forward a.txt | 1 + 1 file changed, 1 insertion(+)
|
我们再来看一下 HEAD
文件
1 2 3 4 5 6 7
| $ cat .git/logs/HEAD 0000000000000000000000000000000000000000 38f74e0a07955212bdb02699f6d73cd7420cd823 Glooow1024 <glooow1024@gmail.com> 1577605375 +0800 commit (initial): commit 1 38f74e0a07955212bdb02699f6d73cd7420cd823 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577606828 +0800 commit: commit 2 b00f88d6e09fd9e767fc3246c971bf0d14f0621e b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577615776 +0800 checkout: moving from master to br1 b00f88d6e09fd9e767fc3246c971bf0d14f0621e 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 Glooow1024 <glooow1024@gmail.com> 1577616495 +0800 commit: commit br 1 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 b00f88d6e09fd9e767fc3246c971bf0d14f0621e Glooow1024 <glooow1024@gmail.com> 1577625498 +0800 checkout: moving from br1 to master b00f88d6e09fd9e767fc3246c971bf0d14f0621e 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 Glooow1024 <glooow1024@gmail.com> 1577625944 +0800 merge br1: Fast-forward
|
这里的 merge
过程实际上就是把 master 对应的指针移到了
br1
对应的指针处,看下图很容易理解(图片来自于
https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6)
删除分支实质上也就是删除了分支文件 .git/refs/heads/br1
和 .git/logs/refs/heads/br1
3. Git 的标签管理
我们先看一下
1 2 3 4
| $ git log --pretty=oneline 4b60ac6ea7ebab972920f84bd07de3d20d7d5804 (HEAD -> master) commit br 1 b00f88d6e09fd9e767fc3246c971bf0d14f0621e commit 2 38f74e0a07955212bdb02699f6d73cd7420cd823 commit 1
|
3.1 git tag
打个标签
然后就会发现 .git/refs/tags/
中多了一个
v0.1
文件,实际上就是一个指针(commit
文件的哈希值),跟 .git/refs/heads/master
中的指针没有区别
1 2
| $ cat .git/refs/tags/v0.1 3ad673290fa12aecc5bb66e7c7d3f83157914957
|
需要注意的是增加 tag 并不会修改 .git/logs
中的文件,因为这个文件夹是维护版本更新历史的,打标签并没有产生新的版本。