前言
一、Git初识
为了能够更⽅便我们管理不同版本的⽂件,便有了版本控制器。所谓的版本控制器,就是能让你了解到⼀个⽂件的历史,以及它的发展过程的系统。通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统,同时也⽅便多⼈协同作业。
⽬前最主流的版本控制器就是Git。Git可以控制电脑上所有格式的⽂件,例如doc、excel、dwg、dgn、rvt等等。对于我们开发⼈员来说,Git最重要的就是可以帮助我们管理软件开发项⽬中的源代码⽂件!
还需要再明确⼀点,所有的版本控制系统,Git也不例外,其实只能跟踪⽂本⽂件的改动,⽐如TXT⽂件,⽹⻚,所有的程序代码等等。版本控制系统可以告诉你每次的改动,⽐如在第5⾏加了⼀个单词“Linux”,在第8⾏删了⼀个单词“Windows”。⽽图⽚、视频这些⼆进制⽂件,虽然也能由版本控制系统管理,但没法跟踪⽂件的变化,只能把⼆进制⽂件每次改动串起来,也就是只知道图⽚从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。
二、Git安装
Git 是开放源代码的代码托管⼯具,最早是在Linux下开发的。开始也只能应⽤于Linux平台,后⾯慢慢的被移植到windows下,现在,Git可以在Linux、Unix、Mac和Windows这⼏⼤平台上正常运⾏了。
1、Linux-centos
如果我们使用的是Linux-centos系统,那么我们可以先试着输入git命令。此时我们看到当前主机是安装了git的。
git
我们也可以输出git --version命令,来查看当前安装的git的版本。
git --version
如果输入上面的两个命令打印的是下面的内容,说明当前主机上没有安装git,此时我们可以安装git。
我们可以输入下面的命令来安装git。
sudo yum install git -y
当然,我们也可以输入下面的命令来删除git。
sudo yum remove git -y
2、Linux-ubuntu
如果我们使用的是Linux-ubuntu系统,那么我们依然可以通过git命令或git --version命令来查看当前主机是否安装了git。只不过不同的是如果我们没有安装git,此时我们需要输入下面的命令来进行安装git。
sudo apt-get install git -y
我们还可以使用下面的命令来删除git。
sudo apt-get remove git -y
三、Git基本操作
1、创建Git本地仓库
仓库是进⾏版本控制的⼀个⽂件⽬录。我们要想对⽂件进⾏版本控制,就必须先创建⼀个仓库出来。 创建⼀个Git本地仓库对应的命令为git init ,注意命令要在⽂件⽬录下执⾏。例如我们新创建一个文件夹gitcode,此时如果我们想要对这个文件夹进行版本控制,那么我们就要在这个文件夹下创建一个git本地仓库。
我们看到gitcode目录下多了一个.git的隐藏目录,.git目录是Git来跟踪管理仓库的,不要手动修改或删除这个目录里面的文件,这会把Git仓库给破坏。
2、配置Git
当安装Git后首先要做的就是配置Git,即设置用户名和e-mail地址,这是非常重要的。配置命令为:
//将 Your Name 改为自己的昵称
git config [--global] user.name "Your Name"
//将email@example.com改为自己的邮箱
git config [--global] user.email "email@example.com"
我们可以通过下面的命令来查看当前目录Git库的配置。
git config -l
我们还可以通过下面的命令来重置某一个配置。我们看到刚刚上面配置的用户名和邮箱没有了。
git config --unset user.name
git config --unset user.email
在执行git config命令时,–global是一个可选项,如果使用了该选项,表示当前主机上所有的Git仓库都会使用这个配置,如果我们希望不同的仓库使用不同的name或e-mail的话,那么我们在执行git config命令时就不要加–global选项。但是需要注意的是,执行命令时必须要在仓库内。
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
我们还需要注意,使用git config --global配置的内容,使用git config --unset命令是重置不了的,需要使用git config --global --unset命令才可以重置。
git config --global --unset user.name
git config --global --unset user.email
3、认识工作区、暂存区、版本库
工作区:是在电脑上要写代码或文件的目录,例如我们创建的gitcode目录。
暂存区:英文叫stage或index。一般存放在.git目录下的index文件(.git/index)中,我们把暂存区有时也叫作索引(index)。
版本库:又名仓库,英文名repository。工作区有一个隐藏目录.git,它不算工作区,而是Git的版本库。这个版本库里面的所有文件都可以被Git管理起来,每个文件的修改、删除、Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以”还原“。
下面这个图展示了工作区、暂存区和版本库之间的关系:
- 图中左侧为⼯作区,右侧为版本库。Git的版本库⾥存了很多东西,其中最重要的就是暂存区。
- 在创建Git版本库时,Git会为我们⾃动创建⼀个唯⼀的master分⽀,以及指向master的⼀个指针叫HEAD。
- 当对⼯作区修改(或新增)的⽂件执⾏ git add 命令时,暂存区⽬录树的⽂件索引会被更新。
- 当执⾏提交操作git commit 时,master分⽀会做相应的更新,可以简单理解为暂存区的⽬录树才会被真正写到版本库中。
由上述描述我们便能得知:通过新建或粘贴进⽬录的⽂件,并不能称之为向仓库中新增⽂件,⽽只是在⼯作区新增了⽂件。必须要通过使⽤ git add 和 git commit 命令才能将⽂件添加到仓库中进⾏管理。例如我们在gitcode目录下创建一个ReadMe文件,此时Git版本库是不能管理ReadMe这个文件的,因为gitcode目录下为工作区,我们在工作区创建的文件ReadMe并没有通过git add命令更新到缓存区,也没有通过git commit命令提交到Git版本库,所以Git版本库不会管理ReadMe文件。
git add命令将工作区中修改的内容更新到版本库的暂存区中,git commit命令会将暂存区的内容更新到分支。工作区中的内容修改后每一次执行git add命令都会在版本库的对象库中形成一个新的git对象。
在Git版本库中有一个HEAD指针,该指针指向master分支,master分支中记录了每一次修改形成的git对象的索引,通过索引找到对应的git对象,就可以知道每一次修改的具体内容了。
4、添加文件 – 场景一
在包含.git的⽬录下新建⼀个ReadMe⽂件,我们可以使⽤git add命令将文件添加到暂存区。
- 添加一个或多个文件到暂存区:
//file1和file2为文件名
git add [file1] [file2]
- 添加指定目录到暂存区,包括子目录:
//dir为目录名
git add [dir]
- 添加当前目录下所有文件改动到暂存区:
git add .
再使用git commit 命令将暂存区内容提交到本地Git仓库中。
- 提交暂存区全部内容到本地仓库中:
//message为本次提交的说明信息
git commit -m "message"
- 提交暂存区的指定文件到本地仓库:
//file1和file2为文件名,message为本次提交的说明信息
git commit [file1] [file2] -m "message”
注意,git commit 后面的 -m 选项,要跟上描述本次提交的message,由我们自己提交时写,这部分内容绝对不能省略,并且要好好描述,因为这是用来记录我们本次提交的细节,方便以后观看。
例如下面我们修改ReadMe文件中的内容,然后执行git add命令将工作区中的修改更新到暂存区,再执行git commit命令将暂存区的内容提交到版本库。我们看到git commit命令执行成功后会打印出来信息,该信息告诉我们一个文件被改动(即ReadMe文件被改动),插入了两行内容(即我们向ReadMe文件中添加的两行内容)。
我们还可以多次add不同的文件,而只commit一次便可以提交所有文件,这是因为需要提交的文件统统被更新到了暂存区中,此时执行一次git commit就可以将暂存区中的内容提交到版本库中。
此时我们可以使用git log命令来查看历史提交记录。git log命令可以打印从近到远的提交记录。commit后面的是commitID,每一次提交都会形成一个不同的commitID。
git log
我们还可以使用下面的命令来将提交日志一行一行打印出来。需要说明的是我们看到的提交日志中一大串类似24c7f3354…88d8这样内容,是每一次提交的commitID(版本号),Git的commitID不是1,2,3…递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示。
git log --pretty=oneline
5、查看.git文件
下面我们来查看.git的目录结构。
- index就是我们的暂存区,执行git add后的内容就是添加到这里的。
- HEAD就是我们的默认指向master分支的指针,而默认的master分支中存的就是最近一次commit的commitID。
- objects为Git的对象库,里面包含了创建的各种版本库对象及内容。当执行git add命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,就位于“.git/objects”目录下。下面我们来看看这些对象有何用处。
查找object对象时要将commitID分成两部分,其中commitID前两位是文件夹名称,后38位是文件名称。找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过sha(安全哈希算法)加密过的文件,不过我们可以使用下面的命令来查看版本库对象的内容。
//-p使打印出来的内容更美观
git cat-file -p
6、添加文件 – 场景二
下面我们再展示一种添加文件的场景,能加深对工作区、暂存区、版本库的理解,示例如下。
提交后我们发现打印的结果中显示只有一个文件改变了,但是我们不是在工作区添加了两个文件吗?这是因为git add是将文件添加到暂存区,git commit是将暂存区的内容添加到本地仓库中。由于我们在工作区新增file5文件后并没有使用git add file5命令来将file5新增到暂存区中,所以file5就不在暂存区中维护,那么我们git commit提交时,其实只是把file4提交了,而遗漏了工作区中的file5。如果我们想将file5也提交到本地仓库,那么我们只需要先使用git add file5将file5更新到暂存区中,然后再使用git commit即可。
7、修改文件
Git比其他版本控制系统设计的优秀,因为Git跟踪并管理的是修改,而非文件。例如我们在某个文件中新增了一行,删除了一行,甚至创建了一个新文件,这都算是修改,而Git中就是记录这一次次的修改。
下面我们修改ReadMe文件中的内容。
此时工作区中的ReadMe文件中的内容和版本库里面ReadMe文件中的内容是不一样的,我们可以通过下面的命令来查看当前仓库的状态,这个命令用于查看我们上次提交之后是否对工作区中的文件进行再次修改。
git status
上面的结果告诉我们,ReadMe文件被修改了,但是还没有完成添加到暂存区和提交到版本库。我们看到上面的打印结果只告诉了我们此时文件被修改了,如果我们想要查看文件具体被修改了哪些内容的话,我们可以使用下面的命令。
//显示暂存区和工作区ReadMe文件的差异
git diff ReadMe
//显示版本库和工作区ReadMe文件的差异
git diff HEAD -- ReadMe
下面我们执行git add命令将工作区修改的内容添加到暂存区中。我们看到再次执行git status命令查看时,就没有显示让我们git add的信息了。此时我们执行git diff ReadMe命令可以看到此时工作区和暂存区中ReadMe文件的内容没有差异。而我们执行git diff HEAD ReadMe命令可以看到此时工作区和版本库中ReadMe文件还存在差别。
然后我们再执行git commit命令。之后我们再执行git diff HEAD ReadMe命令就不会再看到工作区和版本库中ReadMe文件存在差别了。
8、版本回退
Git 能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天我们发现之前的⼯作出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本回退的功能了。
执⾏ git reset 命令⽤于回退版本,可以指定退回某⼀次提交的版本。要解释⼀下“回退”本质是要将版本库中的内容进⾏回退,⼯作区或暂存区是否回退由命令参数决定。
git reset [--soft | --mixed | --hard | ] [HEAD]
- –mixed为默认选项,使用时可以不用带该参数。该参数将暂存区的内容退回为指定提交版本内容,工作区文件保持不变。
- –soft该参数对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。
- –hard 该参数将暂存区与工作区都退回到指定版本。切记工作区有未提交的代码时不要用这个命令,因为工作区会回滚,没有提交的代码就再也找不回了,所以使用该参数前一定要慎重。
HEAD说明:
- 可直接写成commitID,表示指定退回的版本。
- HEAD表示当前版本
- HEAD^表示上一个版本
- HEAD^^表示上上一个版本
- 以此类推
也可以使用~数字表示:
- HEAD~0表示当前版本
- HEAD~1表示上一个版本
- HEAD~2表示上上一个版本
- 以此类推
下面我们使用commitID来进行版本回退。我们看到回退到了最初的版本,此时打印日志,发现只有一个提交日志。
当我们回退到最初的版本后,我们还能通过原来最近一次提交的commitID来回退到原来最近一次提交的版本。可以看到创建的文件和ReadMe修改的内容,还有修改的记录都回来了。
刚刚我们能使用原来最近一次提交的commitID来使版本库回退到最近一次提交,但是当我们不知道最近一次提交的commitID时,我们可以通过git reflog命令来查看,该命令记录了每一次的提交和版本回退等操作。可以看到虽然使用git reflog命令打印出来的commitID很短,但是通过这个短的commitID也可以回退到对应版本。我们只要找到了commitID,就可以回退到对应的版本,但是如果找不到commitID,那么就无法回退到对应的版本了。
git reflog
版本回退原理:
版本回退只是将master里面的commitID进行了修改,所以版本回退操作很快就完成了。
9、撤销修改
当我们在工作区写了很长时间的代码后,我们发现代码越来越差,想要回复到上一个版本时,就可以使用撤销修改操作。
9.1 情况一:对于工作区的代码,还没有add
在ReadMe文件中修改代码,此时没有执行add和commit操作,那么可以使用下面的命令来撤销工作区中代码的修改。该命令就是将文件恢复到最近一次add或者commit时的状态。
// --这两个横线很重要,需要加上,如果不加那么会是另外一种操作。
git checkout -- ReadMe
9.2 情况二:对于工作区的代码,add到了暂存区,但是没有commit到版本库
在ReadMe中修改代码,并且使用add命令更新到了暂存区中,但是没有commit到版本库中,此时如果想要撤销ReadMe文件在工作区和暂存区的修改,可以使用前面学的git reset命令。
我们可以直接使用git reset命令的–hard选项,该选项可以直接将暂存区和工作区的内容更改为与版本库一致。
我们还可以使用git reset命令的–mixed选项,该选项会将暂存区的内容和版本库的内容一致,然后此时就变为了情况一,我们在使用git checkout命令使工作区的内容撤销到和最近一次add或commit时一样。
9.3 情况三:对于工作区的代码,add到了暂存区,commit到了版本库,但是没有push到远程仓库。
当工作区、暂存区和版本库中都存在想要撤销的代码时,此时必须要没有向远程仓库进行push操作。然后本地的工作区、暂存区和版本库都想要恢复到上一个版本。
工作区中修改ReadMe文件中的内容,然后add到暂存区中,commit到版本库中,此时如果想要回退到上一个版本,可以使用git reset --hard HEAD ^ 命令,该命令可以将工作区、暂存区、版本库中的内容都回退到上一个版本。HEAD ^表示上一个版本,HEAD ^ ^表示上上个版本,也可以不使用HEAD ^而使用commitID来进行回退操作。
10、删除文件
在工作区中使用rm命令进行文件的删除,然后add到暂存区中,然后commit到版本库,这样工作区、暂存区、版本库就都进行了文件的删除操作。
也可以使用git rm命令,该命令可以在工作区和暂存区中将文件进行删除,但是还需要commit到版本库中,然后版本库中的文件才被删除。即git rm省略了删除时的add步骤。
git rm
四、分支管理
1、理解分支
分⽀就是科幻电影⾥⾯的平⾏宇宙,当你正在电脑前努⼒学习 C++ 的时候,另⼀个你正在另⼀个平⾏宇宙⾥努⼒学习 JAVA。如果两个平⾏宇宙互不⼲扰,那对现在的你也没啥影响。不过,在某个时间点,两个平⾏宇宙合并了,结果,你既学会了 C++ ⼜学会了 JAVA!
在版本回退⾥,我们已经知道,每次提交,Git都把它们串成⼀条时间线,这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前,只有⼀条时间线,在Git⾥,这个分⽀叫主分⽀,即 master 分⽀。再来理解⼀下HEAD,HEAD 严格来说不是指向提交,⽽是指向master,master才是指向提交的,所以,HEAD 指向的就是当前分⽀。master中有parent记录上一次提交的commitID,通过master我们就可以知道master分支的提交时间线,既可以知道这个提交线上的每一次提交的commitID。
2、创建分支
Git支持我们查看或创建其他分支,下面我们创建一个新的分支dev。我们可以使用git branch命令来查看当前本地所有分支。可以使用git branch dev来创建一个新的dev分支。
//查看当前本地所有分支
git branch
//新建分支dev
git branch dev
当我们创建新的分支后,Git新建了一个指针叫dev,*表示当前HEAD指向的工作分支是master分支。另外可以发现master分支和dev分支当前指向同一个commitID,即指向同一个修改。
这是因为创建的dev分支就是在当前最新提交的基础上创建的,所以dev指向最新一次的commitID。
3、切换分支
我们可以通过git checkout命令切换工作分支,需要注意,此时git checkout命令后面没有两个横线。
//切换工作分支为dev分支
git checkout dev
下面我们在dev分支下修改ReadMe文件中的内容,然后进行add和commit。此时使用cat命令查看ReadMe文件中内容,可以看到修改的内容。但是当我们使用git checkout命令将工作分支切换到master分支下后,我们在master分支下使用cat命令查看ReadMe文件中的内容,可以看到在dev分支下添加的内容现在在master分支下看不到了。
然后我们再切换到dev分支下,我们看到ReadMe文件中添加的内容又出现了。此时在dev分支下,我们查看dev中存的commitID,可以看到已经发生了变化,然后我们看到这个commitID对应的记录中parent的commitID才是我们创建dev时的commitID。
即我们在dev分支上进行了一次提交,那么dev分支中存的就是最新一次的commitID。
4、合并分支
如果想要在master分支上也看到dev分支提交的内容,那么可以进行分支合并。
如果我们想要在master分支上看到dev分支上提交的内容,那么我们就需要让master分支合并dev分支,所以此时要在master分支上进行操作。
在master分支下使用git merge命令可以将dev分支合并到master分支上,此时我们看到在master分支下也可以看到ReadMe文件增加的内容了,而且此时master分支中存的commitID就是dev分支中存的commitID,即最新一次提交的commitID。Fast-forward 代表“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度⾮常快。当然,也不是每次合并都能 Fast-forward,我们后⾯会讲其他⽅式的合并。
5、删除分支
合并完成后, dev 分⽀对于我们来说就没⽤了, 那么dev分⽀就可以被删除掉,注意如果当前正处于某分⽀下,就不能删除当前分⽀。如我们不能在dev分支下删除dev分支,需要先切换到master分支,然后才能成功删除dev分支。
git branch -d dev
6、合并冲突
在实际分支合并的时候,并不是想合并就能合并成功的,有时候可能会遇到代码冲突的问题。
例如下面的冲突场景:原来在master分支下的ReadMe文件中内容为aaa,然后切换到dev1分支下将ReadMe文件中的内容aaa修改为bbb,然后再切换到master分支下将ReadMe文件中的bbb内容修改为ccc。此时如果在master分支下合并dev1分支的话,就会产生合并冲突,因为git不知道是以dev1分支修改的内容为准还是以master分支修改的内容为准。
下面我们使用git checkout -b命令来切换到dev1分支上,该命令可以切换到一个指定分支上,如果这个分支不存在,那么就先创建这个分支,然后再切换到这个分支上。当切换到dev1分支上后,我们将ReadMe文件中的aaa修改为bbb,然后进行add和commit操作。此时ReadMe文件的内容在dev1分支上为bbb。
//切换到dev1分支上,如果没有该分支,那么就先创建该分支,然后再进行分支切换
git checkout -b dev1
然后我们切换到master分支上,此时可以看到ReadMe文件中的内容为aaa,此时我们修改ReadMe文件中的内容为ccc,然后进行add和commit操作。此时ReadMe文件的内容在master分支上为ccc。
此时版本库中的内容如下图所示。
此时我们在master分支中将dev1分支合并到master分支上时,可以看到就出现了合并冲突。
此时我们查看ReadMe文件中的内容,可以看到如下所示。
这种情况下需要我们手动来选择保持master分支修改的代码还是dev1分支修改的代码,如果保存dev1分支修改的代码,那么就将其它的代码进行删除。
此时将修改后的内容进行add和commit操作。
此时版本库中的内容如下图所示。
下面我们验证master分支和dev1分支此时指向的commitID,可以看到和上图中的情况一致。
7、git log命令详解
前面我们知道了git log命令可以打印出来提交日志,下面我们来详细了解git log命令。
- git log命令可以打印从远到近的历史提交记录
- git log --pretty=oneline可以将提交日志一行一行打印出来
- git log --graph --abbrev-commit命令可以将上面的分支图打印出来,–graph选项为图示的意思,–abbrev-commit选项可以将长的commitID进行缩写。
下面来演示该命令。
git log --graph --abbrev-commit
8、分支管理策略
通常合并分⽀时,如果可能,Git 会采⽤ Fast forward 模式。在这种 Fast forward 模式下,删除分⽀后,查看分⽀历史时,会丢掉分⽀信息,看不出来最新提交到底是 merge 进来的还是正常提交的。
例如下面的合并分支采用了Fast forward模式。我们先创建了一个dev2分支,然后切换到master分支上合并dev2分支,可以看到这一次合并采用了Fast forward模式,而且查看日志信息时可以看到master分支合并dev2分支的过程并没有当作一次commit提交进行记录。
当我们进行分支合并时,可以加上–no-ff选项来强制禁用Fast forward模式,当禁用了Fast forward模式后,分支合并时就会生成一个新的commit提交,这样就可以从日志信息中看到这一次提交为分支合并了。所以在合并分⽀时,加上 --no-ff 参数就可以⽤普通模式合并,合并后的历史有分⽀,能看出来曾经做过合并,⽽ fast forward 合并就看不出来曾经做过合并。而且因为禁⽤ Fast forward 模式后合并会创建⼀个新的 commit ,所以需要加上 -m 参数,把描述写进去。
9、Bug分支
在实际开发中,master分支应该是非常稳定的,也就是仅用来发布新版本,所以开发人员平常开发新功能时应该创建类似于dev的分支,然后在dev分支上进行新功能的开发,当功能开发完成后,在dev分支上经过一系列测试后,最后将稳定的代码合并到master分支上。
但是有可能会出现这样的情况,开发人员创建了一个dev2分支用来开发新功能,在开发过程中突然发现master分支上面有bug需要解决,但是此时dev2分支的代码还未完成,不能进行提交。此时我们就可以使用下面的命令来将dev2分支工作区的代码进行储藏,该储藏可以在以后恢复回来。
在Git中,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。虽然也可以在dev2分支上修复bug,但是每个创建的分支都有自己的职责,dev2分支的职责就是开发新需求,如果在上面修改bug的话是很不符合规则的,所以应该重新回到master分支创建一个bug分支用来单独修改bug。
下面我们创建一个fix_bug分支用来修改master分支上的bug,在fix_bug分支上修改完bug后,切换到master分支上进行分支合并。这样就完成了master分支bug的修改。
然后再次切换到dev2分支,恢复存储区的内容。
//该命令可以查看存储区列表,可以查看存储信息
git stash list
//该命令恢复存储区内容
git stash pop
此时继续在dev2分支接着开发需求,开发完成后在dev2分支进行代码提交。但是此时虽然master分支的bug在master分支上被更改了,但是dev2上还没有被修改,这是因为dev2分支是在fix_bug分支之前创建的,所以dev2分支上的代码还是有bug的。此时版本库如下面的情况。
此时如果切换到master分支,然后将dev2分支合并到master分支上来的话,那么就会出现合并冲突,而需要手动解决冲突,在手动解决冲突的过程中,master分支可能又会出现bug。这个操作是不推荐的。
所以就不能像上面那样操作,而应该切换到dev2分支上,然后将master分支合并到dev2分支上,此时虽然也会出现冲突,也需要手动修改冲突,但是如果有问题了也只是在dev2分支上出现问题,而不会影响master分支,master分支此时还可以正常运行,不过就是没有dev2的新功能。
我们在dev2分支上将冲突问题都解决完,测试完之后,然后此时再切换到master分支,将dev2分支合并到master分支上,因为我们已经将冲突在dev2分支上解决好了,所以这次合并就不会出现冲突了。例如dev2分支合并的冲突其实就是master的bug修改和dev2的bug没有修改出现了冲突,我们手动将master的bug修改更新,此时就解决了冲突。而因为master分支上bug已经修改完成,所以合并dev2分支时不会出现合并冲突了。这个操作是推荐的。
最后将dev2分支和fix_bug分支进行删除,这样就完成了为master分支添加新需求和更改bug的操作。
10、强制删除分支
当dev分支进行了提交等操作后,那么git就认为这个分支是一个有用分支。如果没有将dev分支合并到master分支上,就使用git branch -d命令删除dev分支,git会删除这个dev分支失败,因为git认为dev分支还是有用的。此时如果真的想要删除dev分支,就可以使用git branch -D命令,该命令是强制删除分支。
五、远程操作
1、远程仓库
我们⽬前所说的所有内容(⼯作区,暂存区,版本库等等),都是在本地,即在我们自己的计算机上。而我们的 Git 其实是分布式版本控制系统!可以简单理解为,我们每个⼈的电脑上都是⼀个完整的版本库,这样我们就能在自己的版本库中进行代码的编写,如果其他同事想要看到我们写的代码,我们将自己的修改推送给他就可以了。分布式版本控制系统的安全性要⾼很多,因为每个⼈电脑⾥都有完整的版本库,某⼀个⼈的电脑坏掉了不要紧,随便从其他⼈那⾥复制⼀个就可以了。在实际使⽤分布式版本控制系统的时候,其实很少在两⼈之间的电脑上推送版本库的修改,因为这样如果其中一个人的电脑没有打开,那么就会推送不成功。因此,分布式版本控制系统通常也有⼀台充当“中央服务器”的电脑,这台服务器24小时开机,每个开发人员在编写完代码后都将修改的内容推送到服务器上,开发人员也可以从服务器上拉取新的代码来获得其他人修改的代码。
我们在学习Git时没有必要自己搭建一台运行Git的服务器,我们可以使用GitHub或者Gitee这两个网站,这两个网站就类似于上面我们说的中央服务器,我们可以将自己的代码远程托管到上面。
下面我们来学习Gitee的使用。
我们在创建远程仓库时,在设置模板选项中选择了Readme文件选项,该选项会为我们在仓库中自动生成一个Readme文件,这个文件的内容介绍了这个仓库代码的一些信息。
Issue模板文件选项会为我们在.git目录下自动生成ISSUE_TEMPLATE.zh-CN.md文件,该文件的内容如下。这个模板文件就类似一个问题反映报告。如果我们将远程仓库设为开源,那么其他人就可以访问我们的远程仓库,看到仓库里面的代码。如果其他访客发现了我们远程仓库中代码的bug,就可以通过Issues来向我们反映并且发送一个问题报告。
然后我们就可以通过问题报告来查看远程仓库中代码的bug,然后进行修改。
Pull Request模板文件选项会为我们在.git目录下自动生成
PULL_REQUEST_TEMPLATE.zh-CN.md文件,该文件的内容如下。实际开发过程中是不会让开发者随便在master分支中合并其他分支的,如果开发者想要将自己的dev开发新需求的分支中的代码合并到master中,就需要向仓库管理者发送一个PR,即合并申请单,这就是Pull Requests,合并申请单中写明了本次合并的详细信息,然后仓库管理者看到申请单后决定是否要将开发者的分支合并到主分支中。综上可知开发者是不会自己操作将自己的分支合并到master分支的,这是管理者完成的步骤。所以Pull Request模板文件就是一个合并申请单的模板。
2、克隆远程仓库-HTTPS协议
克隆/下载远端仓库到本地,需要使⽤ git clone 命令,后⾯跟上我们的远端仓库的链接,远端仓库的链接可以从仓库中找到:选择“克隆/下载”获取远程仓库链接。
SSH 协议和 HTTPS 协议是 Git 最常使⽤的两种数据传输协议。SSH 协议使⽤了公钥加密和公钥登陆机制,体现了其实⽤性和安全性,使⽤此协议需要将我们的公钥放上服务器,由 Git 服务器进⾏管理。使⽤ HTTPS ⽅式时,没有要求,可以直接克隆下来。
下面我们演示使用HTTPS协议克隆远端仓库到本地。
点击克隆/下载后,选择HTTPS选项,然后复制远端仓库的链接。
然后进入到本地想要放置该远端仓库的目录下,执行下面的git clone命令,这样就可以将远端仓库克隆到本地了。需要注意的是在克隆远程仓库时,当前所在的目录不能已经有一个.git本地仓库了。例如我们不能在gitcode目录下执行克隆远程仓库的操作,因为gitcode目录下已经有一个本地仓库了。
origin是远程仓库的默认名称,通过git remote命令可以查看远程仓库的名称。git remote -v命令可以查看详细信息。fetch说明本地仓库有远程仓库的拉取权限,push说明本地仓库有向远程仓库推送的权限。
3、克隆远程仓库-SSH协议
当我们使用SSH协议的链接克隆远程仓库时,如果没有配置公钥,会克隆失败。
此时我们就需要创建SSH Key。在我们的电脑中执行下面的命令一直按回车来创建公钥,然后可以看到当前用户目录下的.ssh目录下面多了这些文件。
然后复制id_rsa.pub文件中的所有字符。
然后回到我们的Gitee账号中,点击设置中的SSH公钥选项,将我们刚刚复制的内容粘贴到对应的公钥框中,为这个公钥添加一个标题,点击确定就可以添加一个公钥了。如果有多个⼈协作开发,GitHub/Gitee 允许添加多个公钥,只要把每个⼈的电脑上的Key 都添加到 GitHub/Gitee,就可以在每台电脑上往 GitHub/Gitee 上提交推送了。
然后我们再次拿到远程仓库SSH的链接,在本地目录下进行克隆。此时就可以克隆成功了。
4、向远端仓库推送
当我们将远程仓库克隆到本地后,就是下面的情况。我们在本地的工作区更新代码后,通过add和commit将修改提交到本地仓库。
我们可以通过push操作将本地仓库中的修改推送到远端仓库中。push操作为分支与分支之间的交互,例如将本地仓库的master分支推送到远程仓库的master分支。
我们在本地工作区创建file.txt文件,然后将修改提交到本地仓库。
然后使用git push命令将本地仓库的修改推送到远程仓库。下面的操作为将本地仓库的master分支的修改推送到远程仓库的master分支上。此时就可以在远程仓库上看到刚刚的推送。这里因为我们使用的是 SSH 协议,所有不用每次推送都输入密码,方便了我们的推送操作。如果我们使用的是HTTPS协议的话,那么每次推送时都需要输入Gitee账号的密码。
git push <远程主机名> <本地分支名>:<远程分支名>
//如果本地分支名与远程分支名相同,则可以省略冒号
git push <远程主机名> <本地分支名>
我们上面直接就推送成功了,这是因为当我们从远程仓库克隆后,实际上 Git 会⾃动把本地的 master 分⽀和远程的 master 分⽀对应起来,所以才可以直接推送成功。如果想要本地仓库的其他分支和远程仓库的其他分支建立联系,那么需要手动建立联系。
5、拉取远程仓库
当远端仓库中的代码被其他同事推送更新了时,此时我们的本地仓库想要获得新的代码,就需要使用git pull命令来拉取远端仓库的更新。如果远程仓库的代码比本地仓库的代码要新时,本地仓库在向远程仓库push推送代码时会失败,因为远程仓库的代码比本地仓库的代码新,多了新的内容。此时就需要本地仓库先拉取远端仓库更新的内容。pull操作其实做了两个操作,第一个操作是将远程仓库的代码拉取到本地,第二个操作是将远程仓库分支中的代码与本地仓库分支中的代码进行合并。
6、忽略特殊文件
在日常开发中,我们有些⽂件不想或者不应该提交到远端,比如保存了数据库密码的配置文件,那怎么让 Git 知道呢?在 Git ⼯作区的根⽬录下创建⼀个特殊的 .gitignore ⽂件,然后把要忽略的⽂件名填进去,Git 就会⾃动忽略这些⽂件了。在创建远端仓库时,也可以点击.gitignore模板,这样Gitee在创建仓库时就会为我们自动生成.gitignore文件了。因为前面我们创建远程仓库时,没有点击添加.gitignore选项。所以此时需要在当前目录下自己创建一个.gitignore文件,然后按照格式添加内容即可。
然后此时在工作区创建一个.so结尾或者.ini结尾的文件。此时使用git status发现并没有报出来需要提交的提示。即Git就会忽略.so或.ini结尾的文件了。
如果我们想要将另一个c.so文件添加到本地仓库中,但是.gitignore文件中规定了.so结尾的文件会被忽略。此时我们可以使用git add -f来强制添加。但是不推荐这种使用方法,因为破坏了.gitignore的使用规则。
git add -f [filename]
我们可以使用下面的方法来将c.so文件添加到本地仓库中,即在.gitignore文件中添加下面的语句,该语句的意思为不忽略c.so文件。
如果以后.gitignore文件中有很多忽略文件的语句后,我们想要查看某一个文件为什么会被忽略掉,可以通过下面的语句。例如下面的结果中显示了是因为.gitignore文件中的哪个语句才导致了d.so文件被忽略,如果我们不想该文件被忽略,就可以选择修改.gitignore文件中对应的语句。
git check-ignore -v [filename]
当有些时候,我们编写了规则排除了部分文件时,例如下面的语句。
但是我们发现.*这个规则把.gitignore也排除了。虽然可以使用git add -f强制添加进去,但是还是不要破坏.gitignore的规则。这个时候,我们可以添加另外一条规则。这样.gitignore文件就不会被忽略了。把指定文件排除在.gitignore规则外的写法就是!+文件名。
7、给命令配置别名
在我们使用Git期间,有的命令很长,此时我们就可以通过给命令配置别名,来简化命令。
例如下面的命令就是给git status命令起别名为git st。此时这两个命令就是等价的,我们还可以通过git config -l命令来看到我们配置的命令别名。
git config --global alias.st status
当然我们也可以通过git config --unset alias.st命令来删除这个配置。
如果要起别名的命令较长的话,那么就使用单引号圈起来。例如下面的这条命令。
六、标签管理
标签tag,可以简单理解为是对某次commit的一个标识,相当于起了一个别名。例如,在项目发布某个版本的时候,针对最后一次commit起一个v1.0这样的标签来标识里程碑的意义。相比于难以记住的commitID,tag标签可以很好的解决这个问题,因为tag标签一般都会起一个让人容易记住,并且有意义的名字。当我们需要回退到某个重要版本时,直接使用标签就能很快定位到。
1、创建标签
在Git中打标签非常简单,首先切换到需要打标签的分支上。然后使用下面的命令就可以创建一个新标签。
//创建一个新标签
git tag [name]
//查看所有标签
git tag
当我们创建标签后,我们看到.git目录下的refs目录下多了一个tags目录,里面有一个文件v1.0,我们查看v1.0文件的内容看到就是最近一次提交的commitID。因为默认标签是打在最新提交的 commit 上的。
那如何在指定的commit上打标签呢?方法是找到历史提交的commitID,然后打上就可以了,示例如下。需要注意的是标签的列表不是根据打标签的时间来排序的,只是根据字母名称排序。
另外我们还可以创建带有说明的标签,用-a指定标签名,-m指定说明文件,格式如下。
git tag -a [name] -m "xxxx" [commitID]
git show命令后面跟上标签名称,就可以看到这个标签的详细信息了。可以看到详细信息中包含了标签所标识的commit提交的详细内容。
git show [tagname]
2、删除标签
如果标签打错了,我们可以使用下面的命令来删除标签。
git tag -d [tagname]
3、推送标签
我们创建的标签都只存储在本地,不会自动推送到远程。如果要推送某个标签到远程,可以使用下面的命令。
git push origin [tagname]
如果我们想要一次将本地的所有标签都推送到远端仓库,可以使用下面的命令
git push origin --tags
如果标签已经推送到远程仓库,想要删除远程仓库中的标签的话就比较麻烦了,我们需要先在本地删除标签,然后再从远程仓库删除标签,远程删除标签的命令如下。
git push origin :refs/tags/v1.0
七、多人协作
1、多人协作场景一
下面我们就来使用上面所学的Git的知识来实现下面的一个多人协作开发的场景。
(1)准备工作
目前,我们的仓库中只有⼀个 master 主分⽀,但在实际的项目开发中,在任何情况下其实都是不允许直接在 master 分⽀上修改代码的,这是为了保证主分⽀的稳定。所以在开发新功能时,常常会新建其他分⽀,供开发时进行迭代使用。那么接下来,就让我们在 Gitee 上新建 dev 远程分支供我们使用。
然后我们现在需要模拟开发者1号和开发者2号,开发者1号我们就在虚拟机中的linux系统下进行开发,开发者2号在window系统下进行开发。
此时开发者2号因为是新克隆的远程仓库,所以开发者2号可以看到远程仓库中的dev分支。
但是开发者1号没有拉取最新的远程仓库内容,所以看不到远程仓库的dev分支。此时开发者1号需要执行git pull命令拉取远端仓库的最新内容。然后开发者1号就可以看到远程仓库的dev分支了。这里拉取时我们直接使用了git pull命令,并没有使用git pull origin master:master命令,这是因为在克隆仓库时,Gitee已经自动将远端仓库的master分支与本地仓库的master分支建立连接了。上面我们知道了push操作和pull操作都是分支与分支之间的操作,在进行这两个操作时都需要指定远程仓库和本地仓库分支之间的连接,如果使用git push origin master:master命令的话,master:master就表明了是本地仓库的master分支与远端的master分支建立连接。而如果要直接使用git push命令的话,那么使用这个命令的前提是本地仓库的master分支与远端的master分支已经建立连接了。git pull操作也是同理。因为在克隆仓库时已经自动将master分支建立连接了,所以开发者1号可以直接使用git pull命令。
(2)协作开发
开发者1在通过git pull拉取到远程仓库的更新后,执行下面的代码在本地新建一个dev分支。并且在建立dev分支时将本地dev分支与远程dev分支建立连接,因为Gitee只会自动建立master分支的连接,所以其他分支的连接需要开发者手动进行连接。
git branch -a //查看所有分支
git branch -r //查看远端仓库分支
git branch -vv //查看分支建立的连接
然后开发者1号在dev分支上修改file.txt文件,添加aaa。修改成功后将修改push推送到远端仓库。因为前面已经将本地仓库的dev分支和远程仓库的dev分支建立连接了,所以此时可以直接使用git push命令。如果前面没有建立连接的话,那么使用git push推送时,后面还需要指明本地仓库的哪个分支推送到远程仓库的哪个分支。
接下来开发者2号先在本地创建dev分支,我们让开发者2在创建本地dev分支时没有将本地dev分支与远端dev分支建立连接。通过git branch -vv命令看到dev分支后面没有远端分支。
此时直接执行git pull命令或git push命令是不会成功的,因为本地dev分支和远程dev分支没有建立连接。
此时可以执行下面的命令来将分支之间建立连接。然后我们就看到本地dev分支和远端dev分支已经建立连接了,此时开发者2号执行git pull或git push命令就会执行成功了。
git branch --set-upstream-to=origin/dev dev
然后开发者2号在dev分支上修改file.txt文件,添加bbb。然后开发者2号将修改的内容推送到远程仓库,此时推送出现了错误。这是因为远程仓库的内容刚刚被开发者1号更新了,所以此时开发者2号的代码不是最新的,需要先使用git pull操作拉取远端仓库的最新代码。
当使用git pull命令拉取远程仓库的代码后,我们看到出现了合并冲突,这是因为开发者1号在file.txt文件中添加了aaa,开发者2号在file.txt文件中添加了bbb,所以Git不知道需要保存哪一个开发者修改的内容,所以就出现了冲突,此时需要手动解决冲突。
当开发者2号手动解决冲突后,将修改push到远程仓库。此时推送成功。
(3)将内容合并到master分支
此时远端仓库的dev分支上是最新的代码,但是master分支上不是最新的代码,所以我们接下来要做的就是将远端仓库的dev分支合并到master分支上。这个操作可以通过两个方法实现。
方法一:开发者在本地仓库中将dev分支合并到master分支上,然后再将本地master分支推送到远程master分支。这个方法不推荐。
方法二:开发者提交一个合并申请单,即我们前面介绍的Pull Requests,管理员看到后,在远程仓库中操作,直接在远程仓库中将远程仓库的dev分支合并到master分支上。推荐。
方法二中开发者创建一个PR申请单进行提交,然后开发者的任务就完成了。远程仓库的管理员看到了申请单后进行操作即可。这样更具有保障性。
方法二推荐大家使用,但是现在我们在探索Git的使用,所以下面我们演示方法一。
方法一中,当我们在本地的master分支上直接合并dev分支时,可能会出现合并冲突,然后在master分支上手动解决冲突时,可能会形成新的bug,这就不能保障master分支的稳定性了。所以我们前面建议了在合并时可以先切换到dev分支上,然后将master分支合并到dev分支上,然后在dev分支上手动解决冲突。但是在将master分支合并到dev分支上时,我们需要保证master分支上的代码为最新的远程仓库中的master分支中的代码,所以在master分支合并到dev分支之前,我们还需要先切换到master分支上,执行git pull命令,将master分支上的代码更新为最新代码后,再切换到dev分支上进行合并,然后在dev分支上手动解决合并冲突后,再切换到master分支上,让dev分支合并到master分支上。然后再将本地master分支的更新推送到远程仓库的master分支。
在上面的协作开发中,开发者2号的dev分支代码和远端仓库的dev分支代码是最新的,所以我们需要将开发者1号的dev分支代码也先更新为最新代码。
然后先切换到master分支上,从远程仓库拉取master分支到本地仓库的master分支,这样做可以保证本地仓库的master分支是最新的代码。
然后再切换到dev分支上,将master分支合并到dev分支上,如果有合并冲突就手动解决合并冲突。
然后再切换到master分支上,将dev分支合并到master分支上,这一次合并就不会产生合并冲突了。
然后再将本地仓库master分支上的修改push到远程仓库的master分支上。此时远端仓库的master分支上就为最新代码了。
然后dev分支就没有用了,可以在远端仓库中删除dev分支。
(4)总结
总结⼀下,在同⼀分⽀下进⾏多⼈协作的⼯作模式通常是这样:
- 首先,可以试图⽤用git push origin branch-name 推送⾃⼰的修改;
- 如果推送失败,则因为远程分⽀比你的本地更新,需要先⽤ git pull 试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
- 功能开发完毕,将分支merge 进 master,最后删除分支。
2、多人协作场景二
一般情况下,如果有多需求需要多人同时进行开发,是不会在一个分支上进行多人开发的,而是一个需求或一个功能点就要创建一个feature分支。例如现在同时有两个需求需要开发者1号和开发者2号进行开发,那么这两个开发者就可以各自创建一个分支来完成自己的工作。
下面我们来演示多人协作如下的场景。
(1)协作开发1
创建新分支时可以在远程创建, 也可以在本地创建再推送到远程,但是本地的master分支不能保证是最新的代码,而远程的master分支一定能保证是最新的代码,那么创建的分支也一定是最新的代码,所以推荐远程创建分支。在场景一中我们已经了解了可以从Gitee上直接创建远程分支,下面我们就演示在本地创建分支,然后通过推送的方式发送到远端。
开发者1号此时的master分支的代码可能不是最新的代码,所以在创建新分支之前,我们应该先切换到master分支,然后执行git pull命令,将master分支下的代码更新为最新的代码。
此时直接push是肯定不行的,因为此时远程仓库还没有新的feature-1分支。此时直接将feature-1分支先推送到远端,可以看到远端仓库就多了一个feature-1分支。此时开发者1号就完成了自己的操作,即创建一个新分支,然后添加一个文件后,将修改和新建的分支推送到远端仓库。
下面开发者2号也在本地创建一个新分支。在创建新分支前,我们也先切换到master分支下,更新master分支的代码为最新代码。
开发者2号创建feature-2新分支,然后在该分支下创建function2文件进行开发,最后将修改进行提交。此时开发者2号直接push也是不行的,还是需要先将feature-2分支先push到远端仓库。
此时远程仓库和本地仓库的情况如下。这样我们就完成了开发者1号创建function1文件,开发者2号创建funciton2文件。
(2)协作开发2
我们在开发时,有时会遇到如下的情况。例如开发者2号因为某些原因无法继续开发了,此时需要开发者1号在自己的机器上切换到feature-2分支帮开发者2号继续开发,此时开发者1号需要做的操作如下。
开发者1号需要先从远端拉取开发者2号的feature-2分支。此时直接在feature-1分支上执行git pull是不行的,因为上面我们没有将本地的feature-1分支和远端的feature-1分支进行连接。但是我们看到git pull将远端仓库的feature-2分支拉取下来了,这是因为git pull这个命令会有两个操作。
此时开发者1号的本地还没有创建feature-2分支,下面在本地创建feature-2分支,并且在创建时将本地feature-2分支和远端的feature-2分支进行连接。然后开发者1号帮开发者2号开发完成代码后,将修改进行提交并且推送到远端仓库。
此时如果开发者2号想要继续开发自己的代码的话,需要先切换到feature-2分支下,先执行pull操作拉取远程仓库feature-2分支的更新代码,然后再进行后续开发。直接git pull的话是不行的,因为此时开发者2号本地的feature-2分支和远端仓库的feature-2分支还未建立连接,所以需要先将分支建立连接,然后再进行git pull操作。如果没有提前建立连接的话,那么执行git pull命令时后面就要跟上是本地的哪个分支推送到远端仓库的哪个分支,这样也可以推送成功。
接下来开发者2号继续开发自己的代码,将自己的代码开发完成后,先将修改提交,然后再push推送到远端仓库。
(3)将feature-1分支和feature-2分支的内容合并到master分支。
下面我们演示让开发者2号使用提交Pull Requests的方式来进行合并,在开发者2号提交Pull Requests之前,开发者2号应该现在本地仓库中切换到feature-2分支,然后让master分支合并到feature-2分支上,这次合并如果出现冲突了,在feature-2分支上手动解决冲突后,再将feature-2分支push到远端仓库,然后开发者2号再提交Pull Requests。这样做可以避免远端仓库的master分支合并feature-2分支时出现合并冲突。
开发者2号提交一个Pull Requests合并申请单,请求将远端仓库的feature-2分支合并到远端仓库的master分支。
下面的操作就是审查员的事情了,审查员可以看到开发者提交的Pull Requests合并申请单,点击文件可以看到文件的差异。
下面演示开发者1号将远端仓库的feature-1分支合并到远端仓库的master分支。但是此时也需要注意,在将远端仓库的feature-1分支合并到远端仓库的master分支时,是有可能会出现冲突的,以后协同开发的文件和代码增多了,这样的情况是会发生的。所以此时我们就需要再次执行上面的一套步骤,开发者1号先在本地仓库中将master分支合并到feature-1分支上,手动解决冲突后,然后再将feature-1分支push到远端仓库,然后远端仓库的master分支再合并feature-1分支。
下面我们进行具体的操作。
开发者1号先切换到master分支上,执行git pull命令,将本地的master分支更新为最新代码。然后再切换到feature-1分支,将master分支合并到feature-1分支上。
然后开发者1号将本地仓库的feature-1分支的修改push到远程仓库。此时我们执行git status命令可以看到并没有提示我们需要push操作,这是因为开发者1号的本地分支feature-1和远端仓库的feature-1分支并没有进行连接,通过git branch -vv命令我们也可以看到。此时我们可以先将本地仓库的feature-1分支和远端仓库的feature-1分支进行连接,然后使用git push命令进行推送,也可以使用git push命令后面跟上本地分支和远端分支名称来进行推送。
此时开发者1号就可以提交一个Pull Requests合并申请单,然后审查员看到后就可以进行远程仓库的feature-1分支合并到master分支上的操作了。至此我们的演示目标就完成了。
(4)解决git branch -a打印已被删除的远端分支的方法
上面我们在演示时,当我们在远端仓库直接删除掉分支后,开发者1号和开发者2号在本地执行git branch -a命令时,还是会看到远端仓库已经删除掉的分支,此时执行下面的操作即可将远程仓库已经删除掉的分支不显示。如果开发者想要删除掉本地分支的话,那么执行git branch -d [branchName]进行删除即可。
八、企业级开发模型
我们知道,一个软件从零开始到最终交付,大概包括以下几个阶段:规划、编码、构建、测试、发布、部署和维护。
最初,程序比较简单,工作量不大,程序员一个人可以完成所有阶段的⼯作。但随着软件产业的日益发展壮大,软件的规模也在逐渐变得庞⼤。软件的复杂度不断攀升,一个人已经很难开发和管理了,就开始出现了精细化分⼯。
但在传统的 IT 组织下,开发团队(Dev)和运维团队(Ops)之间诉求不同。
- 开发团队(尤其是敏捷团队)追求变化
- 运维团队追求稳定
双发往往存在利益的冲突。比如,精益和敏捷的团队把持续交付作为目标,而运维团队则为了线上的稳定而强调变更控制。部门墙由此建立起来,这当然不利于 IT 价值的最⼤化。为了弥合开发和运维之间的鸿沟,需要在文化、工具和实践方面的系列变革⸺DevOps正式登上舞台。
DevOps(Development和Operations的组合词)是⼀种重视“软件开发⼈员(Dev)”和“IT运维技术⼈员(Ops)”之间沟通合作的⽂化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。在DevOps的软件开发过程包含计划、编码、构建、测试、预发布、发布、运维、监控,由此可见DevOps的强⼤。
在我们开发人员看来,一个软件的迭代其实就是对代码的迭代,那么就需要对代码进行管理,而如何管理我们的代码呢?这就需要使用到Git(分布式版本控住系统)了,所以Git对于我们开发人员来说是很重要的。
1、系统开发环境
对于开发人员来说,在系统开发过程中最常用的几个环境是需要了解的。
- 开发环境:开发环境是程序员们专门用于日常开发的服务器,为了开发调试方便,一般打开全部错误报告和测试工具,是最基础的环境。
- 测试环境:一个程序在测试环境工作不正常,那么肯定不能把它发布到生产机上。该环境是开发环境到生产环境的过渡环境。
- 预发布环境:该环境是为避免因测试环境和线上环境的差异等带来的缺陷漏测而设立的一套环境。其配置等基本和生产环境一致,目的是能让我们发布到正式环境时更有把握!所以预发布环境是产品质量最后一道防线,因为下一步项目就要上线了。要注意预发布环境服务器不在线上集成服务器范围之内,为单独的一些机器。
- 生产环境:是指正式提供对外服务的线上环境,例如我们目前在移动端或PC端能访问到的APP都是生产环境。
这几个环境也可以说是系统开发的三个重要阶段:开发->测试->上线。一张图总结如下:
对于规模大的公司来说,可能不止这么几个环境,比如项目正式上线前还存在仿真/灰度环境,再比如还存在多套测试环境,以满足不同版本上线前测试的需要。
一个项目的开始从设计开始,而一个项目的成功则从测试开始。一套良好的测试体系可以将系统中绝大部分的致命Bug 解决在系统上线之前。测试系统的完善和成熟也是衡量一个软件企业整体水平的重要指标之一,测试往往被忽视,因为它对软件开发企业不产生直接的效益,但是它却是软件质量的最终保障,甚至是项目能否成功的重要因素!
2、Git分支设计规范
环境有了概念后,那么对于开发人员来说,一般会针对不同的环境来设计分支,例如:
注:以上表格中的分支和环境的搭配仅是常用的一种,可视情况而定不同的策略。
(1)master分支
- master为主分支,该分支为只读且唯一分支。用于部署到正式发布环境,一般由合并release分支得到。
- 主分支作为稳定的唯一代码库,任何情况下不允许直接在master分支上修改代码。
- 产品的功能全部实现后,最终在master分支对外发布,另外所有在master分支的推送应该打标签(tag)做记录,方便追溯。
- master分支不可删除。
(2)release分支
- release为预发布分支,基于本次上线所有的feature分支合并到develop分支之后,基于develop分支创建。可以部署到测试或预发布集群。
- 命名以release/开头,建议的命名规则:release/version_publishtime(即release/版本_预计发布事件)。
- release分支主要用于提交给测试人员进行功能测试。发布提测阶段,会以release分支代码为基准进行提测。
- 如果在release分支测试出问题,需要回归验证develop分支看是否存在此问题。
- release分支属于临时分支,产品上线后可选删除。
(3)develop分支
- develop为开发分支,基于master分支创建的只读且唯一分支,始终保持最新完成以及bug修复后的代码。可部署到开发环境对应集群。
- 可根据需求大小程度确定是由feature分支合并,还是直接在上面开发(非常不建议)。
(4)feature分支
- feature分支通常为新功能或新特性开发分支,以develop分支为基础创建feature分支。
- 命名以feature/开头,建议的命名规则:feature/user_createtime_feature(feature/用户名称_创建时间_feature名称)。
- 新特征或新功能开发完成后,开发人员需合到develop分支。
- 一旦该需求发布上线,便将其删除。
(5)hotfix分支
- hotfix分支为线上bug修复分支或叫补丁分支,主要用于对线上的版本进行bug修复。当线上出现紧急问题需要马上修复时,需要基于master分支创建hotfix分支。
- 命名以hotfix/开头,建议的命名规则:hotfix/user_createtime_hotfix(hotfix/用户名称_创建事件_hotfix名称)。
- 当问题修复完成后,需要合并到master分支和develop分支并推送远程。一旦修复上线,便将其删除。
(6)Git Flow模型
下面这个图是企业级常用的一种Git分支设计规范:Git Flow模型,其中的各个分支的作用和规范我们在上面都介绍了。但要说的是,该模型并不是适用于所有的团队、所有的环境和所有的文化。所以项目中具体要采用的Git分支设计规范还要取决于需求、分支优缺点等一系列因素。
3、企业级项目管理实战
(1)准备工作
我们先创建应该Git企业版账号。企业名称和企业空间地址可以随意填写,只要格式填写正确就可以。
然后我们在企业账号中创建一个项目。
点击创建后我们就可以在项目选项中看到新建的项目了。
下面我们新建一个仓库。
一个企业里面可以有多个项目,一个项目中可能有多个仓库,每个仓库开发不同的功能。企业、项目与仓库的关系类似下面的情况。
我们可以通过下面的操作来邀请成员加入到企业中。下面我们演示通过链接邀请成员加入到企业中。此时其他成员就可以通过点击链接或者扫描二维码的方式加入到企业中。
当新成员加入到企业中后,我们再邀请该成员加入到项目中。
下面我们再邀请该成员加入到仓库。
(2)开发场景-基于Git Flow模型的实践
我们首先需要新增一个feature分支。如果我们在前面创建仓库时选择分支模型是开发/发布/缺陷分离模型(支持 master/develop/feature/release/hotfix 类型分支)时,那么在创建仓库后就会自动为我们创建这五个分支,此时如果我们自己再创建feature分支就会提示该分支已经被创建。但是因为我们前面选择的是生产/开发模型 (支持 master/develop 类型分支)模型,所以只会自动为我们创建master分支和develop分支。
下面我们创建一个feature分支。
然后我们可以在本地克隆这个远程仓库。
然后在本地创建开发分支并且连接到远端仓库的开发分支,然后在本地的开发分支下增加代码,最后将修改的内容提交后push推送到远端仓库。
然后我们在远端仓库中切换到feature/drh_20240902_add分支下,就可以看到刚刚本地仓库的推送了。
下面我们创建一个请求进行代码评审单,其内容是请求审查员将feature分支合并到develop分支上,模拟开发人员提交审评单的过程。
当新建请求进行代码评审单并提交后,仓库审查员就可以看到开发者提交的这一次申请了。下面模拟审查员在远端仓库进行分支合并,即将feature分支合并到develop分支上。审查完成后就进行合并操作。
然后就可以在develop分支上看到新加入的代码了。develop分支可以在流水线中进行部署。
下面创建一个release分支进行代码测试,当在release分支下测试代码没有问题后,此时就需要将release分支合并到master分支上。将release分支合并到master分支上的步骤和前面分支合并的步骤相似。
因为合并后release分支的任务就完成了,所以可以选择合并后删除提交分支选项。
然后审查员完成合并后,此时切换到master分支,就可以看到添加的新内容了。
此时feature分支已经完成了任务,也可以进行删除了。至此这一次基于Git Flow模型的提交与合并操作就完成了。
(3)Bug修复
修复测试环境 Bug:
- 在 develop 测试出现了Bug,建议⼤家直接在 feature 分支上进行修复。修复后的提测上线流程与新需求加入的流程一致。
修改预发布环境 Bug:
- 在 release 测试出现了 Bug,首先要回归下 develop 分支是否同样存在这个问题。如果存在,修复流程与修复测试环境 Bug流程一致。如果不存在,这种可能性比较少,大部分是数据兼容问题,环境配置问题等。
修改正式环境 Bug:
- 在 master 测试出现了Bug,首先要回归下 release 和 develop 分支是否同样存在这个问题。如果存在,修复流程与修复测试环境 Bug流程一致。如果不存在,这种可能性也比较少,大部分是数据兼容问题,环境配置问题等。
紧急修复正式环境 Bug:
- 需求在测试环节未测试出 Bug,上线运行一段时候后出现了 Bug,需要紧急修复的。有的企业面对紧急修复时,支持不进行测试环境的验证,但还是建议验证下预发布环境。可基于 master 创建 hotfix/xxx 分支,修复完毕后发布到 master 验证,验证完毕后,将master 代码合并到 develop 分支,同时删掉 hotfix/xxx 分支。