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 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 了
来源: https://www.cnblogs.com/kidney/p/8469659.html