Git深入理解与常用操作的本质

一、前言

Git当前十分流行的版本管理系统(VCS)。它是一种分布式的VCS,即Git的每一个仓库都具有完整的内容和功能,可以独立运行。

通常来说代码和文件体积比较小,项目的依赖只需要记录对应的地址,一个维护了很多年的项目,可能只有几个G的大小。同步的开销不会很大,因而适合分布式的管理方式。而像一些游戏开发项目,涉及到大量的媒体文件(音频、视频、图片),代码仓库会非常的大,更适合用中央式的代码管理系统。

基于分布式的特性,Git可以在任意位置clone代码仓库,进行开发和提交。使得对代码的管理十分方便。

下面对Git涉及的主要概念以及经常会用到的Git命令进行讲解。帮助大家更好的理解、掌握和使用Git。

本文篇幅较长,请耐心阅读。

二、常见概念

1、仓库

我们执行git clong拷贝一个Git仓库到本地,会生成一个隐藏的.git目录。这既是我们说的一个Git仓库。这里面记录了仓库所有的分支、提交历史等。

2、分区

Git的分区包括工作区、可察觉区和暂存区。当我们clone一个仓库,即建立了一个工作区。

当工作区发生修改时,修改的内容会被自动检测,并捕捉到“可察觉区”中;

执行“git add”操作将可察觉区内容,添加到暂存区;

再执行"git commit"指令,将暂存区中内容,提交到仓库中。(暂存区也叫“stage”区,stage原意是舞台的幕后,即表示此时在幕后进行准备,随时准备登台)。这里“git commit”操作会产生一个具体的提交,理解为链接到当前分支引用所指向的提交链表上。需要注意的是,每一个提交一旦产生便是不可变的。所有针对某一笔提交的回退、修改、增加、删除等操作,实际上基本都是替换操作。

通过“git status”命令可以查看当前“可察觉区”和“暂存区”发生改动的文件。

可以通过"git diff"操作,查看具体的改动。“git diff --staged",用于对比查看工作区与暂存区的差异;

“git diff”,对比查看当前工作区与最近一次提交的差异。

示例:

修改了MyTest.txt文件,执行“git status"命令,Git可以捕捉修改的内容,此时修改内容进入可察觉区。

执行“git diff”,查看具体修改的内容。

执行"git add MyText.txt"(也可以用命令“git add .”添加所有在可察觉区的修改),将修改内容加入暂存区。

执行“git commit”指令,生成提交,加入当前分支引用指向的提交链条中。

(git commit -m "xx",是简便的写法。通过“git commit”可以填写更加详细的提交信息)

执行"git log",查看提交历史,此时我们的提交加入到master分支,他的父提交即是它下面的提交。

2、提交、引用和分支

每一次执行commit,即会产生一个“提交”,提交按照先后相互串联起来形成提交链,当前提交的前一笔提交称为他的“父提交”。在Git中一切皆引用,我们常说的分支、Head、远端镜像,以及提交,实际都是引用。所谓分支,实际上是一个指向某一提交的引用,给该引用起的一个别名。

Head是一个比较特别的引用,他指示的是当前的工作位置。可以指向一个提交或者一个分支。实际上它是记录在.git目录中HEAD文件中的地址。

下图的Head指向的是一个分支。如果指向的是提交,就是SHA2 256的字符串,即是该提交的commit-id。

如下示例,此时feature1和master均指向“edb11b...”这笔提交,HEAD引用当前指向feature1分支。

3、git clone的过程

弄清楚了上述Git相关的一些概念。来看一下Clone的过程。Clone实际上是拷贝几乎整个远端仓库的内容以及它完整的历史。这个过程主要分为4个步骤:

1、拷贝远端的分支到本地;

2、将远端分支镜像拿到本地。镜像记录的是远端分支的情况。镜像不能被HEAD和分支的引用所指向,指向镜像的引用会自动指向镜像所指向的具体提交。镜像也不能被修改,每次与远端同步时,镜像会跟随远端自动改变;

2、拷贝远端的HEAD引用到本地,即本地的默认分支;

3、从远端拷贝所有的提交到本地。

如下图示例,此时远端镜像origin/release、origin/master,拷贝到本地的release分支,均指向“ead4de”这一笔提交。HEAD引用,当前指向release。

三、常用操作及本质

1、checkout

通过命令“git checkout 分支名”切换到目标分支(创建分支用“git checkout -b 分支名”命令)。实际上执行的是修改HEAD引用的指向。

需要注意的是,镜像分支无法被引用所指向,执行checkout到镜像分支,HEAD会直接指向镜像所指向的提交。

示例,切换分支:

执行“git checkout feature1”,HEAD引用修改为指向feature1

示例,切换到镜像分支:

查看HEAD文件,此时HEAD指向的是具体的提交。

git checkout --detach

当在仓库之间进行同步代码时,到目标分支被HEAD引用指向则会导致提交失败,这个时候可以使用--detach指令,detach即是“脱钩”的意思。它使得当前HEAD不在指向分支引用而是滑落到指向它的具体提交上。

2、merge

当需要将两个工作分支的内容进行合并,就需要用到合并的命令。Gti提供的merge指令将其他分支的修改,拉倒当前的分支。

示例:

在mater分支,merge feture1分支的改动。

在执行merge过程中如果产生冲突,则处理冲突的流程为:“git merge 分支” -> 有冲突 -> 解冲突 –> “git add 冲突文件”–> “git merge --continue”

快速向前与--no-ff

"git merge 分支名 --no-ff",这里"no-ff"是 no fast forward的缩写,即“禁止快速向前”的意思。当带合并分支,完全领先于当前分支,会执行快速向前的合并操作。不会创建合并的节点(下图中的"M"号节点)。通过git log查看,就是没有"Merge .."这一笔提交产生。

feature1的提交完全领先mater分支。通过快速向前,master分支引用直接指向feature1指向的提交。

当指定--no-ff参数,即使分支完全领先也会创建合并的节点。通过--no-ff参数可以完整的记录分支变化的历史。

3、rebase

rebase也是用于进行合并分支的操作。rebase是站在当前分支的角度,让当前分支重新基于一个新的提交。如下图中“3”号节点原本基于“2”号节点。经过rebase之后,修改为基于master所指向的“a”节点。rebase前后对比如下图。在执行rebase过程中可能产生冲突,此时处理过程为:"git rebase 分支” –> 产生冲突 –> 解冲突 –> “git add 冲突文件"-> “git rebase –continue”。

前面文章提到,所有的提交一旦产生便是不可变的,rebase过程看上去是修改了提交,而实际上是对每一个需要变基的提交复制了一个全新的提交。rebase操作的本质如下图所示:

4、cherry pick

当我们想要从一个分支中选取某些提交拿到当前分支,就要用到cherry pick的操作。cherry pick原意是“摘樱桃”,这里表示从目标分支摘取特定提交到当前分支。

cherry pick操作不需要指定目标分支,只需要提交的commit id。每一个提交的commit id是根据提交的信息用SHA2 256算法生成的摘要,是不会重复的。

示例:

经过cherry pick将feature1的提交“add feature1 message 2”拿到了master分支上。

5、commit --amend

在工作中,我们可能遇到这样情形:刚提交一笔修改,发现该提交有问题,需要修改。

当出现需要修改当前最新提交的情形,则用到“git commit --amend”的指令。该指令的原理即是生成一个全新的提交,然后让分支的引用指向该提交。达到“修改”提交的效果,实际上是替换。

commit --amend操作的局限性是只能用于当前最新的提交。

示例:

可以看到经过git commit --amend处理, 两个提交的commit - id是不同的。它们并不是同一笔提交。

6、reset

当我们在工作中需要从当前位置回到之前提交的位置,则可以用get reset操作实现。get reset操作的本质是用HEAD引用拖动当前分支的引用一起,向目标提交的位置进行移动。

"git resest 提交"操作默认执行的是“git reset 提交 --soft”指令。当执行该操作,分支引用向前移动时,当前工作空间的内容不会被修改,因而会被检测出于当前的提交有差异,而被可察觉区捕获到。通过“git status”可以看到可察觉区有内容。通过reset可以灵活将分支在提交链条上进行移动。在工作空间查看历史提交的内容。也可以通过这种方法放弃最新的一些修改,从某个历史节点重新开始。

示例:

可以看到通过reset可以回到前一笔提交,工作区中的内容没有丢掉。

git reset用一种指定--hard参数的命令,git reset --hard,会在移动到目标位置时,将可察觉区中的内容删除。即在分支向前移动时,同时修改当前工作空间的内容到当前提交的位置。工作空间和当前提交之间没有内容差异,因而没有被捕获到可察觉区中。

示例:

通过“get reset --hard”的方式移动分支引用的指向,会让工作区也同时变动到当前最新提交。用“git status”命令查看可察觉区中没有内容。

7、revert

revert的本意是“还原”。在这里是对目标提交进行反向操作,原来增加的删掉,原来删除的加回来等。这个操作的使用场景一般是需要对已经同步到远端的提交进行撤回。

经过前面的讲解我们知道一旦提交产生便是不可修改的,而提交一旦同步到远端就是历史。其他同事的本地仓库也都会基于这个“历史”来展开工作。如果通过revert、commit amend方式修改远端的内容,就意味着改变远端的历史。这样会给一起合作的同事带来不便。因此,对远端提交的错误,我们用revert的方式,增加一个反向操作的提交进行处理。

示例:

8、pull

pull操作是从远端同步内容到本地。这个操作实际是git featch 和 get merge的合并操作。git fetch操作是从远端获取所有的分支,将本地的“远端镜像”引用进行更新,再将缺失的提交更新到本地。get merge即是将HEAD所指向的引用与远端对应的分支进行合并。

git pull操作默认只合并HEAD指向分支,可以通过参数控制pull所有分支,但是产生冲突的可能性非常大,并且处理起来也会很麻烦,一般不建议这么做。可以看到第3步,只merge了HEAD指向的mater分支,本地的feature分支是没有动的。如何需要同步feature分支,则建议切到featrue进行操作。

9、push

push操作是将本地的分支推送到远端,与远端仓库进行合并。这个过程分为四个步骤:1、同步本地分支引用到远端;2、同步本地提交到远端;3、更新远端HEAD引用;4、更新在本地的“远端镜像”。

四、Git Flow

1、Tag

Tag是一个指向提交的常量引用。该引用一旦建立,则不可以被修改,也不可以被其他引用所指向,包括HEAD引用。所有指向Tag引用的引用,会直接指向Tag所指向的提交。

Tag存在的意义是对某个关键节点进行记录。在发布稳定版本时,每一个稳定版本理应都要有一个Tag所指向。便于随时定位这个版本。

通过“git tag 标签名”可以简单的在当前位置建立一个标签。完整的tag命令,通过“git tag -a 标签名 -m 标签说明 commit-id”在指定提交的位置建一条有详细说明的标签。

示例:直接用“git tag xxx”, 就可以创建一个tag

2、工作流

Git Flow即是Git的工作流。下面是比较经典的Git Flow模型图。他是Vincent Driessen在2010年提出的模型,虽然已经过去很多年,但是到现在依然在各个项目中被广发借鉴甚至被直接拿来使用。下面详细讲一下这张图Git Flow详细的流程。

Feature分支,只用于功能和特性的开发。当有功能或特性需要开发时,迁出Feature分支。功能开发完成将修改同步到Developer分支,然后将Featurea分支删除(删除分支用“git branch -d 分支名”命令)。

Developer分支,翻译过来叫“开发分支”,实际上它并不做开发的工作,而是用于合并开发特性的分支。从该分支可以随时打出具有最新特性的版本(非稳定版本)。

Release分支,是当从某个节点开始,决定不在合入新特性,只做缺陷的修复工作被迁出来。缺陷修复工作完成,将修改合并到Master分支,并在Master打一个Tag。同时将修改同步回Developer分支。这些工作完成,Release分支将被删除。

Mater分支,是在Release分支测试稳定后合入到Mater。从Mater分支Tag节点打出的版本,认为是“没有Bug的”(不是绝对的没有bug)的稳定版本。

Hotfix分支,是快速修复分支,在版本从Master发布后,发现有Bug。此时从当前Tag节点开出Hotfix分支,用于紧急修复和验证Bug。缺陷处理完毕,该分支同步到Master,再打一个Tag。同时将修复改动同步回Master分支。完成以上工作将该分支删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值