Git 工作流程和变基与合并问题

我已经和其他开发人员一起在一个项目上使用 Git 几个月了。我在SVN方面有几年的经验,所以我想我为这段感情带来了很多麻烦。

我听说 Git 非常适合分支和合并,到目前为止,我只是看不到它。当然,分支是非常简单的,但是当我尝试合并时,一切都会陷入困境。现在,我已经习惯了 SVN,但是在我看来,我只是将一个低于标准的版本控制系统换成了另一个。

我的伴侣告诉我,我的问题源于我想要合并 will-nilly 的愿望,在许多情况下,我应该使用 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

本质上,创建一个功能分支,始终将其从 master 转移到 master,然后从分支合并回到 master。需要注意的重要一点是,分支始终位于本地。

这是我开始的工作流程

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 而不是重新设置基础,并且将功能分支(和功能分支的提交)推送到远程存储库。

我之所以选择远程分支机构,是因为我希望在工作时备份自己的工作。我们的存储库将自动备份,如果出现问题可以将其还原。我的笔记本电脑不是,还是不够彻底。因此,我讨厌笔记本电脑上没有其他地方的代码。

我之所以选择合并而不是重新设置基准,是因为合并似乎是标准的,而重新确定基准似乎是一项高级功能。我的直觉是我要尝试的不是高级设置,因此无需重新设置基准。我什至仔细阅读过有关 Git 的新《实用程序设计》一书,它们涵盖了合并的内容,几乎没有提及重新设置基础。

无论如何,我在最近的分支上跟踪我的工作流程,当我尝试将其合并回 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(其他吗?)拉取请求 - 如果您想知道合并 / 变迁与拉取请求之间的关系,我建议您按照上述所有步骤进行操作,直到准备好合并回主服务器为止。您只需接受 PR,而不是与 git 手动合并。请注意,这不会进行壁球合并(至少默认情况下不会),但是在请求请求社区中,非壁球,非快进是可接受的合并约定(据我所知)。具体来说,它的工作方式如下:

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 了。如果您在挣扎,只需坚持下去,最终您会在隧道尽头看到光明。

答案

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch
git config --global merge.conflictstyle diff3
<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch
TextMessage.send(:include_timestamp => false)

“冲突” 是指 “相同内容的平行演变”。因此,如果在合并过程中 “全部陷入困境”,则意味着您在同一组文件上进行了大量改进。

相比于合并,rebase 更好的原因是:

  • 您使用主数据库之一重写了本地提交历史记录(然后重新应用您的工作,然后解决了所有冲突)
  • 最后的合并肯定是 “快进” 的合并,因为它将具有主数据库的所有提交历史记录,并且仅包含您要重新应用的更改。

我确认在这种情况下(正确的通用文件集)正确的工作流程是首先重新设置基础,然后合并

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


关于该主题(先重新整理然后合并工作流), barraponto在评论中提到了两个有趣的帖子,均来自randyfay.com

使用此技术,您的工作将始终像最新的HEAD补丁一样在 public 分支之上。

存在与集市类似的技术)

在我的工作流程中,我尽可能地重新设置基础(并且我经常尝试这样做。不让差异急剧累积会减少分支之间冲突的数量和严重性)。

但是,即使在主要基于基础的工作流中,也有合并的地方。

回想一下,合并实际上会创建一个具有两个父节点的节点。现在考虑以下情况:我有两个独立的特征 br A 和 B,现在想在依赖 A 和 B 的特征分支 C 上开发东西,而 A 和 B 正在接受审查。

接下来,我要做的是:

  1. 在 A 的顶部创建(并签出)分支 C。
  2. 与 B 合并

现在,分支 C 包含来自 A 和 B 的更改,我可以继续对其进行开发。如果我对 A 进行任何更改,则可以通过以下方式重构分支图:

  1. 在 A 的新顶部创建分支 T
  2. 将 T 与 B 合并
  3. 将 C 重设到 T
  4. 删除分支 T

通过这种方式,我实际上可以维护分支的任意图,但是要完成比上述情况更复杂的操作已经太复杂了,因为在父级发生更改时没有自动工具来进行基础调整。

在几乎所有情况下都不要使用 git push origin --mirror。

它不会询问您是否确定要执行此操作,因此最好确定一下,因为它会清除不在本地设备上的所有远程分支。

http://twitter.com/dysinger/status/1273652486

git checkout master
git pull origin
git checkout my_new_feature

根据您的情况,我认为您的伴侣是正确的。进行基础调整的好处是,对于外部人员而言,您的更改看起来像是所有更改都是独立完成的。这表示

  • 您的更改很容易检查
  • 您可以继续做出不错的小型提交,但是您可以一次将所有这些提交集公开(通过合并到 master 中)
  • 当您查看 public master 分支时,您会看到不同开发人员针对不同功能进行的一系列不同的提交,但不会将它们混合在一起

为了备份,您仍然可以继续将您的私有开发分支推送到远程存储库,但是其他人不应将其视为 “公共” 分支,因为您将要重新定基础。顺便说一句,执行此操作的一个简单命令是git push --mirror origin

使用 Git 打包软件一文很好地解释了合并与重新基准之间的权衡。情况稍有不同,但原理相同–基本上取决于您的分支机构是公开的还是私有的,以及您打算如何将它们集成到主线中。

无论如何,我在最近的分支上跟踪我的工作流程,当我尝试将其合并回 master 时,一切都陷入了困境。与本不重要的事物存在大量冲突。冲突对我来说毫无意义。我花了一天的时间来整理所有内容,最终以强迫推送到远程主机的方式达到了顶峰,因为我的本地主机解决了所有冲突,但是远程主机仍然不满意。

在您的合作伙伴或建议的工作流程中,您都应该遇到没有意义的冲突。即使您有,但如果您遵循建议的工作流程,则在解决之后,就不需要 “强制” 推送。这表明您实际上并没有合并要推送到的分支,但必须推送不是远程技巧的后代的分支。

我认为您需要仔细查看发生了什么。在您创建本地分支与尝试将其重新合并到本地分支的点之间,是否有人(有意或无意)倒退了远程主分支?

与许多其他版本控制系统相比,我发现使用 Git 涉及的工具较少,可让您着手解决源流中最根本的问题。 Git 不会执行魔术操作,因此发生冲突的更改会引起冲突,但是它应该通过跟踪提交父母身份来简化写操作。

从我观察到的情况来看,即使在合并之后,git merge 也会使分支保持分离,而 rebase 然后合并将其合并为一个分支。后者更干净,而在前者中,即使合并后,更容易找出哪些提交属于哪个分支。

“即使您是一个只有几个分支的开发人员,还是值得养成使用 rebase 和正确合并的习惯。基本的工作模式如下所示:

  • 从现有分支 A 创建新分支 B

  • 在分支 B 上添加 / 提交更改

  • 对来自分支机构 A 的更新进行重新评估

  • 从分支 B 合并更改到分支 A“

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/

使用 Git,没有 “正确” 的工作流程。使用漂浮在船上的东西。但是,如果合并分支机构时经常发生冲突,也许应该与其他开发人员更好地协调工作?听起来你们两个一直在编辑相同的文件。此外,请注意空格和 Subversion 关键字(即 “$ Id $” 和其他关键字)。