How do I tell git to always select my local version for conflicted merges on a specific file?
On the specific instance of a config file, I would agree with Ron's answer:
a config should be "private" to your workspace (hence "ignored", as in "declared in a .gitignore
file").
You may have a config file template with tokenized values in it, and a script transforming that config.template
file into a private (and ignored) config file.
However, that specific remark does not answer what is a broader more general question, i.e. your question(!):
How do I tell git to always select my local version for conflicted merges on a specific file ? (for any file or group of file)
This kind of merge is a "copy merge", in which you will always copy 'ours' or 'theirs' version of a file whenever there is a conflict.
(as Brian Vandenberg notes in the comments, '
ours
' and 'theirs
' are here used for a merge.
They are reversed for a rebase: see "Why is the meaning of “ours” and “theirs” reversed with git-svn
", which uses a rebase, "git rebase
, keeping track of 'local' and 'remote'")
For "a file" (a file in general, not speaking of a "config" file, since it is a bad example), you would achieve that with a custom script called through merges.
Git will call that script because you will have define a gitattributes value, which defines a custom merge driver.
The "custom merge driver" is, in this case, a very simple script which basically will keep unchanged the current version, hence allowing you to always select your local version.
IE., As noted by Ciro Santilli:
echo 'path/to/file merge=ours' >> .gitattributes
git config --global merge.ours.driver true
Let's test that in a simple scenario, with a msysgit 1.6.3 on Windows, in a mere DOS session:
cd f:\prog\git\test
mkdir copyMerge\dirWithConflicts
mkdir copyMerge\dirWithCopyMerge
cd copyMerge
git init
Initialized empty Git repository in F:/prog/git/test/copyMerge/.git/
Now, let's make two files, which will both have conflicts, but which will be merged differently.
echo a > dirWithConflicts\a.txt
echo b > dirWithCopyMerge\b.txt
git add -A
git commit -m "first commit with 2 directories and 2 files"
[master (root-commit) 0adaf8e] first commit with 2 directories and 2 files
We will introduce a "conflict" in the content of both those files in two different git branches:
git checkout -b myBranch
Switched to a new branch 'myBranch'
echo myLineForA >> dirWithConflicts\a.txt
echo myLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in myBranch"
[myBranch 97eac61] add modification in myBranch
git checkout master
Switched to branch 'master'
git checkout -b hisBranch
Switched to a new branch 'hisBranch'
echo hisLineForA >> dirWithConflicts\a.txt
echo hisLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in hisBranch"
[hisBranch 658c31c] add modification in hisBranch
Now, let's try to merge "hisBranch" upon "myBranch", with:
- manual resolution for conflicting merges
-
except for
dirWithCopyMerge\b.txt
where I always want to keep my version ofb.txt
.
Since the merge occurs in 'MyBranch
', we will switch back to it, and add the 'gitattributes
' directives which will customize the merge behavior.
git checkout myBranch
Switched to branch 'myBranch'
echo b.txt merge=keepMine > dirWithCopyMerge\.gitattributes
git config merge.keepMine.name "always keep mine during merge"
git config merge.keepMine.driver "keepMine.sh %O %A %B"
git add -A
git commit -m "prepare myBranch with .gitattributes merge strategy"
[myBranch ec202aa] prepare myBranch with .gitattributes merge strategy
We have a .gitattributes
file defined in the dirWithCopyMerge
directory (defined only in the branch where the merge will occurs: myBranch
), and we have a .git\config
file which now contains a merge driver.
[merge "keepMine"]
name = always keep mine during merge
driver = keepMine.sh %O %A %B
If you do not yet define keepMine.sh, and launch the merge anyway, here is what you get.
git merge hisBranch
sh: keepMine.sh: command not found
fatal: Failed to execute internal merge
git st
# On branch myBranch
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: dirWithConflicts/a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
type dirWithConflicts\a.txt
a
<<<<<<< HEAD:dirWithConflicts/a.txt
myLineForA
=======
hisLineForA
>>>>>>> hisBranch:dirWithConflicts/a.txt
That is fine:
-
a.txt
is ready to be merged and has conflict in it -
b.txt
is still untouched, since the merge driver is supposed to take care of it (due to the directive in the.gitattributes
file in its directory).
Define a keepMine.sh
anywhere in your %PATH%
(or $PATH
for our Unix friend. I do both of course: I have an Ubuntu session in a VirtualBox session)
As commented by lrkwz, and described in the "Merge Strategies" section of Customizing Git - Git Attributes, you can replace the shell script with the shell command true
.
git config merge.keepMine.driver true
But in the general case, you can define a script file:
keepMine.sh
# I want to keep MY version when there is a conflict
# Nothing to do: %A (the second parameter) already contains my version
# Just indicate the merge has been successfully "resolved" with the exit status
exit 0
(that was one simple merge driver ;) (Even simpler in that case, use true
)
(If you wanted to keep the other version, just add before the exit 0
line:cp -f $3 $2
.
That's it. You merge driver would aways keep the version coming from the other branch, overriding any local change)
Now, let's retry the merge from the beginning:
git reset --hard
HEAD is now at ec202aa prepare myBranch with .gitattributes merge strategy
git merge hisBranch
Auto-merging dirWithConflicts/a.txt
CONFLICT (content): Merge conflict in dirWithConflicts/a.txt
Auto-merging dirWithCopyMerge/b.txt
Automatic merge failed; fix conflicts and then commit the result.
The merge fails... only for a.txt.
Edit a.txt and leave the line from 'hisBranch', then:
git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version
Let's check that b.txt has been preserved during this merge
type dirWithCopyMerge\b.txt
b
myLineForB
The last commit does represent the full merge:
git show -v 77bc81f5e
commit 77bc81f5ed585f90fc1ca5e2e1ddef24a6913a1d
Merge: ec202aa 658c31c
git merge hisBranch
Already up-to-date.
(The line beginning with Merge does prove that)
Consider you can define, combine and/or overwrite merge driver, as Git will:
- examine
<dir>/.gitattributes
(which is in the same directory as the path in question): will prevail upon the other.gitattributes
in directories - Then it examines
.gitattributes
(which is in the parent directory), will only set directives if not already set - Finally it examines
$GIT_DIR/info/attributes
. This file is used to override the in-tree settings. It will overwrite<dir>/.gitattributes
directives.
By "combining", I mean "aggregate" multiple merge driver.
Nick Green tries, in the comments, to actually combine merge drivers: see "Merge pom's via python git driver".
However, as mentioned in his other question, it only works in case of conflicts (concurrent modification in both branches).
As @ciro-santilli has commented, the simple way to do it to use .gitattributes
with settings it:
path/to/file merge=ours
and enable this strategy with:
git config --global merge.ours.driver true
(I am adding this as an answer to make it more visible but making it a Community Wiki to not try to get above user's credits for myself. Please upvote his comment under the Q here to give him kudos!)