Git 原理简介

本质上,Git是一套内容寻址(content-addressable)文件系统,而和我们直接接触的Git界面,只不过是封装在其之上的一个应用层。

Git工作区、暂存区和版本库

工作区:项目文件夹即工作区 working directory
版本库:在初始化git版本库之后会生成一个隐藏的文件 .git ,可以将该文件理解为git的版本库 repository,
暂存区:在.git 文件夹里面还有很多文件,其中有一个index 文件 就是暂存区也可以叫做 stage

git还为我们自动生成了一个分支master以及指向该分支的指针head ,如下图
20151030110206984

git add file 是把文件从工作区提交到暂存区,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中
git commit -m "prompty" file 是把文件从暂存区提交到了分支master下面,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新
git reset HEAD 暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响
git rm --cached file 直接从暂存区删除文件,工作区则不做出改变
git checkout -- file 用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动
git checkout HEAD file 用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件

Git的目录结构

  • config文件:该文件主要记录针对该项目的一些配置信息,例如是否以bare方式初始化、remote的信息等,通过git remote add命令增加的远程分支的信息就保存在这里;
  • objects文件夹:该文件夹主要包含git对象。关于什么是git对象,将会在下一节进行详细介绍。Git中的文件和一些操作都会以git对象来保存,git对象分为BLOB、tree和commit三种类型,例如git commit便是git中的commit对象,而各个版本之间是通过版本树来组织的,比如当前的HEAD会指向某个commit对象,而该commit对象又会指向几个BLOB对象或者tree对象。objects文件夹中会包含很多的子文件夹,其中Git对象保存在以其sha-1值的前两位为子文件夹、后38位位文件名的文件中;除此以外,Git为了节省存储对象所占用的磁盘空间,会定期对Git对象进行压缩和打包,其中pack文件夹用于存储打包压缩的对象,而info文件夹用于从打包的文件中查找git对象;
  • HEAD文件:该文件指明了git branch(即当前分支)的结果,比如当前分支是master,则该文件就会指向master,但是并不是存储一个master字符串,而是分支在refs中的表示,例如ref: refs/heads/master。
  • index文件:该文件保存了暂存区域的信息。该文件某种程度就是缓冲区(staging area),内容包括它指向的文件的时间戳、文件名、sha1值等;
  • Refs文件夹:该文件夹存储指向数据(分支)的提交对象的指针。其中heads文件夹存储本地每一个分支最近一次commit的sha-1值(也就是commit对象的sha-1值),每个分支一个文件;remotes文件夹则记录你最后一次和每一个远程仓库的通信,Git会把你最后一次推送到这个remote的每个分支的值都记录在这个文件夹中;tag文件夹则是分支的别名,这里不需要对其有过多的了解;

存储对象

一般来讲记录版本信息的方式主要有两种:

  1. 记录文件每个版本的快照;
  2. 记录文件每个版本之间的差异

GIT采用第一种方式。像Subversion和Perforce等版本控制系统都是记录文件每个版本之间的差异,这就需要对比文件两版本之间的具体差异,但是GIT不关心文件两个版本之间的具体差别,而是关心文件的整体是否有改变,若文件被改变,在添加提交时就生成文件新版本的快照,而判断文件整体是否改变的方法就是用SHA-1算法计算文件的校验和。GIT能正常工作完全信赖于这种SHA-1校验和,当一个文件的某一个版本被记录之后会生成这个版本的一个快照,但是一样要能引用到这个快照,GIT中对快照的引用,对每个版本的记录标识全是通过SHA-1校验和来实现的。

GIT对象
  每个对象(object) 包括三个部分:类型,大小和内容。大小就是指内容的大小,内容取决于对象的类型,有四种类型的对象:"blob"、"tree"、 "commit" 和"tag"。

  • “blob”用来存储文件数据,通常是一个文件。
  • “tree”有点像一个目录,它管理一些“tree”或是 “blob”(就像文件和子目录)
  • 一个“commit”指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如提交时间、提交说明、作者、提交者、指向上次提交(commits)的指针等等。
  • 一个“tag”是来标记某一个提交(commit) 的方法

示例1:

$ git add README test.rb LICENSE2
$ git commit -m 'initial commit of my project'

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如下图:
20193835-53a201b735d34b1e98cd882a40396932
 如果进行多次提交,仓库的历史会像这样:
20194015-e690fe536bd94cde81f132bb5e2470c2
分支引用
  所谓的GIT分支,其实就是一个指向某一个Commit对象的指针,像下面这样,有两个分支,master与testing:
20194528-f5bf7e93960148a782f9d72f93a134bc

分布式版本库

git是一个分布式的结构,意味着本地和远程是一个相对的名称

  • git remote 本地的repo仓库要与远程的repo配合完成版本对应必须要有 git remote子命令
  • git branch [-r] 操作本地分支或远程分支
  • git push [-remote] [-branch] [-branch],将本地版本库的分支推送到远程服务器上对应的分支
  • git fetch [-remote] [-remote_branch:local branch] 更新git remote中远程仓库提交
  • git pull 比对本地的FETCH_HEAD记录与远程仓库的版本号,然后git fetch 获得当前指向的远程分支的后续版本的数据,然后再利用git merge将其与本地的当前分支合并

版本工作差别

与svn差别
1.GIT是分布式的,SVN不是:
2.GIT把内容按元数据方式存储,而SVN是按文件:
3.GIT分支和SVN的分支不同:
4.GIT没有一个全局的版本号,而SVN有:
5.GIT的内容完整性要优于SVN
缺点: 复杂的信息模型,提交麻烦,命令复杂,
操作流程对比如下图:
TIM--20180525163025