当前位置:首页 > 技术分析 > 正文内容

git分治之道

ruisui882个月前 (03-08)技术分析17

git内部原理

1.什么是git

git的官方定义:

Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals.

可以看出,git是一个具有丰富命令集的版本控制系统,它的特点就是快速、可扩展以及分布式。

而git实际上是一个简单内容记录工具(the stupid content tracker)。从根本上来讲,git 是一套内容寻址 (content-addressable) 文件系统,在此之上提供了一个版本控制系统的用户界面。

1.1 an unusually rich command set

在当前版本里,git一共有158个命令,但是常用命令只有10多个,其他命令可以视为底层命令。由于git最开始被设计的时候用来作为the stupid content tracker,只是现在被最广泛地用来作为revision control system,但是实际上git也可以被用来做其它事情。因此,git不仅一开始提供了功能齐全的底层命令,而且还提供了许多友好的高层命令。

git命令分为两种:

1. Procelain(高层命令)

  • user-friendly commands: init, add, commit, push, checkout, branch, merge, etc.

2. Plmbing(底层命令)

  • bunch of verbs that do low-level work: hash-object, update-index, write-tree, etc.

1.2 revision control system

版本控制系统,学名:software change and configuration management system(SCM)。它需要具有的特点如下:

  1. 记录和控制软件的变化点和版本号

  2. 能清楚知道什么东西被改了,被谁改了

  3. 在多人完成的大项目中,能更加容易的合作和管理文件

  4. 最重要的是,要有回滚功能

在实际生活中,版本控制就被经常用到,例如写论文时的版本控制。但是,这只是简单的本地版本控制。在多人的项目合作中,这就显得力不从心了。因为使用这种方法时,首先需要大量的拷贝工作,以保证能够回滚嘛;其次当多人更改同一项目出现写写冲突情况时,虽然Linux提供diff和patch命令,但是解决起来肯定极其痛苦。

因此,版本控制系统,核心就是让在不同系统上的开发者协同工作,需要解决的主要问题就是防止各种人一块写最后写乱套的情况。

2. git有何不同

2.1 集中式版本控制系统

首先,谈谈传统的集中式版本控制系统。

集中式版本控制系统中,版本库是集中存放在中央服务器(server),每个开发者都作为客户端(client)。协同工作的开发者都通过客户端连到中央服务器,取出(checkout)最新的文件或者提交(commit)更新。它的主要特点如下:

  1. 每次checkout拿到的都是当前版本库最新的一个快照状态(snapshot)。它并不知道版本库之前的历史状态信息。

  2. 每次commit都会更新中央版本库。

  3. 为了保证不丢失数据,在每次commit时都需要re-sync。

而集中式版本控制系统的缺点,则有如下几点:

1. 对于每次commit操作,都需要re-sync;

  • 对于集中式版本控制系统,在多人协作中,可以认为如果commit越早,麻烦也就越少。

  • 从这个角度上看,可以认为git做了两步commit操作,第一次为commit,第二次为pull,只有pull才需要re-sync

2. 单点故障;

  • 在没有commit之前所有文件都在于client端的本地文件,如果此时不小心删除掉了一些文件,除非重新访问server进行回滚操作,否则就真的删除掉了。

  • 当server没有备份并且宕掉了,就算能恢复当前版本的数据,但是之前的历史数据信息是恢复不了的。

3. 每次工作的时候,需要联网;

4. 速度慢;

  • 大部分时间都是需要client和server进行沟通协作的。2.2 分布式版本控制系统

我们再来看看git,它是一个分布式版本控制系统。

与传统的集中式版本控制系统相比,git最大的不同在于,它是分布式版本控制系统。分布式意味着,不止服务器保存有version database,每个客户端也保存有version database。git的主要特点如下:

  1. 每次clone都真正的拷贝所有的数据。

  2. 在初始clone之后,大多数的操作都是在本地进行的。

而git版本控制系统的主要优点如下三点:

1. Fast:

  • 除了第一次clone的时候慢,之后的操作都比较快。因为大多数操作都是在本地进行的,不需要联网。

2. Scalable:

  • 你可以对本地的version database做任何你想做的事情,他并不会对其他人造成任何影响。

3. Distributed:

  • 在client端,肯定不法彻底避免单点故障,但是可以减少这样可能性。因为本地保存有version database,所以当本地不小心丢失文件时,往往可以本地出错本地解决。

  • 在server坏掉的时候,可以根据client里面的version database进行恢复,甚至可以把client当成一个新的server。

3. git如何存储内部数据

从上一小节,我们知道git是一个分布式版本控制系统,无论是服务端还是客户端都拥护一份自己的version database。那么本小节,首先看看version database到底是什么。然后,分别深入看看git init、git add、git commit这个三个常用命令具体的工作过程。

version database

git的version database到底是什么?

  1. 本质上,是一套内容寻址文件系统(Content Addressable Filesystem)。

  2. 实现上,是一个简单的KV数据库

  3. Key: sha-1 hash(everything is hashed)

  • 20 bytes, 40 hex, 160 bit, 2.9e48 distinct keys

  • How would git handle a SHA-1 collision on a blob?

4. Value: binary files

  • Commits : actual git commits

  • Trees : directories(structure of file system)

  • Blobs : contents of files/data

git以一种类似UNIX文件系统但更简单的key-value方式来存储内容。key是对文件内容的哈希值。value则包含有commit object、tree object以及blob object三种对象。

所有内容以tree或blob对象存储,其中tree对象对应于UNIX中的目录,blob对象则大致对应于inodes或文件内容。commit对象则可以看作是对当前版本的一个快照。

一个单独的tree对象包含一条或多条tree记录,每一条记录含有一个指向blob或子tree对象的SHA-1指针,并附有该对象的权限模式、类型和文件名信息。

接下来一步一步,看来git flow里到底发生了什么:

3.1 git init命令

git init主要是创建了四个文件夹:.git、.git/refs/heads、.git/refs/tags以及.git/objects,和一个文件.git/HEAD,并把初始化./git/HEAD里面的内容为指向master branch的HEAD。

那么如何用普通的Linux命令实现git init呢?答案其实很简单:

mkdir .git

mkdir -p .git/refs/heads

mkdir -p .git/refs/tags

mkdir -p .git/objects

touch .git/HEAD

echo "ref:refs/heads/master" > .git/HEAD

以上便是git init, without git init的具体实现做法。其中,objects目录存储所有数据内容,refs目录存储指向数据(分支)的提交对象的指针,HEAD文件指向当前分支。

3.2 git add命令

git add做了两件事情:第一,把文件写成blob object作为value,并把文件内容的hash值作为key;第二,更新了index文件,把文件放入了暂存区(staging area)。

echo "Hello World" > hello.txt

git add .

上面的操作可以用下面底层语句实现:

echo "Hello World" | git hash-object -w --stdin

git update-index --add --cacheinfo 100644 557db03de997c86a4028e1ebd3a1ceb225be238 hello.txt

git checkout -- hello.txt

第一个命令中,参数-w指示hash-object命令存储(数据)对象,若不指定这个参数该命令仅仅返回键值。该命令输出长度为40个字符的SHA-1哈希值校验和,并创建以它的前2个字符为名称的子目录,剩下38个字符作为文件命名(保存至子目录下)。

第二个命令,update-index为一个单独文件创建一个index。更新完staging area后,由于仅仅Index里有,但是本地仍没有,如果git status,所以会显示为deleted。此时,使用第三条命令可以把repository进行checkout出来。

3.3 git commit命令

我们进行第一次git commit操作。

git commit -m "First Commit"

此时会多出两个文件。

第一个是commit object,它实际上是对于跟踪项目内容的一次快照,里面的格式内容有:指明了该时间点项目快照的顶层树对象、作者/提交者信息以及提交注释信息;

第二个则是那个被指向tree object,它实际上一个文件根目录,里面指向一个blob object,而这个blob object便是之前git add时的内容。

虽然执行git log命令可以查看完整的历史信息并以此找到文件,那么问题来了,系统又是如何找到commit log的呢? 答案是:refs!

在.git/HEAD里存的是一个ref,ref是一个file,这个file里面又存了一个commit的hash值,从而便可以找到了当前的base版本。

我们尝试进行第二次git commit操作。 首先,git add一个简单的脚本文件hello.sh。

/*************hello.sh的具体内容如下:

!/bin/sh

echo "Hello World"

**************/

git add hello.sh

此时,git add只会增加一个blob object,而不会增加多一个tree object。也就是说,在add的时候并不写目录,只是更新了暂存区,在index里会保存目录信息。因为在git commit操作前,可以进行多次git add操作。

git commit -m "Second Commit"

此时,会多出三个文件。第一个是commit object,第二个是根目录的tree object,第三个是文件目录的tree object。

通过下图,可以发现commit object中还带有个parent指针,它指向上一次的commit object。最重要的是,它还更新自身的refs/heads/master信息;

通过命令git ls-files --stage查看staging area信息。你还可以发现,暂存区里只存储blob object,并不存储tree object和commit object。

4. branch/merge在git内部是如何工作

相对于git init、git add、git commit这三个命令,git branch和git merge则显得十分的小巧机智。结合下列几幅图,能更轻松的理解这两个命令。

4.1 branch操作

在git里,branch操作,实际上仅仅增添一个41-bytes的引用文件(在refs/heads/目录底下)。可以看出,这样的操作是极其的简单廉价的,示意图如下:

同理,在git里,checkout操作,实际上也仅仅是更改了引用文件的指针而已。

4.2 merge操作

最后,我们来分析一下,git里的merge操作。它的操作流程和示意图如下:

  1. 计算出当前branch和公共branch的diff;

  • 这步操作的速度很快。因为只需要去比较文件的hash值是否匹配即可。

2. 应用该diff去订正本地文件;

  • 这步操作并不会覆盖分支里之前的版本数据。

3. 解决所有的冲突;

当merge操作完成后,示意图如下:

首先,可以发现,这是会新建一个mater处的commit object,而它有两个parent指针,分别指向之前两个branch的commit object。

其次,如果不小心做了一个bad merge,而又不想让他人知道时,其实只需要更改下master和feature指针的hash信息即可。

最后,通过branch和merge操作,你会发现在git的世界里,它会尽量不让你做删除的事情。

小结

Git is the stupid content tracker.

You can represent renames on top of git - git itself really doesn't care. In many ways you can just see git as a filesystem - it's content-addressable, and it has a notion of versioning, but I really really designed it coming at the problem from the viewpoint of a _filesystem_person (hey, kernels is what I do), and I actually have absolutely _zero_interest in creating a traditional SCM system. -- by Linus Torvalds

我们应该如何看待并理解git呢?本质上,git是一套文件寻址系统。

虽然git具有版本控制的功能,但是在Linus最初设计git的时候,完完全全是从一个文件系统的角度进行设计和实现这个产品的。所以说,git本质上仍然是一个文件系统,只是它很适合地被用作版本控制系统。

本文作者:Junnan Liu,更多内容,请访问:BitTiger.io, 扫描下面二维码,关注微信公众账号“论码农的自我修养”

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/2607.html

标签: git删除commit
分享给朋友:

“git分治之道” 的相关文章

细数5款国外热门Linux发行版

Linux系统已经与我们的生活息息相关,当你用Android手机浏览这篇文章时,你就已经在使用Linux系统。当然作为编程开发最热门的系统,他还有很多专注于开发使用的版本。Fedora热门入门推荐,一款优秀的程序猿专供Linux发行版,自带开发者门户,集成大量教程指南、开发集成环境、虚拟机等工具,简...

深入理解Vue.js组件通信:父子组件与子父组件数据交互详解

什么是Vue组件通讯 Vue.js 组件通信是指在 Vue 应用的不同组件之间进行数据交换和状态同步的过程。由于 Vue 的组件是基于单文件组件(SFCs)的模块化设计,每个组件都有自己的作用域,因此它们不能直接访问彼此的数据。为了使组件之间能够协同工作,Vue 提供了几种不同的通信方式。以下是 V...

「2022」打算跳槽涨薪,必问面试题及答案——VUE篇

1、为什么选择VUE,解决了什么问题?vue.js 正如官网所说的,是一套构建用户界面的渐进式框架。与其它重量级框架不同的是,vue 被设计为可以自底向上逐层应用。vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另外一方面,当与现代化工具链以及各种支持类库结合使用时,vu...

vue中组件之间的通信方式

** 1.1 父子组件**a. 父向子传数据: 第1种: 父通过属性传值,子组件通过props接收数据(注:props传过来的数据是单向的,不可以进行修改)第2种:子组件可以通过$parent来获取父组件里的数据和调用父组件的方法(注:数据是双向的,还要注意如用了UI组件并且在该UI组件里重新定义一...

Windows 下 Git 拉 Gitlab 代码

读者提问:『阿常你好,Windows 下 Git 拉 Gitlab 代码的操作步骤可以分享一下吗?』阿常回答:好的,总共分为五个步骤。一、Windows 下安装 Git官网下载链接:https://git-scm.com/download/winStandalone Installer(安装版)注意...

用IDEA开发如何用Git快速拉取指定分支代码?

1,准备空的文件夹,git init2,关联远程仓库,git remote add origin gitlab地址3,拉取远程分支代码,git pull origin 远程分支名再用IDEA打开项目即可...