我已经和另一个开发人员在一个项目中使用Git好几个月了。我在SVN方面有几年的经验,所以我想我给这种关系带来了很多包袱。

我听说Git在分支和合并方面非常出色,但到目前为止,我还没有看到这一点。当然,分支非常简单,但当我尝试合并时,一切都变得一团糟。现在,我已经习惯了SVN的版本,但对我来说,我只是把一个低于标准的版本系统换成了另一个。

我的合作伙伴告诉我,我的问题源于我想要合并的愿望,并且在许多情况下我应该使用rebase而不是合并。例如,这是他制定的工作流程:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

本质上,创建一个特性分支,总是从主分支到分支,并从分支合并回主分支。需要注意的是,分支始终保持在本地。

这是我开始的工作流程

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

有两个本质的区别(我认为):我总是使用merge而不是rebase,并且我将我的特性分支(以及我的特性分支提交)推到远程存储库。

我使用远程分支的理由是,我希望在工作时备份我的工作。我们的存储库是自动备份的,如果出现问题可以恢复。我的笔记本电脑没有,或者说没有那么彻底。因此,我讨厌我的笔记本电脑上的代码没有镜像到其他地方。

我选择合并而不是rebase的原因是合并似乎是标准的,而rebase似乎是一个高级特性。我的直觉是,我试图做的不是一个先进的设置,所以rebase应该是不必要的。我甚至仔细阅读了关于Git的新Pragmatic Programming书,其中涉及了大量的merge,而很少提到rebase。

不管怎样,我在最近的分支上遵循我的工作流,当我试图将它合并回master时,一切都糟透了。与本不重要的事情有很多冲突。这些冲突对我来说毫无意义。我花了一天的时间来整理所有的事情,最终在强制推送到远程的主人,因为我的本地主人已经解决了所有的冲突,但远程的主人仍然不高兴。

对于这样的事情,“正确的”工作流是什么?Git应该让分支和合并变得超级简单,但我没有看到这一点。

更新2011-04-15

这似乎是一个非常受欢迎的问题,所以我想我应该更新一下我第一次问这个问题以来两年的经验。

原来的工作流程是正确的,至少在我们的例子中是这样。换句话说,这就是我们所做的,而且很有效:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

事实上,我们的工作流程有点不同,因为我们倾向于做压缩合并而不是原始合并。(注:这是有争议的,见下文。)这允许我们将整个特性分支转换为在master上的单个提交。然后我们删除我们的特征分支。这允许我们在master上逻辑地构造提交,即使它们在我们的分支上有点乱。这就是我们所做的:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

壁球合并争议-正如一些评论者指出的,壁球合并将丢弃你的特性分支的所有历史。顾名思义,它将所有提交压缩成一个单一的提交。对于小功能来说,这是有意义的,因为它将其压缩成一个包。对于较大的特性,这可能不是一个好主意,特别是如果您的个人提交已经是原子的。这实际上取决于个人喜好。

Github和Bitbucket(其他的?)Pull Requests——如果你想知道merge/rebase与Pull Requests有什么关系,我建议你按照上面所有的步骤,直到你准备好合并回master。而不是用git手动合并,你只是接受PR。注意,这不会做挤压合并(至少默认情况下不会),但非挤压,非快进是Pull Request社区接受的合并约定(据我所知)。具体来说,它是这样工作的:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

我已经爱上了Git,再也不想回到SVN。如果你在挣扎,坚持下去,最终你会看到隧道尽头的光明。


当前回答

在我的工作流程中,我尽可能多地调整基数(并且我尝试经常这样做。不让差异累积会大大减少分支之间冲突的数量和严重程度)。

然而,即使在主要基于rebase的工作流中,也有合并的位置。

回想一下,merge实际上创建了一个有两个父节点的节点。现在考虑以下情况:我有两个独立的特性分支A和B,现在想在特性分支C上开发一些东西,它依赖于A和B,而A和B正在被审查。

接下来我要做的是:

在A之上创建(并签出)分支C。 与B合并

现在分支C包含了来自A和B的变更,我可以继续在此基础上进行开发。如果我对A做任何改变,那么我将按照以下方式重建分支图:

在A的新顶部创建分支T 将T与B合并 把C改成T 删除分支T

通过这种方式,我实际上可以维护分支的任意图,但是如果做比上面描述的情况更复杂的事情就太复杂了,因为当父节点发生变化时,没有自动工具来执行重基操作。

其他回答

“冲突”意味着“同一内容的平行演变”。因此,如果它在合并期间“完全失败”,这意味着您在同一组文件上有大量的演进。

为什么rebase比merge更好的原因是:

你用master中的一个重写你的本地提交历史(然后重新应用你的工作,然后解决任何冲突) 最终的合并肯定会是一个“快进”的合并,因为它将拥有主节点的所有提交历史,加上只需要重新应用的更改。

我确认在这种情况下正确的工作流程(在公共文件集上的演进)是先改基,然后合并。

然而,这意味着,如果您推送本地分支(出于备份的原因),该分支不应该被其他任何人拉取(或至少使用)(因为提交历史将被后续的rebase重写)。


关于这个话题(重构然后合并工作流),barraponto在评论中提到了两个有趣的帖子,都来自randyfay.com:

Git的Rebase工作流:提醒我们先获取,Rebase:

使用这种技术,您的工作总是在公共分支的顶部,就像一个补丁,是最新的与当前的HEAD。

(类似的技术也存在于集市)

避免Git灾难:一个血淋淋的故事:关于Git push -force(而不是Git pull -rebase)的危险

Git没有“正确”的工作流程。使用任何能让你的船浮起来的东西。然而,如果你在合并分支时经常遇到冲突,也许你应该更好地与其他开发人员协调工作?听起来你们俩一直在编辑同样的文件。另外,要注意空白和颠覆关键字(即“$Id$”和其他)。

根据我的观察,git merge倾向于在合并后保持分支独立,而rebase然后merge则将其合并为单个分支。 后者的结果要干净得多,而在前者中,即使在合并之后,也更容易找出哪些提交属于哪个分支。

在你的情况下,我认为你的搭档是对的。重基的好处是,对于外人来说,你的更改看起来都是按照一个干净的顺序发生的。这意味着

您的更改非常容易检查 您可以继续进行漂亮的小提交,但您可以将这些提交集(通过合并到master)同时公之于众 当您查看公共主分支时,您将看到不同开发人员针对不同特性的不同提交系列,但它们不会全部混合在一起

出于备份的目的,您仍然可以继续将您的私有开发分支推到远程存储库,但其他人不应该将其视为“公共”分支,因为您将进行重基。顺便说一下,一个简单的命令是git push——mirror origin。

使用Git打包软件这篇文章很好地解释了合并与重基之间的权衡。这是一个有点不同的上下文,但原则是相同的——基本上归结为您的分支是公共的还是私有的,以及您计划如何将它们集成到主线中。

看了你的解释后,我有一个问题:难道你从来没有做过

git checkout master
git pull origin
git checkout my_new_feature

在你的功能分支中做“git rebase/merge master”之前?

因为你的主分支不会从你朋友的存储库中自动更新。你必须在git pull origin中这样做。也就是说,也许你总是会从一个永远不会改变的本地主分支重新建立数据库?然后是推送时间,你正在推送一个你从未见过的(本地)提交的存储库,因此推送失败。