一、维护

  • Git 会不定时地自动运行一个叫做 “auto gc” 的命令,大多数时候,这个命令并不会产生效果。然而,如果有太多松散对象(不在包文件中的对象)或者太多包文件,Git 会运行一个完整的 git gc 命令。“gc” 代表垃圾回收,这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中,将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。
  • 可以像下面一样手动执行自动垃圾回收:
$ git gc --auto
  • 就像上面提到的,这个命令通常并不会产生效果,大约需要 7000 个以上的松散对象或超过 50 个的包文件才能让 Git 启动一次真正的 gc 命令,可以通过修改 gc.auto 与 gc.autopacklimit 的设置来改动这些数值。
  • gc 将会做的另一件事是打包你的引用到一个单独的文件,假设仓库包含以下分支与标签:
$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1
  • 如果执行了 git gc 命令,refs 目录中将不会再有这些文件。为了保证效率 Git 会将它们移动到名为 .git/packed-refs 的文件中,就像这样:
$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9
  • 如果更新了引用,Git 并不会修改这个文件,而是向 refs/heads 创建一个新的文件,为了获得指定引用的正确 SHA-1 值,Git 会首先在 refs 目录中查找指定的引用,然后再到 packed-refs 文件中查找。所以,如果在 refs 目录中找不到一个引用,那么它或许在 packed-refs 文件中。
  • 注意:这个文件的最后一行,它会以 ^ 开头,这个符号表示它上一行的标签是附注标签,^ 所在的那一行是附注标签指向的那个提交。

二、数据恢复

  • 在使用 Git 的时候,可能会出现意外丢失一次提交的情况,通常这是因为强制删除了正在工作的分支,但是最后却发现还需要这个分支,亦或者硬重置了一个分支,放弃了想要的提交,如果这些事情已经发生,该如何找回相应的提交呢?
  • 如下所示,将硬重置测试仓库中的 master 分支到一个旧的提交,以此来恢复丢失的提交。首先,来看看仓库现在在什么地方:
$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
  • 现在,将 master 分支硬重置到第三次提交:
$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
  • 现在顶部的两个提交已经丢失了,没有分支指向这些提交,需要找出最后一次提交的 SHA-1 然后增加一个指向它的分支,窍门就是找到最后一次的提交的 SHA-1,但是如果记不起来了,怎么办呢?
  • 最方便,也是最常用的方法,是使用一个名叫 git reflog 的工具,当正在工作时,Git 会默默地记录每一次改变 HEAD 时它的值,每一次提交或改变分支,引用日志都会被更新,引用日志(reflog)也可以通过 git update-ref 命令更新,我们在 Git 引用 有提到使用这个命令而不是是直接将 SHA-1 的值写入引用文件中的原因,可以在任何时候通过执行 git reflog 命令来了解曾经做过什么:
$ 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 log -g,这个命令会以标准日志的格式输出引用日志:
$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:22:37 2009 -0700third commitcommit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700modified repo.rb a bit
  • 看起来下面的那个就是丢失的提交,可以通过创建一个新的分支指向这个提交来恢复它。例如,可以创建一个名为 recover-branch 的分支指向这个提交(ab1afef):
$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
  • 不错,现在有一个名为 recover-branch 的分支是 master 分支曾经指向的地方,再一次使得前两次提交可到达了。接下来,假设丢失的提交因为某些原因不在引用日志中,那么我们可以通过移除 recover-branch 分支并删除引用日志来模拟这种情况,现在前两次提交又不被任何分支指向了:
$ git branch -D recover-branch
$ rm -Rf .git/logs/
  • 由于引用日志数据存放在 .git/logs/ 目录中,现在已经没有引用日志了,这时该如何恢复那次提交? 一种方式是使用 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
  • 本例中,可以在 “dangling commit” 后看到丢失的提交,现在可以用和之前相同的方法恢复这个提交,也就是添加一个指向这个提交的分支。

三、移除对象

  • Git 有很多很棒的功能,但是其中一个特性会导致问题,git clone 会下载整个项目的历史,包括每一个文件的每一个版本。如果所有的东西都是源代码那么这很好,因为 Git 被高度优化来有效地存储这种数据。然而,如果某个人在之前向项目添加了一个大小特别大的文件,即使将这个文件从项目中移除了,每次克隆还是都要强制的下载这个大文件,之所以会产生这个问题,是因为这个文件在历史中是存在的,它会永远在那里。
  • 当迁移 Subversion 或 Perforce 仓库到 Git 的时候,这会是一个严重的问题,因为这些版本控制系统并不下载所有的历史文件,所以这种文件所带来的问题比较少。如果从其他的版本控制系统迁移到 Git 时发现仓库比预期的大得多,那么就需要找到并移除这些大文件。
  • 警告:这个操作对提交历史的修改是破坏性的,它会从必须修改或移除一个大文件引用最早的树对象开始重写每一次提交,如果在导入仓库后,在任何人开始基于这些提交工作前执行这个操作,那么将不会有任何问题。否则, 必须通知所有的贡献者他们需要将他们的成果变基到新提交上。
  • 为了演示,将添加一个大文件到测试仓库中,并在下一次提交中删除它,现在我们需要找到它,并将它从仓库中永久删除。首先,添加一个大文件到仓库中:
$ curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'add git tarball'
[master 7b30847] add git tarball1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 git.tgz
  • 其实这个项目并不需要这个巨大的压缩文件,现在将它移除:
$ git rm git.tgz
rm 'git.tgz'
$ git commit -m 'oops - removed large tarball'
[master dadf725] oops - removed large tarball1 file changed, 0 insertions(+), 0 deletions(-)delete mode 100644 git.tgz
  • 执行 gc 来查看数据库占用了多少空间:
$ git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)
  • 也可以执行 count-objects 命令来快速的查看占用空间大小:
$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0
  • size-pack 的数值指的是包文件以 KB 为单位计算的大小,所以大约占用了 5MB 的空间。在最后一次提交前,使用了不到 2KB,显然,从之前的提交中移除文件并不能从历史中移除它。每一次有人克隆这个仓库时,他们将必须克隆所有的 5MB 来获得这个微型项目,只因为意外地添加了一个大文件,现在来彻底的移除这个文件。
  • 首先必须找到它,在本例中,已经知道是哪个文件了,但是如果不知道,该如何找出哪个文件或哪些文件占用了如此多的空间? 如果执行 git gc 命令,所有的对象将被放入一个包文件中,可以通过运行 git verify-pack 命令,然后对输出内容的第三列(即文件大小)进行排序,从而找出这个大文件,也可以将这个命令的执行结果通过管道传送给 tail 命令,因为只需要找到列在最后的几个大对象:

$ 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
  • 可以看到这个大对象出现在返回结果的最底部占用 5MB 空间。为了找出具体是哪个文件,可以使用 rev-list 命令,如果传递 --objects 参数给 rev-list 命令,它就会列出所有提交的 SHA-1、数据对象的 SHA-1 和与它们相关联的文件路径。可以使用以下命令来找出数据对象的名字:
$ 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
  • 必须重写 7b30847 提交之后的所有提交来从 Git 历史中完全移除这个文件。为了执行这个操作,要使用 filter-branch 命令:
$ git filter-branch --index-filter \'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten
  • –index-filter 选项类似于在Git之深入解析如何重写提交历史 中提到的的 --tree-filter 选项,不过这个选项并不会让命令将修改在硬盘上检出的文件,而只是修改在暂存区或索引中的文件。
  • 必须使用 git rm --cached 命令来移除文件,而不是通过类似 rm file 的命令,因为需要从索引中移除它,而不是磁盘中。还有一个原因是速度,Git 在运行过滤器时,并不会检出每个修订版本到磁盘中,所以这个过程会非常快。如果愿意的话,也可以通过 --tree-filter 选项来完成同样的任务,git rm 命令的 --ignore-unmatch 选项告诉命令:如果尝试删除的模式不存在时,不提示错误。最后,使用 filter-branch 选项来重写自 7b30847 提交以来的历史,也就是这个问题产生的地方。否则,这个命令会从最旧的提交开始,这将会花费许多不必要的时间。
  • 历史中将不再包含对那个文件的引用,不过,引用日志和你在 .git/refs/original 通过 filter-branch 选项添加的新引用中还存有对这个文件的引用,所以必须移除它们然后重新打包数据库。在重新打包前需要移除任何包含指向那些旧提交的指针的文件:
$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)
  • 来看看省了多少空间:
$ git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0
  • 打包的仓库大小下降到了 8K,比 5MB 好很多,可以从 size 的值看出,这个大文件还在松散对象中,并没有消失;但是它不会在推送或接下来的克隆中出现,这才是最重要的。如果真的想要删除它,可以通过有 --expire 选项的 git prune 命令来完全地移除那个对象:
$ git prune --expire now
$ git count-objects -v
count: 0
size: 0
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0

Git内部原理之深入解析维护与数据恢复相关推荐

  1. Git内部原理之深入解析Git的引用和包文件

    一.Git 分支本质 如果对仓库中从一个提交(比如 1a410e)开始往前的历史感兴趣,那么可以运行 git log 1a410e 这样的命令来显示历史,不过需要记得 1a410e 是查看历史的起点提 ...

  2. Git内部原理之深入解析Git对象

    一.底层命令与上层命令 Git 有很多的子命令,例如 checkout.branch.remote 等,然而由于 Git 最初是一套面向版本控制系统的工具集,而不是一个完整的.用户友好的版本控制系统, ...

  3. Git内部原理之深入解析传输协议

    一.哑协议 如果正在架设一个基于 HTTP 协议的只读版本库,一般而言这种情况下在版本库之间传输数据使用的就是哑协议,这个协议之所以被称为"哑"协议,是因为在传输过程中,服务端不需 ...

  4. Git内部原理之深入解析环境变量

    一.前言 Git 总是在一个 bash shell 中运行,并借助一些 shell 环境变量来决定它的运行方式. 有时候,知道它们是什么以及它们如何让 Git 按照想要的方式去运行会很有用. 二.全局 ...

  5. Git内部原理之深入解析引用规范

    一.引用规范 在 Git 使用的过程中,会使用一些诸如远程分支到本地引用的简单映射方式,这种映射可以更复杂.假设现在在本地创建了一个小的 Git 仓库,现在想要添加一个远程仓库: $ git remo ...

  6. Git详解之九 Git内部原理

    以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...

  7. Git 内部原理图解——对象、分支以及如何从零开始建仓库

    我们中的许多人每天都在使用  git,但是有多少人知道它的内部是怎么运作的呢? 例如我们使用  git commit  时发生了什么?提交(commit)与提交之间保存的是什么?两次提交之间难道只是文 ...

  8. git(9)Git 内部原理

    9 Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要 ...

  9. Git详解:Git内部原理

    原文地址见伯乐在线:http://blog.jobbole.com/26209/ 2012/09/01 · IT技术, 书籍与教程 · Git, Pro Git, 版本控制 分享到: 36 Andro ...

最新文章

  1. javascript ES6有趣的Set,数组去重、并集、交集、差集
  2. 带你玩玩转 MySQL 查询
  3. 查车的行驶轨迹_怎么查车辆行驶轨迹?
  4. GDCM:区分音量DiscriminateVolume的测试
  5. 【知识小课堂】mongodb 之 查询关键词使用
  6. Package require os(darwin) not compatible with your platform(win32)
  7. dnSpy反编译、部署调试神器
  8. 为什么说边缘计算的发展比5G更重要?
  9. 为什么 Java 在 25 年之后依旧如此年轻:一个架构师的看法
  10. 哈达玛变换的应用SATD、SAD等匹配算法
  11. 计算机组成原理实用教程第3版课后答案,计算机组成原理实用教程课后习题答案.docx...
  12. java configuration类_使用@Configuration编写自定义配置类
  13. Oracle Windows ODBC 数据源配置。
  14. 计算机视觉论文-2021-06-02
  15. 不断网情况下,如何关闭红蜘蛛多媒体网络教室
  16. 超级狗C++的Demo程序运行流程
  17. Linux中的进程管理
  18. 怎样用python定位别人在哪_python程序员教你用微信给对方定位!你说回家!却还在外面鬼混?...
  19. 如何打开探月工程数据发布与信息服务系统下载的数据
  20. informatica 许可_Informatica安装及使用文档

热门文章

  1. Python基础之(面向对象初识)
  2. 通过监测DLL调用探测Mimikatz
  3. Python第四章__装饰器、迭代器
  4. Training—Managing Audio Playback
  5. 【原】动态申请二维数组并释放的三种方法
  6. android 屏蔽home键操作
  7. Android UI开发第二篇——多级列表(ExpandableListView)
  8. 屏幕录像 Camstudio
  9. 知识库 IIS6.0中Response 对象 错误 ASP 0251 : 80004005
  10. uniny 物体运动到一个点停止_Unity3D中的逐点运动