如何理解git的分布式 分支

util
  • 3.2k

看了Git官网的这一段介绍更加迷惑了

用 Git 的话,就算你在飞机或者火车上,都可以非常愉快地频繁提交更新, 等到了有网络的时候再上传到远程仓库。同样,在回家的路上,不用连接VPN 你也可以继续工作。换作其他版本控制系统,这么做几乎不可能,抑或非常麻烦。
链接地址》》》

假如

有一个商城项目正在上线,团队中有A、B、C三个程序员,使用git来进行版本控制。总共开发天数是10天,从第1天到第5天,A、B、C都是一起开发,一起提交,一个版本。但是到了第6天开始,C就不提交到远程了,一直都是在自己修改制作。整个项目很多文件,C会把项目中的文件GlobalFunction.php改动很多次,修改了很多函数,也加入了很多自己的函数。随着开发的继续,C还会不断的陆续修改GlobalFunction.php文件。而一直都提交到远程的A和B,也需要经常修改GlobalFunction.php文件,但是A和B会保持同步到Git远程。

合并

到了最后第10天,C可以联网了,然后C提交到远程。可C电脑里面的GlobalFunction.php 跟 Git远程上的 GlobalFunction.php 已经完全不一样了,而且远程的这个文件还经历过了二十多个版本。而C自己电脑里面的也经历过了几十个不同版本。

极大的疑惑

根据git介绍中的那一段的描述,这样的情况下,Git是可以自动合并?如何自动合并?

C已经在GlobalFunction.php中加入了很多函数且期间也经历过了很多次的版本。


补充

看到很多介绍Git的文章重点都放在了不需要push到远程,不需要联网就可以使用,真的觉得实在是难以理解。我在sf问也有很多人也在说不用push,http://segmentfault.com/q/1010000000605934#c-1020000000606050-1050000000609657,更让人迷惑。不用push,假如1年或者10年了从来都不push到远程,那么还算是协同工作了吗,git会那么神奇10年之后再联网push过去还可以自动合并

回复
阅读 10.7k
6 个回答
TooBug
  • 1.1k

把一个场景推到极端是不好的论证方法。

首先,提交的目的是什么,如果只是为了同步代码给别人,那git真没什么亮点,甚至离线提交还会让人误解,算是个缺点。

我个人认为提交的目的,一是暂存代码(版本管理,方便后续回滚神马的),二是作为代码的阶段性节点(这个功能做完了,提交一下),三才是向服务器同步,与他人共享。

Git的离线提交可以很好地完成以上目的一二两点。而且因为是离线提交的,你可以在与服务器同步之前很方便地修改提交记录,这有什么用呢?比如我写了一个方法,它可以工作,但是我想重构一下,又不确定重构后能否正常工作,于是先提交一个版本,万一重构坏了还可以回滚回来。等我重构完了之后,再提交一次。此时就有了同一功能的两次提交,而你可能只希望别人看到一个提交“XX功能完成”,于是你可以很方便地将这两次提交合并起来。最后合并之后,代码版本库是非常干净的,所有的提交记录都是因为真的要提交而提交,这些提交是可以直接写入release note的,而不会出现“测试XXX”,“尝试XXX”之类的提交。基于同样的结果,你还可以将一次写的代码分多次提交,比如我写了三个功能,我可以选择提交三次,最后再同步到服务器,而在SVN中,你(在没网时写的代码)只能一次提交上去,然后写“完成了A,B,C三个功能”,这样其实不便于别人查看你的代码,也不利于对A,B,C分别进行回滚。

所以git的离线提交带来的是灵活性。并不是说有了离线提交,我就可以不需要服务器,空气会自动帮我完成协同工作。这是我开头说的“把一个场景推到极端是不好的论证方法”的意思。git相比svn,协同的方式会多一些,也灵活一些,但并不能避开协同的问题,你仍然需要协同和合并代码。

在合并代码的过程中,和SVN一样,也会遇到冲突的情况,所以条件允许的情况下及时与服务器同步代码仍然是个好习惯。但是,由于git是基于内容跟踪版本,svn是基本文件跟踪版本,导致git的合并比svn靠谱得多,也就是同样情况下,git碰到冲突的可能性比svn要小很多。

最后,由于git的离线提交特性,再加上git的分支特别好用,还可以实现本地多分支管理功能,也就是你在做一个功能时可以在本地开一个分支,然后在本地提交,做完了再合并进去推到服务器,对其他人而言,这个分支是不存在的,但是对自己而言,却可以随时切换工作状态(写新功能还是切回去改bug)。svn也可以切分支,但它的分支是无法只存在本地的,如果采用分支协作的方式,会导致分支非常多,而有一些根本是其它人不需要关心的。而更为麻烦的是,如果你新功能写到一半,要切回去改bug,此时只能将未完成的代码提交到svn服务器,我个人认为(将未完成的代码提交到版本库服务器)是一种非常不好的行为,而用git,因为分支是离线的,我提交完,切回去改bug,再切回来就是,等功能做完后合并一下提交记录就一切圆满了。

王子亭
  • 11.5k

如果双方改的是不同的文件,或者同一个文件的不同部分,那么 git 是可以自动合并的。

如果修改的是同一个文件同一个部分,git 就没办法自动合并了,这种情况被称为「冲突」,git 会列出双方的最新版本,然后由进行合并的人(例子里是 C), 手动进行编辑。

在实际开发中,如果两个人只是往这个文件里加函数,是完全可以自动合并的,冲突主要是出现了修改了同一行的情况。

对补充的回答:

很多介绍强调 git 可以离线 commit, 意在于和 svn 做对比。svn 中,只有服务器上有完整的版本库,客户端的代码历史并不是完整的。而 git 中每一个客户端都是一个完整的版本库,push 和 pull 操作其实是在多个完整的版本库之间做同步。

这样的优势就是不怕服务器挂掉或者丢失,每个人都有完整的版本库,这也是 Linus 开发 git 的出发点(Linux 是一个多人分布式协作的开源项目). 而劣势就在于权限管理比较差,每个人都有完整的版本库有可能会导致代码(历史上所有的修改)不小心泄露。

douglarek
  • 448

终于遇到一个可以好好回答的问题了;

首先说一下题主的假设:

C 从第 6 天开始不跟远程交互了,自己玩了,这是做什么?这不是分布式,分布式是在网络可用下说的,分布式不是隔着几天不跟远程同步;

既然文件叫 GlobalFunction.php,那么这个文件就是修改极少的(或者说修改相同的一行是很少的),类似于 Python 下的 setup.py,有时只是到了新版本发布的时候你才会修改一下 version 变量吧;

GIT 的分支概念个人理解是可以更快速的基于某个或某些分支进行协作开发,比如一个仓库 foo 有一个 分支叫做:master ,那么 A,B,C 进行协同开发的时候会在本地基于这个共同的分支 master 进行本地分支,比如 A 的本地分支叫 a-dev,B 的叫:b-dev,C 的叫 c-dev,怎么来的呢?

git checkout -b xxx origin/master # xxx 代表 `a-dev` 等

其次说一下题主说的合并:

如果 C 在本地基于 master 进行了本地分支 c-dev 那么 C 一直可以更新本地 master 分支而不影响本地分支 c-dev(而此时你可以一直在 c-dev 分支):

git pull origin master:master

那么把本地 master 更新了,怎么反应在 c-dev 呢? 很简单,GIT 已经为你想好了:

git rebase master # 此时你在 `c-dev` 分支

经过了上面,可能会发生的情况无非是两种:1. 出现合并冲突,rebase 失败;2. 无冲突(可能会自动 merge),本地 master 最新状况跟本地 c-dev 合并;

对于 1 的出现,GIT 不会自动搞定冲突,却可以尝试自动合并(merge),失败了,叫出现了冲突;出现了冲突一般是指不同的提交,修改了同一文件的同一行(几行)内容,需要开发者自己搞定冲突;

个人总结

分支是 GIT 区别于其他集中式版本控制系统的杀手锏(killer feature);rebase 是 GIT 分支的杀手锏;GIT 的分布式特性离不开 GIT 良好的分支特性;

Jealone
  • 1
新手上路,请多包涵

在发生冲突的时候SVN是逐行比较,而Git是记录的操作,比如增加一行、删除一行或者修改一行,因而合并的过程更容易

你说的问题只能通过分支解决

简单地说,可能可以自动合并,也可能不行。
Git的每一个commit保存的是diff,楼主可以随便git show一个commit看一下。diff会在原始修改行的前后若干行里去尝试匹配,如果能匹配上,那么这个diff就可以自动合并。
举个例子,如果C的patch修改了第五行的一个值,他没有提交。A和B一直在GlobalFunction.php里添加新的功能,这些功能都在文件的尾部添加,那么C在合并的时候,Git可以通过cherry-pick的方式完成合并。
如果双方都修改了同样的地方,那就得手动解决冲突了。

宣传栏