如何理解git及子模块

近期公司由svn转向了git,迁移过后发现周边同事各种不适应,对于分支理解不到位,胡乱操作出现各种问题。所以我觉得有必要跟大家说一些有关git的知识。这篇文章不涉及基础操作,也不涉及git命令,只涉及一些对git操作的理解,希望大家学完后在运行git命令的时候,知道自己大概做了些什么。以下都是出自本人的理解,如有偏差,还请指正。

我们以本地git库为例,由于概念是相同的,所以理解本地git库后远程库也能自然理解。

点、线、树

点-从提交说起

提交大家都很清楚,就是把自己的改动,提交到版本库生成一个保存点,在任何时候我们都能回到这个点来,就跟单机游戏存档一样。git会根据你的提交内容,生成一个hash值,并保证每个提交的标识(hash值)都是独一无二的。那么只要你能提供这个标识,就肯定能找到你的这个提交。我们先不要去想各保存点间的关系如何,就记住一个概念:提交就是生成一个独一无二的保存点。如下图的三个提交。

线-建立父子关系

那么我们在一个版本(也就是一个保存点)的基础上,提交了另一个版本。这样我们就存在两个保存点了,而我们知道这两个点其实是有关系的,git的做法是每个保存点都会记录他是基于哪个保存点之上的,我们可以称之为父保存点。这样就相当于将两个点用线连接了起来,多次基于最新版本建立新的保存点,这样就行成了多个点连结而成的长线。而基于这条长线,我们就能轻易找到各个点之间的关系。

如图可知。A1是基于A0修改而来,A5是基于A0修改5次后而来。

树-怎么理解分支

大家都知道建立一个git库之后,库里会自带一个master分支,而我们经常也会说,开发的时候拉一个分支出来,那分支到底是一个什么东西呢?
其实很简单,分支就是一个指针!(只是它记录的不是内存地址,而是我们之前说到的保存点独一无二的hash标识)
在上面我们知道了多个版本是怎么连结起来的,假设我们上图就是在master分支上干的,应该怎样表示呢?直接一个指针指向就完事了。

git有个优点就是建立分支非常之快,那为什么快呢?因为就只要加一个指针就完事了。如下图,我们新建一个develop分支,其实就是加了一个指针指向我们创建分支的版本,这里我们就是基于A5版本创建的develop分支。

分支怎么来的弄清楚了,那么git怎么知道我们现在在哪个分支上呢?这里就要引入一个非常重要的指针了,HEAD指针。HEAD会指在你当前所在的分支上。假设我们当前在develop分支上,如下图。(有一点需要说明的是HEAD指针不一定是指向分支的噢,那就是我们有时候会遇到的游离指针了,后面会提到的。)

 假设我们在develop上又提交了多次,会怎么样呢?如图。

这时我们切到master上再提交多次呢?如图。

那么如果两个分支合并呢?谁向谁合不重要,过程都是一样的。假设我们这时要把develop的分支内容合到master分支上去,那么我们要在master分支上执行合并命令,git会在合并内容后(假设没有冲突)在master上建立一个合并提交点,并将master指针指向最新提交。如下图。

 删除分支就不画图了,删除相应的指针即可。不过master分支是不可删除的。
一个仓库经过长时间多次迭代之后,我们的git历史提交就会形成一个我们常见的提交树了。

以上就是git的日常使用中需要理解的一些操作了。

git子模块

开发过程我们经常会遇到一种情况,有一个模块(库)为多个项目引用。那么在进行版本管理的时候就会出现一些麻烦,是将公共模块单独存放,还是拷贝多份每个项目放一份?这两种方式都有其利弊,git子模块在这种情况下就派上用场了。
子模块就是将要用的公共代码放到一个或几个单独的git库里,然后需要使用它的父模块建立一个类似Linux下的软链接指向这个库就好啦。
那么如何理解子模块这个功能?我把它看成一个库中库,也就是说一个git库中包含着另一个git库。那么顺其自然的,想操作子模块的话,就进入子模块中按日常操作即可。当然git也提供了一些命令,可以直接在父模块中操作子模块。
其中需要特别提出的一点是关于父子模块是怎样关联起来的。简单来说就是父模块中存储了与之相关联子模块的版本号。因为父模块中只是记录了子模块的版本号,所以当我们用git clone --recurse-submodules将父模块仓库拉下来后,切到子模块所在的目录中,你会发现此时的HEAD是指向特定的版本号,而不指向任何一个分支,这就是游离分支了。
因为这个游离分支的存在,导致了下面这种坑爹情况的存在:
你在修改父模块的时候,顺带修改了子模块,提交时又没注意子模块游离分支的告警,强行提交。这样的情况下,子模块由于HEAD不指向任何一个分支会导致推送远端失败。父模块推送成功后会与一个不存在的版本号相关联。导致当另一个人去拉取此仓库时就会出错。所以一定要牢记,除非你清楚自己在做什么,否则对于任何操作都要在分支上进行。
另外,修改后推送远端时千万要记得将父子模块都推送。

如果觉得太复杂,那么就记住以下的总结吧:

别人更新了子模块,需要拉取

  1. 进入子模块,进行拉取操作。
  2. 进入父模块,提交子模块关联版本更新,推送远端。

我要更新子模块

  1. 进入子模块,确认自己在分支上后,提交修改,推送远端。
  2. 进入父模块,提交子模块关联版本更新, 推送远端。

别人更新子模块后,我也要更新子模块

  1. 进入子模块,确认自己在分支上后,提交修改。
  2. 拉取别人的更新,合并后提交,推送远端。
  3. 进入父模块,提交子模块关联版本更新, 推送远端。

 

posted @ 2020-09-15 23:22  castchen  阅读(1472)  评论(0编辑  收藏  举报