版本控制(3) Git和github

Git

Git介绍

Git是一个开源的分布式版本控制系统,分布式相比集中式的最大区别是Git没有“中央服务器”,每位开发者都可以通过克隆远程库,在本地机器上存储一个完整的Git仓库,还可以把代码的修改提交到本地库

目的:借助github托管项目代码。

基本概念:

  • 仓库(repository):仓库用来存放项目代码,每个项目对应一个仓库,多个开源项目则有多个仓库。

  • 收藏(star):仓库主页star按钮,意思为收藏项目的人数,在github上如果你有一个项目获得100个star都算很不容易了。

  • 复制克隆项目(Fork):该fork的项目是独立存在的

    image-20200820143503940

Git结构

https://images2018.cnblogs.com/blog/628084/201807/628084-20180719174820181-1928988154.png

Git中独有的术语,理解这些术语非常重要:

  • 远程仓库(Remote):也叫作资源库,是远程机器上的代码库。

  • 本地库(Repository):是用户在本地创建的目录,拥有远程库的一个快照,由工作区和版本库构成。

  • 工作区(Workspace):本地库的根目录中除.git目录以外的内容,存储内容的实际文件;

  • 版本库(.git目录):是本地库的根目录中的一个隐藏目录.git,用于记录版本信息,Git进行版本控制所需要的文件,则都放在.git文件夹中;

  • 暂存区(Index):也叫做缓存区,暂存信息存放在.git目录"下的index文件(.git/index)中,用于临时保存内容的修改;

  • HEAD文件:是.git目录下的HEAD文件(.git/HEAD),指向最后依次提交的结果。

  • 分支(Branch):本地库中默认创建一个主(master)分支,分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。

Git安装

1、进入/local,新建目录git

cd /usr/local/
mkdir git  

2、进入git目录,下载git的源码包,最新

cd /usr/local/git; wget https://github.com/git/git/archive/v2.17.0.tar.gz  

3、安装编译源码所需依赖,出现提示输入y即可

yum  install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc  perl-ExtUtils-MakeMaker  

4、解压

tar zxvf v2.17.0.tar.gz  

5、进入目录,执行编译

make prefix=/usr/local/git all  

6、make install

make prefix=/usr/local/git install  

7、配置环境变量

vi  /etc/profile  

PATH=$PATH:/usr/local/git/bin
export PATH

8、编辑环境变量,让配置即时生效

source  /etc/profile  

9、测试

git --version  

image-20200820113650270

Git命令行操作

3.4.1 初始化

git init命令可以把当前目录变成git可以管理的仓库。

mkdir /home/gitrepo
cd gitrepo
git init	

初始化完成后,就多一个隐藏了.git文件,ls -la能看到具体细节

ll .git

image-20200820105031504

3.4.2 设置签名

类似于设置了用户信息,需要设置用户名和邮箱

注意:这里设置的签名和远程库的账号、密码没有任何关系

设置本地仓库用户名和邮箱(必须cd到仓库的目录里

cd /home/gitrepo
git config user.name caichang
git config user.email caichangfast@qq.com

信息放在.git/config配置文件中

cat config

image-20200820114226692

设置全局用户名和邮箱

git config --global user.name caichang_global
git config --global user.email caichangfast@sohu.com 

存放路径放在/root/.gitconfig,因为是用root用户登录

cat /root/.gitconfig

image-20200820114204698

3.4.3 查看状态

注意路径要在新建的版本库位置上,不能在具体的隐藏文件下

cd /home/gitrepo
git status

image-20200820114351369

3.4.4 添加并提交(是2个动作)

1、在仓库下任意新建文件,然后添加,提交。

cd /home/gitrepo
vi helloGit.txt

git add helloGit.txt
git commit helloGit.txt


注意:1、要撤销add,可以采用
git rm --cached helloGit.txt

2、提交后,需要输入一些内容,然后保存退出,跟svn一样,这些内容是作为提交的一个注释或者备注的。

this is my first commit.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
#
# Initial commit	
#
# Changes to be committed:
#       new file:   httpd.conf
# 

3、修改文件后想再次提交,直接commit就可以

image-20200820114955832

4、提交时采用带 -m "具体message"就可以,这样就不用进vi编辑器了

 git commit -m "second commit"  httpd.conf

github

注册github账号

网站:https://github.com/

我的GitHub账号:

  • 用户名:jimmy688
  • 邮箱:15219743651@163.com

新建github远程库

新建一个远程库,右上角,new repository,填入对应信息即可。新创建的库有提示,这个提示很重要,可以用命令去实现提交等,但实际往往用一些工具去处理。

image-20200820121527676

image-20200820125549892

最好不好勾选Initialize this repository with a README,原因是:https://blog.csdn.net/ever69/article/details/97565768。

当点击创建仓库时,它会帮我们做一次初始提交。于是我们的仓库就有了README.md和.gitignore文件,然后我们把本地项目关联到这个仓库,并把项目推送到仓库时,回报文章开题的那个错误,这是因为,我们在关联本地与远程时,两端都是有内容的,但是这两份内容并没有联系,当我们推送到远程或者从远程拉取内容时,都会有没有被跟踪的内容,于是你看git报的详细错误中总是会让你先拉取再推送,但是拉取总是失败。

想要避免这种问题,就要保持创建的仓库是一个空仓库,什么都没有。

image-20200820125846743

…or create a new repository on the command line
echo "# nba" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/jimmy688/nba.git
git push -u origin master

…or push an existing repository from the command line
git remote add origin https://github.com/jimmy688/nba.git
git push -u origin master

image-20200820152729065

image-20200820153538731

会得到自己的远程库地址:https://github.com/jimmy688/nba.git (使用https协议连接)

ssh协议连接的远程库地址:git@github.com:jimmy688/nba.git

https协议连接和ssh协议连接的地址是不同的,用ssh协议会更快一点)

老师给的地址是:https://github.com/caichang5201314/mtesting.git

给远程库取别名

不然特别麻烦,每次都要输入一长串远程地址,mtesting为远程库的别名

git remote add origin https://github.com/jimmy688/nba.git
或者
git remote add origin_ssh git@github.com:jimmy688/nba.git

远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

如果要删除远程库的设置,操作如下:
git remote rm origin

4、查看,fetch表示取回地址,push表示推送

[root@localhost gitrepo]# git remote -v

image-20200820154828144

把本地库推送给远程库

需要输入你远程库的用户名和密码

https协议:
[root@localhost gitrepo]# git push -u origin master	

ssh协议:
[root@localhost gitrepo]# git push -u origin_ssh master	

image-20200820153814793

5、刷新远程库,就可以看到已推送过去了

image-20200820153837328

克隆远程库

1、进入任何你想要clone的目录,如:/tmp/test

2、输入命名clone下来

cd /tmp/test

用https协议来克隆:
[root@Jimmy test]# git clone https://github.com/jimmy688/nba.git

用ssh协议来克隆:
[root@Jimmy test]# git clone git@github.com:jimmy688/nba.git

image-20200820155107625

时光穿梭

首先来查看一下git的状态:

cd /home/gitrepo
git status

image-20200820160950214

Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(working tree clean)的。

下面,修改一个文件的内容来试验一下:

image-20200820163257094

git status告诉我们,将要被提交的修改包括 hello3.txt

git diff顾名思义就是查看difference,显示的格式正是Unix通用的diff格式,可以从上面的命令输出看到,我们在第三行添加了一个change2单词。

知道了对hello3.txt作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,第一步是git add

git add	hello3.txt

同样没有任何输出。在执行第二步git commit之前,我们再运行git status看看当前仓库的状态:

image-20200820163804578

提交之后,status又回归到了clean的状态。

版本回退

下面继续以hello3.txt文件为例,讲解版本回退:

查看gitlog日志:

git log

image-20200820165422701

git log命令显示从最近到最远的提交日志。看到的一大串类似ef042f...的是commit id(版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。

每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:

git-log-timeline

注意:这个版本不是针对某个文件的,而是针对整个仓库来讲的。

现在,我们尝试回退到上一个版本

首先必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交ef042f...(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

现在,我们要把当前版本add change2(版本名称是我们之前提交时候的备注 -m) 回退到上一个版本c2,就可以使用git reset命令:

git reset --hard HEAD^
cat hello3.txt

image-20200820165558883

可以看到,hello3.txt的内容已经回到上一次的状态了,即回退成功了。

不过且慢,我们用git log再看看现在版本库的状态:

git log

image-20200820165851975

最新的那个版本add change2已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?

办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个add change2commit idef042f3636c6fe146935f4eac66f7363a8411e81,于是就可以指定回到未来的某个版本:

git reset --hard ef042f

image-20200820170041923

版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。

可以看到,hello3.txt内容又变回到add change2版本的状态了。

工作区与暂存区

工作区(Working Directory)

就是linux的一个目录,比如下面的gitrepo就是一个工作区:

image-20200820172155614

working-dir

版本库(Repository)

工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库。

Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD

git-repo

分支和HEAD的概念我们以后再讲。

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

  • 第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
  • 第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。

因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

例如,先对helloGit.txt做个修改,加上一行内容.

然后,在工作区新增一个readme.txt文本文件(内容随便写)。

最后用git status查看一下状态:

image-20200820172633084

Git非常清楚地告诉我们,helloGit.txt被修改了,而readme.txt还从来没有被添加过,所以它的状态是Untracked

现在,使用两次命令git add,把helloGit.txtreadme.txt都添加后,用git status再查看一下:

image-20200820172920864

现在,暂存区的状态就变成这样了:

git-stage

所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。

image-20200820173120935

一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:

现在版本库变成了这样,暂存区就没有任何内容了:

git-stage-after-commit

管理修改

掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。

你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。

为什么说Git管理的是修改,而不是文件呢? 我们还是做实验。

  • 第一步,对readme.txt做一个修改,比如加一行内容:
  • 第二步,add添加到暂存区。
  • 第三步,再修改readme.txt
  • 最后,提交,查看状态

image-20200820174055304

咦,怎么第二次的修改没有被提交?

别激动,我们回顾一下操作过程:

第一次修改 -> git add -> 第二次修改 -> git commit

你看,我们前面讲了,Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

提交后,用git diff HEAD -- readme.txt命令可以查看工作区和版本库里面最新版本的区别:

git deff HEAD -- readme.txt
或者 
git diff readme.txt

image-20200820174503350

可见,第二次修改确实没有被提交。

那怎么提交第二次修改呢?你可以继续git addgit commit,也可以别着急提交第一次修改,先git add第二次修改,再git commit,就相当于把两次修改合并后一块提交了:

第一次修改 -> git add -> 第二次修改 -> git add -> git commit

撤销修改

注意,这里的撤销修改跟版本回退是有区别的。

如果你在readme.txt末尾中添加了一行 My stupid boss still prefers SVN.

image-20200820180951195

在你准备提交前,你猛然发现了stupid boss可能会让你丢掉这个月的奖金!

既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用git status查看一下:

image-20200820181108616

你可以发现,Git会告诉你,git checkout -- file可以丢弃工作区的修改:

git checkout -- readme.txt

命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况:

  • 一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
  • 一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

总之,checkout就是让这个文件回到最近一次git commitgit add时的状态。

现在,看看readme.txt的文件内容:

image-20200820181158304

文件内容果然复原了。

git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。

再举一个撤销例子:

现在假定是凌晨3点,你不但写了一些胡话,还git add到暂存区了:

$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.

$ git add readme.txt

庆幸的是,在commit之前,你发现了这个问题。用git status查看一下,修改只是添加到了暂存区,还没有提交:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   readme.txt

Git同样告诉我们,用命令git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区:

$ git reset HEAD readme.txt
Unstaged changes after reset:
M	readme.txt

git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。

再用git status查看一下,现在暂存区是干净的,工作区有修改:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   readme.txt

还记得如何丢弃工作区的修改吗?

$ git checkout -- readme.txt

$ git status
On branch master
nothing to commit, working tree clean

整个世界终于清静了!

假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把stupid boss提交推送到远程版本库,你就真的惨了……

删除文件

在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交:

$ git add test.txt

$ git commit -m "add test.txt"
[master b84166e] add test.txt
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了:

$ rm test.txt

这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit

$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 test.txt

现在,文件就从版本库中被删除了。

小提示:先手动删除文件,然后使用git rm <file>和git add<file>效果是一样的。

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

git checkout -- file

image-20200820185101338

git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!

分支管理

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。

但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

创建和合并分支

版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向mastermaster才是指向提交的,所以,HEAD指向的就是当前分支

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

git-br-initial

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

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

git-br-create

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

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

git-br-dev-fd

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

git-br-ff-merge

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

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

git-br-rm

真是太神奇了,你看得出来有些提交是通过分支完成的吗?

下面开始实战。

首先,我们创建dev分支,然后切换到dev分支:

git checkout -b dev

image-20200820190709738

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

git branch dev   创建
git checkout dev   切换

然后,用git branch命令查看当前分支:

git branch

image-20200820190916531

git branch命令会列出所有分支,当前分支前面会标一个*号。

然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,末尾加上一行new content:

image-20200820191026206

然后提交:

image-20200820191343647

image-20200820191352895

现在,dev分支的工作完成,我们就可以切换回master分支:

image-20200820191412229

切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!

image-20200820191446861

因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:

git-br-on-master

现在,我们dev分支的工作成果合并到master分支上

git merge dev
cat readme.txt

image-20200820191520818

git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并完成后,就可以放心地删除dev分支了:

git branch -d dev

删除后,查看branch,就只剩下master分支了:

git branch

image-20200820191635839

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

switch

我们注意到切换分支使用git checkout <branch>,而前面讲过的撤销修改则是git checkout -- <file>同一个命令,有两种作用,确实有点令人迷惑。

实际上,切换分支这个动作,用switch更科学。因此,最新版本的Git提供了新的git switch命令来切换分支:

创建并切换到新的dev分支,可以使用:

git switch -c dev   不是最新版本的git有可能没有这个命令

直接切换到已有的master分支,可以使用:

git switch master

使用新的git switch命令,比git checkout要更容易理解。

小结:

  • 查看分支:git branch
  • 创建分支:git branch <name>
  • 切换分支:git checkout <name>或者git switch <name>
  • 创建+切换分支:git checkout -b <name>或者git switch -c <name>
  • 合并某分支到当前分支:git merge <name>
  • 删除分支:git branch -d <name>

解决合并冲突

创建分支feature1并切换到该分支:

git branch feature1
git checkout feature1

修改readme.txt最后一行,改为 quick And simple:

image-20200820194308665

feature1分支上提交:

git add readme.txt
git commit -m 'q And s' readme.txt 

切换到master分支:

git checkout master

image-20200820194849784

Git还会自动提示我们当前master分支比远程的master分支要超前1个提交。

master分支上把readme.txt文件的最后一行改为 quick & simple,然后提交:

vi readme.txt
git add readme.txt
git commit -m 'q & s' readme.txt

现在,master分支和feature1分支各自都分别有新的提交,变成了这样:

git-br-feature1

这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:

git merge feature1

image-20200820194656678

果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:

image-20200820194927202

我们可以直接查看readme.txt的内容:

image-20200820195154820

Git用<<<<<<<=======>>>>>>>标记出不同分支的内容,我们修改如下后保存:

image-20200820195249310

再提交:

image-20200820195600154

注意:提交的时候不需要指定readme.txt文件,否则会报错:during a merge

现在,master分支和feature1分支变成了下图所示:

git-br-conflict-merged

用带参数的git log也可以看到分支的合并情况:

git log --graph --pretty=oneline --abbrev-commit

image-20200820195737476

最后,删除feature1分支:

git branch -d feature1

工作完成。

分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

下面我们实战一下--no-ff方式的git merge

首先,仍然创建并切换dev分支:

$ git switch -c dev
Switched to a new branch 'dev'

修改readme.txt文件,并提交一个新的commit:

$ git add readme.txt 
$ git commit -m "add merge"
[dev f52c633] add merge
 1 file changed, 1 insertion(+)

现在,我们切换回master

$ git switch master
Switched to branch 'master'

准备合并dev分支,请注意--no-ff参数,表示禁用Fast forward

$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。

合并后,我们用git log看看分支历史:

$ git log --graph --pretty=oneline --abbrev-commit
*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed
...

可以看到,不使用Fast forward模式,merge后就像这样:

git-no-ff-mode

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

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

git-br-policy

小结

Git分支十分强大,在团队开发中应该充分应用。

合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

Feature分支

软件开发中,总有无穷无尽的新的功能要不断添加进来。

添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支

现在,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。

于是准备开发:

$ git switch -c feature-vulcan
Switched to a new branch 'feature-vulcan'

5分钟后,开发完毕:

$ git add vulcan.py

$ git status
On branch feature-vulcan
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   vulcan.py

$ git commit -m "add feature vulcan"
[feature-vulcan 287773e] add feature vulcan
 1 file changed, 2 insertions(+)
 create mode 100644 vulcan.py

切回dev,准备合并:

$ git switch dev

一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。

但是!

就在此时,接到上级命令,因经费不足,新功能必须取消!

虽然白干了,但是这个包含机密资料的分支还是必须就地销毁:

$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.

销毁失败。Git友情提醒,feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数。。

现在我们强行删除:

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).

终于删除成功!

posted @ 2020-08-25 06:31  Whatever_It_Takes  阅读(144)  评论(0编辑  收藏  举报