版本控制(3) Git和github
Git
Git介绍
Git是一个开源的分布式版本控制系统,分布式相比集中式的最大区别是Git没有“中央服务器”,每位开发者都可以通过克隆远程库,在本地机器上存储一个完整的Git仓库,还可以把代码的修改提交到本地库
目的:借助github
托管项目代码。
基本概念:
-
仓库(
repository
):仓库用来存放项目代码,每个项目对应一个仓库,多个开源项目则有多个仓库。 -
收藏(
star
):仓库主页star
按钮,意思为收藏项目的人数,在github
上如果你有一个项目获得100个star
都算很不容易了。 -
复制克隆项目(
Fork
):该fork
的项目是独立存在的
Git结构
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
Git命令行操作
3.4.1 初始化
git init
命令可以把当前目录变成git可以管理的仓库。
mkdir /home/gitrepo
cd gitrepo
git init
初始化完成后,就多一个隐藏了.git
文件,ls -la
能看到具体细节
ll .git
3.4.2 设置签名
类似于设置了用户信息,需要设置用户名和邮箱
注意:这里设置的签名和远程库的账号、密码没有任何关系
设置本地仓库用户名和邮箱(必须cd到仓库的目录里)
cd /home/gitrepo
git config user.name caichang
git config user.email caichangfast@qq.com
信息放在.git/config
配置文件中
cat config
设置全局用户名和邮箱
git config --global user.name caichang_global
git config --global user.email caichangfast@sohu.com
存放路径放在/root/.gitconfig
,因为是用root用户登录
cat /root/.gitconfig
3.4.3 查看状态
注意路径要在新建的版本库位置上,不能在具体的隐藏文件下
cd /home/gitrepo
git status
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就可以
4、提交时采用带 -m "具体message"就可以,这样就不用进vi编辑器了
git commit -m "second commit" httpd.conf
github
注册github账号
我的GitHub账号:
- 用户名:jimmy688
- 邮箱:15219743651@163.com
新建github远程库
新建一个远程库,右上角,new repository
,填入对应信息即可。新创建的库有提示,这个提示很重要,可以用命令去实现提交等,但实际往往用一些工具去处理。
最好不好勾选Initialize this repository with a README
,原因是:https://blog.csdn.net/ever69/article/details/97565768。
当点击创建仓库时,它会帮我们做一次初始提交。于是我们的仓库就有了README.md和.gitignore文件,然后我们把本地项目关联到这个仓库,并把项目推送到仓库时,回报文章开题的那个错误,这是因为,我们在关联本地与远程时,两端都是有内容的,但是这两份内容并没有联系,当我们推送到远程或者从远程拉取内容时,都会有没有被跟踪的内容,于是你看git报的详细错误中总是会让你先拉取再推送,但是拉取总是失败。
想要避免这种问题,就要保持创建的仓库是一个空仓库,什么都没有。
…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
会得到自己的远程库地址: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
把本地库推送给远程库
需要输入你远程库的用户名和密码
https协议:
[root@localhost gitrepo]# git push -u origin master
ssh协议:
[root@localhost gitrepo]# git push -u origin_ssh master
5、刷新远程库,就可以看到已推送过去了
克隆远程库
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
时光穿梭
首先来查看一下git
的状态:
cd /home/gitrepo
git status
Git
告诉我们当前没有需要提交的修改,而且,工作目录是干净(working tree clean
)的。
下面,修改一个文件的内容来试验一下:
git status
告诉我们,将要被提交的修改包括 hello3.txt
git diff
顾名思义就是查看difference
,显示的格式正是Unix通用的diff格式,可以从上面的命令输出看到,我们在第三行添加了一个change2
单词。
知道了对hello3.txt
作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,第一步是git add
:
git add hello3.txt
同样没有任何输出。在执行第二步git commit
之前,我们再运行git status
看看当前仓库的状态:
提交之后,status
又回归到了clean
的状态。
版本回退
下面继续以hello3.txt文件为例,讲解版本回退:
查看git
的log
日志:
git log
git log
命令显示从最近到最远的提交日志。看到的一大串类似ef042f...
的是commit id
(版本号),和SVN不一样,Git的commit id
不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id
和我的肯定不一样,以你自己的为准。为什么commit id
需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git
就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:
注意:这个版本不是针对某个文件的,而是针对整个仓库来讲的。
现在,我们尝试回退到上一个版本:
首先必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,也就是最新的提交ef042f...
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
现在,我们要把当前版本add change2
(版本名称是我们之前提交时候的备注 -m) 回退到上一个版本c2
,就可以使用git reset
命令:
git reset --hard HEAD^
cat hello3.txt
可以看到,hello3.txt的内容已经回到上一次的状态了,即回退成功了。
不过且慢,我们用git log
再看看现在版本库的状态:
git log
最新的那个版本add change2
已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?
办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个add change2
的commit id
是ef042f3636c6fe146935f4eac66f7363a8411e81
,于是就可以指定回到未来的某个版本:
git reset --hard ef042f
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
可以看到,hello3.txt
内容又变回到add change2
版本的状态了。
工作区与暂存区
工作区(Working Directory)
就是linux的一个目录,比如下面的gitrepo
就是一个工作区:
版本库(Repository)
工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
分支和HEAD
的概念我们以后再讲。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
- 第一步是用
git add
把文件添加进去,实际上就是把文件修改添加到暂存区; - 第二步是用
git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
例如,先对helloGit.txt
做个修改,加上一行内容.
然后,在工作区新增一个readme.txt
文本文件(内容随便写)。
最后用git status
查看一下状态:
Git非常清楚地告诉我们,helloGit.txt
被修改了,而readme.txt
还从来没有被添加过,所以它的状态是Untracked
。
现在,使用两次命令git add
,把helloGit.txt
和readme.txt
都添加后,用git status
再查看一下:
现在,暂存区的状态就变成这样了:
所以,git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:
现在版本库变成了这样,暂存区就没有任何内容了:
管理修改
掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
为什么说Git管理的是修改,而不是文件呢? 我们还是做实验。
- 第一步,对
readme.txt
做一个修改,比如加一行内容: - 第二步,add添加到暂存区。
- 第三步,再修改
readme.txt
- 最后,提交,查看状态
咦,怎么第二次的修改没有被提交?
别激动,我们回顾一下操作过程:
第一次修改 -> git add
-> 第二次修改 -> git commit
你看,我们前面讲了,Git管理的是修改,当你用git add
命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit
只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
提交后,用git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别:
git deff HEAD -- readme.txt
或者
git diff readme.txt
可见,第二次修改确实没有被提交。
那怎么提交第二次修改呢?你可以继续git add
再git commit
,也可以别着急提交第一次修改,先git add
第二次修改,再git commit
,就相当于把两次修改合并后一块提交了:
第一次修改 -> git add
-> 第二次修改 -> git add
-> git commit
撤销修改
注意,这里的撤销修改跟版本回退是有区别的。
如果你在readme.txt
末尾中添加了一行 My stupid boss still prefers SVN.
:
在你准备提交前,你猛然发现了stupid boss
可能会让你丢掉这个月的奖金!
既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用git status
查看一下:
你可以发现,Git会告诉你,git checkout -- file
可以丢弃工作区的修改:
git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
- 一种是
readme.txt
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态; - 一种是
readme.txt
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,checkout就是让这个文件回到最近一次git commit
或git add
时的状态。
现在,看看readme.txt
的文件内容:
文件内容果然复原了。
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
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
分支管理
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
创建和合并分支
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始的时候,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
分支:
真是太神奇了,你看得出来有些提交是通过分支完成的吗?
下面开始实战。
首先,我们创建dev
分支,然后切换到dev
分支:
git checkout -b dev
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
git branch dev 创建
git checkout dev 切换
然后,用git branch
命令查看当前分支:
git branch
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
然后,我们就可以在dev
分支上正常提交,比如对readme.txt
做个修改,末尾加上一行new content:
然后提交:
现在,dev
分支的工作完成,我们就可以切换回master
分支:
切换回master
分支后,再查看一个readme.txt
文件,刚才添加的内容不见了!
因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
现在,我们把dev
分支的工作成果合并到master
分支上:
git merge dev
cat readme.txt
git merge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt
的内容,就可以看到,和dev
分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
git branch -d dev
删除后,查看branch
,就只剩下master
分支了:
git branch
因为创建、合并和删除分支非常快,所以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:
在feature1
分支上提交:
git add readme.txt
git commit -m 'q And s' readme.txt
切换到master
分支:
git checkout master
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无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
git merge feature1
果然冲突了!Git告诉我们,readme.txt
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
我们可以直接查看readme.txt
的内容:
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:
再提交:
注意:提交的时候不需要指定readme.txt
文件,否则会报错:during a merge
。
现在,master
分支和feature1
分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
git log --graph --pretty=oneline --abbrev-commit
最后,删除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后就像这样:
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;
你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
小结
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).
终于删除成功!