Git总结(二)
摘要
Git是一个免费的开源分布式版本控制系统,旨在快速高效的处理从小到大的所有项目。
本篇文章介绍Git中的分支概念和相关操作,主要依托网站https://learngitbranching.js.org/?demo=&locale=zh_CN,这个网站以动画的形式形象的介绍了分支的操作。此外,还将介绍远程仓库相关的概念,实际上,上一篇中就已经涉及到远程仓库的常见操作,这里将更详细的介绍。
分支和创建分支
我们知道,在Git中不断提交的记录形成一条版本时间线,例如在master
分支中提交了若干次之后,形成了下图的时间线(提交的时候我把备注直接写成C1
,C2
这种形式,相比于对比哈希值更便于观察版本关系)。
这样在master
分支下提交了三次,现在我们要创建一个新的分支dev
, 使用命令git branch dev
:
1 | git branch <newBranch> |
这样新创建的分支dev
和当前分支指向的提交记录一样,都是指向C2
,就是这么简单,只是添加了一个指向当前最新提交的指针罢了。因此在Git中,分支的本质不过是一个指向提交对象的指针,这也是Git在版本控制系统中脱颖而出的特性之一,有些版本控制器创建分支需要将项目文件进行拷贝放置单独的目录下,这是很慢的。而在Git中仅仅是进行指针的操作。
需要注意的是图中带*号的是当前所处的分支,使用上述命令创建分支并不会改变当前所处的分支。
下列命令可以查看所有分支和切换分支:
1 | git branch //查看分支 |
还有一个常用的命令是创建分支的同时切换过去:
1 | git checkout -b <newBranch> //创建并切换至newBranch分支 |
对不需要的分支进行删除:
1 | git branch -d <branchName> |
分支合并
现在,我们在新分支下提交一次,先使用git checkout dev
切换到dev
分支,然后进行一次提交(提交前对文件进行些修改),提交之后的版本图如下:
我们看到master
分支并没有前进,只有dev
分支前进了一次提交,这是显然的,因为我们现在处在dev
分支上进行了一次提交。注意这时我们切换回master
分支进行一次提交,现在的情况是,两个分支各有一次新的提交,也就是说版本图发生了“分叉”(注意:两个分支进行的两个提交不能修改同一个文件,不然分支合并时会引起冲突):
从dev
分支来看,提交记录是从C2
到C3
,而从master
分支来看,提交记录为从C2
到C4
;若两个分支做的工作为项目的不同部分(即未修改同一个文件),我们可以用以下命令将分支进行合并:
1 | git merge <branchName> //将branchName分支的内容合并至当前所处的分支 |
如使用在master
分支用git merge dev
将dev
分支的内容合并过来:
这样master
分支就包含了所有的修改了,但是dev
分支依旧处在C3
提交处没有改变。若想将dev
分支也获得所有的修改,我们只需再切换回dev
分支,然后把master
分支合并过来即可git checkout dev
,git merge master
:
因为master
分支继承自dev
分支,所以合并的时候,Git什么都不用做,只需要将dev
指针移动到master
分支所指的提交记录就行了!
实际上,使用git merge
命令只是分支合并的一种方式,常用的还有另外一种合并分支的方法:
1 | git rebase <branchName> |
rebase
操作实际上是取出一系列的提交记录,“复制它们”,然后在另一个地方逐个放下去,使用rebase
的优势是可以创建更加线性的提交历史。
还是用之前这张图举例:
在master
分支上使用git rebase dev
,就把master
分支的工作直接移到了dev
的下面,就好像两个分支是顺序开发的一样,但实际上是并行开发的。
注意C4
分支的提交记录依然是存在的(图中的透明节点),C4'
是rebase
至dev
分支上的一个副本,现在的唯一问题是dev
分支还没有更新,同样在dev
分支上使用命令git rebase master
更新:
由于master
继承自dev
,Git其实什么都不用做,只需要把dev
分支引用移动到master
指向的提交处!总的来说,使用rebase
也可以达到合并分支的作用,它的优点就是提交历史很干净,都是线性的。当然它的缺点就是掩盖了真实的开发情况,把并行的分支开发记录成了线性的顺序开发。至于用什么方式进行合并,看个人习惯。
HEAD指针和相对引用
HEAD
是对当前工作分支的一个别名指针,它总是指向当前分支上最近一次提交记录,也就是通常情况下HEAD
是指向分支名的。如最开始使用master
提交三次时,HEAD
指针会一直跟着master
移动,始终指向master
:
但实际上,HEAD
指针并不一定总是指向分支名,只是大多时候是这样,可以使用分支切换命令git checkout
将HEAD
指针进行移动,例如移动至某个提交记录上,这时候进入了HEAD分离状态。如将HEAD
移动至C1
上:git checkout [C1哈希值]
即通常情况下是HEAD
-->branch
-->提交记录
进入分离状态之后就是HEAD
-->提交记录
可以使用以下reset
命令进行版本的回退:
1 | git reset <pos> //将当前分支指针置于pos处,这里的pos可以是提交哈希值或相对引用 |
通过指定版本哈希值比较麻烦,虽然Git只需要你输出哈希值的前几位。此外,Git还提供两种相对引用形式,一种是^
,表示向上一个提交记录;另一种是~<num>
,如HEAD~2
表示HEAD
指针的前2个提交记录,如git reset HEAD^
:将当前分支回退至HEAD
的前一次提交:
但是使用reset
进行提交的撤回对于远程版本是无效的,为了将撤销分享给远程版本, 有一个“曲线救国”的命令:
1 | git revert <pos> 将当前分支指针置于pos的上一个版本的复制上,体现为提交一次新的更改 |
例如,git revert HEAD
:
这里,实际上是提交了一个新的版本,只是这个版本和之前的C1
版本一致,相当于回退了版本,这样的好处就是能够将这个回退分享至远程版本。
远程仓库操作
实际上远程仓库并不复杂,其本质上不过是本地仓库在Git服务器上的一份拷贝,上一篇实际上已经涉及到常用的远程仓库操作了,这里深入介绍相关远程操作的原理,各个命令执行时发生了什么。
为了进行远程仓库操作,首先需要执行clone
获得远程仓库的一份拷贝:
1 | git clone <gitAddress> //从服务器地址处获取一份远程仓库的拷贝 |
可以看到执行git clone
之后本地仓库多出了一个分支:origin/master
;这个分支叫做远程分支,它本质上是一个处在本地仓库的分支,但是它有一些特殊的属性。
远程分支反映了远程仓库(在你上次和它通信时)的状态。远程分支有一个特殊的属性,就是当你想要切换至origin/master
时,会进入分离HEAD状态,这个状态之前也已经提过;这样设定的目的就是让你无法在远程分支origin/master
分支上进行操作,因为这个分支用于反应远程工作,只能通过远程操作更改。倘若想要切换至origin/master
分支后进行一次提交,效果如下:
当切换至origin/master
分支进行提交,进入了分离HEAD状态,之后进行提交时origin/master
分支并不会前进,只是在当前HEAD
指针上前进了一次提交,实际上在分离HEAD状态进行提交不会影响到任何一个分支。
接下来介绍一个从远程仓库获取数据的命令:
1 | git fetch //从远程仓库获取本地缺失的提交记录并更新远程分支 |
fetch
命令就是做以下两件事情:
- 从远程仓库获取本地缺失的提交
- 更新本地仓库中的远程指针,如
origin/master
下面是一个执行fetch
命令的例子:
可以清楚的看到,执行完fetch
命令之后,本地仓库将远程的C2
,C3
提交下载了下来,并将origin/master
移动了;但是需要注意的是:本地master
分支并没有移动,且本地工作区的文件也不会发生任何改变,不要误以为执行了fetch
命令之后就和远程仓库同步了。
还要说明的一点就是,如果远程有多个分支,fetch
命令会将所有分支中本地仓库没有的提交下载下来,并移动相应的远程指针。假如远程仓库还有一个dev
分支,那么会将本地的origin/dev
分支进行更新。所以,远程分支就是这样在和远程仓库互动的时候更新,因此说远程指针反映了远程仓库(在你上次和它通信时)的状态。
为了将远程仓库的提交更新到我们本地的工作中,可以执行完fetch
命令之后再通过分支合并的方法,将远程分支合并。例如在上面的基础上执行git merge origin/master
或者git rebase origin/master
就会将远程分支的提交合并至master
中。在日常操作中先拉取远程仓库提交再合并至本地分支是十分常见的操作,因此Git提供了一个专门的命令来干这件事情:
1 | git pull //拉取远程仓库分支并和本地分支合并 |
实际上pull
命令就是fetch
命令和merge
命令的组合。下面是这个过程:
好了,现在已经知道如何将远程仓库的数据同步至本地了,那么如何将本地的提交分享至远程仓库呢?答案是使用push
命令:
1 | git push //将本地提交推送至远程仓库,并更新远程分支 |
push
命令会将本地的新提交上传至指定的远程仓库并在远程仓库完成分支的合并,也就是push
完成之后,别人就能够下载到你的提交了。
先看最简单的情况:
执行完push
之后本地仓库和远程仓库同步了,这就是最简单的提交情况。
实际工作时时常发生下面的情况,你在一周前clone
了一个仓库,对其中的某个新功能进行开发,现在终于开发完成了,在本地仓库提交完成之后想要push
至远程仓库;但不幸的是,你的一个同事在过去的一周里修改了许多代码,甚至包括一些你开发新功能时用的API。这时候Git不知道该怎么合并了,到底是放弃你的工作还是同事的工作呢?
实际上,这时候你直接push
会发生错误,Git会强制要求你将现在的远程仓库代码在本地合并之后才能上传代码。因此,需要先执行pull
命令,再执行push
命令。
可以看到,先执行pull
的时候,将远程仓库的提交在本地进行了合并,然后再执行push
时就直接将本地合并之后的记录一并提交了上去。实际上,为了合并时产生线性的提交记录,可以使用前面提到的pull --rebase
命令。
关于远程仓库的操作就介绍到这里了,实际上pull
和push
还有一些参数可以用于指定拉取或推送的分支,这里就不介绍了。