I like to distinguish three different types of conflict from a version control system (VCS):
- textual
- syntactic
- semantic
A textual conflict is one that is detected by the merge or update process. This is flagged by the system. A commit of the result is not permitted by the VCS until the conflict is resolved.
A syntactic conflict is not flagged by the VCS, but the result will not compile. Therefore this should also be picked up by even a slightly careful programmer. (A simple example might be a variable rename by Left and some added lines using that variable by Right. The merge will probably have an unresolved symbol. Alternatively, this might introduce a semantic conflict by variable hiding.)
Finally, a semantic conflict is not flagged by the VCS, the result compiles, but the code may have problems running. In mild cases, incorrect results are produced. In severe cases, a crash could be introduced. Even these should be detected before commit by a very careful programmer, through either code review or unit testing.
My example of a semantic conflict uses SVN (Subversion) and C++, but those choices are not really relevant to the essence of the question.
The base code is:
int i = 0;
int odds = 0;
while (i < 10)
{
if ((i & 1) != 0)
{
odds *= 10;
odds += i;
}
// next
++ i;
}
assert (odds == 13579)
The Left (L
) and Right (R
) changes are as follows.
Left's 'optimisation' (changing the values the loop variable takes):
int i = 1; // L
int odds = 0;
while (i < 10)
{
if ((i & 1) != 0)
{
odds *= 10;
odds += i;
}
// next
i += 2; // L
}
assert (odds == 13579)
Right's 'optimisation' (changing how the loop variable is used):
int i = 0;
int odds = 0;
while (i < 5) // R
{
odds *= 10;
odds += 2 * i + 1; // R
// next
++ i;
}
assert (odds == 13579)
This is the result of a merge or update, and is not detected by SVN (which is correct behaviour for the VCS), so it is not a textual conflict. Note that it compiles, so it is not a syntactic conflict.
int i = 1; // L
int odds = 0;
while (i < 5) // R
{
odds *= 10;
odds += 2 * i + 1; // R
// next
i += 2; // L
}
assert (odds == 13579)
The assert
fails because odds
is 37.
So my question is as follows. Is there a simpler example than this? Is there a simple example where the compiled executable has a new crash?
As a secondary question, are there cases of this that you have encountered in real code? Again, simple examples are especially welcome.
It is not obvious to come up with simple relevant examples, and this comment sum up best why:
[Which is basically what your example illustrates]
In other words, the real case of semantic conflicts I have encountered in real code after a merge were not simple, but rather quite complex.
That being said, the simplest example, as illustrated by Martin Fowler in his article Feature Branch is a method rename:
As Ole Lynge mentions in his answer (upvoted), Martin Fowler did write today (time of this edit) an post about "semantic conflict", including the following illustration:
Again, this is based on function renaming, even though subtler case based on internal function refactoring are mentioned:
I think a middle ground needs to be found between shot-lived branches and feature-branches.
And merging often is key if you have a group of developer on the same feature branch.
Check out the example in this post by Martin Fowler: http://martinfowler.com/bliki/SemanticConflict.html
Scenario: A method
foo()
exists. Two branches start at this point.foo()
tofood()
.foo()
.When branches 1 and 2 merge, there is no detectable conflict. However, branch 2's call to
foo()
now references a method that no longer exists.