Git理论基础

本文围绕Git展开,介绍了版本控制概念及分类,对比了SVN与Git。阐述Git框架分工作目录、暂存区、本地仓库和远程仓库,说明了基本流程。详细讲解Git存储原理、方式,以及分支与标签概念。还深入剖析Index空间,纠正初始理解误区。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Git基本概念

 

  • 1.1、什么是版本控制

            版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。

  • 实现跨区域多人协同开发
  • 追踪和记载一个或者多个文件的历史记录
  • 组织和保护你的源代码和文档
  • 统计工作量
  • 并行开发、提高开发效率
  • 跟踪记录整个软件的开发过程
  • 减轻开发人员的负担,节省时间,同时降低人为错误

就是多人协同合作开发时的一种文件管理技术。

 

  • 1.2、版本控制的分类

版本控制可分为以下三类:

            本地版本控制(RCS):适合个人使用,对文件每次的更新都可以生成一个版本的快照。

            集中版本控制(SVN):所有的版本保存在服务器上,用户需要手动从服务器下载或同步代码,在修改后提交服务器。

分布式版本控制(Git):所有版本信息及代码文件全部同步到本地的用户中,只要有一台用户的数据,就可以还原服务器的数据,实际上服务器也可以看成是一个本地用户,只不过它不进行本地修改等操作,作为本地用户之间的一个中转站。

SVN及GIt是市面上较为常用的版本控制,其主要的区别在于集中式与分布式

SVN版本库在服务器中,协同操作eg:A、B同时修改C文件,最先上传的修改成功,后上传的会提示文件冲突,A修改C文件、B修改D文件,上传服务器后,本地更新即可互相查看,每次上传的是跟服务器文件的diff。

Git版本库在本地中,可以实现本地相互推送完成版本协同,每次上传的是版本快照(即修改的独立文件)。

 二、GIt框架及流程

  • 2.1、Git框架

Git分为四个区域:

1、工作目录 (Work)                                                      2、暂存区(Stage/Index)

3、本地仓库 (Repository)                                              4、远程仓库(Remote)

 

  • 2.2、Git基本流程

Git中工作流程为在Work中添加、修改文件→将文件 git add 到Git库中→将git文件git commit提交仓库

故而可将文件状态分为三类:已修改(Modified)、已暂存(Staged)、已提交(Committed)

 

 三、Git基本原理

 

 

  • 3.1、Git存储原理

Git中一共存在三种数据对象

Blob   ------------- 仓库中的文件

Tree   --------------仓库中的文件夹

Commit   ----------每个版本的对象

Git实质为一个存放不同对象的hash table,hash table的key值为对象的hash值,这样即可保证不同对象之间不会相互覆盖。

 

以下通过实验验证Git存储的原理

 

  • 1、建立一个新的 Git repo

 

$ mkdir git_test

$ cd git_test $ git init

 

  • 2、建立新文件并添加到Git

 

$ mkdir git_test

$ cd git_test $ git init

$ echo "version 1" > index.txt

$ git add index.txt

 

现在去查看git中hash table的对象

$ git cat-file --batch-check --batch-all-objects

16f4c48319f2bb70f5289fc1ea325dabc9d14729 blob 8

 

 

可以看到在执行git add后其实git中就已经存储了文件的信息,而不是仅仅只加到了暂存区Index,我们查看16f4c48319f2bb70f5289fc1ea325dabc9d14729 对象

$ git cat-file -p 16f4c48319f2bb70f5289fc1ea325dabc9d14729

version 1



index文件确实已经作为Blob对象存储到了Git hash table 中。
 

 

  • 3、建立一个新文件夹里的新文件

$ mkdir dir

$ echo "version 1, inside" > ./dir/inside.txt

$ git add ./dir/inside.txt

 

再查看一下Git的hash table

$ git cat-file --batch-check --batch-all-objects 
16f4c48319f2bb70f5289fc1ea325dabc9d14729 blob 8
 bf97e71de76bcff2bd8aba44710aa5e665eacb99 blob 21



增加了一个文件增加了一个Blob对象,符合预期

 

 

  • 4、上传本地仓库

 

$ git commit -m "Commit 1"
$ git cat-file --batch-check --batch-all-objects
16f4c48319f2bb70f5289fc1ea325dabc9d14729 blob 8
312940d2b3d55f429bd796ddb3155566dc06b0c7 tree 67
39bdea2300491cc90f47551887bfe9503cd4a9d9 tree 38
bddef1a9efb1a84bcc6de74882ebe93b6336ade1 commit 203
bf97e71de76bcff2bd8aba44710aa5e665eacb99 blob 21


当git commit 后再查看hash table可以发现在预期的两个Blob对象外还出现了两个Tree 和一个 commit

$ git cat-file -p 312940d2b3d55f429bd796ddb3155566dc06b0c7

040000 tree 39bdea2300491cc90f47551887bfe9503cd4a9d9 dir

100644 blob 16f4c48319f2bb70f5289fc1ea325dabc9d14729 index.txt



查看一下b0c7尾号的Tree,可以发现里面包含了一个Tree对象和index文件,再查看a9d9的Tree

$ git cat-file -p 39bdea2300491cc90f47551887bfe9503cd4a9d9
100644 blob bf97e71de76bcff2bd8aba44710aa5e665eacb99 inside.txt


里面存放的inside文件,对应我们测试目录不难发现,a9d9的Tree就是dir文件夹,b0c7的Tree即是我们根目录

 

最后再看看commit对象

$ git cat-file -p bddef1a9efb1a84bcc6de74882ebe93b6336ade1

tree 312940d2b3d55f429bd796ddb3155566dc06b0c7 
author xavier.xie <xavier.xie@sigmastar.com.tw> 1541076281 +0100
committer xavier.xie <xavier.xie@sigmastar.com.tw> 1541076281 +0100

Commit 1

可以看到commit对象包含了整个版本的根目录对象Tree的指针、commitrer的个人信息以及注释。

 

以上,我们可以总结出其三者间的联系

commit -> tree ->tree ->blob

 

 

 

  • 3.2、Git的存储方式

前文描述到Git并不是像SVN一样存储不同版本的差异,而是把同一个文件的每个版本当作独立的文件存储,将快照存放于Index区域中

当我们继续验证,修改index文件

$ echo "version 2" > index.txt
$ git add index.txt
$ git commit -m "Commit 2"

查看修改之后的hash table

$ git cat-file --batch-check --batch-all-objects
16f4c48319f2bb70f5289fc1ea325dabc9d14729 blob 8
1c5c8a007e7e4abd6fc7c80d9ef01effe30fd7b1 tree 67
312940d2b3d55f429bd796ddb3155566dc06b0c7 tree 67
39bdea2300491cc90f47551887bfe9503cd4a9d9 tree 38
8231f0fdc862f06b2bd7b7bfd2f42082d3086b71 blob 13
82b70486b425018a6f9250f5e50ffdbc3db24359 commit 251
bddef1a9efb1a84bcc6de74882ebe93b6336ade1 commit 203
bf97e71de76bcff2bd8aba44710aa5e665eacb99 blob 21

相对于修改之前,多了一个blob、一个tree以及一个commit对象,进去index文件

$ git cat-file -p 8231f0fdc862f06b2bd7b7bfd2f42082d3086b71    //修改后
version 2
$ git cat-file -p 16f4c48319f2bb70f5289fc1ea325dabc9d14729    //修改前
version 1

即证明Gite存储了同一个文件的每个版本文件,查看修改后的tree对象

$ git cat-file -p 1c5c8a007e7e4abd6fc7c80d9ef01effe30fd7b1
040000 tree 39bdea2300491cc90f47551887bfe9503cd4a9d9    dir
100644 blob 8231f0fdc862f06b2bd7b7bfd2f42082d3086b71    index.txt

tree对象指向的dir指针与修改前相同,因为dir没有发生改变,而修改的index文件的指针已经变成了新的blob的hash值,成为了一个新的blob对象。

最后查看修改后的commit对象

$ git cat-file -p 82b70486b425018a6f9250f5e50ffdbc3db24359
tree 1c5c8a007e7e4abd6fc7c80d9ef01effe30fd7b1
parent bddef1a9efb1a84bcc6de74882ebe93b6336ade1
author xavier.xie <xavier.xie@sigmastar.com.tw> 1541076281 +0100
committer xavier.xie <xavier.xie@sigmastar.com.tw> 1541076281 +0100

Commit 2

跟修改之前的区别在于:修改后的commit指向了新的tree根目录对象,多了一个指向上一个版本的parent指针,由此可见commit是一个单向链表

有下图可以概括其存储的方式:

                  

 

  • 3.3、Git分支与标签

到这里我们已经知道:

commit是一个单向链表结构,所以只要我知道最新的commit,我就可以获得以往所有的commit即以往所有的版本,当我们知道commit2在这个单向链表中的位置,就可以通过根目录的tree获取到所有的版本文件,但是我们该如何找到commit2的位置?

本文一开始讲到,Git相当于hash table,而每个hash table 的键值就是每个对象的hash值,比如commit2的键值为82b70486b425018a6f9250f5e50ffdbc3db24359,你可以直接以键值切换到对应的版本中。

但是键值过于冗长,不易于记忆,因此Git增加 branch 概念代替键值记忆位置,其实质上是指向某个commit的指针,存放在./.git/refs/heads里面。

 

一般来说,一个新建的Git repo有一个默认的master branch,其存储在./.git/refs/heads/master文件内,我们可以cat查看

$ cat ./.git/refs/heads/master
82b70486b425018a6f9250f5e50ffdbc3db24359

//82b70486b425018a6f9250f5e50ffdbc3db24359 commit 251

可以看出master就是指向一个commit的指针

 

HEAD是一个指向工作目录的commit指针,其存储在./.git/HEAD文件中

HEAD的作用:HEAD指向的那个branch在commit时,branch指针会自动的指向最新的一个

$ cat ./.git/HEAD
ref: refs/heads/master

HEAD指向的是一个branch,即commit指针

HEAD也可以不指向branch,Git就会警告detached HEAD,这个时候commit 很容易会丢失,不过你可以创建一个分支,就像master一样。

                            

tag相当与静态的branch,其存储在./git/refs/tags中,当HEAD指向它时,它不像branch一样跟随着HEAD移动指向最新的commit,tag只会一直指向标记的那个commit

 

四、深入理解Git原理

  • 4.1、Index空间

Index又为stage/cache。

在我初始的理解状态中Index为一个缓存区,每次新切换分支时即会清空,使用git add的时候上传到缓存区,并没有加入Git数据库中。

以上理解中存在以下错误:

  • Index并不是临时存放新文件的地方,切换分支、上传到仓库会清空数据
  • 新文件并不是在git commit的时候才加入git数据库,在git add时就已加入

 

为了验证以上论点,我们将index.txt修改并git add

$ echo "version 3" > index.txt
$ git add index.txt
$ git ls-files --stage
100644 bf97e71de76bcff2bd8aba44710aa5e665eacb99 0       dir/inside.txt
100644 cd80567970da17e3c5d718e5b3db8700dbf320bf 0       index.txt

在第三章我们可知,git add时index文件已经加入到了Git的hash table中,Git数据库中已经加入了index文件

所以执行git add时是更新Index中的blob对象指针数据,如果我们接着执行 git commit,那么 Git 会基于Index空间内部指向的数据来生成一个 tree 对象,最终再生成一个 commit 对象。

                                                            

 

而第一点,上传或切换分支时是不会清空Index区域的,这一点可以大家自行实验证明。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值