progit笔记:Git内部原理

progit笔记:Git内部原理

从根本上来讲 Git 是一个内容寻址(content-addressable)文件系统,并在此之上提供了一个版本控制系统的用户界面。

.git目录结构:

  1. description 文件仅供 GitWeb 程序使用,我们无需关心。
  2. config 文件包含项目特有的配置选项。
  3. info 目录包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在 .gitignore文件中的忽略模式(ignored patterns)。
  4. hooks 目录包含客户端或服务端的钩子脚本(hook scripts)。

重要:

  1. objects 目录存储所有数据内容。
  2. refs 目录存储指向数据(分支、远程仓库和标签等) 的提交对象的指针。
  3. HEAD 文件指向目前被检出的分支。
  4. index 文件保存暂存区信息。

Git对象

# 可将任意数据保存于 .git/objects 目录(即对象数据库)并返回指向该数据对象的唯一的键
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 'test content' 对应的新生成文件
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# git cat-file -p 选项可指示该命令自动判断内容的类型,并为我们显示大致的内容:
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
# git cat-file -t 命令让 Git 告诉我们其内部存储的任何对象类型
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
version 2

树对象

能解决文件名保存的问题,也允许我们将多个文件组织到一起。Git 以一种类似于 UNIX 文件系统的方式存储内容,但作了些许简化。 所有内容均以树对象和数据对象的形式存储,其中树对象对应了 UNIX 中的目录项,数据对象则大致上对应了 inodes 或文件内容。 一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的 SHA-1 指针,以及相应的模式、类型、文件名信息。

通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记 录一个对应的树对象, 如此重复便可依次记录(某个时间段内)一系列的树对象。

提交对象

提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照; 然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交); 之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳); 留空一行,最后是提交注释。

git addgit commit :Git 所做的工作实质就是将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。

Git引用

Git 分支的本质:一个指向某一系列提交之首的指针或引用。

git branch <branch> 这样的命令时,Git 实际上会运行 update-ref 命令, 取得当前所在分支最新提交对应的 SHA-1 值,并将其加入你想要创建的任何新引用中。

HEAD 引用
当你执行 git branch <branch> 时,Git 如何知道最新提交的 SHA-1 值呢? 答案是 HEAD 文件。

# HEAD 文件的内容
$ cat .git/HEAD
ref: refs/heads/master

标签引用

标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。 主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。 它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。

远程引用

如果添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。

包文件

Git 最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式(每个版本保存一份文件)。

但是,Git 会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。 当版本库中有太多的松散对象,或者你手动执行 git gc 命令,或者你向远程服务器执行推送时,Git 都会这样做。

引用规范

[remote "origin"]
  url = https://github.com/schacon/simplegit-progit
  fetch = +refs/heads/*:refs/remotes/origin/*

引用规范的格式由一个可选的 + 号和紧随其后的 : 组成, 其中 是一个模式(pattern),代表远程版本库中的引用; 是本地跟踪的远程引用的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。

维护与数据恢复

维护

# 自动垃圾回收:
$ git gc --auto

如果你执行了 git gc 命令,refs 目录中将不会再有这些文件(refs/heads/main、 refs/heads/testing)。 为了保证效率 Git 会将它们移动到名为 .git/packed-refs 的文件中。

数据恢复

问题:删除了正在工作的分支,或者硬重置了一个分支,找回想要的提交?

使用一个名叫 git reflog 的工具。 当你正在工作时,Git 会默默地记录每一次你改变 HEAD 时它的值。 每一次你提交或改变分支,引用日志都会被更新。 引用日志(reflog)也可以通过 git update-ref 命令更新,我们在 Git 引用有提到使用这个命令而不是是直接将 SHA-1 的值写入引用文件中的原因。

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb

再通过创建一个新的分支指向这个提交来恢复它

$ git branch recover-branch ab1afef

或者使用 git fsck, 如果使用一个 --full 选项运行它,它会向你显示出所有没有被其他对象指向的对象:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293
# 以标准日志的格式输出引用日志:
$ git log -g

移除对象

问题:某个版本引入了一个大文件,现在需要从仓库中永久删除。

警告:这个操作对提交历史的修改是破坏性的。

在.idx文件中找出大文件,

# 输出内容的第三列(即文件大小)进行排序,找出大文件:
$ git verify-pack -v .git/objects/pack/pack-29…69.idx \
  | sort -k 3 -n \
  | tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob 4975916 4976258 1438
# rev-list 找出具体那个文件:
$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz
# 查看哪些提交对这个文件产生改动:
$ git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball

从 Git 历史中完全移除这个文件

$ git filter-branch --index-filter \
  'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..

删除引用日志和其他文件对这个文件的引用

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc

执行git count-objects -v,可以从 size 的值看出,这个大文件还在你的松散对象中,并没有消失;但是它不会在推送或接下来的克隆中出现,这才是最重要的。

如果真的想要删除它,可以通过有 --expire 选项的 git prune 命令来完全地移除那个对象:

$ git prune --expire now
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值