理解 Git

Git 如何保存文件

其它版本管理系统一般会保存全部文件及其历次提交的差别(diff / revision),经过 merge 原始文件与各阶段的差别就能获取任何版本的状态vue

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

这里比较费解的是:快照到底是什么?数据库

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

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

全部文件快照都会被储存在 .git 仓库文件夹下的 objects 目录中设计

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

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

能够想象,随着版本不断迭代,.git 仓库目录的体积每每会超过工做区全部文件的体积之和,由于哪怕只作了一丁点的改变,git 都会从新生成快照。以下图所示,我仅仅删掉了 vue.runtime.js 的一行注释,而后执行 `git add -A`,.git 中就从新生成了一份快照对象

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

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

 

Git 如何保存文件版本

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

root: {

  sub1: {

     hash

     hash

     ...

  }

  sub2: {

     hash

     hash

     ...

  }

}

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

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

而每次 commit 就是一个版本记录

能够看到,全部的 commit 造成了一个链表,而这个链表有一个形象的名称:分支

但咱们最好不要把分支这个概念当作是一条“链”,而应该当作是某个版本(commit)的索引,咱们说合并两个分支,合并的不是两条链,而是两个版本(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 branch -d dev` 删除分支:

{

  master: commit-3

}

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 了

相关文章
相关标签/搜索