Conflicts solved
This page gives an overview of the sorts of conflicts that Mergiraf is able to handle and which ones are left for the user to solve.
Changes to independent syntax elements
Consider the following situation:
void notify_attendees(long status_code);
void notify_attendees(int status_code);
int notify_attendees(int status_code);
The left side changes the type of the argument of this C function while the right side changes its return type. Both changes can be done independently, so this is resolved to:
int notify_attendees(long status_code);
Neighbouring insertions and deletions of elements whose order does not matter
Another example:
class Bird {
String species;
int weight;
}
class Bird {
String species;
}
class Bird {
String species;
double wingspan;
}
The left and right sides add different attributes to the same Java class. The order of declaration of those attributes does not matter, so the conflict can be resolved to:
class Bird {
String species;
int weight;
double wingspan;
}
In contrast to this, conflicting additions of instructions in a block, or conflicting additions of arguments to a function declaration are not automatically resolved as above, given that the order in which they are inserted matters.
while (true) {
mowTheLawn();
rechargeBatteries();
}
while (true) {
mowTheLawn();
}
while (true) {
mowTheLawn();
returnToHomeBase();
}
In this example, human intervention is needed to decide in which order the rechargeBatteries()
and returnToHomeBase()
statements should be inserted, so a conflict is created.
This type of conflict resolution is enabled for a set of syntactic contexts (called "commutative parents") configured on a per-language basis. To enable the resolution above, we mark field_declaration_list
nodes in Java to be commutative parents. Similarly, the order of attributes in an HTML tag is irrelevant, so given the following revisions
<input type="text" autocomplete="off" />
<input type="text" />
<input type="text" value="hello" />
Mergiraf will output the following, since self_closing_tag
is marked as a commutative parent in HTML:
<input type="text" autocomplete="off" value="hello" />
Conflicting formatting and content changes
When one side reformats the file and the other makes changes to its contents, Mergiraf attempts to retain both the new formatting and the new contents.
In the following example, the left side reformats a function declaration and the right side changes the type of one of its arguments.
fn plan_route(
start: &Location,
end: &Location,
settings: &RouteSettings,
) -> Route {
todo!();
}
fn plan_route(start: &Location, end: &Location, settings: &RouteSettings) -> Route {
todo!();
}
fn plan_route(start: &Location, end: &Location, settings: Option<&RouteSettings>) -> Route {
todo!();
}
In this case, Mergiraf produces the following merge:
fn plan_route(
start: &Location,
end: &Location,
settings: Option<&RouteSettings>,
) -> Route {
todo!();
}
Moving edited elements
Mergiraf can detect that a particular section of source code has been moved in one revision and has been modified by the other revision. In this case, the changes on the latter branch are replayed at the new location. Consider this example:
impl Lawnmower {
fn find_home_station(&self) -> Option<&Station> {
self.neighbouring_stations()
.iter()
.find(|station| self.is_suitable_home(station))
}
fn is_suitable_home(&self, station: &Station) -> bool {
station.id == self.home_station_id
&& !station.occupied
&& station.color == StationColor::Red
}
}
impl Lawnmower {
fn find_home_station(&self) -> Option<&Station> {
self.neighbouring_stations().iter().find(|station| {
station.id == "home"
&& !station.occupied
&& station.color == StationColor::Red
})
}
}
impl Lawnmower {
fn find_home_station(&self) -> Option<&Station> {
self.neighbouring_stations().iter().find(|station| {
station.id == "home"
&& !station.occupied
&& station.color == StationColor::Blue
})
}
}
The left revision extracts the boolean condition out of the closure to turn it into a method, making some changes to it in the same go.
The right revision makes some other changes to the boolean expression (turning Red
into Blue
).
In such a case, Mergiraf is able to replay the changes of the right branch onto the new location of the boolean expression in the left branch,
which gives the following result:
impl Lawnmower {
fn find_home_station(&self) -> Option<&Station> {
self.neighbouring_stations()
.iter()
.find(|station| self.is_suitable_home(station))
}
fn is_suitable_home(&self, station: &Station) -> bool {
station.id == self.home_station_id
&& !station.occupied
&& station.color == StationColor::Blue
}
}
Resolving this sort of conflicts is generally not possible in fast mode and will only work when Mergiraf has access to the original base, left and right revisions.
Line-based merges
In addition to the above, if the files merge cleanly using Git's usual line-based merging algorithm, so will they with Mergiraf. There is however one notable exception. Consider the following situation:
{
"new_letter": "left value",
"alpha": "α",
"beta": "β",
"gamma": "γ",
"delta": "δ"
}
{
"alpha": "α",
"beta": "β",
"gamma": "γ",
"delta": "δ"
}
{
"alpha": "α",
"beta": "β",
"gamma": "γ",
"delta": "δ",
"new_letter": "right value",
}
Git's line-based merging algorithm happily merges those revisions into:
{
"new_letter": "left value",
"alpha": "α",
"beta": "β",
"gamma": "γ",
"delta": "δ",
"new_letter": "right value"
}
This is a problem, as the "new_letter"
key appears twice. In such a situation, Mergiraf outputs a conflict:
{
<<<<<<< Left
"new_letter": "left value",
||||||| Base
=======
"new_letter": "right value",
>>>>>>> Right
"alpha": "α",
"beta": "β",
"gamma": "γ",
"delta": "δ"
}
This works by defining so-called "signatures" for certain children of commutative parents. A signature defines how to build a key for a syntactic element, child of a commutative parent. Such keys should be unique among all children of a given commutative parent. Beyond this example in JSON, this mechanism is used to ensure the uniqueness of import statements, method signatures, struct fields and many other syntactic constructs in various languages. For more details about how they are defined, see the tutorial to teach Mergiraf a new language.
And what about human conflicts?
Have you ever heard of nonviolent communication, also known as giraffe language? It's an interesting framework, suggesting which communication patterns to use or avoid when tensions arise. Check it out!