I have a mercurial repository with two permanent branches, default and UAT. Every once in a while, we deploy (promote) a new version of our application to the UAT environment and we do this by merging a stable default commit across to the UAT branch. Occasionally things will get bug-fixed in the UAT branch, and these bug-fixes get merged back to default.
On the UAT branch I need to change a few things for deployment purposes - connection strings and various environmental settings. What I tried to do was to make these change in the UAT branch and commit them (all as one commit) right after having merged default into UAT. I then dummy-merged this one commit back onto default - the thinking being that because default now has this commit in its ancestry future bugfix merges from UAT onto default will not try to redo these UAT-specific changes.
However things have not gone as smoothly as I had hoped. Starting with the dummy-merged commit onto default, I've tried both of the following scenarios:
1) Make a few more commits to default and then "promote" to UAT (merge default onto UAT)
2) Make a bugfix on UAT and "backport" it to default (merge UAT onto default)
In between running #1 and #2 I've stripped everything out so that both scenarios start from the same point.
What I'm seeing is that depending on the last direction merged I still need to inspect the changed files after doing one or the other merge and revert - sometimes the merge tries to put the default configurations into UAT and sometimes the UAT configurations into merge.
If I revert the configuration changes and commit the merge, then future merges in the same direction behave properly, but the minute I go in the other direction, the merge again puts the wrong configurations into the files.
What am I missing?
I believe the problem is similar to the problem in this question: merging doesn't work the way you think it works. Merging is only a question of comparing file-states, it's not a matter of applying changes from one branch onto another.
Your starting point is a history like this:
UAT: ... x --- y --- z
\
default: ..... a --- b --- c
where x
and y
contain the config settings for UAT and b
is the dummy merge with no config settings. So the files in b
look like they did in a
— they were dummy merged.
If you now make a new change on default
that you want to promote to UAT, you'll work with:
UAT: ... x --- y
\
default: ..... a --- b --- c
The merge is between y
and c
. It's a degenerate merge where the common ancestor is y
itself. This means that all changes between b
and c
will "win" in the three-way merge. The table for how hunks are merged in a three-way merge is:
ancestor local other -> merge
old old old old (nobody changed the hunk)
old old new new (they changed the hunk)
old new old new (you changed the hunk)
old new new new (hunk was cherry picked onto both branches)
old foo bar <!> (conflict, both changed hunk but differently)
Note that the merge result doesn't depend on the "direction" of the merge: the table is symmetric with regard to the local
and other
columns. Here, both ancestor
and local
is y
, and other
is c
. So the table becomes:
ancestor local other -> merge
old old new new (they changed the hunk)
You can see that the merge result always contains the new
change that was made in c
.
It's not important that the merge was degenerate. Assuming you have a new commit on UAT and that this commit doesn't touch the config strings, then you'll get the same behavior when you merge (in either direction, merges are symmetric).
The normal solution to this problem is to externalize the config strings. Put them somewhere outside of version control — environment variables, an unversioned config file, etc. If you can, then put a config file under version control as a template. You then create an unversioned config file for the UAT branch that includes the version controlled config file. You override settings as needed in this unversioned config file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With