我已经和另一个开发人员在一个项目中使用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。如果你在挣扎,坚持下去,最终你会看到隧道尽头的光明。


当前回答

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

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

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

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

其他回答

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

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

无论是在你的伴侣还是你建议的工作流程中,你都不应该遇到不合情理的冲突。即使你有,如果你遵循建议的工作流程,那么解决后不应该需要“强制”推送。这表明您实际上并没有合并您所推入的分支,而是不得不推入一个不是远程尖端后代的分支。

我觉得你需要仔细看看发生了什么。是否有其他人(有意或无意)在您创建本地分支和试图将其合并回本地分支之间重新启动了远程主分支?

与许多其他版本控制系统相比,我发现使用Git涉及的工具较少,并且允许您着手解决源代码流的基本问题。Git并没有魔法,因此冲突的更改会导致冲突,但是它应该通过跟踪提交父项来简化写操作。

我只使用rebase工作流,因为它在视觉上更清晰(不仅在GitKraken中,在Intellij和gitk中也是如此,但我最推荐的是第一个):你有一个分支,它起源于master,它回到master。当图表干净美观时,您将知道没有什么会出错。

我的工作流程几乎和你的一样,但只有一个小的不同:我在把我的分支重新基于master上的最新更改之前,把提交压缩到我的本地分支中,因为:

Rebase在每次提交的基础上工作

这意味着,如果你有15个提交和master一样修改同一行,如果你没有挤压,你必须检查15次,但重要的是最终结果,对吧?

所以,整个工作流程是:

Checkout to master and pull to ensure that you have the latest version From there, create a new branch Do your work there, you can freely commit several times, and push to remote, no worries, because it is your branch. If someone tells you, "hey, my PR/MR is approved, now it is merged to master", you can fetch them/pull them. You can do it anytime, or in step 6. After doing all your work, commit them, and if you have several commits, squash them(they are all your work, and how many times you change a line of code does not matter; the only important thing is the final version). Push it or not, it doesn't matter. Checkout to master, pull again to ensure you have the latest master in local. Your diagram should be similar to this:

如您所见,您在本地分支上,这源于master上的过时状态,而master(本地和远程)已随着同事的更改而向前移动。

签出回您的分支,并将base转到master。您现在只有一次提交,所以您只能解决一次冲突。(在GitKraken中,你只需要将你的分支拖到master并选择“Rebase”;这也是我喜欢它的另一个原因。 在那之后,你会像这样:

So now, you have all the changes on the latest master, combined with changes on your branch. You can now push to your remote, and, if you have pushed before, you will have to force push; Git will tell you that you cannot simply fast forward. That's normal, because of the rebase, you have changed the start point of your branch. But you should not fear: wisely use the force, but without fear. In the end, the remote is also your branch so you do not affect master even if you do something wrong. Create PR/MR and wait until it is approved, so master will have your contribution. Congrats! So you can now checkout to master, pull your changes, and delete your local branch in order to clean up the diagram. The remote branch should be deleted too, if this is not done when you merge it into master.

最后的图表再次清晰明了:

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

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

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

接下来我要做的是:

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

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

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

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

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

git checkout master
git pull origin
git checkout my_new_feature

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

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