一. 基本概念
- Git的优势
Git
是一个分布式
代码管理工具,在讨论分布式之前避免不了提及一下什么是中央式
代码管理仓库
中央式:所有的代码保存在中央服务器,所以提交必须依赖网络,并且每次提交都会带入到中央仓库,如果是协同开发可能频繁触发代码合并,进而增加提交的成本和代价。最典型的就是svn
分布式:可以在本地提交,不需要依赖网络,并且会将每次提交自动备份到本地。每个开发者都可以把远程仓库clone一份到本地,并会把提交历史一并拿过来。代表就是Git
那Git
相比于svn
有什么优势呢?打个比方:"巴拉巴拉写了一大堆代码,突然发现写的有问题,我想回到一个小时之前",对于这种情况Git
的优势就很明显了,因为commit的成本比较小并且本地会保存所有的提交记录,随时随刻可以进行回退。在这并不是说svn
的不能完成这种操作,只是Git
的回退会显得更加的优雅。Git
相比于中央式
工具还有很多优点,就不一一列举了,感兴趣的可自行了解。
2. 文件状态
在Git中文件大概分为三种状态:已修改(modified)、已暂存(staged)、已提交(committed)
- 修改:Git可以感知到工作目录中哪些文件被修改了,然后把修改的文件加入到modified区域
- 暂存:通过add命令将工作目录中修改的文件提交到暂存区,等候被commit
- 提交:将暂存区文件commit至Git目录中永久保存
3. commit节点
节点其实就是commit提交
在Git中每次提交都会生成一个节点
,而每个节点都会有一个哈希值作为唯一标示,多次提交会形成一个线性
节点链(不考虑merge的情况),如图
![91c387c089d86e2c7ab5a1d6559db496.png](https://i-blog.csdnimg.cn/blog_migrate/21ecd0428261fdaf0a05fa1bfff3ae67.png)
C2
节点包含C1
提交内容,同样C3
节点包含C1、C2
提交内容
4. HEAD
HEAD
是Git中非常重要的一个概念,你可以称它为指针
或者引用
,它可以指向任意一个节点
,并且指向的节点始终为当前工作目录,换句话说就是当前工作目录(也就是你所看到的代码)就是HEAD
指向的节点。
还以图1-1举例,如果HEAD
指向C2
那工作目录对应的就是C2
节点。
同时HEAD
也可以指向一个分支
,间接指向分支
所指向的节点
如果HEAD 直接指向某个commit此时处于分离头指针的状态。如果我们在这个状态下做了更改然后我们切换分支然而我们忘记将这个没有属于任何分支的commit和分支挂钩的话git可能就会清除我们之前的commit
5. 远程仓库
虽然Git会把代码以及历史保存在本地,但最终还是要提交到服务器上的远程仓库。通过clone
命令可以把远程仓库的代码下载到本地,同时也会将提交历史
、分支
、HEAD
等状态一并同步到本地,但这些状态并不会实时更新,需要手动从远程仓库去拉取,至于何时拉、怎么拉后面章节会讲到。
通过远程仓库为中介,你可以和你的同事进行协同开发,开发完新功能后可以申请提交至远程仓库,同时也可以从远程仓库拉取你同事的代码。注意点:
因为你和你的同事都会以远程仓库的代码为基准,所以要时刻保证远程仓库的代码质量,切记不要将未经检验测试的代码提交至远程仓库
6. 分支
分支
也是Git中相当重要的一个概念,当一个分支
指向一个节点
时,当前节点
的内容即是该分支
的内容,它的概念和HEAD
非常接近同样也可以视为指针
或引用
,不同的是分支
可以存在多个,而HEAD
只有一个。通常会根据功能
或版本
建立不同的分支
那分支有什么用呢?
举个例子:你们的 App 经历了千辛万苦终于发布了v1.0
版本,由于需求紧急v1.0
上线之后便马不停蹄的开始v1.1
,正当你开发的兴起时,QA同学说用户反馈了一些bug,需要修复然后重新发版,修复v1.0
肯定要基于v1.0
的代码,可是你已经开发了一部分v1.1
了,此时怎么搞?
面对上面的问题通过引入分支
概念便可优雅的解决,如图2-1
![e9e3d133a38fd48fd7643efcbead0145.png](https://i-blog.csdnimg.cn/blog_migrate/0a78faf5e94d2769241f3ac216c1d976.png)
先看左边示意图,假设C2
节点既是v1.0
版本代码,上线后在C2
的基础上新建一个分支ft-1.0
再看右边示意图,在v1.0
上线后可在master
分支开发v1.1
内容,收到QA同学反馈后提交v1.1
代码生成节点C3
,随后切换到ft-1.0
分支做bug修复,修复完成后提交代码生成节点C4
,然后再切换到master
分支并合并ft-1.0
分支,到此我们就解决了上面提出的问题
除此之外利用分支还可以做很多事情,比如现在有一个需求不确定要不要上线,但是得先做,此时可以单独创建一个分支开发该功能,等到啥时候需要上线直接合并到主分支即可。分支适用的场景很多就不一一列举了。注意点:
当在某个节点创建一个分支后,并不会把该节点对应的代码复制一份出来,只是将新分支指向该节点,因此可以很大程度减少空间上的开销。一定要记着不管是HEAD
还是分支
它们都只是引用而已,量级非常轻
三. 命令详解
- 提交相关
前面我们提到过,想要对代码进行提交必须得先加入到暂存区,Git中是通过命令 add
实现
添加某个文件到暂存区:
git add 文件路径
添加所有文件到暂存区:
git add .
同时Git也提供了撤销工作区
和暂存区
命令
撤销工作区改动:
git checkout -- 文件名
清空暂存区:
git reset HEAD 文件名
提交:
将改动文件加入到暂存区后就可以进行提交了,提交后会生成一个新的提交节点,具体命令如下:
git commit -m "该节点的描述信息"
2. 分支相关
创建分支
创建一个分支后该分支会与HEAD
指向同一节点,说通俗点就是HEAD
指向哪创建的新分支就指向哪,命令如下:
git branch 分支名
切换分支
当切换分支后,默认情况下HEAD
会指向当前分支,即HEAD
间接指向当前分支指向的节点
git checkout 分支名
同时也可以创建一个分支后立即切换,命令如下:
git checkout -b 分支名
删除分支
为了保证仓库分支的简洁,当某个分支完成了它的使命后应该被删除。比如前面所说的单独开一个分支完成某个功能,当这个功能被合并到主分支后应该将这个分支及时删除。
删除命令如下:
git branch -d 分支名
3. 合并相关
关于合并的命令也是最重要的。我们常用的合并命令大概有两个merge
、rebase
mergemerge
是最常用的合并命令,它可以将某个分支或者某个节点的代码合并至当前分支。具体命令如下:
git merge 分支名/节点哈希值
如果需要合并的分支完全领先于当前分支,如图3-1所示
![f5fcee5e4f45aeb1cebad262c4a503b9.png](https://i-blog.csdnimg.cn/blog_migrate/2dfa4597ff51eb264d41b62bd11f9662.png)
由于分支ft-1
完全领先分支ft-2
即ft-1
完全包含ft-2
,所以ft-2
执行了“git merge ft-1”
后会触发fast forward(快速合并)
,此时两个分支指向同一节点,这是最理想的状态。但是实际开发中我们往往碰到是是下面这种情况:如图3-2(左)
![e9f936164d0fa5cd2ff2df5d6600840f.png](https://i-blog.csdnimg.cn/blog_migrate/75e83b104a0e84d211ec303e9b348671.png)
这种情况就不能直接合了,当ft-2
执行了“git merge ft-1”
后Git会将节点C3
、C4
合并随后生成一个新节点C5
,最后将ft-2
指向C5
如图3-2(右)注意点:
如果C3
、C4
同时修改了同一个文件中的同一句代码,这个时候合并会出错,因为Git不知道该以哪个节点为标准,所以这个时候需要我们自己手动合并代码
rebaserebase
也是一种合并指令,命令行如下:
git rebase 分支名/节点哈希值
与merge
不同的是rebase
合并看起来不会产生新的节点(实际上是会产生的,只是做了一次复制),而是将需要合并的节点直接累加 如图3-3
![9af3f0e429c775b30e0ea514ab36e4b8.png](https://i-blog.csdnimg.cn/blog_migrate/826873893b09389164a6f55268815c82.png)
当左边示意图的ft-1.0
执行了git rebase master
后会将C4
节点复制一份到C3
后面,也就是C4'
,C4
与C4'
相对应,但是哈希值却不一样。rebase
相比于merge
提交历史更加线性、干净,使并行的开发流程看起来像串行,更符合我们的直觉。既然rebase
这么好用是不是可以抛弃merge
了?其实也不是了,下面我罗列一些merge
和rebase
的优缺点:
merge优缺点:
- 优点:每个节点都是严格按照时间排列。当合并发生冲突时,只需要解决两个分支所指向的节点的冲突即可
- 缺点:合并两个分支时大概率会生成新的节点并
分叉
,久而久之提交历史会变成一团乱麻
rebase优缺点:
- 优点:会使提交历史看起来更加线性、干净
- 缺点:虽然提交看起来像是线性的,但并不是真正的按时间排序,比如图3-3中,不管
C4
早于或者晚于C3
提交它最终都会放在C3
后面。并且当合并发生冲突时,理论上来讲有几个节点rebase
到目标分支就可能处理几次冲突
对于网络上一些只用rebase
的观点,作者表示不太认同,如果不同分支的合并使用rebase
可能需要重复解决冲突,这样就得不偿失了。但如果是本地推到远程并对应的是同一条分支可以优先考虑rebase
。所以我的观点是 根据不同场景合理搭配使用merge
和rebase
,如果觉得都行那优先使用rebase
4. 回退相关分离HEAD:
在默认情况下HEAD是指向分支的,但也可以将HEAD从分支上取下来直接指向某个节点,此过程就是分离HEAD
,具体命令如下:
git checkout 节点哈希值 //也可以直接脱离分支指向当前节点 git checkout --detach
由于哈希值是一串很长很长的乱码,在实际操作中使用哈希值分离HEAD很麻烦,所以Git也提供了HEAD基于某一特殊位置(分支/HEAD)直接指向前一个
或前N个
节点的命令,也即相对引用,如下:
//HEAD分离并指向前一个节点
git checkout 分支名/HEAD^
//HEAD分离并指向前N个节点
git checkout 分支名~N
将HEAD分离
出来指向节点有什么用呢?举个例子:如果开发过程发现之前的提交有问题,此时可以将HEAD指向对应的节点,修改完毕后再提交,此时你肯定不希望再生成一个新的节点,而你只需在提交时加上--amend
即可,具体命令如下:
git commit --amend 回退:
回退场景在平时开发中还是比较常见的,比如你巴拉巴拉写了一大堆代码然后提交,后面发现写的有问题,于是你想将代码回到前一个提交,这种场景可以通过reset
解决,具体命令如下:
//回退N个提交 git reset HEAD~N reset
和相对引用
很像,区别是reset
会使分支
和HEAD
一并回退。
5. 远程相关
当我们接触一个新项目时,第一件事情肯定是要把它的代码拿下来,在Git中可以通过clone
从远程仓库复制一份代码到本地,具体命令如下:
git clone 仓库地址
前面的章节我也有提到过,clone
不仅仅是复制代码,它还会把远程仓库的引用(分支/HEAD)
一并取下保存在本地,如图3-5所示:
![207326102108ccb61881e1026a56d07a.png](https://i-blog.csdnimg.cn/blog_migrate/525100b6777042871fdda5b0d1311e7a.jpeg)
其中origin/master
和origin/ft-1
为远程仓库的分支,而远程的这些引用状态是不会实时更新到本地的,比如远程仓库origin/master
分支增加了一次提交,此时本地是感知不到的,所以本地的origin/master
分支依旧指向C4
节点。我们可以通过fetch
命令来手动更新远程仓库状态小提示:
并不是存在服务器上的才能称作是远程仓库,你也可以clone
本地仓库作为远程,当然实际开发中我们不可能把本地仓库当作公有仓库,说这个只是单纯的帮助你更清晰的理解分布式
fetch:
说的通俗一点,fetch
命令就是一次下载
操作,它会将远程新增加的节点以及引用(分支/HEAD)
的状态下载到本地,具体命令如下:
git fetch 远程仓库地址/分支名
pull:pull
命令可以从远程仓库的某个引用拉取代码,具体命令如下:
git pull 远程分支名
其实pull
的本质就是fetch
+merge
,首先更新远程仓库所有状态
到本地,随后再进行合并。合并完成后本地分支会指向最新节点
另外pull
命令也可以通过rebase
进行合并,具体命令如下:
git pull --rebase 远程分支名
pushpush
命令可以将本地提交推送至远程,具体命令如下:
git push 远程分支名
如果直接push
可能会失败,因为可能存在冲突,所以在push
之前往往会先pull
一下,如果存在冲突本地解决。push
成功后本地的远程分支引用会更新,与本地分支指向同一节点
综上所述
- 不管是
HEAD
还是分支
,它们都只是引用
而已,引用
+节点
是 Git 构成分布式的关键 merge
相比于rebase
有更明确的时间历史,而rebase
会使提交更加线性应当优先使用- 通过移动
HEAD
可以查看每个提交对应的代码 clone
或fetch
都会将远程仓库的所有提交
、引用
保存在本地一份pull
的本质其实就是fetch
+merge
,也可以加入--rebase
通过rebase方式合并
四. 上传代码
git ssh配置及使用:
确保已安装好git
在管理Git项目上,很多时候都是直接使用https url克隆到本地,当然也有有些人使用SSH url克隆到本地。
这两种方式的主要区别在于:使用https url克隆对初学者来说会比较方便,复制https url然后到git Bash里面直接用clone命令克隆到本地就好了,但是每次fetch和push代码都需要输入账号和密码,这也是https方式的麻烦之处。
而使用SSH url克隆却需要在克隆之前先配置和添加好SSH key,因此,如果你想要使用SSH url克隆的话,你必须是这个项目的拥有者。否则你是无法添加SSH key的,另外ssh默认是每次fetch和push代码都不需要输入账号和密码,如果你想要每次都输入账号密码才能进行fetch和push也可以另外进行设置。前面的几篇介绍Git的博客里面采用的都是https的方式作为案例,
今天主要是讲述如何配置使用ssh方式来提交和克隆代码。
大概可以分为一下几个步骤
- 设置Git的user name和email:(如果是第一次的话)
- 检查是否已经有SSH Key。
- 生成密钥
- 添加密钥到ssh-agent
- 登陆GitLab, 添加 ssh
- 测试:
1. 设置Git的user name和email:(如果是第一次的话)
# 这里的"zhuwenqi" 可以替换成自己的用户名
git config --global user.name "zhuwenqi"
# 这里的邮箱 wenqi.zhu@aihuishou.com 替换成自己的邮箱
git config --global user.email "wenqi.zhu@aihuishou.com"
2. 检查是否已经有SSH Key。
cd ~/.ssh
接着输入ls,
ls
列出该文件下的文件,看是否存在 id_isa 和 id_isa.pub 文件(也可以是别的文件名,只要 yourName 和 yourName.pub 承兑存在),如果存在的话,证明已经存在 ssh key了,可以直接跳过生成密钥这一步骤,
下图是存在的情况下:
3. 生成密钥
# 这里的邮箱 wenqi.zhu@aihuishou.com 替换成自己的邮箱
ssh-keygen -t rsa -C "wenqi.zhu@aihuishou.com"
连续3个回车。如果不需要密码的话。
最后得到了两个文件:id_rsa和id_rsa.pub。
默认的存储路径是:
/Users/ahs/.ssh
4. 登陆GitLab, 添加 ssh 。
把id_rsa.pub文件里的内容复制到这里
![5ce7dc8a9b85493cdf39362db394e547.png](https://i-blog.csdnimg.cn/blog_migrate/d5458322cf9b7a0da930c76763bb076f.png)
5. 测试:
ssh -T git@github.com
你将会看到:
Hi zhuwenqi! You’ve successfully authenticated, but GitHub does not provide shell access.
6. 扩展
如果我之前的仓库是用https提交的,那么我现在想用ssh 的方式提交,怎么办呢 ,别急,下面就来教你怎样操作了。
这里同样以我本机目录下的一个仓库为例子,
找到仓库下.git 文件夹下的config文件,打开,可以看到以下内容
![7e6178b993a8ad3c1ce803fa0e80ce92.png](https://i-blog.csdnimg.cn/blog_migrate/fb100ab9c10da2fe7c2cb673514631b9.jpeg)
将文件中的url = https://github.com/jemise111/react-native-swipe-list-view.git
更改为 url = git@github.com:jemise111/react-native-swipe-list-view.git
将代码上传到gitLab:
- Git global setup (全局设置)
git config --global user.name "zhuwenqi"
git config --global user.email "wenqi.zhu@aihuishou.com"
2. Create a new repository (创建一个新仓库)
git clone git@code.aihuishou.com:zhuwenqi/q.git
cd q
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master
3. Push an existing folder (存在的本地工程,推送到新的远程分支)
cd existing_folder
git init
git remote add origin git@code.aihuishou.com:zhuwenqi/q.git
git add .
git commit -m "Initial commit"
git push -u origin master
4. Push an existing Git repository (存在的工程,推送到新的远程仓库地址)
cd existing_repo
git remote rename origin old-origin
git remote add origin git@code.aihuishou.com:zhuwenqi/q.git
git push -u origin --all
git push -u origin --tags
五. reset
reset 的本质:移动 HEAD 以及它所指向的 branch
实质上,reset 这个指令虽然可以用来撤销 commit ,但它的实质行为并不是撤销,而是移动 HEAD ,并且「捎带」上 HEAD 所指向的 branch(如果有的话)。也就是说,reset 这个指令的行为其实和它的字面意思 "reset"(重置)十分相符:它是用来重置 HEAD 以及它所指向的 branch 的位置的。
而 reset --hard HEAD^ 之所以起到了撤销 commit 的效果,是因为它把 HEAD 和它所指向的 branch 一起移动到了当前 commit 的父 commit 上,从而起到了「撤销」的效果:
![147f4ed196fd7ce5edadc4a17243919c.gif](https://i-blog.csdnimg.cn/blog_migrate/ef1b922ff9ba845d0877a0e0c546a890.gif)
Git 的历史只能往回看,不能向未来看,所以把 HEAD 和 branch 往回移动,就能起到撤回 commit 的效果。
所以同理,reset --hard 不仅可以撤销提交,还可以用来把 HEAD 和 branch 移动到其他的任何地方。
git reset --hard branch2
![95e89fdfc3f42edfa2a775262f5a5a30.png](https://i-blog.csdnimg.cn/blog_migrate/f1fca38aa4142190698a3188f381d0b1.png)
reset三种模式区别和使用场景
区别:
- --hard:你的HEAD和当前branch切到上一条commit的同时,你工作目录里的新改动和已经add到stage区的新改动也会一起全都消失:
举例:
testGitFlow git:(feature/1.0.0) git status
On branch feature/1.0.0
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: testGitFlow/AppDelegate.swift
no changes added to commit (use "git add" and/or "git commit -a")
➜ testGitFlow git:(feature/1.0.0) ✗ git add .
➜ testGitFlow git:(feature/1.0.0) ✗ git status
On branch feature/1.0.0
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: testGitFlow/AppDelegate.swift
➜ testGitFlow git:(feature/1.0.0) ✗ git reset --hard
HEAD is now at 35a56d0 new
➜ testGitFlow git:(feature/1.0.0) git status
On branch feature/1.0.0
nothing to commit, working tree clean
2. reset --soft:保留工作目录,并把重置 HEAD 所带来的新的差异放进暂存区reset --soft 会在重置 HEAD 和 branch 时,保留工作目录和暂存区中的内容,并把重置 HEAD 所带来的新的差异放进暂存区。
什么是「重置 HEAD 所带来的新的差异」?就是这里:由于 HEAD 从 4 移动到了 3,而且在 reset 的过程中工作目录和暂存区的内容没有被清理掉,所以 4 中的改动在 reset 后就也成了工作目录新增的「工作目录和 HEAD 的差异」。这就是上面一段中所说的「重置 HEAD 所带来的差异」。
![147f4ed196fd7ce5edadc4a17243919c.gif](https://i-blog.csdnimg.cn/blog_migrate/ef1b922ff9ba845d0877a0e0c546a890.gif)
举例:
3. reset 不加参数(mixed):保留工作目录,并清空暂存区
六. git stash
(1) 应用场景
a.当正在dev分支上开发某个项目时,这时项目出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换hotfix分支进行bug修复,修复完成后,再次切换到dev分支,从堆栈中恢复刚刚保存的内容
b.由于疏忽,本应该在dev分支开发的内容,却在mster上进行了开发,需要重新切回到dev分支进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可
c.总的来说,git stash命令的作用就是将目前还不想提交但是已经修改的内容保存至堆栈中,后续可以在某个分支上恢复出堆栈中的内容.也就是说,stash中的内容不仅仅可以恢复到原来开发的分支,也可以恢复到其他任意的分支.git stash作用的范围包括工作区和暂存区中的内容,也就是说没有提交的内容都会保存至堆栈中
(2) 命令
能够将所有未提交的修改(工作区和暂存区)保存到堆栈,用于后续恢复当前目录
git stash git stash
将当前stash中的内容弹出,并应用到当前分支对应的目录上.该命令将堆栈中最近保存的内容删除(栈是先进后出)
git stash pop
可加注释
git stash save
查看当前stash中的内容
git stash list
七. rebase和merge
- 在开发过程中,使用git rebase还是git merge
搞清楚这个问题首先要搞清楚merge和rebase背后的含义。
先看merge,官方文档给的说明是:
git-merge - Join two or more development histories together
顾名思义,当你想要两个分支交汇的时候应该使用merge。
根据官方文档给的例子,是git merge topic,如图:
A---B---C topic
/
D---E---F---G---H master
然而在实践中,在H这个commit上的merge经常会出现merge conflict。为了避免解决冲突的时候引入一些不必要的问题,工程中一般都会规定no conflict merge。比如你在github上发pull request,如果有conflict就会禁止merge。
所以,在当前的topic分支,想要引入master分支的F、G commit上的内容以避免merge conflict,方便最终合并到master。
这种情况下用merge当然是一个选项。用merge代表了topic分支与master分支交汇,并解决了所有合并冲突。然而merge的缺点是引入了一次不必要的history join。如图:
A--B--C-X topic
/ /
D---E---F---G---H master
其实仔细想一下就会发现,在引入master分支的F、G commit这个问题上,我们并没有要求两个分支必须进行交汇(join),我们只是想避免最终的merge conflict而已。
rebase是另一个选项。rebase的含义是改变当前分支branch out的位置。这个时候进行rebase其实意味着,将topic分支branch out的位置从E改为G,如图:
A---B---C topic
/
D---E---F---G master
在这个过程中会解决引入F、G导致的冲突,同时没有多余的history join。但是rebase的缺点是,改变了当前分支branch out的节点。如果这个信息对你很重要的话,那么rebase应该不是你想要的。rebase过程中也会有多次解决同一个地方的冲突的问题,不过可以用squash之类的选项解决。个人并不认为这个是rebase的主要问题。
综上,其实选用merge还是rebase取决于你到底是以什么意图来避免merge conflict。实践上个人还是偏爱rebase。一个是因为branch out节点不能改变的情况实在太少。另外就是频繁从master merge导致的冗余的history join会提高所有人的认知成本。
2. rebase过程
![0da8240817d3166717c85c3d357077b0.png](https://i-blog.csdnimg.cn/blog_migrate/218773b6c4b3f36ab09a25deb7b8c888.png)
切到B1分支,执行git rebase master
![5ff12fb613a06c7e44864f29368c192c.png](https://i-blog.csdnimg.cn/blog_migrate/75ca071bab34ba5074da4dc3faa714ef.jpeg)
这时候本地的B1分支就执行了变基操作,但是因为B1还没推送到远程分支,所以origin/B1还是在那里这时候对B1进行远程推送,直接推送是推不上去的,要用强制推送git push -
执行完了远程推送,就会变成下图所示,B1在master最新节点的上方
![7011b11fa07c3f41febfb2f8ef11cf68.png](https://i-blog.csdnimg.cn/blog_migrate/9b5e233686c006f0679b31a5d10894fe.png)
在B2分支执行变基
![c477e09a349098c057c93da45bc29da5.png](https://i-blog.csdnimg.cn/blog_migrate/46faff257e6db72658ad740e5adfced5.png)
直线有一个分线的意思是,对于B1分支和B2分支来水,都在master进行了变基,所以B1,B2都接在master最新节点到上面
变基的过程中存在冲突: 解决冲突 --- add file --- git rebase --continue(或者放弃变基: git rebase --abort)
3. merge过程
分别执行git merge A1和git merge A2
![4ac7a4cc65b2f777d52b09da9bb7815f.png](https://i-blog.csdnimg.cn/blog_migrate/f67698d6dfdc4143220a7d8a3d007843.png)
merge的过程中存在冲突: 解决冲突 --- add file --- commit
八. 冲突
- git pull
git pull = git merge + git rebase
在本地A1分支修改a.text文件,在远程分支A1修改a.text文件,然后两者修改的位置相同.先把本地的更改进行add commit,这时候进行pull操作就会产生冲突
<<<<<<< HEAD
555
666
=======
A1 555
A1 666
>>>>>>> A1 555
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort"
git pull = git fetch + git merge
testGitFlow git:(A1) git pull
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From http://code.aihuishou.com/zhuwenqi/testGitFlow
a33b7c0..22085aa A1 -> origin/A1
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
Automatic merge failed; fix conflicts and then commit the result.
git pull命令默认使用了--merge的方式更新代码,如果你不指定用--rebase,有时候就会发现日志里有这样的一次提交Merge branch 'dev'...into dev?自己分支合并到了自己分支,显然这样没什么必要,而且在时间轴上也不好看,平白无故多了一条线出来,对于强迫症的人来说看着就难受.
设置git pull的方式: git config --global --bool pull.rebase true
2. push
比如在同一个分支下,对本地的a文件做出了修改,此时我们在进行pull或push时如果远程分支下下的a文件也有修改,那么代表本地和远程分支的代码是不同步的,此时也会引起冲突。
3. 不同分支下的merge
比如在不同分支下进行合并分支时,我们在本地修改了a文件并把a文件的修改push到了test分支下,接着切换到master分支下将test分支上的修改合并到当前master分支下时,如果master分支下的a文件也有修改的话,这时再进行merge也会产生冲突.(因为这两个分支是不同步的,两个分支下的同一个文件都有修改)
九. GitFlow
- GitFlow是什么
Git Flow 是一个基于 Git 的开发流程管理的模型, 因极其适合多人协作有效地进行并行开发而被广泛用于项目流程的源代码管理.Git Flow 的核心优势体现在以下几点:
- 并行开发. Git Flow 使得并行开发极其容易. 每个功能对应一个
feature
分支, 一个人可以很方便的同时开发多个不同的feature
. - 多人协作. 各个
feature
之间彼此隔离, 多人可以共同开发同一个feature
分支, 只需要专心为某一个功能提供commit. - 临时发布区域. 某个版本的所有
feature
分支都从develop
分支出发, 开发完合并回develop
分支.develop
分支包含了所有开发完的新功能, 相当于一个新发布版本的临时区域. - 支持紧急修复. Git Flow 支持为生产环境的
TAG
版本做紧急的bug修复.
2. Git Flow 分支
1) master
分支
主分支, 随项目一直存在的长期分支.master
分支HEAD
所在的位置, 永远是当前生产环境的代码.master
分支不允许直接提交代码, 仅允许从release
或者hotfix
分支通过merge request合并代码.
2) develop
分支
开发分支, 随项目一直存在的长期分支.develop
分支的HEAD
所在的位置, 永远是下一个版本中已开发完成的新特性的最新代码.develop
分支的代码每天自动构建并部署到测试环境.develop
分支不允许直接提交代码, 仅允许从feature
, release
或者hotfix
分支通过merge request合并代码.
当develop
分支中下一个版本的新特性已经全部开发完毕后, 从develop
分支开出release
分支, 进入测试阶段.
在下个版本的release
分支创建之前, 非下个版本的feature
分支不允许向develop
分支合并.
3) feature
分支feature
分支是一类以feature/
为前缀(gitflow默认值, 可以更换)的分支的统称.
每一个feature
分支从develop
分支新建, 进行==某一个功能==的开发. 功能开发并测试稳定后, feature
分支将合并回develop
分支.
同一个人可以同时开发多个feature
分支, 同一个feature
分支也可以同时被多个人开发.
多个feature
同时开发的情形, 后开发完的分支在最后合并回develop
时, 往往会遇到冲突的情况. 此时一般遵循一下两种方法解决冲突.
- 先将最新的
develop
分支向当前feature
分支进行合并, 然后再将当前feature
分支合并回develop
. - 先将当前的
feature
分支向最新的develop
分支进行rebase, 然后再将当前feature
分支合并回develop
.
两种方式的比较, 可以参考文章Merging vs. Rebasing, 或其中文版代码合并:Merge、Rebase 的选择
4) release
分支release
分支是一类以release/
为前缀(gitflow默认值, 可以更换)的分支的统称.develop
分支上的下一个版本的所有新特性开发完毕, 从develop
分支开出一个该版本的release
分支, 并进行测试.release
分支不允许进行新特性开发, 而只进行bug修复和更新版本mata信息(如版本号, 构建日期, 更新日志等), 并且可以不定期将新的bug修复改动合并回develop
.
当release
充分测试稳定后, 同时合并进入master
分支和develop
分支, 并在master
分支上的建议该release版本的TAG
.
5) hotfix
分支
当生产环境发现bug时,可以通过新建hotfix
分支,来修复bug, 修复后双向合并到develop
和master
.