This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.


Merge conflicts in source control


<n>This article originally appeared on <a href="">earthli News</a> and has been cross-posted here.</n> <hr> I was recently asked a question about merge conflicts in source-control systems. <bq>[...] there keep being issues of files being over written, changes backed out etc. from people coding in the same file from different teams.</bq> My response was as follows: <abstract>tl;dr: The way to prevent this is to keep people who have no idea what they're doing from merging files.</abstract> <h>Extended version</h> Let's talk about bad merges happening accidentally. Any source-control worth its salt will support at least some form of automatic merging. An automatic merge is generally not a problem because the system will <i>not</i> automatically merge when there are conflicts (i.e. simultaneous edits of the same lines, or edits that are "close" to one another in the base file). An automatic merge can, however, introduce <i>semantic</i> issues. For example if both sides declared a method with the same name, but in different places in the same file, an automatic merge will include both copies but the resulting file won't compile (because the same method was declared twice). Or, another example is as follows: <div align="left"> <h>Base file</h> <code> public void A(B b) { var a = new A(); b.Do(a); b.Do(a); b.Do(a); } </code> </div> <div align="left" style="margin-left: 30px"> <h>Team One version</h> <code> public void A(B b) { var a = new A(); b.Do(a); b.Do(a); b.Do(a); <hl>a.Do();</hl> } </code> </div> <div align="left" style="margin-left: 30px"> <h>Team Two version</h> <code> public void A(B b) { var a = <hl>null</hl>; b.Do(a); b.Do(a); b.Do(a); } </code> </div> <div align="left" style="margin-left: 30px"> <h>Automatic merge</h> <code> public void A(B b) { var a = <hl>null</hl>; b.Do(a); b.Do(a); b.Do(a); <hl>a.Do();</hl> } </code> </div> <div style="clear: both"> The automatically merged result will compile, but it will crash at run-time. Some tools (like ReSharper) will display a warning when the merged file is opened, showing that a method is being called on a provably null variable. However, if the file is never opened or the warning ignored or overlooked, the program will crash when run.</div> In my experience, though, this kind of automatic-merge "error" doesn't happen very often. Code-organization techniques like putting each type in its own file and keeping methods bodies relatively compact go a long way toward preventing such conflicts. They help to drastically reduce the likelihood that two developers will be working in the same area in a file. With these relatively rare automatic-merge errors taken care of, let's move on to errors introduced deliberately through maliciousness or stupidity. This kind of error is also very rare, in my experience, but I work with very good people. <bq>Let's say we have two teams: Team One - branch one > Works on file 1 Team Two - branch two > Works on file 1 Team One promotes file 1 into the Master B branch, there are some conflicts that they are working out but the file is promoted.</bq> I originally answered that I wasn't sure what it meant to "promote" a file while still working on it. How can a file be commited or checked in without having resolved all of the conflicts? As it turns out, it can't. As documented in <a href="" source="Stack Overflow">TFS Server 2012 and Promoting changes</a>, promotion simply means telling TFS to pick up local changes and add them to the list of "Pending Changes". This is part of a new TFS2012 feature called "Local Workspaces". A promoted change corresponds to having added a file to a change list in Perforce or having staged a file in Git. The net effect, though, is that the change is purely local. That is has been promoted has nothing to do with merging or committing to the shared repository. Other users cannot see your promoted changes. When you pull down new changes from the server, conflicts with local "promoted" changes will be indicated as usual, even if TFS has already indicated conflicts between a previous change and another promoted, uncommitted version of the same file. Any other behavior else would be madness.<fn> <bq>Team Two checks in their file 1 into the Master B branch. They back out the changes that Team One made without telling anyone anything.</bq> There's your problem. This should never happen unless Team Two has truly determined that their changes have replaced all of the work that Team One did or otherwise made it obsolete. If people don't know how to deal with merges, then they should not be merging. Just as Stevie Wonder's not allowed behind the wheel of a car, neither should some developers be allowed to deal with merge conflicts. In my opinion, though, any developer who can't deal with merges in code that he or she is working on should be moved another team or, possibly, job. You have to know your own code and you have to know your tools.<fn> <bq>Team One figures out the conflicts in their branch and re-promotes file one (and other files) to Master B branch. The source control system remembers that file 1 was backed out by Team Two so it doesn't promote file 1 but doesn't let the user know.</bq> This sounds insane. When a file is promoted---i.e. added to the pending changes---it is assumed that the current version is added to the pending changes, akin to staging a file in Git. When further changes are made to the file locally, the source-control system should indicate that it has changed since having been promoted (i.e. staged). When you re-promote the file (re-stage it), TFS should treat that as the most recent version in your workspace. When you pull down the changes from Team 2, you will have all-new conflicts to resolve because your newly promoted file will still be in conflict with the changes they made to "file 1"---namely that they threw away all of the changes that you'd made previously. And, I'm not sure how it works in TFS, but in Git, you can't "back out" a commit without leaving a trail: <ul> Either there is a merge commit where you can see that Team Two chose to "accept their version" rather than "merge" or "accept other version" Or, there is a "revert" commit that "undoes" the changes from a previous commit </ul> Either way, your local changes will cause a conflict because they will have altered the same file in the same place as either the "merge" or "revert" commit and---this is important---will have done so <i>after</i> that other commit. To recap, let me summarize what this sounds like: <ul> <b>T1</b>: I want to check in file1 <b>TFS</b>: You have conflicts <b>T1</b>: Promote file1 so that TFS knows about (other users can't see it yet because it hasn't been committed) <b>TFS</b>: Okie dokie <b>T2</b>: I want to check in file1 <b>TFS</b>: You have conflicts <b>T2</b>: F&#$ that. Use my version. Oh, and, f&#$ T1. <b>TFS</b>: I hear and obey. T2/file1 it is. <b>T1</b>: OK, I resolved conflicts; here's the final version of file1 <b>TFS</b>: Thanks! *tosses T1/file1 out the window* </ul> I don't believe that this is really possible---even with TFS---but, if this is a possibility with your source-control, then you have two problems: <ol> You have team members who don't know how to merge Your source control is helping them torpedo development </ol> There is probably a setting in your source-control system that disallows simultaneous editing for files. This is a pretty huge restriction, but if your developers either can't or won't play nice, you probably have no choice. <hr> <ft>This is not to rule out such behavior 100%, especially in a source-control system with which I am largely unfamiliar. It only serves to indicate the degree to which I would be unwilling to work with any system that exhibits this kind of behavior.</ft> <ft>Different companies can have different grace periods for learning these two things, of course. I <i>suppose</i> that grace period can be interminably long, but...</ft>