文章目录
  1. 1. git 数据类型
    1. 1.1. 基本数据类型
      1. 1.1.1. 空间维度 x 时间维度
    2. 1.2. git objects database
      1. 1.2.1. content-addressed filesystem
        1. 1.2.1.1. hash 生成方法
        2. 1.2.1.2. 对象存储
        3. 1.2.1.3. tag 和 branch
        4. 1.2.1.4. tag 存储方式
        5. 1.2.1.5. branch 存储方式
    3. 1.3. git 存储流程
      1. 1.3.1. modified
      2. 1.3.2. added
      3. 1.3.3. committed
      4. 1.3.4. pushed
  2. 2. 使用进阶
    1. 2.0.1. 怎样恢复代码?
    2. 2.0.2. 怎样回滚代码?
    3. 2.0.3. 怎样排查是哪一个commit 引入了某个bug?
    4. 2.0.4. 怎样暂存当前的提交?
    5. 2.0.5. 怎样 merge?
  • 3. 总结
  • 4. 参考文章
  • git 是 Linus Torvalds 设计的源代码版本控制系统,以其方便简洁的使用,高效的操作,分布式的架构 已经替换掉了原来的SVN,成为了主流的版本控制系统。随着我对 git 的不断使用,我逐渐对 git 的原理产生了浓厚的兴趣,所以本文会着重分析 git 原理,并指导更加清晰、高效地使用 git。

    git 数据类型

    基本数据类型

    1. blob 存储 文件内容
    2. tree 存储目录下文件的文件名,权限
    3. commit 存储提交信息

    blob 作为文件的基本元素,存储文件,blob一旦生成,不可修改。新添加的文件或者修改文件,会生成新的blob。tree表示文件目录,一个tree下面可以有tree和blob。一个commit对象存储提交信息,用户每次提交,都会形成新的tree,生成新的commit指向这个新tree。

    空间维度 x 时间维度

    在空间维度上,blob 和 tree 足够表达源代码的目录结构。但是对于版本管理系统来说,我们还需要记录每个提交点的快照(snapshot),所以需要时间维度的抽象数据结构,commit 就是时间维度上的数据结构,commit的之间的引用,也形成了一个时间线上 tree 结构(DAG,有向无环图)。

    git objects database

    content-addressed filesystem

    git 是一个 content-addressed filesystem 基于内容寻址的系统。git的三种基本对象 blob,tree,commit,存储的位置都是基于内容的hash算出来的。

    hash 生成方法

    1
    2
    header = "<type> " + content.length + "\0"
    hash = sha1(header + content)
    1. 生成header, 为 blob,tree,commit,三个种字符串,content.length 是内容(文件的内容,tree对象的内容,commit的内容)的字节长度,末尾加上 \0 字符和内容分开
    2. header + content 是二进制拼接,而不是文本拼接
    3. sha1 就是常用的hash 算法 SHA-1,生成40位hash值

    对象存储

    1. 存储路径 .git/objects/hash[0, 2]/hash[2, 40] , 40位 hash 前两位为目录名,后38位为 文件名
    2. 存储的内容 是把字节内容经过zlib deflate 压缩后的结果

    tag 和 branch

    1. tag 就是commit的别名,tag 指向的 commit 不变。
    2. branch 本质也是commit的别名,只是这个 commit 会一直跟着开发的提交在变。但是 branch 一直指向最顶端的 commit

    tag 存储方式

    运行

    1
    git tag firsttag

    会在.git/refs/tags 生成 firsttag文件,文件内容存储的是 commit的hash

    branch 存储方式

    .git 目录中 .git/HEAD 文件存储的是当前用户工作的 branch

    refs/heads/ 目录下存储以 branch 名字命名的文本文件,文件内容 存储的是 commit 的 hash。

    通过 git show <commit_hash> 就可以查看 commit的 内容。

    1
    git show d8a5333173c2bf5de7853b11b24839715275cce2

    1. 用户从 master checkout 到 firstbranch, 会更新 .git/HEAD 文件的内容,写入内容 ref: refs/heads/firstbranch
    2. 用户在 firstbranch branch 做commit 操作,会更新 refs/heads/firstbranch 文件的内容,写进新的 commit hash

    所以本质上branch 也是个 commit的别名,只是这个commit会随着开发提交而改变。

    git 存储流程

    一个commit 的最终分四种状态, 提交分为三个阶段

    modified

    所有修改完的代码,在你本机源代码的路径下,也就是你的workspace 里,目前和git 还没关系

    added

    1
    git add xxx

    跑完了 add 命名。这个阶段后,git才开始工作,你修改的文件 会生成 git 的 blob object 存储在 .git/objects

    committed

    1
    git commit -m "xxx"

    跑完 commit 命令,git生成 tree object,指向当前的 snapshot,生成 commit object 保存提交信息,并且这个commit是指向tree的。

    pushed

    git push 则是把修改真正的提交到远程仓库。

    使用进阶

    怎样恢复代码?

    1. 你代码commit 了吗?没有commit, 又没有手动备份,无法恢复。
    2. 你已经commit,但是找不到这个commit 了。执行 git reflog , 按照你操作的时间点排查,应该是哪个commit
    1
    git reflog

    怎样回滚代码?

    1. 你提交到中心仓库了吗?如果提交了 你应该 git revert <commit-hash> 生成一个新的 reverted-commit,你再push这个commit 到线上

    2. 你没有提交到中心仓库

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # 回滚 commit
      git reset --hard HEAD~ # HEAD 指你当前branch的最上方一个commit,HEAD~ 是最上方开始的第二个commit,HEAD~~ 是最上方开始的第三个commit,以此类推。--hard 是指 不要commit 信息,也不要文件修改后的内容。整个这一句连起来就是 回滚到 让让前branch 删除 最新的一个提交
      git reset --soft HEAD~ # 只清除commit 信息,不回滚文件修改的内容

      # 回滚文件
      git checkout -- xx/xxx # 当一个文件为commit时,重置一个单独的文件, 回滚当前 branch的HEAD时的内容
      # 假设当前 branch 为 master
      git checkout master~ xx/xxx # 回滚文件 xx/xxx 到 HEAD 算起的 第二个commit
      git checkout master~2 xx/xxx # 回滚文件 xx/xxx 到 HEAD 算起的 第三个 commit

    怎样排查是哪一个commit 引入了某个bug?

    二分查找法

    1
    2
    3
    4
    5
    6
    7
    git bisect start [终点commit] [起点commit] # 执行后 当前branch 被reset到 两个commit的 最中间的那个commit
    # 到达二分之一处,如果代码没有 bug,则执行 git bisect good
    # 到达二分之一处,如果代码有 bug,则执行 git bisect bad
    # 假设有bug
    git bisect bad # 说明bug出现两个提交的前半段,git 会reset当前branch 到 前半段的 1/2 处 就是 最开始的 1/4 处
    # 继续不断的 bisect bad / good 直到出现
    # <xxx> is the first bad commit

    怎样暂存当前的提交?

    比如当你本地代码没改完,但是需要马上 pull 最新代码时,可以用

    1
    2
    3
    git stash # 暂存所有的修改,不commit
    git pull # 拉取所有修改
    git stash pop # pop出刚才的 暂存
    1
    git stash list # 查看 所有暂存的列表, 是一个stack 结构

    怎样 merge?

    假如面临这样一种情况, 你在你本机的 devel branch 开发好了两个 commit ,但是 你想 merge 到 master 时,发现别人也提交了一个commit,你要怎么merge?

    直接 merge 不合并要 merge 的 commit,不改变原有 commit history

    1
    2
    3
    4
    5
    git checkout master
    git pull
    git merge devel
    git branch -D devel
    git push origin master

    megrge with squash,把要 merge 的 commit,合并成一个commit history

    1
    2
    3
    4
    5
    git checkout master
    git pull
    git merge --squash devel
    git branch -D devel
    git push origin master

    rebase 通过 rebase -i 调整 commit, 重新编辑 commit history

    1
    2
    3
    4
    5
    6
    git checkout master
    git pull
    git checkout devel
    git rebase -i master
    git checkout master
    git merge devel

    总结

    git的设计是精巧的,很值得学习。一个好的系统不仅是表象上功能强大,更是内部设计的精巧。这种精巧来源于对应用场景深入洞察,对各种数据结构、编程技术的巧妙应用。git 命令是很丰富的,一般我们用的比较多的是 high level 命令,其实 git 还有很多 low level 命令,多去探索,你也会发现很多乐趣。最后分享 Linus 的关于程序设计的一段话。

    “Bad programmers worry about the code. Good programmers worry about data structures and their relationships.”

    –Linus Torvalds

    参考文章

    1. 一文讲透 Git 底层数据结构和原理
    2. git architecture
    3. Git内部原理之Git对象哈希
    文章目录
    1. 1. git 数据类型
      1. 1.1. 基本数据类型
        1. 1.1.1. 空间维度 x 时间维度
      2. 1.2. git objects database
        1. 1.2.1. content-addressed filesystem
          1. 1.2.1.1. hash 生成方法
          2. 1.2.1.2. 对象存储
          3. 1.2.1.3. tag 和 branch
          4. 1.2.1.4. tag 存储方式
          5. 1.2.1.5. branch 存储方式
      3. 1.3. git 存储流程
        1. 1.3.1. modified
        2. 1.3.2. added
        3. 1.3.3. committed
        4. 1.3.4. pushed
    2. 2. 使用进阶
      1. 2.0.1. 怎样恢复代码?
      2. 2.0.2. 怎样回滚代码?
      3. 2.0.3. 怎样排查是哪一个commit 引入了某个bug?
      4. 2.0.4. 怎样暂存当前的提交?
      5. 2.0.5. 怎样 merge?
  • 3. 总结
  • 4. 参考文章