基本概念

版本库

Git版本库(repository)只是一个简单的数据库,其中包括所有用来维护与管理项目的修订版本和历史信息。而Git版本不仅会维护项目整个生命周期的完整副本,还会提供版本库本身的副本。

之前也提到,Git在版本库中会维护一组配置值,而这些配置值并不会在clone版本库的时候进行clone,相反Git还会对每个网站,每个用户和每个版本库的配置和设置信息进行管理和检查。

在版本库中,Git会维护两个主要的数据结构:对象库(object store)和索引(index),所有这些版本库数据都存放在工作目录下的.git目录中。

  • 对象库在复制操作的时候能够进行有效复制。
  • 索引是暂时的信息,对版本库来说是私有的,并且可以在需要的时候按需求进行创建和修改。

Git对象类型

对象库包含版本库的原始数据文件、所有日志信息、作者信息、日期和其它用于重建项目任意版本或分支的信息。

Git对象库中的对象只有4种类型:

  • 块(binary large object, blob):文件的每一个版本表示为一个块。blob用来指代某些可以包含任意数据的变量或文件,同时其内部结构会被程序忽略。一个blob被认为是一个黑盒,其中存储一个文件的数据,但不包含任何关于该文件的元数据,甚至没有文件名。
  • 目录树(tree):一个目录树对象代表一层目录信息,其记录blob标识符、路径名和在一个目录里所有文件的一些元数据。其也可以递归引用其它目录树或子树对象,从而建立一个包含文件和子目录的完整层次结构。
  • 提交(commit):一个提交对象保存版本库中每一次变化的元数据,包括作者、提交者、提交日期和日志消息。每一个提交对象指向一个目录树对象,该目录树对象在一张完整的快照中捕获提交时版本库的状态。除了最初的提交没有父提交,大多数提交都会有一个父提交。
  • 标签(tag):一个标签对象分配一个任意的且可读的名字给一个特定对象,通常是一个提交对象。

而随着项目开发的推进,所有信息在对象库中会变化和增长,项目的编辑、添加和删除都会被跟踪和建模,而为了有效利用磁盘空间,Git会把对象压缩并存储在打包文件(pack file)中,这些文件也在对象库中。

索引

索引则是临时的,动态的二进制文件,其描述了整个版本库的目录结构。或者可以说,索引捕获项目在某个时刻的整体结构的一个版本,相当于一个快照。项目的状态可以用一个提交和一个目录树来表示,其可以来自项目历史中的任意时刻,或者是正在开发的未来状态。

比如,用户可以在索引中暂存变更,之后索引会记录和保存这些变更,直到准备提交。同时也可以删除或替换索引中的变更。

可寻址内容名称

Git对象库被组织及实现成一个内容寻址的存储系统。具体实现为,对象库中的每个对象都有一个唯一的名称,该名称是对象内容的SHA1散列值,该值能够唯一且有效地指向特定内容。

Git追踪内容

Git的内容追踪主要表现为两种形式:

  • 首先Git的对象库基于其对象内容的散列值,而不是文件目录或文件名,因此可以说Git追踪的是内容。也就是说,如果两个文件的内容完全一样,无论是否处于同一目录,Git在对象库中只会保存一份blob形式的文件副本,因为根据这两个文件内容得到的散列值是一致的。
  • 而当文件从一个版本演进到下一个版本时,Git的内部数据库有效地存储每个文件地每个版本,而不是它们的差异。因此Git是对文件内容的散列值作为文件名,因此其必须对每个文件的完整副本进行操作。

路径名与内容

Git维护了一个明确的文件列表来组成版本库的内容。但Git列表并不是基于文件名,实际上,Git认为文件名是区别于文件内容的数据。

系统 索引机制 数据存储
UNIX文件系统 目录(/path/to/file) 数据块
Git .git/objects/hash、树对象内容 blob对象、树对象

文件名和目录名来自底层的文件系统,但Git并不关心这些名字,而只记录每个路径名,并且确保能够通过该内容精确地创建文件和目录,而这些内容都是通过散列值来索引的。

打包文件

之前提到当文件从一个版本演进到下一个版本时,Git的内部数据库有效地存储每个文件的每个版本,而不是它们的差异。那么这样的效率是否会很低。

其实Git使用了打包文件作为更加有效的存储机制。

要创建一个打包文件,Git首先定位内容非常相似的全部文件,然后为其中一个存储整个内容,然后计算相似文件之间的差异并只存储差异。比如只是更改或添加文件中的一行,Git可能会存储新版本的全部内容,然后记录有差异的一行作为差异,记录到包中。

而Git又是根据对象内容散列值进行追踪的,因此其并不关心两个文件之间的差异是否属于同一文件的不同版本,而是将版本库中任意位置取出两个文件并计算差异,只要Git认为它们足够相似来产生良好的数据压缩。而如果定位和匹配版本库中全局候选差异则是一套相当复杂的算法。

同时Git还维护打包文件中每个完整文件(包括完整内容的文件和差异构建文件)的原始blob的散列值,这也是定位包内对象的索引机制的基础。

对象库图

Git中的对象库图示如上:

  • blob对象在数据结构的底层,其什么都不引用而只被树对象引用,由矩形表示
  • 树对象指向若干blob对象,也可能指向其它树对象,许多不同的提交对象可能指向任何给定的树对象,由三角形表示
  • 提交对象指向特定的树对象,并且该树对象是由提交对象引入版本库的,由圆形表示
  • 标签最多指向一个提交对象,由菱形表示
  • 分支不是基本的Git对象,但其在命名提交对象的时候起到了重要作用,由圆角矩形表示

上图表示各部分是如何协同的,该版本库表示了两个文件进行初始提交后的状态,两个文件都在顶级目录中,同时其master分支和标签V1.0都指向ID为1492的提交对象。

同时若上述两文件不变,添加一个包含一个文件的新子目录,对象库图为:

上图中,新的提交对象添加了一个关联的书对象来表示目录和文件结构的总状态。这里是ID为cafe00d的树对象。

而因为顶级目录被添加的新子目录改变了,顶级树对象的内容也跟着改变了,所有Git引入了一个新的树对象cafe00d。

而blob对象dead23和feeble在两次提交中并没有发生改变,因此可以直接被新的cafe00d树对象直接引用和共享。

而初始提交1492和提交11235也会存在一种指向关系,即初始提交1492是提交11235的父提交。

Git工作时的概念

当使用git init初始化一个空的版本库时,会在.git目录下创建如下文件:

$ find .
.
./.git
./.git/config
./.git/description
./.git/HEAD
./.git/hooks
./.git/hooks/applypatch-msg.sample
./.git/hooks/commit-msg.sample
./.git/hooks/fsmonitor-watchman.sample
./.git/hooks/post-update.sample
./.git/hooks/pre-applypatch.sample
./.git/hooks/pre-commit.sample
./.git/hooks/pre-merge-commit.sample
./.git/hooks/pre-push.sample
./.git/hooks/pre-rebase.sample
./.git/hooks/pre-receive.sample
./.git/hooks/prepare-commit-msg.sample
./.git/hooks/push-to-checkout.sample
./.git/hooks/update.sample
./.git/info
./.git/info/exclude
./.git/objects
./.git/objects/info
./.git/objects/pack
./.git/refs
./.git/refs/heads
./.git/refs/tags

可以看到,.git目录包括很多内容,这些文件是基于模板目录显示的,可以根据需要进行调整。

在一般情况下,不需要查看或者操作.git目录下的文件,而认为该目录下的文件是Git底层或者配置的一部分。

.git/objects目录用来存放所有Git对象的目录,在初始化空的版本库时,除了几个占位符外,该目录是空的:

$ find ./objects/
./objects/
./objects/info
./objects/pack

而创建如下的hello.txt对象:

$ echo "hello world" > hello.txt
$ git add hello.txt

则会在.git/objects下生成新的目录:

$ find .git/objects/
.git/objects/
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/info
.git/objects/pack

对象、散列和blob

当创建hello.txt时,也为该文件创建了对象,Git并不关心hello.txt的文件名,而只关心文件内容。Git会对该blob计算散列值,并把散列值的十六进制表示作为文件名放入对象库中。

比如此时的160位的散列值为3b18e512dba79e4c8300dd08aeb37f8e728b8dad,因此该内容另存为.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad,而Git在前两个数字后面插入一个/以提高文件系统效率。

而为了表示Git没有对文件内容进行很多操作,可以使用散列值将之从对象库中提取出来:

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world

该命令的作用为:

NAME
git-cat-file - Provide content or type and size information for repository objectsSYNOPSIS
git cat-file (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object>
git cat-file (--batch[=<format>] | --batch-check[=<format>]) [ --textconv | --filters ] [--follow-symlinks]DESCRIPTION
In its first form, the command provides the content or the type of an object in the repository. The type is required unless -t or -p is used to find the object type, or -s is used to find the object size, or --textconv or --filters is used (which imply type "blob").
In the second form, a list of objects (separated by linefeeds) is provided on stdin, and the SHA-1, type, and size of each object is printed on stdout. The output format can be overridden using the optional <format> argument. If either --textconv or --filters was specified, the input is expected to list the object names followed by the path name, separated by a single whitespace, so that the appropriate drivers can be determined.

也就是说该命令可以获得版本库对象的内容或其它信息。

文件和树

既然hello.txt这一文件内容对应的blob已经在对象库中了,那么该文件的文件名的对应操作是什么。

之前提到,Git通过目录树对象来跟踪文件的路径名。当使用git add命令时,Git会给添加的每个文件的内容创建一个对象,但并不会马上为树创建一个对象,相反,索引会发生更新。

.git/index表示索引,它跟踪文件的路径名和相应的blob。每次执行命令(比如,git add、git rm或者git mv),Git会用新的路径名和blob信息来更新索引。

无论什么时候,都可以从当前索引创建一个树对象,只要通过底层的git write-tree命令来捕获索引当前信息的快照就可以了。

当前,该索引只包含一个文件,即hello.txt:

$ git ls-files -s
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0       hello.txt

该命令的含义为:

NAME
git-ls-files - Show information about files in the index and the working treeSYNOPSIS
git ls-files [-z] [-t] [-v] [-f](--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*(-[c|d|o|i|s|u|k|m])*[--eol][--deduplicate][-x <pattern>|--exclude=<pattern>][-X <file>|--exclude-from=<file>][--exclude-per-directory=<file>][--exclude-standard][--error-unmatch] [--with-tree=<tree-ish>][--full-name] [--recurse-submodules][--abbrev[=<n>]] [--] [<file>…​]
DESCRIPTION
This merges the file listing in the index with the actual working directory list, and shows different combinations of the two.One or more of the options below may be used to determine the files shown:

从上述解释来看,该命令会显示索引和工作目录中的文件信息。

之后,可以捕获索引状态并将之保存到一个树对象中:

$ git write-tree
68aba62e560c0ebc3396e8ae9335232cd93a3f60

该命令的含义为:

NAME
git-write-tree - Create a tree object from the current indexSYNOPSIS
git write-tree [--missing-ok] [--prefix=<prefix>/]
DESCRIPTION
Creates a tree object using the current index. The name of the new tree object is printed to standard output.The index must be in a fully merged state.Conceptually, git write-tree sync()s the current index contents into a set of tree files. In order to have that match what is actually in your directory right now, you need to have done a git update-index phase before you did the git write-tree.

生成树对象后,会将树对象保存到之前提到的.git/objects目录中:

$ find .git/objects
.git/objects
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/68
.git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60
.git/objects/info
.git/objects/pack

此时版本库中便存在了两个对象,3b18e5的hello.txt文件内容对象和68aba6的树对象,这跟之前是完全对应的。

这里再用git cat-file命令看一下树对象:

$ git cat-file -p 68aba62e560c0ebc3396e8ae9335232cd93a3f60
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    hello.txt

上边的显示内容中,100644为对象文件属性的八进制表示,散列值为hello.txt文件内容的blob对象,hello.txt则是与该blob关联的名字。

树层次结构

实际开发中,项目会包含复杂且深层嵌套的目录结构,并且会随着时间的推移而重构和移动。通过创建一个新的子目录,该目录包含hello.txt的一个副本,看下Git是如何处理的:

$ git add subdir/hello.txt
$ git write-tree
492413269336d21fac079d4a4672e55d5d2147ac$ git cat-file -p 492413269336d21fac079d4a4672e55d5d2147ac
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    hello.txt
040000 tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60    subdir

在上面的打印中,添加了hello.txt的副本,然后捕获索引状态到一个树对象中,然后看下该对象的内容。从结果来看,该树对象包含了hello.txt文件内容的blob和subdir的树对象,但是subdir的散列值却是之前出现过的。

这是因为subdir的树对象只包含一个文件hello.txt,该文件和旧的hello.txt内容一致,所以subdir树和之前的树对象是一致的,也就有和之前一样的散列值。

而此时.git/objects目录也发生了改变:

$ find .git/objects/
.git/objects/
.git/objects/3b
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/49
.git/objects/49/2413269336d21fac079d4a4672e55d5d2147ac
.git/objects/68
.git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60
.git/objects/info
.git/objects/pack

根据之前的描述,上面的三个对象分别为:

  • 3b对应hello.txt文件内容的blob
  • 68对应包含hello.txt的树对象
  • 49对应包含hello.txt文件内容的blob和68对应的树对象

提交

既然可以通过git write-tree创建树对象,便也可以通过底层命令创建提交对象:

$ echo -n "Commit a file that says hello\n" | git commit-tree 492413269336d21fac079d4a4672e55d5d2147ac
f45c97a19d5d6ee01295c619fe24805d9888cb04

该命令的含义为:

NAME
git-commit-tree - Create a new commit objectSYNOPSIS
git commit-tree <tree> [(-p <parent>)…​]
git commit-tree [(-p <parent>)…​] [-S[<keyid>]] [(-m <message>)…​][(-F <file>)…​] <tree>
DESCRIPTION
This is usually not what an end user wants to run directly. See git-commit(1) instead.Creates a new commit object based on the provided tree object and emits the new commit object id on stdout. The log message is read from the standard input, unless -m or -F options are given.The -m and -F options can be given any number of times, in any order. The commit log message will be composed in the order in which the options are given.A commit object may have any number of parents. With exactly one parent, it is an ordinary commit. Having more than one parent makes the commit a merge between several lines of history. Initial (root) commits have no parents.While a tree represents a particular directory state of a working directory, a commit represents that state in "time", and explains how to get there.Normally a commit would identify a new "HEAD" state, and while Git doesn’t care where you save the note about that state, in practice we tend to just write the result to the file that is pointed at by .git/HEAD, so that we can always see what the last committed state was.

即该命令可以基于提供的树对象创建新的提交对象,并在标准输出中打印新的提交对象ID。

上面的命令针对树对象492413269336d21fac079d4a4672e55d5d2147ac创建了提交对象f45c97a19d5d6ee01295c619fe24805d9888cb04,而该提交对象又是:

$ git cat-file -p f45c97a19d5d6ee01295c619fe24805d9888cb04
tree 492413269336d21fac079d4a4672e55d5d2147ac
author wood_glb <wood_glb@git.com> 1655545440 +0800
committer wood_glb <wood_glb@git.com> 1655545440 +0800Commit a file that says hello\n

从上面的打印可以看出,该对象是个提交对象,该类对象主要包括:

  • 标识关联文件的树对象名称
  • 创作新版本的作者名字和提交时间
  • 提交者的名字和提交时间
  • 日志消息

标签

Git只实现了一种标签对象,但是有两种基本的标签类型:

  • 轻量型的(lightweight)
  • 带附注的(annotated)

轻量级标签只是一个提交对象的引用,通常被版本库认为是私有的。这些标签并不在版本库中创建永久对象。

带附注的标签会创建一个对象,其包含一条提交信息,并可根据密钥进行数字签名。

Git在命名一个提交的时候对轻量级的标签和带附注的标签同等对待。不过,默认情况下,很多Git命令只对带附注的标签起作用。

可以通过git tag命令创建一个带有提交信息、带附注且未签名的标签:

$ git tag -m "Tag version 1.0" V1.0 f45c97a19d5d6ee01295c619fe24805d9888cb04

再看一下该标签对象,而该标签对象的散列值可通过如下命令获取:

$ git rev-parse V1.0
92dfd6790e2e45e9f080984cb589a89aed77f835

该命令的含义为:

NAME
git-rev-parse - Pick out and massage parametersSYNOPSIS
git rev-parse [<options>] <args>…​
DESCRIPTION
Many Git porcelainish commands take mixture of flags (i.e. parameters that begin with a dash -) and parameters meant for the underlying git rev-list command they use internally and flags and parameters for the other commands they use downstream of git rev-list. This command is used to distinguish between them.

即该命令可以通过标签值获取散列值。

既然获知了散列值,再看下该对象:

$ git cat-file -p 92dfd6790e2e45e9f080984cb589a89aed77f835
object f45c97a19d5d6ee01295c619fe24805d9888cb04
type commit
tag V1.0
tagger wood_glb <wood_glb@git.com> 1655546625 +0800Tag version 1.0

从该显示来看,该标签指向了一个提交f45c97a19d5d6ee01295c619fe24805d9888cb04,标签名为V1.0,也打印了标签作者和时间。

Git通常给指向树对象的提交对象打标签,这个树对象包含版本库中文件和目录的整个层次结构的总状态。

Git通过某些分支来给特定的提交命名标签。

Git版本控制管理——基本Git概念相关推荐

  1. Git版本控制管理(二)--git配置

    在系统上安装好 Git后,还需要配置Git 环境. 每台计算机上只需要配置一次,程序升级时会保留配置信息,也可以在任何时候再次通过运行命令来修改它们. 配置文件位置 Git 自带一个 git conf ...

  2. 《Git版本控制管理(第2版)》——4.3 Git在工作时的概念

    本节书摘来自异步社区<Git版本控制管理(第2版)>一书中的第4章,第4.3节,作者:[美]Jon Loeliger , Matthew McCullough著,更多章节内容可以访问云栖社 ...

  3. git 强制更新远程_版本控制管理工具git的常见指令合集

    今天小千就来给大家介绍一下git中比较常见的指令合集,方便大家使用git,建议收藏起来方便日后查阅. 一.常见的命令 git help <command> # 显示command的help ...

  4. 【Git版本控制管理】Gitee(码云)和GitHub的使用

    远程仓库的使用 文章目录 远程仓库的使用 使用码云(Gitee) 使用GitHub 远程仓库是指托管在因特网或其他网络中的你的项目的版本库. 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读 ...

  5. java中git版本控制,git版本控制管理是什么?git如何实现版本控制?

    大家好,今天要跟大家讲的是关于git版本控制管理的一点小知识,git相信程序员小伙伴们都已经很熟悉了,很多项目开发都需要git,所以,git版本控制管理到底是干嘛的呢?Git又如何实现版本控制呢?下面 ...

  6. 版本控制管理工具Git/SVN

    Git Git:是一款分布式管理控制系统(团队人员不在一起),可以有效.高速地处理项目版本管理(代码管理和版本回退),有助于团队协同开发 获取项目的Git仓库 1:在现存的目录下,通过导入所有文件来创 ...

  7. Git版本控制管理——远程版本库

    之前提到的Git的所有操作都是在本地完成的,而实际项目开发并不是一个人就可以搞定的,通常需要团队的协作,而这些协作可能又不是在同一个地区的,这就涉及到Git的分布式特性了. Git的分布式特定会涉及到 ...

  8. Git 版本控制管理(一)

    Git 是一个分布式版本控制工具,它的作者 Linus Torvalds 是这样给我们介绍 Git  -- The stupid content tracker(傻瓜式的内容跟踪器) 关于 Git 的 ...

  9. Git版本控制管理——简介

    说明 在大型项目开发或者多人协作开发时,都希望可以对软件代码进行管理和追踪,以便确认开发的进度和方便问题追溯.这就需要使用到版本控制系统(VCS),比如Git就是一款很优秀的版本控制工具.如今很多项目 ...

最新文章

  1. Centos8 使用auditd配置系统审计
  2. ai怎么画循环曲线_AI插画设计,用AI制作一个只可爱的短腿柯基插画
  3. vscode 显示多个文件_优秀的 VS Code 前端开发扩展
  4. winform技巧一,errorprovider,任务栏可见,总在最前
  5. jest忽略如何添加忽略_大多数人忽略的基本家庭维护任务
  6. 深入浅出逻辑组合电路(2)
  7. android中ActionBar的几个属性
  8. day28 socketserver
  9. Python实现大自然数分解为最多4个平方数之和(1)
  10. [转]网页板块设计研究
  11. 万由nas系统安装MySQL_ESXi安装万由OS(U-NAS 3.0.9)
  12. java架构师是做什么的 java架构师的工作内容
  13. 数据仓库的分层,你知道吗?
  14. 计算机专业英语讲课笔记(1)
  15. 2006公务员考试-杂七杂八常识(1)
  16. (Android学习)点击按钮Button,更换背景颜色
  17. b B kb kB kbps KBps 换算
  18. oracle 分区之 interval range 分区
  19. 【2022牛客多校5 A题 Don‘t Starve】DP
  20. 吴恩达机器学习入门笔记12/13-聚类与降维

热门文章

  1. tomcat服务器连接数问题解决
  2. Scalar的基础篇(零)简单介绍
  3. Scalar类-颜色类
  4. PyQt5 基本教程
  5. redis 十一. IO 多路复用
  6. 安装win10出现“计算机意外的重新启动或遇到错误。Windows安装无法继续。若要安装Windows,请单击“确定”重新启动计算机,然后安装系统。”
  7. 大整数运算-大数的存储与运算
  8. 2021 计算机 保研经历 保研经验贴 保研知识扫盲 保研时间线(合肥工业大学 软件工程 rk4/165,211 3%)
  9. React 中闭包陷阱问题分析
  10. dba怎么报考_深圳dba双证报考时间