哪些年,我们玩过的Git

前言一、前期工作常用基本概念的理解Git环境的搭建用户名和邮箱的配置二、Git的理论基础工作区域工作流程版本库的初始化文件的四种状态Git的初步操作三、关于文件的各种操作文件修改之后版本的回退撤销修改文件的删除四、本地项目远程提交到Github仓库五、Git的分支管理分支的创建与合并单人分支合并时的冲突解决多人协作下的冲突解决六、标签管理总结参考资料

前言

关于Git,相信每一位该领域的朋友都了解其的强大。Git是一种广受欢迎的代码管理工具,在实际开发中,我们可以通过Git和团队更好管理我们的项目版本,也大大提高了团队的开发效率。在实际使用Git的过程中,我们一般只需要掌握其中的十几条命令就够用了,Taoye之前对Git也只是停留在会用的状态,而由于对Git内部的一些细节平时接触比较少,所以还是会有一点盲区存在。所以,乘着考研结束的这段空闲时间,对之前学习过的Git做一个整理,一方面分享给各位读者,另一方面也方便自己日后的复习。本文主要介绍了在实际开发过程中所常用的一些Git操作,由于博主技术水平有限,在内容上的表述难免会有疏忽和遗漏,也恳请各位Coder多多指教。

一、前期工作

常用基本概念的理解

  • 版本控制

所谓的版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。简单的讲,就是方便我们对项目代码的各种版本进行管理。

我们可以举个例子来进行说明一下:相信每一位学生,无论是大学毕业或是研究生毕业,都避免不了完成一篇学术性的论文。我们都知道,学术性论文的撰写都是一个长期的过程,在这个过程也会经历不断地反复修改,这样就会产生多个论文版本。相信每一位有过此经历的同学都会感到痛苦,版本多了甚至都不知道每一个版本的论文内容都修改了啥。而我们的Git就能够很好的管理各种版本的论文,在每一次提交的时候,都可以在comment中记录我们对论文的修改内容,并且每一个版本的论文都可以进行回滚等操作,也就是随意的切换各种版本。如此一来,岂不快哉???

此外,我们还可以使用廖大大提到的例子来解释一下:在许多游戏中,都会有一个存档的操作,如果你对接下来的挑战Boss没有足够的信心,就可以进行一次存档,当我们的角色因为挑战Boss而丧命,我们就可以读取其中的存档而从当前状态继续游戏,而不需要从头再来,假如游戏有保存多个存档的操作,我们还可以读取不同的存档来继续游戏。同样地,在我们对文件进行修改到一定程度的时候,就可以保存一个“快照”,一旦我们对自己的操作的不满意,就可以进行恢复或是回滚操作,这样就可以“穿梭”到操作之前状态继续工作,而无需从头再来。

  • 工作区(Working Directory)

可以理解成就是电脑本地磁盘的目录,比如我们在本地创建了一个temp目录,那这个目录就叫做工作区。

  • 暂存区(Staging area)

一般存放在"git目录"下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

  • 版本库(Repository)

我们的工作区有个隐藏目录.git,它就是Git的本地版本库。

对于以上部分概念,有些读者可能不是很了解,大家可以阅读下面内容之后再回过头来进行推敲,相信大家一定会有更加透彻的理解。

Git环境的搭建

对于Git的安装,在前面我们讲解Hexo搭建博客的时候有介绍过,这里我们再简单的回顾一下。

你可在git官网中根据自己的需要进行下载:https://git-scm.com/。打开之后你将看到如下内容,就无脑download for Windows
哪些年,我们玩过的Git-LMLPHP

将其下载到指定的磁盘,然后Windows系统下傻瓜式安装即可。安装好后我们打开cmd终端(win+r -> 输入cmd -> 回车),执行git --version,若出现git version 2.19.2.windows.1之类的版本输出,那么恭喜你已经成功安装Git。

对于Linux操作系统下,我们可以直接通过命令的形式来进行安装:

1# Linux下安装Git
2sudo apt-get install git

用户名和邮箱的配置

我们在安装完成Git之后,首先第一步要做的就是配置Git的用户名和邮箱,这个是提交项目的用户的唯一标识。

1# 配置用户名和邮箱,配置好之后会写入C:\Users\M的.gitfig文件中
2git config --global user.name "username"
3git config --global user.email "[email protected]"

global说明:global代表全局的意思,也就是说我们在之后提交的每一个项目采用的都是该用户名和邮箱。假如我们需要在不同的项目中使用不同的用户名和邮箱,我们则不需要添加--global参数。

我们将此信息配置好之后,就会写入C:\Users\M中的,gitfig文件中。此外,我们也可以用过以下命令来查询我们所配置的用户名和邮箱:

1# 查询配置
2git config -l

二、Git的理论基础

该部分的内容来自:https://www.cnblogs.com/best/p/7474442.html

工作区域

Git本地有三个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)。如果在加上远程的git仓库(Remote Directory)就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:

  • Workspace:工作区,就是你平时存放项目代码的地方
  • Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
  • Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
  • Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换

本地的三个区域确切的说应该是git仓库中HEAD指向的版本

  • Directory:使用Git管理的一个目录,也就是一个仓库,包含我们的工作空间和Git的管理空间。
  • WorkSpace:需要通过Git进行版本控制的目录和文件,这些目录和文件组成了工作空间。
  • .git:存放Git管理信息的目录,初始化仓库的时候自动创建。
  • Index/Stage:暂存区,或者叫待提交更新区,在提交进入repo之前,我们可以把所有的更新放在暂存区。
  • Local Repo:本地仓库,一个存放在本地的版本库;HEAD会只是当前的开发分支(branch)。
  • Stash:隐藏,是一个工作状态保存栈,用于保存/恢复WorkSpace中的临时状态。

工作流程

git的工作流程一般是这样的:

1. 在工作目录中添加、修改文件;

2. 将需要进行版本管理的文件放入暂存区域;

3. 将暂存区域的文件提交到git仓库。

因此,git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)

版本库的初始化

我们需要创建一个目录来作为我们的项目根目录,进入到该目录之后,右键git bash来启动git的操作窗口

1# 选择对应的目录,右键点击git bash,然后创建一个目标项目目录,并进入该目录
2mkdir temp_project
3cd temp_project

之后,我们需要将创建的目录初始化为Git所能识别仓库,可以通过git init来实现。初始化完成之后,就会在该目录中自动创建出一个.git的隐藏目录,关于我们项目的版本信息都会存储在该目录当中。

1# 初始化一个仓库,之后会多出.git隐藏文件
2git init

以上就是我们自定义一个仓库的过程。此外,我们还可以基于已经存在Git仓库来进行操作,在Github中查找自己想要的Git仓库,复制其链接,然后通过git clone来对该仓库进行克隆,从而将该仓库下载到我们的本地目录中:

1# 使用clone克隆Github中已经存在的仓库
2git clone XXXXXX.git

文件的四种状态

我们要对文件的版本进行控制,首先需要明白当前文件处于什么样的状态,对于不同状态下的文件,我们可以进行不同的操作。而在Git中,文件主要是有四种状态:

  • Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.
  • Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. 如果使用git rm移出版本库, 则成为Untracked文件
  • Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改
  • Staged: 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified

Git的初步操作

了解了文件的状态之后,我们不妨模拟一个关于论文的例子来初步了解下版本库以及文件的简单操作:

  • 创建一个工作区,并进行初始化
1# 创建一个工作区
2mkdir my_dir
3# 进入工作区
4cd my_dir
5# 通过init命令将工作区转化成Git可以管理的仓库
6git init
7# 在该仓库中创建一个paper.txt,用于编写我们的论文
8touch paper.txt
  • 我们在paper.txt之中编辑如下内容:
1# 我们在`paper.txt`之中编辑如下内容:
2Oh, my god, I will write my graduate paper.
3Come on, Taoye.
  • 通过add命令将paper.txt文件添加到暂存区
1# 通过add命令将paper.txt文件添加到暂存区
2$ git add paper.txt
  • 通过commit命令将暂存区中的内容提交到仓库
1# 通过commit命令将暂存区中的内容提交到仓库,指定提交paper.txt文件
2$ git commit -m "start writing my paper" paper.txt
3
4# 对工作区中的文件统一提交至暂存区
5$ git commit -m "XXXXXXXXXXX"

使用commit命令之后,就能将我们暂存区中的内容提交至仓库中,其中的-m参数表示的是提交说明,用于解释说明本次提价的内容。提交完成之后,我们可以在Git中通过git status来查看文件的状态:

1# 查看指定文件的状态
2git status paper.txt
3# 查看所有文件的状态
4git status

以上操作过程如下图所示:

三、关于文件的各种操作

文件修改之后

在上节中,我们已经介绍了文件的四种状态,并且以关于论文的例子来初步了解了版本库以及文件库的简单操作。

假设现在论文指导老师对Taoye提出了要求:“Taoye同学,时间来不及了,你今天必须给我完成论文的摘要部分!!!否则,后果自负!!!”

“Excuse me?今天?我游戏打得正嗨呢。面对老师如此强硬的要求,没办法了,只能暂停上王者的上分阶段了,开始刚论文。”对此,Taoye进行了一个骚气的三下五除二操作,广泛的涉猎各种优秀的学者文献并进行构思,迅速开写paper.txt中的摘要部分,完成之后的内容如下所示:

1Oh, my god, I will write my graduate paper.
2Come on, Taoye!
3
4I have finished the summary today!

之后,我们再通过git status命令来查看一下该论文文件的状态:

1$ git status paper.txt
2On branch master
3Changes not staged for commit:
4  (use "git add <file>..." to update what will be committed)
5  (use "git checkout -- <file>..." to discard changes in working directory)
6
7        modified:   paper.txt
8
9no changes added to commit (use "git add" and/or "git commit -a")

此时,我们可以发现工作区中的内容发生了修改,并且与我们上一次提交后版本库中的内容不一致,这个时候,Git就会提醒我们需要再次进行add、commit操作来将修改后的文件添加并提交至版本库。另外,我们也可以使用diff命令来查看据上次提交所修改的内容:

1# 查看据上次提交所修改的内容
2git diff paper.txt

当确认文件修改的内容无误之后,我们就可以使用add、commit操作来提交我们的文件到版本库中:

1# 添加至暂存区
2git add paper.txt
3# 提交至版本库
4git commit -m "finished the summary" paper.txt
5# 查看状态
6git status paper.txt

OK,Taoye从白天刚到深夜。如次一来,总算是完成了老师给的要求,兴奋的将内容提交给老师审阅,然后又继续打王者了。

版本的回退

Taoye连续打了12h的游戏,有点累了。重新打开自己完美的论文欣赏一下,却突然发现有几个错别字,这种低级错误实属不应该出现啊。思虑半刻,Taoye修改之后,再次add commit就完事了:

paper.txt修改之后的内容:

1Oh, my god, I will write my graduate paper.
2Come on, Taoye!
3
4I have finished the summary today!
5Some typos have been fixed.

再次add、commit操作:

1git add paper.txt
2git commit -m "fixed the typos" paper.txt

也就是说,现在我们论文总共是有三个版本,分别是:初始化的论文、完成摘要的论文、修改错别字后的论文。我们可以通过log命令来查看各种版本的论文文件:

1# 显示各种版本文件的详细信息
2$ git log paper.txt
3# 通过一行简单显示文件的信息
4$ git log --pretty=oneline paper.txt
5d7938315aa0f4a4d40c6a94a10ab8db25b50e23b (HEAD -> master) fixed the typos
6454cc579f0fe51fdfd97132384a9c5fcaa1993c2 finished the summary
7dc1dcd9501dec52e6160ce98bb5c118abb805289 start writing my paper

从以上log的输出信息,我们可以知道所提交文件的所有历史记录,其中记录了提交时间、提交作者(邮箱)、提交说明(-m)等信息。并且如果我们仔细一看,会发现每一个版本的文件都会对应一个commit id,这个id其实就相当于每一个版本的唯一标识。比如,从上面的输出结果,我们可以得到第一个版本的commit id = dc1dcd9501dec52e6160ce98bb5c118abb805289,而这个commit id的作用就是方便我们日后自由“穿梭”到各个版本(就是一种穿越时空的意思)。

Taoye的论文指导老师审阅了摘要内容之后,正言厉色的说道:“你这写的啥玩意儿?牛头不对马嘴,而且居然还有错别字?”

没办法了,Taoye只能虚心接受老师的批评,再次苦逼的修改论文了。

我们现在论文是版本三,有没有一种快速有效的方法回退到版本一呢???在Git中,我们可以通过git reset --hard来实现这个需求,也就是版本的回退。版本的回退可以用两种形式,一种是通过HEAD来基于当前版本进行回退,另一种是通过commit id来回退到指定的版本,其用法如下所示:

1# HEAD表示的是当前版本,可以通过HEAD^回退到上一个版本
2git reset --hard HEAD^
3git reset --hard HEAD^^     # 回退到上上版本,回退到多少个版本之前就使用多少个^
4git reset --hard HEAD~50    # 回退到50个版本之前
5
6# 指定commit id来进行版本回退
7git reset --hard dc1dcd

值得注意的是,我们通过指定id来进行版本回退的时候,由于id过长,我们没必要全写,只需要传入前几个字符保证id的唯一性即可。有使用过Docker的朋友,应该会熟悉,我们在指定容器的时候,也是类似的操作。

下面Taoye版本三的论文迅速回退到版本一,骚操作如下:

1$ git reset --hard dc1dcd
2$ cat paper.txt
3Oh, my god, I will write my graduate paper.
4Come on, Taoye.

关于版本穿梭的简单解释:其实,在Git中,有个HEAD指针用于指向当前版本,当我们进行版本回退的时候,其实就是改变HEAD的指向,指向对应的版本也就实现了版本的回退。这有点类似于数据结构中链表的操作。

此外,我们还可以通过git reflog来查看当前版本文件的变换:

1$ git reflog paper.txt
2dc1dcd9 HEAD@{1}: reset: moving to dc1dcd                       # 版本的回退(回退到版本一)
3d793831 (HEAD -> master) HEAD@{2}: commit: fixed the typos      # 第三次提交(版本三)
4454cc57 HEAD@{3}: commit: finished the summary                  # 第二次提交(版本二)
5dc1dcd9 HEAD@{4}: commit (initial): start writing my paper      # 第一次提交(版本一)

撤销修改

假设Taoye在写论文的时候,每天都在不断地修修改改,心里面非常的烦躁,很不是滋味。于是乎,在paper.txt中添加如下一句话:

1I don't like my tutor, he often criticizes me.

然而,在打算提交的时候,想了想还是有点不太妥,要是因为这么一句话,最终导致无法毕业那就完蛋了。对此,我们在对文件commit之前使用'git status paper.txt'命令发现,可以通过checkout --来进行修改撤销,撤销至修之前的状态,操作如下:

1$ git checkout -- paper.txt
2$ cat paper.txt     # 执行之后,可以发现已经撤销到无最后一句的状态

以上是发生在我们对工作区中的文件进行修改,但是还没有执行git add操作,将工作区中的paper.txt添加至暂存区中的场景,已达到撤销至修改之前的状态。假设我们在对paper.txt修改之后,再执行git add paper.txt命令将文件添加至暂存区,那么我们该怎样撤销呢?按照思路,我们可以先通过git reset HEAD将暂存区中的内容撤销掉放回工作区,然后撤销工作区即可实现需求,对此,有如下操作:

1# 将暂存区中的内容撤销掉放回工作区
2git reset HEAD paper.txt
3# 撤销工作区修改的内容
4git checkout -- paper.txt

如此一来,就完美的将I don't like my tutor, he often criticizes me.撤销掉,Taoye也就能顺利毕业了。关于撤销,要记得与版本回退区分开来,撤销是我们在对文件进行修改但是还没有进行commit的时候发生的,而版本回退是在执行了commit提交操作之后发生的。

文件的删除

在上面的内容中,我们已经详细的介绍了关于文件的修改、版本回退、撤销等操作,下面我们来讲讲文件在删除之后应该会出现哪些操作。

假设现在出现了这么一种情况:Taoye有个顽皮的妹妹,她在用我电脑的时候,不小心将我的paper.txt论文文件从本地磁盘删除了。一气之下,Taoye将妹妹关进了小黑屋自我反省七天。悲剧啊,Taoye忙活了将近一个月的论文就此烟消云散,Git该如何挽回这样的结局呢?

paper.txt文件删除之后,我们使用git status来查看一下文件的状态:

1$ git status
2On branch master
3Changes not staged for commit:
4  (use "git add/rm <file>..." to update what will be committed)
5  (use "git checkout -- <file>..." to discard changes in working directory)
6
7        deleted:    paper.txt
8
9no changes added to commit (use "git add" and/or "git commit -a")

在Git看来,你将文件从工作区中删除,其实也是对文件的一种修改。也就是说,现在我们的工作区中的内容与版本库中的内容不一致(工作区没有了paper.txt文件,而版本库依然存在paper.txt文件)。为了将两者空间中的内容达到一致,我们现在有两种选择,一是将版本库中的paper.txt文件删除掉,二是将工作区中的paper.txt文件恢复。

有了以上的思路,我们可以有如下操作:

1# 将版本库中的paper.txt文件删除掉,需要执行commit才会生效
2git rm -f paper.txt
3git commit -m "delete the paper.txt"
4
5# 将工作区中的paper.txt文件恢复
6git checkout -- paper.txt

对于文件的删除,需要注意的是,只有提交到版本库的文件,才能进行恢复,对于为提交到版本库的文件是无法进行恢复的。

四、本地项目远程提交到Github仓库

Taoye在之前参加2019年腾讯微信小程序比赛的时候,开发了一个关于侦探推理的项目。Taoye现在想要将该项目从本地提交到Github,该如何实现呢?

我们在实现该需求之前,首先需要将本地与Github进行联通。对此我们应该通过ssh在本地生成一个公钥,然后在Github中配置该公钥。操作如下:

1# 1、生成公钥,执行之后会在.ssh目录中生成秘钥文件,其中id_rsa.pub表示的是公钥
2cd ~/.ssh
3ssh-keygen -t rsa 26647879@qq.com
4# 2、进入github,settings -> SSH keys -> add key,然后将id_rsa.put中的公钥复制进去
5# 3、配置好公钥之后,需要验证本地与github的连通性
6ssh -T git@github.com

在确认本地与Github联通之后,我们就能正常地将项目从本地远程提交到Github中了。

  • 登录Github,创建一个目标仓库,取名为detective,用来存储我们的项目,并复制其中的.git链接地址
  • 在Github创建仓库之后,需要将该远程仓库与本地关联起来
1# 在本地关联目标仓库,方便之后将项目推送至该远程仓库
2git remote add origin https://XXXXXX.git
  • 进入我们的本地项目,然后初始化为git可管理的仓库
1cd detective
2git init
  • 将工作区中项目的所有文件添加至暂存区
1git add ./*
2

  • 将暂存区中的内容提交到版本库当中
1git commit -m "commit the detective project" ./*
  • 将项目添加到版本库之后,我们就可以将该项目推送至远程仓库了
1# 第一次推送
2git push -u origin master
3# 推送之后,如果我们的项目发生了修改,我们可以不用在使用-u参数进行推送
4git push origin master
5
6# 另外,如果有需要的话,我们还可以使用clone命令将远程仓库克隆到本地
7git clone https://XXXXXX.git

五、Git的分支管理

分支是Git当中一个非常重要的概念,分支有点类似于树枝,也就意味着为了避免影响主线的正常开发,我们可以将任务从主线中分离开来,从而形成一个分支,之后的任务都是在分支中来完成的,当任务完成之后,就可以将完整的任务从分支提交到主线。

在前面版本回退一节当中,我们知道,每一次的提交都会产生一个版本,多次提交自然也就会产生多个版本。我们可以将每一个版本看做是一个珠子,而多个版本就会通过一条线串联起来,从而形成一条版本链。这个版本链其实就是一个master分支,也就是我们上面所说的主线,我们每一次的提交都是基于master分支来完成的,而HEAD指针则是用来指向当前分支(在没有其他分支的前提下,就是指向master)。

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!

不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

所以Git合并分支也很快!就改改指针,工作区内容也不变!

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:

分支的创建与合并

在上面,已经介绍了分支的概念及其断链、成链的原理过程,下面我们通过Git命令来完成分支的创建与合并。

在Git中,我们可以通过git branch来查看有哪些分支、git branch xxx来创建一个分支,其中带有*号的表示当前分支、git checkout xxx来切换分支:

1# 创建一个名为dev的分支
2$ git branch dev
3# 从master分支切换到dev分支中
4$ git checkout dev
5# 查看当前有多少分支
6$ git branch
7
8# 此外,我们还实现分支的创建、切换这两个操作合并执行
9$ git checkout -b dev

现在,我们不妨在刚刚创建的dev分支中对paper.txt的内容进行编辑,在最下方添加这么一句话:I'm writing the content of my paper.。编辑完成并保存之后,我们提交到版本 库,并再次切换到master分支,使用cat命令来查看paper.txt的内容。

1$ git checkout dev      # 切换到dev分支
2$ vim paper.txt         # 编辑paper.txt,并增添一句话
3$ git add paper.txt     # 添加到暂存区
4$ git commit -m "writing the content of this paper" paper.txt   # 提交到版本库
5$ cat paper.txt         # 查看paper.txt内容
6$ git checkout master   # 再次切换到master分支
7$ cat paper.txt         # 查看paper.txt内容

采用cat命令两次查看paper.txt内容时,我们会发现在执行后一次命令时,paper.txt中的内容并没有添加新增的一句话,这主要是因为我们刚刚采用vim编辑paper.txt的时候是基于dev分支进行的,提交也是提交到dev分支,而非master分支,所以当我们切换到master分支的时候并不能够看见paper.txt编辑后的内容。如下图所示:

而我们要想在master分支中查看到paper.txt的新内容,则需要将dev分支合并到master主分支中才行,采用的是git merge命令,操作如下:

1$ git checkout master
2$ git merge dev

合并完成之后,dev的任务已经完成了,也就没有必要存在了,可以通过git branch -d xxx来删除分支:

1$ git branch -d env
2Deleted branch env (was e9c7421).

单人分支合并时的冲突解决

在编程的世界里,多进程占据了一个举足轻重的地位。在高并发、高流量的场景下,我们一般通过多进程来提高项目的服务效率,以便提高用户体验。话虽如此,但是在使用多进程的时候,许多问题同样会慢慢浮出水面。同样地,Git分支虽然能够方便多个的用户协同开发,但是将多个不同内容的分支进行合并的时候却会产生冲突,作为一个对技术有追求的Coder,我们应该要理解为什么会产生冲突,以及产生冲突后我们应该怎样解决。

出现冲突的场景:

  1. 切换到dev分支后,对paper.txt进行编辑,然后将保存后的文件提交到版本库中。
  2. 切换到master分支,对paper.txt进行编辑,将保存后的文件提交到版本库。
  3. master分支下,将dev分支进行合并。
 1# 1、在dev分支中对paper.txt文件进行编辑,并提交到版本库
2git checkout dev
3git vim paper.txt
4git add paper.txt
5git commit -m "the first operation" paper.txt
6
7# 2、在master分支中对paper.txt文件进行编辑,并提交到版本库
8git checkout master
9git vim paper.txt
10git add paper.txt
11git commit -m "the second operation" paper.txt
12
13# 3、在master分支中,将dev分支合并
14git merge dev

此时,在我们执行merge命令进行分支合并的时候,会出现如下内容:

1$ git merge dev
2Auto-merging paper.txt
3CONFLICT (content): Merge conflict in paper.txt
4Automatic merge failed; fix conflicts and then commit the result.

从如上Git给我们提供的信息可以知道,此时已经产生冲突,我们必须手动解决冲突才能再次提交,使用git status paper.txt也能查看产生冲突的信息。我们通过vim打开paper.txt文件可以看见如下内容:

 1Oh, my god, I will write my graduate paper.
2Come on, Taoye!
3
4I have finished the summary today!
5Some typos have been fixed.
6I'm writing the content of my paper.
7<<<<<<< HEAD
8
9the second operation.
10=======
11
12the first operation.
13>>>>>>> dev
14

对此,我们要想解决冲突,则需要在master主分支中手动编辑该文件,编辑并保存完成之后,在将文件提交到版本库即可:

1vim paper.txt
2git add paper.txt
3git commit -m "solve the conflict" paper.txt
4
5# 查看分支的合并情况
6git log --graph --pretty=oneline --abbrev-commit

下面原则来自廖大大的Git教程:https://www.liaoxuefeng.com/wiki/896043488029600/900005860592480
在实际开发中,我们应该按照几个基本原则进行分支管理:

  1. 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
  2. 那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
  3. 你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样:

多人协作下的冲突解决

现在导师给Taoye提出的新需求是:两天内完成论文的初稿并提交。

Taoye大量的时间都花费在开黑中了,两天内完成这个任务对Taoye来说有点困难。于是乎,Taoye想要找妹妹帮忙完成论文的part1部分,而自己完成part2部分。如此一来,两个进程协同进行,也就能完美的实现了导师给的需求。妙哉,妙哉!Taoye心想到这,立马提交自己论文的半成品到远程仓库,然后给妹妹提供一个git链接供其clone。

1# Taoye将本地论文提交到远程仓库
2$ git push origin master
1# 妹妹将远程仓库克隆到本地
2$ git clone XXXXXX.git

OK,既然两人的本地都有了论文文件,那么接下来就要开始并行完成各自的任务了,为了避免自己的操作给主分支带来不好的影响,于是在master分支中创建一个dev分支用来编辑文件。由于妹妹的效率比Taoye要快,所以率先完成了论文part1部分的内容,对此,妹妹有如下操作:

1# 妹妹的操作
2$ git branch dev
3$ git checkout dev
4$ vim paper.txt

并且在paper.txt添加内容:sister have finished the part1.,part1部分的内容完成之后,将dev分支迅速推送至远程仓库:

1$ git add paper.txt
2$ git commit -m "sister have finished the part1" paper.txt
3$ git push origin dev

OK,Taoye交给妹妹的任务已经完成了,于是就兴奋的出去玩耍了。毕竟这篇论文是属于Taoye的,所以还是需要认真的完成,自然花费的时间也就更多了。经历了一晚上通宵的时间,终于是顺利完成了part2部分的内容,于是屁颠屁颠的将论文提交至远程仓库:

1# Taoye的操作
2$ git checkout dev
3$ vim paper.txt
4# 在paper.txt添加一句:taoye have finished the part2
5$ git add paper.txt
6$ git commit -m "taoye have finished the part2" paper.txt
7$ git push origin dev

而就在Taoye推送论文到远程仓库的时候,由于自己推送内容与妹妹推送的内容不一致,所以导致推送失败。我们通过Git给出的提示信息可以知道,要想解决这个冲突,首先需要通过pull命令将最新的提交拉取下来,然后与本地合并,解决冲突之后再推送到远程仓库。为此,Taoye立马执行了pull命令:

1$ git pull

而在执行pull命令的时候,糟糕的问题又接踵而至了,Git提示说:There is no tracking information for the current branch.,也就是说本地的分支与远程没有建立链接。对此,我们可以建立链接后再执行pull命令:

1$ git branch --set-upstream-to=origin/dev dev
2$ git pull

虽然可以执行pull命令,但是会出现冲突提示,所以我们需要首先手动解决冲突,解决的方式和上节中一样:对paper.txt文件进行编辑,然后提交并推送至远程仓库。所以,Taoye对paper.txt文件进行编辑之后,内容如下:

1taoye have finished the part1
2taoye have finished the part2

编辑好后,将文件保存并推送至远程仓库:

1$ git add paper.txt
2$ git commit -m "finished the part1 and part2" paper.txt
3$ git push origin dev

所以,在多人协作工作时我们一般准守如下过程:

  1. 完成任务后首先使用git push origin xxx推送至远程仓库
  2. 如果推送失败,则需要执行git pull命令将最新的提交拉取下来
  3. 如果拉取失败,则可能需要建立连接,所以执行git branch --set-upstream-to=origin/xxx xxx命令
  4. 解决之后,再次执行git pull命令尝试拉取最新提交
  5. 此时,我们需要对冲突文件进行修改,等到修改完成之后,将文件推送至远程仓库

六、标签管理

在上面的内容中,我们有介绍过,可以根据版本号(很长的字符串)来实现任意版本之间的穿越。但是通过这种较长字符串版本号的形式对用户并不是特别友好,看的脑阔有点疼。所以我们一般可以给每一个版本贴上一个标签,并且可以通过这个标签来定位任意的版本,从而更加容易的实现版本穿越等需求。

在这,我们也可以将标签看做是版本号的一个别名,使用标签就相当于使用版本号,两者的作用是等价的。如果有使用过Docker的话,应该会知道,在Docker中,我们每创建一个容器,Docker都会分配一个容器id以便于我们定位对应的容器,而我们自己也可以为每一个容器定义一个别名。我们在使用一系列的Docker命令对容器进行操作的时候,既可以通过容器id,也可以通过别名的形式来进行操作。

Git中,我们主要是采用tag命令来管理标签,由于标签比较的简单,这里就不一一讲解了,与标签相关的命令主要有以下一些:

 1$ git tag                       # 查看所有标签
2$ git tag v1.0                  # 没有指定版本,则默认给当前版本打标签
3# 查看各个版本信息(版本号)
4$ git reflog paper.txt | git log --pretty=oneline paper.txt
5$ git tag v0.9 a35d6b           # 根据版本号来打标签
6# 打标签的同时,给标签添上相应的说明
7$ git tag -a v0.1 -m "version 0.1 released" a35d6b
8$ git show v1.0                 # 查看对应的标签信息
9$ git tag -d v0.9               # 删除标签
10
11$
 git reset --hard v0.9         # 根据标签实现版本穿越

总结

总的来讲,Git上手还是很快的,关键在于平时需要加强Git操作命令的训练。以上关于Git的命令只是一部分,但如果掌握上述内容,还是可以轻松满足Git的实际需求的。如果有兴趣想要了解更多关于Git的知识,可以自行查阅相关的文档或是书籍来进一步的学习。

参考资料

一个小时学会Git:https://www.cnblogs.com/best/p/7474442.html#!comments
廖雪峰Git教程:https://www.liaoxuefeng.com/wiki/896043488029600

06-06 03:30