做为当前世界上最强大的代码管理工具Git
相信你们都很熟悉,但据我所知有很大一批人停留在clone、commit、pull、push...
的阶段,是否是对rebase
内心没底只敢用merge
?遇见版本回退就抓瞎?别问我怎么知道的,问就是:“我曾经就是这样啊~~”
。针对这些问题,今天我就将这几年对Git
的认知和理解分享出来,尽量的从本质去讲解Git
,帮助你一步一步去了解Git
的底层原理,相信读完本篇文章你即可以换种姿态,更加风骚得使用Git
各类指令。git
Git
是一个分布式
代码管理工具,在讨论分布式以前避免不了说起一下什么是中央式
代码管理仓库服务器
- 中央式:全部的代码保存在中央服务器,因此提交必须依赖网络,而且每次提交都会带入到中央仓库,若是是协同开发可能频繁触发代码合并,进而增长提交的成本和代价。最典型的就是svn
- 分布式:能够在本地提交,不须要依赖网络,而且会将每次提交自动备份到本地。每一个开发者均可以把远程仓库clone一份到本地,并会把提交历史一并拿过来。表明就是Git
那Git
相比于svn
有什么优点呢?打个比方:"巴拉巴拉写了一大堆代码,忽然发现写的有问题,我想回到一个小时以前",对于这种状况Git
的优点就很明显了,由于commit的成本比较小而且本地会保存全部的提交记录,随时随刻能够进行回退。在这并非说svn
的不能完成这种操做,只是Git
的回退会显得更加的优雅。Git
相比于中央式
工具还有不少优势,就不一一列举了,感兴趣的可自行了解。markdown
在Git中文件大概分为三种状态:已修改(modified)、已暂存(staged)、已提交(committed)
网络
为了便于表述,本篇文章我会经过节点
代称commit提交
并发
在Git中每次提交都会生成一个节点
,而每一个节点都会有一个哈希值做为惟一标示,屡次提交会造成一个线性
节点链(不考虑merge的状况),如图1-1分布式
节点上方是经过 SHA1计算的哈希值
svn
C2
节点包含C1
提交内容,一样C3
节点包含C一、C2
提交内容工具
HEAD
是Git中很是重要的一个概念,你能够称它为指针
或者引用
,它能够指向任意一个节点
,而且指向的节点始终为当前工做目录,换句话说就是当前工做目录(也就是你所看到的代码)就是HEAD
指向的节点。测试
还以图1-1举例,若是HEAD
指向C2
那工做目录对应的就是C2
节点。具体如何移动HEAD
指向后面会讲到,此处不要纠结。fetch
同时HEAD
也能够指向一个分支
,间接指向分支
所指向的节点
虽然Git会把代码以及历史保存在本地,但最终仍是要提交到服务器上的远程仓库。经过clone
命令能够把远程仓库的代码下载到本地,同时也会将提交历史
、分支
、HEAD
等状态一并同步到本地,但这些状态并不会实时更新,须要手动从远程仓库去拉取,至于什么时候拉、怎么拉后面章节会讲到。
经过远程仓库为中介,你能够和你的同事进行协同开发,开发完新功能后能够申请提交至远程仓库,同时也能够从远程仓库拉取你同事的代码。
由于你和你的同事都会以远程仓库的代码为基准,因此要时刻保证远程仓库的代码质量,切记不要将未经检验测试的代码提交至远程仓库
分支
也是Git中至关重要的一个概念,当一个分支
指向一个节点
时,当前节点
的内容便是该分支
的内容,它的概念和HEAD
很是接近一样也能够视为指针
或引用
,不一样的是分支
能够存在多个,而HEAD
只有一个。一般会根据功能
或版本
创建不一样的分支
那分支有什么用呢?
- 举个例子:大家的 App 经历了千辛万苦终于发布了
v1.0
版本,因为需求紧急v1.0
上线以后便快马加鞭的开始v1.1
,正当你开发的兴起时,QA同窗说用户反馈了一些bug,须要修复而后从新发版,修复v1.0
确定要基于v1.0
的代码,但是你已经开发了一部分v1.1
了,此时怎么搞?
面对上面的问题经过引入分支
概念即可优雅的解决,如图2-1
- 先看左边示意图,假设
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 "该节点的描述信息"
复制代码
建立一个分支后该分支会与HEAD
指向同一节点,说通俗点就是HEAD
指向哪建立的新分支就指向哪,命令以下:
git branch 分支名
复制代码
当切换分支后,默认状况下HEAD
会指向当前分支,即HEAD
间接指向当前分支指向的节点
git checkout 分支名
复制代码
同时也能够建立一个分支后当即切换,命令以下:
git checkout -b 分支名
复制代码
为了保证仓库分支的简洁,当某个分支完成了它的使命后应该被删除。好比前面所说的单独开一个分支完成某个功能,当这个功能被合并到主分支后应该将这个分支及时删除。
删除命令以下:
git branch -d 分支名
复制代码
关于合并的命令是最难掌握同时也是最重要的。咱们经常使用的合并命令大概有三个merge
、rebase
、cherry-pick
merge
是最经常使用的合并命令,它能够将某个分支或者某个节点的代码合并至当前分支。具体命令以下:
git merge 分支名/节点哈希值
复制代码
若是须要合并的分支彻底领先于当前分支,如图3-1所示
因为分支ft-1
彻底领先分支ft-2
即ft-1
彻底包含ft-2
,因此ft-2
执行了“git merge ft-1”
后会触发fast forward(快速合并)
,此时两个分支指向同一节点,这是最理想的状态。可是实际开发中咱们每每碰到是是下面这种状况:如图3-2(左)
这种状况就不能直接合了,当ft-2
执行了“git merge ft-1”
后Git会将节点C3
、C4
合并随后生成一个新节点C5
,最后将ft-2
指向C5
如图3-2(右)
注意点:
若是
C3
、C4
同时修改了同一个文件中的同一句代码,这个时候合并会出错,由于Git不知道该以哪一个节点为标准,因此这个时候须要咱们本身手动合并代码
rebase
也是一种合并指令,命令行以下:
git rebase 分支名/节点哈希值
复制代码
与merge
不一样的是rebase
合并看起来不会产生新的节点(其实是会产生的,只是作了一次复制),而是将须要合并的节点直接累加 如图3-3
当左边示意图的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
cherry-pick
的合并不一样于merge
和rebase
,它能够选择某几个节点进行合并,如图3-4
命令行:
git cherry-pick 节点哈希值
复制代码
假设当前分支是master
,执行了git cherry-pick C3(哈希值),C4(哈希值)
命令后会直接将C3
、C4
节点抓过来放在后面,对应C3'
和C4'
在默认状况下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
一并回退。
当咱们接触一个新项目时,第一件事情确定是要把它的代码拿下来,在Git中能够经过clone
从远程仓库复制一份代码到本地,具体命令以下:
git clone 仓库地址
复制代码
前面的章节我也有提到过,clone
不只仅是复制代码,它还会把远程仓库的引用(分支/HEAD)
一并取下保存在本地,如图3-5所示:
其中origin/master
和origin/ft-1
为远程仓库的分支,而远程的这些引用状态是不会实时更新到本地的,好比远程仓库origin/master
分支增长了一次提交,此时本地是感知不到的,因此本地的origin/master
分支依旧指向C4
节点。咱们能够经过fetch
命令来手动更新远程仓库状态
小提示:
并非存在服务器上的才能称做是远程仓库,你也能够
clone
本地仓库做为远程,固然实际开发中咱们不可能把本地仓库看成公有仓库,说这个只是单纯的帮助你更清晰的理解分布式
说的通俗一点,fetch
命令就是一次下载
操做,它会将远程新增长的节点以及引用(分支/HEAD)
的状态下载到本地,具体命令以下:
git fetch 远程仓库地址/分支名
复制代码
pull
命令能够从远程仓库的某个引用拉取代码,具体命令以下:
git pull 远程分支名
复制代码
其实pull
的本质就是fetch
+merge
,首先更新远程仓库全部状态
到本地,随后再进行合并。合并完成后本地分支会指向最新节点
另外pull
命令也能够经过rebase
进行合并,具体命令以下:
git pull --rebase 远程分支名
复制代码
push
命令能够将本地提交推送至远程,具体命令以下:
git push 远程分支名
复制代码
若是直接push
可能会失败,由于可能存在冲突,因此在push
以前每每会先pull
一下,若是存在冲突本地解决。push
成功后本地的远程分支引用会更新,与本地分支指向同一节点
HEAD
仍是分支
,它们都只是引用
而已,引用
+节点
是 Git 构成分布式的关键merge
相比于rebase
有更明确的时间历史,而rebase
会使提交更加线性应当优先使用HEAD
能够查看每一个提交对应的代码clone
或fetch
都会将远程仓库的全部提交
、引用
保存在本地一份pull
的本质其实就是fetch
+merge
,也能够加入--rebase
经过rebase方式合并文章中图画的有点low,全程ppt,你们知道有比较好的免费的画图工具吗?事先谢过~