工程效率与工具 · 教程

从创建仓库开始

本文写于 2021 年,涉及的组件版本、配置与数据可能已过时,请按经验材料阅读并核对当前版本。原文链接与内容保持不变。

从创建仓库开始

这篇文章分享一下我们在使用git进行版本管理时候的常用的操作!我们从创建仓库开始~

获取代码仓库的方式有两种,一种是从零开始初始化一个git仓库,另一种是clone他人的仓库。

首先我们创建一个空目录,然后初始化一个仓库。

/ cd
# 创建空目录
~ mkdir gitlearn
~ cd gitlearn
# 初始化仓库
~ git init .

就这样我们创建一个了本地仓库, 团队合作的时候这样肯定不行的,不行,后面我们后介绍到。 或者 我们还可以克隆一个远程仓库

git clone https://gitee.com/fangjiaxiaobai/gitlearn.git

我们来看一下新建的仓库目录下都有什么文件。

➜  gitlearn git:(master) l
total 0
drwxr-xr-x   3 bjhl  wheel    96B  7 13 18:23 .
drwxr-xr-x   3 bjhl  wheel    96B  7 13 18:23 ..
drwxr-xr-x  10 bjhl  wheel   320B  7 13 18:25 .git
➜  gitlearn git:(master) cd .git
➜  .git git:(master) l
total 24
drwxr-xr-x  10 bjhl  wheel   320B  7 13 18:30 .
drwxr-xr-x   3 bjhl  wheel    96B  7 13 18:23 ..
-rw-r--r--   1 bjhl  wheel    16B  7 13 20:38 COMMIT_EDITMSG # 存储最新一次的commit信息
-rw-r--r--   1 bjhl  wheel    23B  7 13 18:23 HEAD ## 当前被检出的分支
drwxr-xr-x   2 bjhl  wheel    64B  7 13 18:23 branches # 所有分支
-rw-r--r--   1 bjhl  wheel   315B  7 13 18:23 config # 包含项目特有的配置选项
-rw-r--r--   1 bjhl  wheel    73B  7 13 18:23 description #Git web页面程序使用
drwxr-xr-x  12 bjhl  wheel   384B  7 13 18:23 hooks # 包含客户端或者服务器端的钩子脚本
drwxr-xr-x   3 bjhl  wheel    96B  7 13 18:23 info # 包含一个全局性排除文件,用以防止那些不希望被纳入版本管理的文件,.gitignore文件中记录的
-rw-r--r--   1 bjhl  wheel   126B  7 13 20:39 index ## 保存暂存区信息(进行stash操作之后出现的)
drwxr-xr-x   4 bjhl  wheel   128B  7 13 18:23 objects ## 存储所有数据的内容
drwxr-xr-x   4 bjhl  wheel   128B  7 13 18:23 refs ## 存储指向数据(分支,远程仓库和标签等)的提交对象的指针。

其中有四个条目非常重要,我用 ## 进行了标注。 分别是:HEAD文件,index文件,objects目录,refs目录。

如果你想只是想了解git的话,那么下面的内容就可以忽略了。

我们来搞一点有趣的东西。

objects/目录下存放的是什么东西? — Git对象

前面说过,git是一个内容寻址文件系统。==> git是一个简单的键值对数据库。

即,你可以向git仓库中插入任务类型的内容,它会返回一个唯一的键。通过该键可以在任意时刻再次取回该内容。

上面说过,objects目录下存放的是所有数据的内容。在git中,数据是什么呢?是我们工作目录的所有文件的快照!

这里我们使用两个还没有学习的命令。

  • git add .: 将目录中的文件纳入版本库进行管理起来。此时的状态是:已暂存(新建)。暂存区的目录树会被更新,同时工作区修改(新增的)文件内容被写入到对象库中的一个新文件中,而该对象的id会被记录在暂存区的文件索引中。
  • git commit -m 'xxx':将目录中的文件提交到本地版本库。此时的状态是:已提交。这时,暂存区的目录树写到版本库中,对应的分支会进行相应的更新。

那下面演示一下:objects目录下存储的文件。

首先,我们查看下object目录下的所有文件

# 查看objects下的文件
~ l .git/objects 
total 0
drwxr-xr-x   4 bjhl  wheel   128B  7 14 11:30 .
drwxr-xr-x  10 bjhl  wheel   320B  7 14 11:30 ..
drwxr-xr-x   2 bjhl  wheel    64B  7 14 11:30 info
drwxr-xr-x   2 bjhl  wheel    64B  7 14 11:30 pack

# 创建一个文件: test-objects-files-01.txt
➜  gitlearn git:(master) sudo touch test-objects-files-01.txt
➜  gitlearn git:(master) ✗ l .git/objects
total 0
drwxr-xr-x   4 root  wheel   128B  7 14 15:10 .
drwxr-xr-x  10 root  wheel   320B  7 14 15:10 ..
drwxr-xr-x   2 root  wheel    64B  7 14 15:10 info
drwxr-xr-x   2 root  wheel    64B  7 14 15:10 pack
# 修改文件
➜  gitlearn git:(master) ✗ sudo vim test-objects-files-01.txt
➜  gitlearn git:(master) ✗ l .git/objects
total 0
drwxr-xr-x   4 root  wheel   128B  7 14 15:10 .
drwxr-xr-x  10 root  wheel   320B  7 14 15:10 ..
drwxr-xr-x   2 root  wheel    64B  7 14 15:10 info
drwxr-xr-x   2 root  wheel    64B  7 14 15:10 pack
➜  gitlearn git:(master) ✗ git add .
➜  gitlearn git:(master) ✗ l .git/objects
total 0
drwxrwxrwx   5 root  wheel   160B  7 14 15:34 .
drwxrwxrwx  11 root  wheel   352B  7 14 15:34 ..
drwxr-xr-x   3 bjhl  wheel    96B  7 14 15:34 37
drwxrwxrwx   2 root  wheel    64B  7 14 15:10 info
drwxrwxrwx   2 root  wheel    64B  7 14 15:10 pack
➜  gitlearn git:(master) ✗ sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b # blob类型(文件)
# 使用git cat-file来看下一下文件的内容
➜  gitlearn git:(master) ✗ git cat-file -p 3717929ecb3efabae427dbe3725654b3de1a114b
test - objects - files - 第一行

# 我们进行commit
➜  gitlearn git:(master) ✗ git commit -m "test-object-files add"
[master (root-commit) 1883fd5] test-object-files add
 Committer: bjhl <bjhl@bjhldeMacBook-Pro.local>
# ..省略部分提示
 1 file changed, 1 insertion(+)
 create mode 100755 test-objects-files-01.txt

# 来看一下objects目录下的文件。

➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/18/83fd5aefeb430cb25150e531e92e38f0176f0d
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b
./.git/objects/54/500d5d74f828d1a3193fb685084b87aaf419c7

➜  gitlearn git:(master) git cat-file -p 54500d5d74f828d1a3193fb685084b87aaf419c7
# 表示存储的事blob格式的文件,test-objects-files-01.txt
100755 blob 3717929ecb3efabae427dbe3725654b3de1a114b	test-objects-files-01.txt

➜  gitlearn git:(master) git cat-file -p 1883fd5aefeb430cb25150e531e92e38f0176f0d
# 存储的是 树对象(后面有介绍)
tree 54500d5d74f828d1a3193fb685084b87aaf419c7
author bjhl <bjhl@bjhldeMacBook-Pro.local> 1594712728 +0800
committer bjhl <bjhl@bjhldeMacBook-Pro.local> 1594712728 +0800

test-object-files add

git cat-file 这个命令可以实现从git仓库中取回数据。-p参数可以自动判断内容的类型。还有一个写入的命令。git hash-object .下面简单演示一下。

➜  gitlearn git:(master) echo 'test hash-object function' | git hash-object -w --stdin
11a5f11388c345846bbaa060a98d8e93a1114e99
➜  gitlearn git:(master) git cat-file -p 11a5f11388c345846bbaa060a98d8e93a1114e99
test hash-object function
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/11/a5f11388c345846bbaa060a98d8e93a1114e99
./.git/objects/18/83fd5aefeb430cb25150e531e92e38f0176f0d # commit-id 
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b # 文件
./.git/objects/54/500d5d74f828d1a3193fb685084b87aaf419c7 # 树对象

从上面的例子我们可以看到,git中含有,blob commit-id,tree,这三种对象。 这其实就是 Git的对象:数据对象,提交对象,树对象。

接下来我们来看一个git的对象 - 树对象

树对象

树对象能够解决文件名保存的问题,也允许多个文件组织到一起。所有内容均以树对象和数据独享的形式存储,其中树对象对应了UNIX中的目录项,数据对象则大致上对应了inodes或者文件内容。

一个树对象包含了一条或多条树对象记录,每条记录含有一个指向数据对象或者子树对象的SHA-1指针,以及相应的模式,类型,文件名信息。

我们再来看一下,刚才的三个objects下的校验和。


➜  gitlearn git:(master) git cat-file -p 1883fd5aefeb430cb25150e531e92e38f0176f0d
tree 54500d5d74f828d1a3193fb685084b87aaf419c7
test-object-files add

➜  gitlearn git:(master) git cat-file -p 54500d5d74f828d1a3193fb685084b87aaf419c7
100755 blob 3717929ecb3efabae427dbe3725654b3de1a114b	test-objects-files-01.txt

➜  gitlearn git:(master) git cat-file -p 3717929ecb3efabae427dbe3725654b3de1a114b
test - objects - files - 第一行

我根据 数对象 1883fd 找到了 54500d(表示对应的txt文件),然后可以根据txt文件,看到这次提交的内容371792.如下图。

git工作目录01.png

这时,我们在尝试从修改一下这个文件,进行提交。

➜  gitlearn git:(master) sudo vim test-objects-files-01.txt
Password:
➜  gitlearn git:(master) ✗ git add .
➜  gitlearn git:(master) ✗ sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/18/83fd5aefeb430cb25150e531e92e38f0176f0d
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b # 文件
./.git/objects/54/500d5d74f828d1a3193fb685084b87aaf419c7 # 树对象
./.git/objects/11/a5f11388c345846bbaa060a98d8e93a1114e99 # test hash-object function (本次实验可以忽略)
## 下面两行是新增的,本次操作生成的。
./.git/objects/5b/22ea83f2e8555371b818a7e441d4156795af04 #内容: test - objects - files - 第一行 \n test - objects - files - 第二行

## 提交到版本库中。
➜  gitlearn git:(master) ✗ git commit -m "test-object-files updated"
[master 200e059] test-object-files updated
 1 file changed, 1 insertion(+)
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/18/83fd5aefeb430cb25150e531e92e38f0176f0d # commit - id
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b # 文件
./.git/objects/54/500d5d74f828d1a3193fb685084b87aaf419c7 # 树对象
./.git/objects/11/a5f11388c345846bbaa060a98d8e93a1114e99 # test hash-object function(本次实现忽略) 
./.git/objects/5b/22ea83f2e8555371b818a7e441d4156795af04 # 文件
# 下面两行是新增的,本次操作生成的
./.git/objects/20/0e059f0c0b178378aecd6736f384a74d28df94 # commit-id
./.git/objects/20/db6c4af7677b7509d2b323b503733405e4987e # 树对象

我们的操作如下:

  1. 在git仓库中创建了一个文件,并且将它纳入版本管理,提交到了版本库中
  2. 修改了这个问题,并且再次提交到了版本库中。

经过这个两个步骤之后,出现了如下图的 git 树对象。

git工作目录介绍02.png

这时,我们使用 git log 命令来查看日志. 可以发现。

commit 200e059f0c0b178378aecd6736f384a74d28df94 (HEAD -> master)
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 16:24:02 2020 +0800

    test-object-files updated

commit 1883fd5aefeb430cb25150e531e92e38f0176f0d
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 15:45:28 2020 +0800

    test-object-files add

为了便于大家理解,咱们继续修改这个文件,加上第三行数据: test - objects - files - 第三行:

然后执行

git add .
git commit -m "test-object-files updated 02"

然后查看objects目录下的文件内容:

➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/11/a5f11388c345846bbaa060a98d8e93a1114e99
./.git/objects/18/83fd5aefeb430cb25150e531e92e38f0176f0d
./.git/objects/20/0e059f0c0b178378aecd6736f384a74d28df94
./.git/objects/20/db6c4af7677b7509d2b323b503733405e4987e
./.git/objects/37/17929ecb3efabae427dbe3725654b3de1a114b
./.git/objects/54/500d5d74f828d1a3193fb685084b87aaf419c7
./.git/objects/5b/22ea83f2e8555371b818a7e441d4156795af04
# 以下三行是本次commit产生的。
./.git/objects/a6/2a79d99b37bc5c44ca083cd40f9e4c00aae0bd # commit-id,提交对象
./.git/objects/ba/f6b0843a522f8e77f326d2615cb37ed290d06e # 文件,数据对象
./.git/objects/c4/3622f1e8e728f88eb11adfee1ea08d41c792a0 # 树对象

➜  gitlearn git:(master) git cat-file -p a62a79d99b37bc5c44ca083cd40f9e4c00aae0bd
tree c43622f1e8e728f88eb11adfee1ea08d41c792a0
parent 200e059f0c0b178378aecd6736f384a74d28df94
test-object-files updated 02

➜  gitlearn git:(master) git cat-file -p c43622f1e8e728f88eb11adfee1ea08d41c792a0
100755 blob baf6b0843a522f8e77f326d2615cb37ed290d06e	test-objects-files-01.txt
➜  gitlearn git:(master) git cat-file -p baf6b0843a522f8e77f326d2615cb37ed290d06e
test - objects - files - 第一行
test - objects - files - 第二行
test - objects - files - 第三行

做完上面的操作之后,我们来看一下objects目录下的树结构图。

git工作目录介绍03.png

objects目录总结

  • 作用: 存放所有数据的内容。
  • git是一个内容存执文件管理系统。也就是一个KV数据库,对应的key-value放到了objects目录下,存储形式是一个40位十六机制的校验和。key 是文件名,value是文件内容。
  • 学习了两个底层命令: git cat-file 和 git hash-object. 更多底层命令可以看下这篇文章。// TODO
  • 演示一个文件的三次更新时变化过程,以及各个版本之间的迭代过程,那么如果是多个文件呢? 这里大家可以去试一下,就会更清晰的理解树对象了。(这里演示一个文件并不能完全的解释清树对象这个概念)

Git引用, git工作目录下的refs目录!

➜  gitlearn git:(master) l ./.git/refs/
drwxrwxrwx   3 root  wheel    96B  7 14 17:02 heads
drwxrwxrwx   2 root  wheel    64B  7 14 15:10 tags
  • heads目录, 存储各个分支的HEAD指针指向的版本。
  • tags目录, git tag 命令产生的结果。

heads目录

我们还是根据 objects 部门的操作来演示这部分的内容。 如下图。

git工作目录介绍04-object目录总结.png

通过查看我们目前是在 a62a79d99b37bc5c44ca083cd40f9e4c00aae0bd 这个版本上。

➜  gitlearn git:(master) cat ./.git/refs/heads/master
a62a79d99b37bc5c44ca083cd40f9e4c00aae0bd

我们可以通过 git update-ref 命令来实现切换版本(回退到制定的版本上)

# 我们先使用 git log 命令来查看一下提交记录

commit a62a79d99b37bc5c44ca083cd40f9e4c00aae0bd (HEAD -> master)
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 17:02:46 2020 +0800

    test-object-files updated 02

commit 200e059f0c0b178378aecd6736f384a74d28df94
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 16:24:02 2020 +0800

    test-object-files updated

commit 1883fd5aefeb430cb25150e531e92e38f0176f0d
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 15:45:28 2020 +0800

    test-object-files add

# 我们要回退到第二个版本上。使用 git update-ref,更新引用。
➜ .git git:(master) git update-ref refs/heads/master 200e059f0c0b178378aecd6736f384a74d28df94
# 再去查看git的Head目录下的版本号
➜  gitlearn git:(master) ✗ cat ./.git/refs/heads/master
200e059f0c0b178378aecd6736f384a74d28df94
# 这个时候再使用 git log 去看日志:
commit 200e059f0c0b178378aecd6736f384a74d28df94 (HEAD -> master)
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 16:24:02 2020 +0800

    test-object-files updated

commit 1883fd5aefeb430cb25150e531e92e38f0176f0d
Author: bjhl <bjhl@bjhldeMacBook-Pro.local>
Date:   Tue Jul 14 15:45:28 2020 +0800

    test-object-files add
# 再去看我们的文件: 是不会更新工作区内容的。
➜  gitlearn git:(master) ✗ cat test-objects-files-01.txt
test - objects - files - 第一行
test - objects - files - 第二行
test - objects - files - 第三行

# 我们还可以使用 git update-ref 来创建新的分支。
➜  gitlearn git:(master) ✗ cd .git
➜  .git git:(master) git update-ref refs/heads/test 200e059f0c0b178378aecd6736f384a74d28df94
# 查看test分支的日志
➜  .git git:(master) git log --pretty=oneline test
200e059f0c0b178378aecd6736f384a74d28df94 (HEAD -> master, test) test-object-files updated
1883fd5aefeb430cb25150e531e92e38f0176f0d test-object-files add
# 这时我们去查看我们所处的分支, 发现我们仍然处在master上,并没有进行切换。
➜  .git git:(master) git branch
* master
  test

上面的演示中,我们发现了,虽然只是修改的记录的文件,但是并没有对我们工作区的文件进行更新。怎么解决呢?

HEAD文件

HEAD文件通常是一个符号引用,指向目前所在的分支。所谓符号引用,表示它是一个指向其他引用的指针。

我们查看一下 HEAD 文件中的内容

➜  gitlearn git:(master) ✗ cat .git/HEAD
ref: refs/heads/master

这里呢,有一个命令可以帮助我们实现修改 HEAD 文件的内容

git symbolic-ref

# 查看当前所在的分支
➜  gitlearn git:(master) ✗ git symbolic-ref HEAD
refs/heads/master
# 设置当前所在的分支
➜  .git git:(master) git symbolic-ref HEAD refs/heads/test
# 已经可以看到我们已经把分支切换到了test
➜  .git git:(test)
# 我们去看 工作区里的文件,发现并没有变化!
➜  .git git:(test) cat ../test-objects-files-01.txt
test - objects - files - 第一行
test - objects - files - 第二行
test - objects - files - 第三行

文件的内容的,修改就必须使用 git checkout / git reset 了。

我们回来接着看 refs目录的另一个目录:tags.

tags 是 git tag 命令的产物。

然后,在git中,tags 其实是标签对象,包含了一个标签创建者信息,一个日期,一段注释信息,以及一个指针。标签对象通常指向一个提交对象,而不是一个树对象。永远都会指向同一个提交对象。

标签分为 附注标签 和 轻量标签。

使用命令

# 常见一个轻量标签 -> 一个固定的引用
➜  .git git:(test) git update-ref refs/tags/v1.0 1883fd5aefeb430cb25150e531e92e38f0176f0d

创建一个附注标签的时候,git会创建一个标签对象,并记录一个引用来指向该标签对象,而不是直接指向要提交的对象。

➜  .git git:(test) git tag -a v1.1 1883fd5aefeb430cb25150e531e92e38f0176f0d -m '附注标签'
# 查看标签对象。
➜  .git git:(test) cat refs/tags/v1.1
3f224b87d02aa196b0b8331aa28389a172e3f723
# 查看附注标签详细的内容
➜  .git git:(test) git cat-file -p 3f224b87d02aa196b0b8331aa28389a172e3f723
object 1883fd5aefeb430cb25150e531e92e38f0176f0d
type commit
tag v1.1
tagger bjhl <bjhl@bjhldeMacBook-Pro.local> 1594727545 +0800

附注标签

远程引用

在git的引用中,还有一个远程引用。

这个目录会在我们配置远程仓库的时候出现,我们一起配置一下远程仓库

# 配置远程仓库
➜  gitlearn git:(master) git remote add origin https://gitee.com/fangjiaxiaobai/gitlearn.git

# push 到远程分支。
➜  gitlearn git:(master) git push origin master
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 548 bytes | 548.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/fangjiaxiaobai/gitlearn.git
 * [new branch]      master -> master

# 查看 .git目录下,出现了remotes
 ➜  gitlearn git:(master) l ./.git/refs/
total 8
drwxrwxrwx   6 root  wheel   192B  7 14 19:58 .
drwxrwxrwx  16 root  wheel   512B  7 14 19:58 ..
drwxrwxrwx   4 root  wheel   128B  7 14 19:56 heads
drwxr-xr-x   3 bjhl  wheel    96B  7 14 19:58 remotes
-rw-r--r--   1 bjhl  wheel    41B  7 14 19:56 stash # 进行git stash命令后出现
drwxrwxrwx   4 root  wheel   128B  7 14 19:52 tags

# 查看对应remote配置文件
➜  gitlearn git:(master) cat .git/refs/remotes/origin/master
200e059f0c0b178378aecd6736f384a74d28df94

这个校验码表示 ,远程版本库中和本地的版本库中最新一次交互的commit id 。

远程引用和分支之间最主要的区别,就是远程引用是只读的。Git永远不会讲HEAD引用指向该远程引用,因此,不可能通过commit来更新远程引用。

Index文件

这个文件是 git stage 的文件信息存放地,根据该文件中的内容,可以查看当前哪些文件,或者哪些文件发生了变化,而在commit之后,则会把相应的信息存入 .git/objects 文件夹下。通过 git ls-files -s 可以查 看 index 文件中的 stage 文件的信息。

Index 文件是用二进制存储的,包含有 ctime 和 mtime 时间信息,文件存储的设备信息,磁盘的inode信息,文件的 mode信息,UID,GID,文件大小,文件的SHA-1码,flag,文件的file path等信息。

各个文件的信息按照一定的排列顺序进行排布。提交的时候,按照这种约定进行解析。

包文件

在文章一开始,我们就说过,git是基于快照的方式来进行版本控制的。这就意味着,我们每进行一次commit操作,git就是存储一份我们所有的文件。那么,一个大项目,那岂不是会占用很大的存储空间??

我们先做个实验。看看git会不会占用很大的存储空间?

首先,我们新建一个仓库

# 创建仓库
➜  gitlearn git init .
Initialized empty Git repository in /private/tmp/gitlearn/.git/
# 添加文件
➜  gitlearn git:(master) touch test-package-file
➜  gitlearn git:(master) ✗ vim test-package-file
➜  gitlearn git:(master) ✗ git add .
➜  gitlearn git:(master) ✗ git commit -m "test package file 1"
[master (root-commit) 12cbb50] test package file 1
 Committer: bjhl <bjhl@bjhldeMacBook-Pro.local>
 1 file changed, 1 insertion(+)
 create mode 100644 test-package-file
# 查看数据对象的id
➜  gitlearn git:(master)  git cat-file -p master^{tree}
100644 blob c2e8644a8379ab0cf4f2bc5a14160d94608502f7	test-package-file
# 修改 test ,添加 test package file * 700
➜  gitlearn git:(master) vim test-package-file
➜  gitlearn git:(master) ✗ vim test-package-file
➜  gitlearn git:(master) ✗ git add .
➜  gitlearn git:(master) ✗ git commit -m "test package file 2"
[master 421177c] test package file 2
 Committer: bjhl <bjhl@bjhldeMacBook-Pro.local>
 1 file changed, 740 insertions(+)
# 查看第二次提交的数据对象的id
➜  gitlearn git:(master) git cat-file -p master^{tree}
100644 blob 363980e2f742592594e01648888a661d2d0479b9	test-package-file
### 查看两次数据对象的大小
➜  gitlearn git:(master) git cat-file -s c2e8644a8379ab0cf4f2bc5a14160d94608502f7
18
➜  gitlearn git:(master) git cat-file -s 363980e2f742592594e01648888a661d2d0479b9
13338

## 查看一下这两次文件存储的文件。
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/12/cbb50359469eb29efe29945a3b18e262f6e862
./.git/objects/36/3980e2f742592594e01648888a661d2d0479b9
./.git/objects/42/1177c9026e757e7e123b6ee589bcffa067eb96
./.git/objects/8e/0703665933215a57787a3117b079d4c8cb1921
./.git/objects/c2/e8644a8379ab0cf4f2bc5a14160d94608502f7
./.git/objects/c3/1d0ebb18efdfc791a254ab58a01ff2ed0136e4

现在git中存储了两个文件,分别是我们第一次提交和第二次提交的内容。

如果git像SVN一样记录变更的内容的话,是不是更好呢?

git上本来可以那么做。 git最初向磁盘中存储对象时所使用的格式称为 松散对象格式。 但是,git会时不时的将多个对象打包成一个 称为 “包文件” 的二进制文件。来节省空间和效率。

当仓库中有太多的 松散对象,或者手动执行 git gc命令,或者向远程服务器执行推送时,git都会这么做。

# 执行一下 git gc
➜  gitlearn git:(master) git gc
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (6/6), done.
Total 6 (delta 0), reused 0 (delta 0)
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/info/packs
./.git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.idx
./.git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.pack

这里生成了一个包文件(.pack)和一个索引文件(.idx)。包文件抱愧了刚才从一个文件系统中移除的所有对象的内容,索引文件包含了文件的偏移信息。我们可以通过偏移文件快速的定位任意一个指定对象。

git 打包对象时,会查找命名以及大小相近的文件,并只保存文件不同版本之间的差异内容。可以使用 git verify-pack 这个底层命令查看已经打包内容:

➜  gitlearn git:(master) git verify-pack -v .git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.idx
421177c9026e757e7e123b6ee589bcffa067eb96 commit 238 160 12
12cbb50359469eb29efe29945a3b18e262f6e862 commit 190 131 172
363980e2f742592594e01648888a661d2d0479b9 blob   13338 80 303
c31d0ebb18efdfc791a254ab58a01ff2ed0136e4 tree   45 55 383
8e0703665933215a57787a3117b079d4c8cb1921 tree   45 56 438
c2e8644a8379ab0cf4f2bc5a14160d94608502f7 blob   18 28 494
non delta: 6 objects
.git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.pack: ok

其中第三列是在包文件中各个对象的大小,可以看到,树对象是45字节,数据对象和提交对象,都是原来的大小。

显然这次并没有进行压缩。 我们再次修改文件:

# 修改文件: 最后一行添加 test package file
➜  gitlearn git:(master) vim test-package-file
➜  gitlearn git:(master) ✗ git add .
➜  gitlearn git:(master) ✗ git commit -m "test package file 3"
[master 2ab3a27] test package file 3
 Committer: bjhl <bjhl@bjhldeMacBook-Pro.local>
 1 file changed, 1 insertion(+)
 # 查看目录下的文件
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/2a/b3a275bf16a86f77e632127ccf62bcecf98579
./.git/objects/84/003569b2725d79bc209821c9c3dbb5c2ef8a8d
./.git/objects/df/e088f704e98a835ed3ca2ce31919947d63f884
./.git/objects/info/packs
./.git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.idx
./.git/objects/pack/pack-275c7b3d69b1acacc114c081d6ffa61d6683e8a1.pack
# 进行打包
➜  gitlearn git:(master) git gc
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (9/9), done.
Total 9 (delta 1), reused 5 (delta 0)
➜  gitlearn git:(master) sh /opt/util/dirfile.sh ./.git/objects
./.git/objects/info/packs
./.git/objects/pack/pack-a30aa1dcd93c0fadc605cf3fb5828e8cf186ef29.idx
./.git/objects/pack/pack-a30aa1dcd93c0fadc605cf3fb5828e8cf186ef29.pack
# 查看打包的内容
➜  gitlearn git:(master) git verify-pack -v .git/objects/pack/pack-a30aa1dcd93c0fadc605cf3fb5828e8cf186ef29.idx
2ab3a275bf16a86f77e632127ccf62bcecf98579 commit 238 160 12
421177c9026e757e7e123b6ee589bcffa067eb96 commit 238 160 172
12cbb50359469eb29efe29945a3b18e262f6e862 commit 190 131 332
84003569b2725d79bc209821c9c3dbb5c2ef8a8d tree   45 56 555
c31d0ebb18efdfc791a254ab58a01ff2ed0136e4 tree   45 55 611
8e0703665933215a57787a3117b079d4c8cb1921 tree   45 56 684
dfe088f704e98a835ed3ca2ce31919947d63f884 blob   13356 92 463 # 第三次提交
363980e2f742592594e01648888a661d2d0479b9 blob   7 18 666 1 dfe088f704e98a835ed3ca2ce31919947d63f884 # 第二次提交
c2e8644a8379ab0cf4f2bc5a14160d94608502f7 blob   18 28 740 # 第一次提交
non delta: 8 objects
chain length = 1: 1 object
.git/objects/pack/pack-a30aa1dcd93c0fadc605cf3fb5828e8cf186ef29.pack: ok

我们可以到看到 第三次提交的内容是 13356字节,而第二次提交的内容只有 7个字节。显然,git这次进行压缩。另外,第三个版本是保存了最新的文件内容,原始版本是以差异方式存在的— 这是因为大部分情况下,需要快速的访问文件的最新版本。

总结

  • git创建仓库两种方式:本地创建,clone仓库
  • git仓库的目录结构
    • objects目录 我们通过一个最简单的三次提交来看了一下git仓库是如何存储数据的。 这里,我贴上这张我们操作过程的图,加深一下印象. git工作目录介绍04-object目录总结.png
    • refs: 引用
      • 四种对象: 提交对象,数据对象,树对象,标签对象。
      • 树对象: commit->tree->blob(文件) !!课后作业:多文件的提交记录!!
      • heads/master, heads/test/,tags/.这几个文件的作用
      • git symbolic 命令,
    • Index 文件. 记录当前哪些文件被修改,新增。
    • 包文件: 解决git工作目录过大的问题。压缩。

最后

期望与你一起遇见更好的自己

期望与你一起遇见更好的自己

配套资料 · 免费领取
本文属于「TOOLS」资料包范围:Git 与效率工具速查表。常用命令与工作流速查。
在公众号「方家小白」回复关键词 TOOLS 即可领取。全文无需关注即可阅读。
TOOLS