重构和并发开发分支
假设您的软件现有版本有多个维护分支。 一些开发人员正在维护分支中进行直接更改,并定期合并到主干中。 现在对主干代码线进行了广泛的重构,计划在即将发布的主要版本中进行。 但这使得维护分支从根本上与主干中的代码不兼容,因为它们可能依赖于不再存在的代码。
实践中您如何处理这种情况?
Let's say you have several maintenance branches for existing releases of your software. Some developers are making direct changes in the maintenance branches, and merging periodically into the trunk. Now comes an extensive refactoring in the trunk codeline, scheduled for an upcoming major release. But this makes the maintenance branches fundamentally incompatible with the code in the trunk, as they might depend on code that does not exist anymore, for example.
How do you deal with this situation in practise?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
我认为分支维护开发人员有责任将适当的更改合并到主干的当前状态中。 有几种可能性:
案例1和案例2是通常的维护开发路径。 情况3是您正在考虑的情况,其中主干代码不能接受任何形式的维护补丁。 如果开发人员无法自己确定主干中是否存在相同的问题,那么他应该将问题输入到问题跟踪系统中。 这个问题将引导主干开发人员考虑在维护分支中打补丁的原因以及相同的缺陷是否仍然存在。 针对主干中可能的缺陷输入新问题应该是维护开发人员的最后手段。
让维护开发人员尝试将补丁应用于更新后的主干的好处之一是提高他们对新代码库的熟悉程度。 最终,他们将耗尽维护工作,并需要使用新的主干。 至少具有基本的熟悉程度将大有裨益。
I would consider it the responsibility of the branch maintenance developer to merge the appropriate change into the current state of the trunk. There are several possibilities:
Cases 1 and 2 are the usual maintenance development paths. Case 3 is the case you are considering, where the trunk code cannot accept the maintenance patch in any form. If the developer cannot himself determine whether the same problem might exist in the trunk, then he should enter an issue into the issue tracking system. This issue would direct the trunk developers to consider the reason for the patch in the maintenance branch and whether the same defect might still exist. Entering a new issue for a possible defect in the trunk should be a last resort for the maintenance developer.
One benefit of having maintenance developers try to apply patches to the updated trunk is to increase their familiarity with the new code base. Eventually, they will run out of maintenance work and will need to work with the new trunk. Having at least a basic level of familiarity will be of great benefit.
这归根结底是一个关于团队沟通的问题,而不是一个简单的分支/合并问题。
与所有此类情况一样,第一步是意识到您有问题。 这是你已经做过的事情。
然后,您需要提醒整个团队注意该问题。
一旦你做到了这一点,我认为有两种不同的路径:
如果维护分支不经常使用,比如发布的代码相当成熟并且没有错误,你可以决定代码冻结。 每个开发人员必须在 10 月 32 日之前完成他/她正在做的工作,并将这些更改合并回主干。 然后应该关闭或冻结分支机构。 然后,主干上的工作就可以继续进行,新的软件就可以发布了。
如果分支中存在频繁或紧急的更改和修复,则此问题会更加复杂。 仍然需要进行代码冻结,否则主干将被多次破坏。 但在这里,开发人员仍然需要临时修复问题并将其提供给客户。 我建议主干代码冻结后分支中的每个更改都应记录在错误跟踪数据库中(在每种情况下都必须),并特别指出该问题已在分支 N 中修复但尚未合并到主干中。 这需要仔细记录,以便记住每个相关细节。
在主干重构之后,但在清理、抛光、标记和发布之前,请检查错误数据库,特别是在分支中而不是主干中修复的项目。 它们仍然相关吗? 如果有必要,现在是再次更改代码的时候了。 这可能意味着短时间内需要双重工作,但希望代码现在更易于维护。
一旦所有已知问题都得到修复,新版本就可以发布,旧分支就可以关闭。
This is ultimately a question about team communication, rather than a simple branching/merging question.
The first step, as in all such cases, is realizing that you have a problem. This is something that you've done.
Then, you need to alert the whole team to the issue.
Once you've done that, I think there are two different paths:
If the maintenance branches are used infrequently, say the released code is fairly mature and bug-free, you can decide on a code freeze. Each developer must finish what he/she is working on by 32 Octember, and merge those changes back into the trunk. The branches should then be either closed or frozen. Then, work can continue in the trunk, and the new software can be released.
If there are frequent or urgent changes and fixes in the branches, this issue is more complicated. There still needs to be a code freeze, or the trunk will be clobbered multiple times. But here, developers still need to fix things in the interim and get them out to customers. I suggest that every change in the branches after the trunk code freeze should be recorded in the bug tracking database (a must in every situation) with a special indication that this was fixed in branch N but not yet merged to the trunk. This requires careful logging so that every relevant detail is remembered.
After the trunk is refactored, but before it is cleaned, buffed, tagged, and released, review the bug database, particularly the items fixed in the branches but not the trunk. Are they still relevant? Now is the time to change the code again, if necessary. It may mean double work for a short while, but hopefully the code is much more maintainable now.
Once all the known issues are fixed, the new version can be released, and the old branches can be closed.
我能想到的唯一答案是在开始重构之前创建一个维护主干分支。 您可以像维护主干一样维护这个新分支,并正常合并发布分支的更改。 展望未来,您必须小心混合新旧源库的更改。
另一种选择是尝试类似 MolhadoRef (关于 MolhadoRef 和重构感知 SCM 的博客文章),如果您能找到一个可用于生产的等效系统满足您的需求。 从理论上讲,这是重构感知的源代码控制。 我已经有一段时间没有研究它了,但最后我记得它还远远不只是一篇研究论文和概念验证。
The only answer I've been able to come up with is to create a maintenance-trunk branch just before you start refactoring. You maintain this new branch as if it were a trunk, merging changes to and from release branches as normal. Going forward, you then have to be careful about mixing changes from the old and new source bases.
The other alternative is to try something like MolhadoRef (blog article about MolhadoRef and Refactoring-aware SCM), if you can find a ready-for-production equivalent system that meets your needs. This is, in theory, refactoring-aware source control. I haven't looked into it in a while but last I recall it was still pretty far from being anything more than a research paper and proof-of-concept.
在实践中,您可能需要做额外的工作才能使新更改向后兼容。
第 1 步:开始重构组件。 在每个步骤中,保留旧接口,但将调用迁移到新实现。 请注意,随着新接口/API 的建立,这可以通过几个步骤来完成。 单元测试应该能够验证从旧版本到新版本的迁移是否正确,但此步骤很可能仍会产生测试/QA 开销。
第2步:新版本投入生产; 确保每个人都知道它。 此时,旧版本中不会添加任何新功能,所有新的(或更改的)调用者都使用新版本。
第3步:找到调用旧接口的所有内容(使用工具来执行此操作),并更改所有内容以调用新接口。 这也可能会产生大量的测试/质量检查开销。 不过,每个调用者一次可以提交/释放一个。
第 4 步:此时,新版本已上线,并且没有调用者访问旧版本。 安全删除。
请注意,如果 API 是公开的,并且您无法控制调用它的人(例如 Microsoft 等公司),您可能永远无法完成第 2 步。
这个过程可能很慢,并且需要大量的纪律、沟通和测试。 但在替代方案永远追赶/整合的情况下,这可能是一个合理的选择。
In practice, you may have to do extra work to make your new changes backwards compatible.
Step 1: Start refactoring the component. With each step, keep the old interface around, but have it migrate calls to the new implementation. Note that this can be done in several steps as the new interface/API is built up. Unit tests should be able to verify that the migration from old to new works correctly, but this step will most likely still incur testing/QA overhead.
Step 2: The new version is live in production; make sure everyone knows about it. At this point, no new features are added to the old version, and all new (or changed) callers use the new version.
Step 3: Find everything (use tools to do this) that calls the old interface, and change everything to call the new interface. This probably incurs a lot of testing/QA overhead, too. Each caller can be committed/released one at a time, though.
Step 4: At this point, the new version is live, and there are no callers left that access the old version. Delete it safely.
Note that where the API is public and you don't control the people who call it (companies like Microsoft, for example), you might never be able to go past step #2.
This process can be slow, and it requires a lot of discipline, communications, and testing. But in cases where the alternative is playing catch-up/integration forever, it might be a reasonable option.
鉴于修复错误的大量成本是重现问题并测试修复。 您能否编写一个适用于所有分支的自动化测试,即使必须对每个分支进行不同的代码修复?
Given that a lot of the cost of fixing a bug is reproducing the problem and testing the fix. Can you write an automated test that will work in all the branches, even if the code fix has to be done differently for each branch?
当您的维护分支不再与主干兼容时,是时候为此目的创建新分支了。 也就是说,在大项目开始时,您要确保所有开发人员都知道主干中即将出现新功能,以便他们可以更好地选择在何处实施修复。 据推测,如果主干中发生的代码更改非常显着,以致于无法支持维护,则应将维护合并到主干中。
At the point that your maintenance branches are no longer compatible with the main trunk, it would be time to create new branches for that purpose. That is, at the start of the big project, you make sure all your developers are aware that new functionality is coming in the main trunk, so that they can make a better choice of where to implement fixes. Presumably, if the code changes occurring in the main trunk are so significant as to render the maintenance non-supportable, then the maintenance should be incorporated into the main trunk.
创建一个维护分支并让它充当主干和版本分支之间的缓冲区。
对版本分支的更改会进入维护分支,然后仅在可以的情况下传播到主干,反之亦然。
不过,我认为没有灵丹妙药。 随着分支越来越分歧,它们将变得不兼容,因此您必须考虑支持它们多长时间。 否则,您可能会多次修复错误,但各个分支的情况略有不同。
Create a maintenance branch and have it act as a buffer between trunk and the version-branches.
Changes to the version-branches go into the maintenance branch, and then propegate into trunk only if they can and vice versa.
I don't think there's a silver bullet, though. As branches diverge more and more, they will grow incompatible and so you have to consider for how long you will support them. Otherwise you might end up fixing bugs more than once but slightly differently for the various branches.
这可能是一个工作量很大的建议,但我首先想到的是将所有内容合并回主干。 每个人的更改都会合并回主干副本并将它们全部保存在一起。 然后,根据需要重构主干。 现在你有了一个可以工作的主干,所有的修复都放在一起了。
不幸的是,这意味着维护分支中的任何修复都需要放在一起并放入中央主干中。 我意识到这将是一项繁重的工作,但我认为这将允许重构所有内容,并且维护分支中的任何改进都将属于主分支。 我对此可能很天真,但我还没有真正参与过生产项目,也不知道维护分支上到底有什么。 我认为这将使主干完全更新,并且所有维护改进都将集成到主干中。
我认为这样做可以最大限度地提高所有分支的质量,并使重构扩展到重构后要分支的所有分支。 这也是将您的团队聚集在一起进行所有合并的好方法。
This may be a very work intensive suggestion, but the first thing that comes to mind for me is merging everything back to the trunk. Everyone's changes get merged back to the trunk copy and keep them all together. Then, refactor on the trunk however you want. Now you have a working trunk, with all the fixes put together.
Unfortunately, this would mean that any fixes in the maintainance branches would need to get thrown together and into the central trunk. I realize this would be a whole lot of work, but I think that this would allow everything to be refactored, and any improvements in the maintainance branches would belong in the main branch. I may be naive about this, but I haven't really worked on a production project, and also don't know exactly what's on the maintainance branches. I figure this would make the trunk fully updated and all of your maintainance improvements would be integrated into the trunk.
I figure doing it this way would maximize the quality of all of your branches and have your refactoring spread over all of the branches you would branch after the refactoring. This would be a good way to bring your team together for all of the merging, as well.
我看到有两种不同的方法来解决这个问题:
1.
不应在主干中对主干进行重大更改(例如主要重构)。 它们应该在分支中完成,并在足够稳定时合并回主干。
应定期将主干的更改与其他维护分支合并。 仅在稳定时将重构合并到主干的原因是因为这些将被合并到维护分支中。 但是,如果没有机会使这些更改稳定,那么选项 2 会更好。
对维护分支进行更改后,可以将它们合并回主干。
2.
创建维护分支的一个分支(每个分支一个)。 这将用于将主干与每个维护分支合并。 (请注意,应使用 SVN 外部组件或等效组件,以限制维护分支的数量)。
在主干中进行所有重构并将其合并到维护分支的分支中。 当您发布或认为主干稳定时,请将维护版本的这些分支合并回各自的分支。 然后这些可以依次合并回主干。
实际上,每个维护分支都成为一个“子干线”。
请注意,此场景强调了未来维护和前期维护之间的权衡。 代码中的分支和差异越多,需要的前期维护就越多。 好的一面是增量维护更加容易。
I see two separate ways to tackle this:
1.
Significant changes to the trunk (like a major refactoring) shouldn't be done in the trunk. They should be done in a branch and merged back into the trunk when they are stable enough.
Periodically the changes to the trunk should be merged with the other maintenance branches. The reason for only merging the refactoring into the trunk when it's stable is because these will then get merged into the maintenance branches. If however there is no opportunity to make these changes stable then option 2 would be better.
After changes to the maintenance branches have been made they can then be merged back into the trunk.
2.
Create a branch of the maintenance branches (one branch for each). This will be used for merging the trunk with each maintenance branch. (Note that the use of SVN externals or equivalent should be used in order to limit the number of maintenance branches).
Do all your refactoring in the trunk and merge this into the branches of the maintenance branches. When you release or think that the trunk is stable then merge these branches of the maintenance releases back into their respective branches. These can then in turn be merged back into the trunk.
In effect each maintenance branch becomes a "sub trunk".
Note that this scenario highlights the trade-off between future maintenance and upfront maintenance. The more branches and differences you have in your code the more upfront maintenance is required. The good part is that incremental maintenance is much easier.
我只能回应其他人所说的,同时强调补丁队列可能带来的真正痛苦。
如果你有一个预定义的(并且坚如磐石的)合并窗口,那么你应该只有两周的时间来应对。
I can only echo what others have said, while stressing the real pain in the a$$ that patch queues can become.
If you have a predefined (and iron clad) merge window, you should only have two weeks of hell to deal with.
我认为最好的选择是进行迭代重构。 与其在私有分支上的一个大人物中进行所有重构,不如一次进行一个阶段。 在分支上进行几组更改,然后当您知道它们稳定时,将它们合并到主干。 在其他分支上工作的开发人员将负责不断地使他们的分支与主干保持同步。
经常合并一小部分更改比合并差异很大的大型分支要少得多。 合并的次数越多,最终需要做的工作就越少。
I think your best option is to have iterative refactoring. Instead of doing all of the refactoring in one big shot on a private branch, do it one phase at a time. Make a few sets of changes on the branch and then when you know they are stable, merge them to the trunk. Developers working on other branches would be responsible for continually keeping their branch up to date with the trunk.
Merging a small set of changes very often is going to be a lot less working than merge large branches that differ quite a bit. The more often you merge, the less work you will have to do in the end.
在我们的项目中,我们主要不修复版本维护分支中的更改。 如果存在错误并且
In our project, we don't primarily fix changes in version maintenance branches. If there's a bug and
您是否必须拥有那么多分支?
主干的工作是不是因为项目计划说当前版本已经准备好交付而才开始,因此它已经交付了?
您是否有很多维护分支,因为客户出于某种原因拒绝升级到最新版本? 如果是这样请说明原因。
您是否有太多旧版本,因为与下一个主要版本之间的差距太大?
您是否会向不会升级的客户收取更多维护费用,因为这会增加您的成本?
对评论的回复:
,但即使 XP SP3 已经发布,微软仍然不支持 Window XP SP1。
这不是黑白分明的,即使你无法停止支持旧版本,你也许可以减少支持旧版本的数量。 问题是销售/支持喜欢说“是”,但开发却很痛苦,因此您需要让销售/支持人员站在一边。
Do you have to have that many branches being worked on?
Was work on the trunk only started when it did because the project plan said the current release would be ready to ship, therefore it was shipped?
Have you got lots of maintenance branches because customers are refusing to upgrade to the latest release for some reason? If so address the reason.
Do you have too many old releases becouse the gap before the next main release is too great?
Do you charge the customers that will not upgrade more for maintenance, as it cost you more?
Response to comment:
Is very true, However Microsoft does not still support Window XP SP1 even though XP SP3 is out.
This is not black and white, even if you can’t stop supporting old version, you may be able to reduce the number of old versions you support. The problem is that Sales/Support likes to say yes, but development gets the pain, so you need to get your sales/support people on side.
正如 Greg 指出的那样,有几种可能的情况。
我添加了一个需要手动合并的情况(2.5),但由于您已将方法从其原始位置移开,然后应用了一些更改,因此很难合并,特别是如果“基本”代码也被修改了在“维护”分支中。 这并不像听起来那么罕见,事实上,将方法移动到不同的位置并应用小修复是很常见的。
我们开发了一个名为 Xmerge(交叉合并)的工具,这是实现重构感知合并的第一步。 它还不是自动的,但它有助于处理涉及移动代码的棘手合并。 这里描述了并且已经集成在塑料单片机2.7。
我们正在研究:自动移动检测,并且能够“交叉合并”到多个目标文件(将代码移动到另一个文件,这也很常见)。
As Greg pointed there are several possible scenarios.
I'd add a case (2.5) where manual merge is required, but since you've moved a method away from its original location and then applied some changes, it becomes hard to merge, specially if the "base" code was also modified in the "maintenance" branch. This is not as uncommon as it sounds, in fact moving a method to a different location and applying a small fix is pretty common.
We've developed a tool called Xmerge (cross-merge) which is a first step towards refactor-aware merging. It's not yet automatic, but it helps dealing with tough merges involving moved code. It's described here and is already integrated in Plastic SCM 2.7.
We're working on: automatic move detection and also being able to "cross merge" towards multiple destination files (you move code to another file, which is also pretty common).