zoukankan      html  css  js  c++  java
  • 理解 Git

    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 就是一个版本记录

    可以看到,所有的 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 了

     

  • 相关阅读:
    Leetcode Reverse Words in a String
    topcoder SRM 619 DIV2 GoodCompanyDivTwo
    topcoder SRM 618 DIV2 MovingRooksDiv2
    topcoder SRM 618 DIV2 WritingWords
    topcoder SRM 618 DIV2 LongWordsDiv2
    Zepto Code Rush 2014 A. Feed with Candy
    Zepto Code Rush 2014 B
    Codeforces Round #245 (Div. 2) B
    Codeforces Round #245 (Div. 2) A
    Codeforces Round #247 (Div. 2) B
  • 原文地址:https://www.cnblogs.com/kidney/p/8469659.html
Copyright © 2011-2022 走看看