Git 如何保存文件

其它版本管理系统通常会保存所有文件及其历次提交的差异(diff / revision),通过 merge 原始文件与各阶段的差异就能获取任何版本的状态

而 Git 保存的是每一次提交时所有文件的快照(snapshot),对于发生改变(modified)的文件会生成新的快照,而对于未发生改变的文件,其新版本快照为上一个版本的快照的索引(图中虚线框所示),这样可以减小版本库的体积

这里比较费解的是:快照究竟是什么?

简单的理解:快照就是压缩文件,只不过 git 会将文件内容压缩为 blob 格式,例如仅含一段 hello world 的 txt 文件压缩后的内容为:

7801 4bca c94f 5230 3462 c848 cdc9 c957
28cf 2fca 49e1 0200 4411 0689

所有文件快照都会被储存在 .git 仓库文件夹下的 objects 目录中

经测试,一份 200k 的未经压缩的代码文件,其文件快照大小约 65k

文件名 eef...542 是根据内容生成的 40 位哈希字符串,文件名 + 文件本身就构成了一组键值对。所有文件都以这种形式保存,而 objects 目录就是一个以键值对形式保存文件的数据库

可以想象,随着版本不断迭代,.git 仓库目录的体积往往会超过工作区所有文件的体积之和,因为哪怕只做了一丁点的改变,git 都会重新生成快照。如下图所示,我仅仅删掉了 vue.runtime.js 的一行注释,然后执行 `git add -A`,.git 中就重新生成了一份快照

一个长期维护的代码库,其代码总量可能只有几 MB,但 .git 完全可能大到以 G 计

比起其它版本管理系统仅仅记录差异,git 的这种做法不是显得更浪费空间吗?git 之所这么设计,是出于“空间换时间”的考虑。用过 SVN 的人都知道要从一个几百 MB 的项目库开出一个分支是多么费时,而使用 git 开分支,无论体积有多大,都是一瞬间的事情

Git 如何保存文件版本

理解了 git 保存文件的方式,就很容易理解其保存版本的方式:采用一个树对象来表示目录结构与文件

root: {

sub1: {

hash

hash

...

}

sub2: {

hash

hash

...

}

}

根据文件索引就可以直接从数据库中取出文件,然后再按树对象表征的目录结构进行组合排列,就很容易恢复出一套文件版本

每次 commit 除了保存树对象以外,还会记录提交的作者、批注、上一次提交的索引等信息,每个 commit 都会根据内容生成一个 hash 作为其唯一的索引

可以看到,所有的 commit 形成了一个链表,而这个链表有一个形象的名称:分支

Git 开分支的原理

git 分支的本质,就是指向某个特定 commit 的指针,假设当前只有一个分支,默认就叫做 master,当前已经是第三个提交了:

{

master: commit-3

}

那么开一个分支,无非就是新创建一个指针:

{

master: commit-3

dev: commit-3

}

当前用户处于哪个分支,需要用另一个指针来表示:

{

HEAD: master

}

执行 `git checkout dev` 切换分支后:

{

HEAD: dev

}

在 dev 分支提交一次 commit 后:

{

master: commit-3

dev: commit-4

}

切回 master,执行 `git merge dev` 合并分支:

{

master: commit-4

dev: commit-4

}

执行 `git branch -d dev` 删除分支:

{

master: commit-4

}

master 分支其实并没有什么特殊之处,不仅可以将其它分支合并入 master,也可以在其它分支上将 master 分支合并进来

Git 合并分支的策略

两个分支的合并只有两种情况:无分叉、有分叉

无分叉的情形最简单,合并分支就把 master 指向的 commit 更换为最新的 commit

{

master: commit-3

dev: commit-4

}

merge:

{

master: commit-4

dev: commit-4

}

这种策略被称为 fast forward

有分叉的情况稍微麻烦一些,git 会将两个分支的分叉点和头部的 commit 做一次三方合并,然后形成一个新的 commit:

显然第一种方式最简便,那有没有办法在分叉的情况下仍然采用 fast forward 的策略呢,有

在 experiment 分支上执行 `git rebase master`,首先会计算出分叉点与 experiment 分支头部的两个 commit 的差异,然后以 C3 为新的基础,整合之前计算出的差异,得到一个新的 commit

var patch = C4 - C2

var C4` = C3 + patch

C4`.parent = C3

rebase 就是改变基础的意思。这下回到 master 分支执行 merge 操作,就可以实现 fast forward 了

最新文章

  1. TFS 2015(Visual Studio Team Foundation Server)的下载和安装
  2. Linux shell脚本编程(三)
  3. SQL 表的完整性
  4. 【转】单调队列优化DP
  5. linq 动态组合条件
  6. Can't create/write to file '/tmp/#sql_887d_0.MYD' (Errcode: 17)
  7. 查看博客模板的css代码
  8. [置顶] mybatis的批量新增
  9. winform清空DataGridView中的数据 分类: DataGridView 2014-05-19 20:56 180人阅读 评论(0) 收藏
  10. Node.js服务的重启与监控
  11. MySQLdb 连接Mysql 数据库出错解决
  12. JavaScript(8)——JSON
  13. form表单中的带有name的标签直接转JSON
  14. 面试题(一GC)
  15. apiCloud 版本号
  16. ConstraintLayout (约束布局)属性详情
  17. PHP字符串offset取值特性
  18. delphi 函数参数传递 默认参数(传值)、var(传址)、out(输出)、const(常数)四类
  19. SpringBoot 入门(一)
  20. WEB项目异常处理

热门文章

  1. Linux几个小杂碎点(更新中)
  2. shell第二篇
  3. matlab文件读写处理实例(二)——textread批量读取文件
  4. javacript 组合使用构造函数模式和原型模式
  5. 101490E Charles in Charge
  6. Core Animation 文档翻译 (第七篇)——改变Layer的默认动画
  7. template.helper()方法
  8. 导入一个新项目需要注意的几大问题(jdk1.6+eclipse4.4+tomcat6)
  9. Jmeter_打印当前时间戳&打印偏移时间戳
  10. Jquery DataTable控制显示列,导出EXCEL