0


分布式版本控制工具—Git

整体架构

Git整体架构图

  • 上层命令(Porcelain Commands)
  • 底层命令(Plumbing Commands)
  • 对象数据库(Object Database)

上层命令

日常使用的命令基本都是上层命令,如:commit、add、checkout、branch、remote 等。上层命令通过组合底层命令或直接操作底层数据对象,使 Git 底层实现细节对用户透明,从而为用户提供了一系列简单易用的命令集合。

底层命令

在日常开发中,我们基本接触不到 Git 的底层命令,如果要想使用这些底层命令,我们必须要对 Git 的设计原理有一定的认知。

对象数据库

Git 最核心、最底层 的部分则是其所实现的一套 对象数据库(Object Database),其本质是一个基于 Key-Value 的内容寻址文件系统(Content-addressable File System)。

常用命令

git init# 初始化本地git仓库(创建新仓库)git config --global user.name “xxx”# 配置用户名git config --global user.email “xxx@xxx.com”# 配置邮件git config --global color.ui true# git status等命令自动着色git config --global color.status autogit config --global color.diff autogit config --global color.branch autogit config --global color.interactive autogit config --global --unset http.proxy# remove proxy configuration on gitgit clone git+ssh://git@192.168.53.168/VT.git# clone远程仓库git status# 查看当前版本状态(是否修改)git add xyz# 添加xyz文件至indexgit add .# 增加当前子目录下所有更改过的文件至indexgit commit -m ‘xxx’# 提交git commit --amend -m ‘xxx’# 合并上一次提交(用于反复修改)git commit -am ‘xxx’# 将add和commit合为一步git rm xxx# 删除index中的文件git rm -r *# 递归删除git log# 显示提交日志git log -1# 显示1行日志 -n为n行git log -5git log --stat# 显示提交日志及相关变动文件git log -p -mgit show dfb02e6e4f2f7b573337763e5c0013802e392818# 显示某个提交的详细内容git show dfb02# 可只用commitid的前几位git show HEAD# 显示HEAD提交日志git show HEAD^# 显示HEAD的父(上一个版本)的提交日志 ^^为上两个版本 ^5为上5个版本git tag# 显示已存在的taggit tag -a v2.0 -m ‘xxx’# 增加v2.0的taggit show v2.0# 显示v2.0的日志及详细内容git log v2.0# 显示v2.0的日志git diff# 显示所有未添加至index的变更git diff --cached# 显示所有已添加index但还未commit的变更git diff HEAD^# 比较与上一个版本的差异git diff HEAD – ./lib# 比较与HEAD版本lib目录的差异git diff origin/master…master# 比较远程分支master上有本地分支master上没有的git diff origin/master…master --stat# 只显示差异的文件,不显示具体内容git remote add origin git+ssh://git@192.168.53.168/VT.git# 增加远程定义(用于push/pull/fetch)git branch# 显示本地分支git branch --contains 50089# 显示包含提交50089的分支git branch -a# 显示所有分支git branch -r# 显示所有原创分支git branch --merged# 显示所有已合并到当前分支的分支git branch --no-merged# 显示所有未合并到当前分支的分支git branch -m master master_copy# 本地分支改名git checkout -b master_copy# 从当前分支创建新分支master_copy并检出git checkout -b master master_copy# 上面的完整版git checkout features/performance# 检出已存在的features/performance分支git checkout --track hotfixes/BJVEP933# 检出远程分支hotfixes/BJVEP933并创建本地跟踪分支git checkout v2.0# 检出版本v2.0git checkout -b devel origin/develop# 从远程分支develop创建新本地分支devel并检出git checkout – README# 检出head版本的README文件(可用于修改错误回退)git merge origin/master# 合并远程master分支至当前分支git cherry-pick ff44785404a8e# 合并提交ff44785404a8e的修改git push origin master# 将当前分支push到远程master分支git push origin :hotfixes/BJVEP933# 删除远程仓库的hotfixes/BJVEP933分支git push --tags# 把所有tag推送到远程仓库git fetch# 获取所有远程分支(不更新本地分支,另需merge)git fetch --prune# 获取所有原创分支并清除服务器上已删掉的分支git pull origin master# 获取远程分支master并merge到当前分支git mv README README2# 重命名文件README为README2git reset --hard HEAD# 将当前版本重置为HEAD(通常用于merge失败回退)git rebase.git branch -d hotfixes/BJVEP933# 删除分支hotfixes/BJVEP933(本分支修改已合并到其他分支)git branch -D hotfixes/BJVEP933# 强制删除分支hotfixes/BJVEP933git ls-files# 列出git index包含的文件git show-branch# 图示当前分支历史git show-branch --all# 图示所有分支历史git whatchanged# 显示提交历史对应的文件修改git revert dfb02e6e4f2f7b573337763e5c0013802e392818# 撤销提交dfb02e6e4f2f7b573337763e5c0013802e392818git ls-tree HEAD# 内部命令:显示某个git对象git rev-parse v2.0# 内部命令:显示某个ref对于的SHA1 HASHgit reflog# 显示所有提交,包括孤立节点git show HEAD@{5}.git show master@{yesterday}# 显示master分支昨天的状态git log --pretty=format:‘%h %s’ --graph# 图示提交日志git show HEAD~3.git show -s --pretty=raw 2be7fcb476git stash# 暂存当前修改,将所有至为HEAD状态git stash list# 查看所有暂存git stash show -p stash@{0}# 参考第一次暂存git stash apply stash@{0}# 应用第一次暂存git grep “delete from”# 文件中搜索文本“delete from”git grep -e ‘#define’ --and -e SORT_DIRENTgit gcgit fsck

存储方式

普通文件:使用Blob Object(二进制对象)存储文件内容
目录文件: 使用Tree Object(树对象)存储一系列树对象记录(tree entry)。每个树对象记录存储一个Blob Object(对应一个普通文件)或一个 Tree Object(对应一个目录文件)的元数据。

索引方式

在 Git 文件系统中,使用一个 40 位的 SHA-1 值作为一个文件或目录存储内容时所占用的一个 Object 文件的唯一标识符,最终在 .git/objects/ 目录下进行匹配查找,其中前 2 位 SHA-1 值作为存储子目录,后 38 位 SHA-1 值作为文件名。
Tree Object 和 Blob Object 的 SHA-1 值是根据内容计算得到的,只要内容相同,SHA-1 值相同;而 Commit Object 会结合内容、时间、作者等数据,因此 SHA-1 值很难出现冲突。

对象模型

Git 对象模型主要包括以下 4 种对象

  • 二进制对象(Blob Object)
  • 树对象(Tree Object)
  • 提交对象(Commit Object)
  • 标签对象(Tag Object)

所有对象均存储在 .git/objects/ 目录下,并采用相同格式进行表示,其可以分为两部分:

  • 头部信息:类型 + 空格 + 内容字节数 + \0
  • 存储内容

Git 使用两部分内容的 40 位 SHA-1 值(前 2 为作为子目录,后 38 位作为文件名)作为快照文件的唯一标识,并对它们进行 zlib 压缩,然后将压缩后的结果作为快照文件的实际内容进行存储。

Blob Object

Block Object 用于存储普通文件的内容数据,其头部信息为 “blob” + 空格 + 内容字节数 + \0,存储内容为对应文件的内容快照。

Tree Object

Tree Object 用于存储目录文件的内容数据,其头部信息为 “tree” + 空格 + 内容字节数 + \0,存储内容为 一个或多个树对象记录(Tree Entry)。

其中,树对象记录的结构(Git v2.0.0)为:文件模式 + 空格 + 树对象记录的字节数 + 文件路径 + \0 + SHA-1。

如果某一时刻,Git 仓库的文件结构如下所示,那么在 Git 文件系统中,会建立一个对象关系图,如下图所示。

$ tree
.
├── bak
│   └── test.txt
├── new.txt
└── test.txt

在这里插入图片描述
注意,当我们执行 git add(进入暂存区)时,Git 会为暂存文件创建 Blob Object,为暂存目录创建 Tree Object ,结合未修改文件和目录的 Object,建立一个整体的索引关系,从而形成一个版本快照。

Commit Object

Tree Object 和 Blob Object 用于表示版本快照,Commit Object 则不同,它 用于表示版本索引和版本关系。

此外,Tree Object 和 Blob Object 的 SHA-1 值是根据内容计算得到的,只要内容相同,SHA-1 值相同;而 Commit Object 会结合内容、时间、作者等数据,因此 SHA-1 值很难出现冲突。

Commit Object 的头部信息为 “commit” + 空格 + 内容字节数 + \0,存储内容包含多个部分(Git v2.0.0),具体如下图所示。

  • 对应的根 Tree Object 对应的 SHA-1
  • 一个或多个父级 Commit Object 对应的SHA-1。当进行分支合并时就会出现多个父级 Commit Object。
  • 提交相关内容,包括:作者信息、提交者信息、编码、提交描述等

下图所示,为 Commit Object 与 Tree Object 的关系示意图。每一个 Commit Object 索引一个版本快照,每一个版本快照则是由一个 Tree Object 作为根节点进行构建。不同的版本快照之间会进行数据复用,从而最大限度地节省磁盘空间。每一个 Commit Object 记录了其父版本的索引信息,即另一个 Commit Object 的 SHA-1 值,从而构建了一个完整的版本关系图(有向无环图)。通过版本关系图,我们可以基于一个 Commit Object 回溯其任意历史版本。
在这里插入图片描述

Tag Object

Tag Object 是第 4 种 Git 对象,其头部信息为 “tag” + 空格 + 内容字节数 + \0,存储内容包含多个部分(Git v2.0.0),具体如下图所示。

  • 所引用对象的 SHA-1 值
  • 所引用对象的类型
  • 标签名称
  • 标签创建者和日期
  • 注释信息在这里插入图片描述 Tag Object 通常指向一个 Commit Object,而不是 Tree Object。它像是一个永不移动的分支引用——永远指向同一个 Commit Object,只不过给这个 Commit Object 加上一个更友好的名字罢了。 Tag Object 并非必须指向某个 Commit Object;我们可以对任意类型的 Git 对象打标签

引用

如果我们对仓库的某一个提交及其历史版本感兴趣,那么我们可以使用该提交的 SHA-1 值进行查找。显然,直接使用 SHA-1 值来记忆是非常不便且易错的。对此,Git 提供了容易记忆的 “别名” 来代替 SHA-1 值,这就是 “引用(referrences,简称 refs)”。
Git 支持三种引用类型,不同的引用类型对应的引用文件各自存储在 .git/refs/ 下的不同子目录中。

  • HEAD 引用
  • 标签引用
  • 远程引用

HEAD 引用

当执行 git branch 新建一个分支时,Git 是如何知道最新提交的 SHA-1 值呢?答案就是 HEAD 文件。
HEAD 文件通常是一个 符号引用(symbolic reference),指向当前所在的分支。所谓符号引用,表示它是一个指向其他引用的指针,类似于符号链接。

在某些特殊情况下,HEAD 文件可能会包含一个 Git 对象的 SHA-1 值。当我们在检出一个标签、提交或远程分支时,让仓库变成 “分离 HEAD” 状态时,就会出现这种情况。
执行 git commit 时,该命令会使用 HEAD 文件中引用所指向的 SHA-1 值作为其父提交,创建一个 Commit Object。

$ git checkout master
$ cat.git/HEAD
ref: refs/heads/master

$ git checkout test
$ cat.git/HEAD
ref: refs/heads/test

标签引用

Git 可以给仓库历史中的某一个提交打上标签,以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点( v1.0 、 v2.0 等等)

标签引用(Tag Reference)包含两种类型:轻量标签 和 附注标签。

轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。

附注标签是存储在 Git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。

轻量标签

对于轻量标签,我们可以通过如下命令进行创建。

$ git update-ref refs/tags/v1.0 a9f2652cb992f300c0a251d3607bdabfe8901bb2
$ cat.git/refs/tags/v1.0
a9f2652cb992f300c0a251d3607bdabfe8901bb2

该命令会创建一个以标签名命名的文本文件,文件内容为其所引用的 Commit Object 的 SHA-1 值。

附注标签

对于附注标签,我们可以通过如下命令进行创建。

$  git tag -a v1.1 e73b484c6e1802c36dceae4535f6c85 -m "test tag"

该命令会创建一个 标签对象(Tag Object),存储在 .git/objects/ 目录下。

远程引用

远程引用(Remote Reference)主要用于远程仓库与本地仓库进行映射和对比。如果我们添加了一个远程仓库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 .git/refs/remotes/ 目录下。

远程引用和分支(位于 .git/refs/heads/ 目录下的引用)之间的最主要区别在于:远程引用是只读的。虽然我们可以 git checkout 到某个远程引用,但是 Git 并不会将 HEAD 引用指向该远程引用。因此,我们永远不能通过 git commit 命令来更新远程引用。Git 将这些远程引用作为记录远程服务器上各个分支最后已知位置状态的书签来管理。

包文件

过上文我们知道,如果我们对任意一个文件进行修改,Git 就会创建一个新的 Blob Object,并将该文件的所有内容存储到里面。
如果一个文件非常大,而每次只修改其中极小一部分内容,这样的话,Git 会创建很多 Blob Object,而它们的绝大部分的内容都是相同的,因此会存在严重的磁盘空间浪费问题。
Git 采用了增量存储的方式进行了优化,不定时地自动对仓库中的对象进行打包并移除,最终生成两个文件:

  • 包文件(Pack File) :采用 原始内容 + 增量内容 的形式进存储,从而节省存储空间。
  • 索引文件(Index File):存储了各个包文件中各个对象的大小、偏移、类型等数据,从而便于重建文件快照和对象关系。在这里插入图片描述

文件状态

工作目录下文件有两种状态:已跟踪和未跟踪。
已跟踪(Git已经知道的文件)指的是被纳入版本控制的文件,上一次快照中有它们的记录,在工作一段时间后,它们的状态可能是未修改、已修改或已放入暂存区。
未跟踪指工作目录中除已跟踪文件外的所有文件,既不存在于上次快照,也没有放入暂存区。
文件状态变化

untracked: 未跟踪
unmodified:未修改
modified: 已修改
staged: 暂存

git status

使用git status -s 命令或 git status --short获取工作目录文件状态信息

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

输出中有两栏,左栏指明了暂存区的状态,右栏指明了工作区的状态
?? :新添加的未跟踪文件
A :新添加到暂存区中的文件
M :修改过的文件
例如,上面的状态报告显示: README 文件在工作区已修改但尚未暂存,而 lib/simplegit.rb 文件已修改且已暂存。 Rakefile 文件已修改,暂存后又作了修改,因此该文件的修改中既有已暂存的部分,又有未暂存的部分。

git diff

想知道具体修改了什么地方,可以用 git diff 命令。通常可能会用它来回答这两个问题:当前做的哪些更新尚未暂存? 有哪些更新已暂存并准备好下次提交?

git diff 能通过文件补丁的格式更加具体地显示哪些行发生了改变。此命令比较的是工作目录中当前文件和暂存区域快照之间的差异。 也就是修改之后还没有暂存起来的变化内容。

若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --staged 命令。 这条命令将比对已暂存文件与最后一次提交的文件差异。git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。 所以有时候一下子暂存了所有更新过的文件,运行 git diff 后却什么也没有,就是这个原因。

git rev-list

git rev-list 命令用于列出满足特定条件的提交对象。它的基本语法是:

git rev-list [<options>] <commit>...[--][<path>...]

tips: git rev-list 命令不接受 --stat 和 --pretty 选项同时使用,因为这两个选项的目的不同。–stat 是为了显示每次提交的差异统计,而 --pretty 是为了定制提交信息的显示格式。如果你想要同时显示提交哈希和装饰性引用,以及每次提交的差异统计,你应该分开这两个操作。

git rev-list --pretty=format:"%H" master --since "2024-01-01 00:00:00"--before "2024-01-31 23:59:59"|while read commit_hash;doecho"Commit: $commit_hash"  
    git show --stat --format="%H %D"$commit_hashecho"---------------------------"  
done

参考文章:深入理解 Git 底层实现原理
参考书籍:Pro Git
参考命令:git_toturial

标签: 分布式 git

本文转载自: https://blog.csdn.net/zydbk/article/details/135765315
版权归原作者 Code Lisa 所有, 如有侵权,请联系我们删除。

“分布式版本控制工具—Git”的评论:

还没有评论