Pro Git describes how to get this effect in the “Merge Strategies” section of 8.2 Customizing Git — Git Attributes.
Merge Strategies
You can also use Git attributes to tell Git to use
different merge strategies for specific files in your project. One
very useful option is to tell Git to not try to merge specific files
when they have conflicts, but rather to use your side of the merge
over someone else’s.
This is helpful if a branch in your project has diverged or is
specialized, but you want to be able to merge changes back in from it,
and you want to ignore certain files. Say you have a database settings
file called database.xml
that is different in two branches, and you
want to merge in your other branch without messing up the database
file. You can set up an attribute like this:
database.xml merge=ours
And then define a dummy ours
merge strategy with:
$ git config --global merge.ours.driver true
If you merge in the other branch, instead of having merge conflicts
with the database.xml
file, you see something like this:
$ git merge topic
Auto-merging database.xml
Merge made by recursive.
In this case, database.xml
stays at whatever version you originally
had.
Applying it to your situation, first create fileA
$ echo 'master fileA' > fileA
$ git add fileA ; git commit -m "master fileA"
[master (root-commit) fba9f1a] master fileA
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 fileA
and make it special.
$ echo fileA merge=ours > .gitattributes
$ git add .gitattributes ; git commit -m 'fileA merge=ours'
[master 98e056f] fileA merge=ours
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 .gitattributes
$ git config --global merge.ours.driver true
Now we create a representative release
branch.
$ git checkout -b release
Switched to a new branch 'release'
$ echo 'release fileA' > fileA
$ git add fileA ; git commit -m 'release fileA'
[release 53f3564] release fileA
1 files changed, 1 insertions(+), 1 deletions(-)
Nothing special has happened yet: version control is merely working as it’s supposed to at this point.
Now, we implement Feature B back on master
.
$ git checkout master
Switched to branch 'master'
$ touch featureB ; echo 'With Feature B' >> fileA
$ git add featureB fileA ; git commit -m 'Feature B'
[master 443030f] Feature B
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 featureB
Try to contain your excitement.
Here is where our special merge driver comes into play. Our hero wants to merge the new code from master
into release
. First switch branches.
$ git checkout release
Switched to branch 'release'
Sanity check that fileA
contains what we expect.
$ cat fileA
release fileA
Merge Feature B from master
.
$ git merge master
Auto-merging fileA
Merge made by recursive.
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 featureB
The line Auto-merging fileA
is a clue that something special happened. Indeed:
$ cat fileA
release fileA
How It Works
The section “Defining a custom merge driver” in the gitattributes documentation explains.
The merge.*.driver
variable’s value is used to construct a command to
run to merge ancestor’s version (%O
), current version (%A
) and the
other branches' version (%B
). These three tokens are replaced with the
names of temporary files that hold the contents of these versions when
the command line is built …
The merge driver is expected to leave the result of the merge in the
file named with %A
by overwriting it, and exit with zero status if it
managed to merge them cleanly, or non-zero if there were conflicts.
The custom ours
driver uses almost none of this machinery, only the true
command to exit with zero status. This achieves the desired effect because it starts with fileA
from whichever branch we’re currently on — which is the result we want — then does nothing in the merge-overwrite phase (i.e., ignores the other branch’s version named by %B
), and finally tells git all is well with a successful exit status.