因为最近工作上多处都用到了基于 Git 的开发,需要深入理解 Git 的工作原理,以往的 Git 基本知识已经满足不了需求了,因此写下这篇 Git 进阶的文章,主要是介绍了一些大家平时会碰到但是很少去了解的 Git 知识以及 Git 的一些内部工作原理。我们平时用 Git 的很多操作可能只是记住了一个专业术语,或者一个命令而已,并不知道 Git 为什么要这样做,写下这篇文章也是为了让大家对 Git 应用得更加的得心应手。




读完本篇文章你会了解到 Git 的以下内容:

 ✦ 为什么使用 Git

 ✦ Git 基本用法概述

 ✦ checkout,merge,rebase,reset,reverse 的区别

  Git flow

 ✦ Git hooks

 ✦ Git 子模块

 ✦ Git 内部工作原理



为什么使用Git


集中化的版本控制

例如我们常用的 svn,这种版本控制最大的缺点就是中央服务器的单点故障,一旦中央服务器宕机,所有的开发人员都无法正常更新代码工作了,如果中心数据所在的磁盘损坏又没有备份,版本数据将永久丢失。


分布式版本控制系统

客户端不只是提取最新版本的文件快照,而是将整个代码仓库完整的镜像拉下来,svn 以文件变更列表的方式存储信息,这类系统将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。


Git 基本用法概述


Git 提交与同步流程


Git 撤销更改


Git 常用命令


checkout、reverse、reset、rebase、merge


1.  checkout

这里我们主要讨论 git checkout 检出分支的操作,当我们执行 git checkout <branchname> 操作时,其实原理上是将 head 指针指向了要检出的这个分支上。


分叉情况:

当我们在多个分支上同时开发时,不同的分支会有不同的提交记录,这样就会出现分叉情况。

此时要协同开发我们需要将各种分支合并到主干分支上,也就是 git 中的 merge 操作。如图,上游的分支在之后就与我们的分支有了差异,此时我们执行 merge 命令其实就是将 C , D 提交的内容进行一个融合,然后作为提交。


2.  git merge


3.  git rebase <branchname>

merge 操作是将 C , D 合并成一个 commit,如果我们不想这么做,可以使用变基操作。顾名思义,变基就是将自己的根基改变了(个人理解),rebase 是直接将当前开叉的提交强行给挪到了另一个分支的后面。


4.  git reset

reset 操作 HEAD 指针从 C 变到了 D ,也就是说之后的提交线从 B 的后面开始,C 就变成游离的状态了。


5.  git revert

相对 reset 操作,revert 就相对安全些,因为它并没有将 C 丢弃调,而是在 C 的后面复制了一份 B 作为 B' .



Git flow 


Git 工作流主要是为了让开发者在协同开发时统一规范,统一流程,减少额外的工作量。

简单的说就是我们在开发流程时干特定的事要用到特定的分支,想想如果大家在开发的时候,测试,改 bug,发布都放在同一个分支做,此时项目的管理者要在众多的分支中挑选出他想要的提交进行合并是多么头疼的一件事情。git flow 就是为了解决此类问题。

Git flow 会有两个长期分支:

    master:用于记录官方发布轨迹

    develope:集成分支,用于记录开发新功能的轨迹


此外还有其他的一些临时的分支:

    Feature(新功能分支) :从 develop 分支中派生出来的分支,当需要添加一个新的功能时可用这个分支,新功能完成后,feature 又合并到 develop 中去。

    Release(发布分支) :用于合并 develop 中的 feature 分支。

    Mantaince(热修复分支):线上 bug 修复分支,直接从 maser 分支中派生出来,完成后合并到 master 和 develop 分支上去。

    Release(发布分支) :用于合并 develop 中的 feature 分支。

    Mantaince(热修复分支) :线上 bug 修复分支,直接从 maser 分支中派生出来,

完成后合并到 master 和 develop 分支上去。

上图的蓝色分支是master,紫色是develop,红色是hotfix,橙色是feature,绿色是release


Git flow 开发步骤


1. 创建 develop 分支

     git branch develop

     git push -u origin develop


2. checkout develop 分支

    git checkout -b develop origin/develop


3. 基于 develop 创建新功能分支

    git checkout -b feature/my-feature develop


4. 合并分支

    git pull origin develop

    git checkout develop

    git merge feature/my-feature

    git push

    git branchn  -d feature/my-feature


5. 线上版本发布

    a.从 develop 分支下创建准备发布的 realse 分支

            git checkout -b release-1.0.0  develop

            git push

    b.将realse分支合并到 develop

    c.打标签。realse 是一个 develop 合并到 master 的一个缓冲的分支,每当有源码需要合并到 master 时应该打上标签

    git  tag -a 1.0.0-release -m "first release" master

    git push --tags


6.线上 bug 修复

在代码都提交到 master 后项目开始上线,此时如提交修复线上 bug 的代码?

    a.创建 hotfix分支   git checkout -b issue-#001 master

    b.修复 bug

    c.完成修复合并到 master

    d.打标签

    e.合并到 develop

 

Git hooks (钩子)


Git hooks 是一些可以在仓库中的任何时刻自动运行的脚本,这些脚本类似于一些拦截器,可以在执行 git 操作的前后执行一些特定的事件。

当初始化仓库时,会自动在 .git/hooks 目录下生成 .example 为后缀的文件,这些文件就是 hooks,git 为防止这些hooks默认执行,加了 .example 后缀,要执行这些 hooks只需把 .example 后缀去掉即可。


hooks 分为本地的 hooks 和服务器端的 hooks。

本地 hooks:

        pre-commit

        prepare-commit-msg

        commit-msg

        post-commit

这些都是在 commit 的整个过程中的一些 hooks。

        post-checkout

        pre-rebase

这两个 hooks 是用于做一些额外操作或者一些安全检查。

pre 前缀的 hooks 用于添加多余操作,post 前缀的 hooks 用于放送通知。

案例演示:

    安装一个 prepare-commit-msg hook。

    首先,我们先将 .git/hooks 目录下的 prepare-commit-msg.example 的后缀去掉。


     接着我们使用打开这个文件进行编辑,这是一个 shell 脚本,我们也可以用 python 或 ruby这样的语言写 hooks。


保存退出,这个 hooks 是在 commit 操作时添加 msg 之后触发。

我们来进行一次提交操作。


可以看到在 hooks 中的程序已经执行了

以上的这些 hooks 都是本地的 hooks,还有一些 hooks 是加在远程仓库的,这些就是服务器端的 hooks。

pre-receive:执行 git push 操作时触发

update:每次 push 之后触发

post receive:push 成功之后触发


Git 子模块


在工作当中我们经常会遇到这样的情况:我们在当前项目里想引入例外一个项目,也有可能是一个库,但是假如我们直接把这个项目加入到中,我们必须要确保每个仓库中含有这个库,不然部署很困难,我们每次对这个项目或库进行自定义操作时还会使上游的合并变得异常的困难。如果我们可以将这个库克隆到自己的项目中,并且保持这个库的提交独立那就完美了。git 子模块就是为了解决这个问题。

接下来我们就来演示一下如何在一个被分成多个子项目的项目中进行开发。

首先我们要在我们的主项目 pro-main 中添加子模块 pro-service

pro-main 的仓库地址为:git@github.com:CodeNerverEnd/pro-main.git

pro-service 的仓库地址为:git@github.com:CodeNerverEnd/pro-service.git

先将我们的主项目克隆下来,


然后在主项目中添加子模块,

使用命令  git submodule add git@github.com:CodeNerverEnd/pro-service.git


我们可以在仓库中看到如下目录


多了一个 .gitmodules 文件,打开这个文件看到,保存了子模块的路径,如果你不想将子模块放到主项目仓库路径下,可以将这里的路径改成你想要的路径。


我们把添加了子模块的主项目提交到远程仓库后,所有的开发人员都可以用这个库了,下面我们就来看看其他开发人员怎么用它。

首先其他的开发人员会克隆含子模块的项目,我们可以用以下的命令:

git clone --recursive git@github.com:CodeNerverEnd/pro-main.git

如果我们克隆的时候没有加  --recursive,默认是不会将子模块的内容克隆下来,你只能在项目中看到一个子模块的文件夹,但里面的内容是空的。


现在假如开发人员需要更新子模块,执行以下命令:

git submodule update --remote


假如此时开发人员想要在主项目下编码的同时又在子模块下编码,可以执行以下步骤

1.进入子模块目录检出一个分支

    git checkout stable

2.merge子模块

    git submodule update --remote --merge

3.假如其他人在上游做了修改,我们需要并入

    git submodule --remote --rebase

4.我们在本地改好了以后想发布子模块的改动

    git push --recurse-submodules=ondemand


    对于 Git 来讲,这些远远是不足以叙述它的全面。我将在下一个篇幅里,继续为你们介绍 Git 的进阶。