gitgit-merge

Have Git Select Local Version On Merge Conflict on a Specific File?


Say I'm collaborating with someone via a git repository, and there is a particular file that I never want to accept any external changes to.

Is there any way to set up my local repo to not complain about a conflicted merge every time I git pull? I'd like to always select my local version when merging this file.


Solution

  • On the specific instance of a config file, I would agree with Ron's answer:
    the 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 files)

    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 to define a gitattributes value, which defines a custom merge driver.

    The "custom merge driver" is, in this case, a very simple script that 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:

    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:

    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. Your merge driver would always 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:

    By "combining", I mean "aggregate" multiple merge drivers.
    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).