Search code examples
gitmergegit-merge

How do I accept git merge conflicts from "their" branch for only a certain directory?


Here's my situation: git merge master results in 50 files with merge conflicts. I want 45 of them to just be accepted from master and be done, and I want to manually resolve conflicts in the remaining 5. All 45 of those files are in directory some/dir. The other 5 are scattered elsewhere. IF I wanted just to accept master's changes for all 50 conflicts, I'd run this:

git merge -X theirs master
# OR (same thing):
git merge --strategy-option theirs master

But I don't want that, like I said. I'd like something more like this:

git merge -X theirs master some/dir

so that it automatically chooses "theirs" (master's) side for all conflicts in some/dir, but otherwise let's me manually fix the conflicts. This, however, doesn't exist.

So, my work-around is this:

SHORT DESCRIPTION:

Start a merge, manually fix just the few files I need to, among the many many conflicting files. Back up any files I just fixed. Abort the merge. Redo the merge with git merge -X theirs master to automatically accept master's changes for ALL conflicts in ALL files. Manually copy my few manually-fixed files back into the repo, and commit them.

This avoids having to manually fix 50 files, which can be very tedious and time-consuming when only 5 really require my attention.

FULL, DETAILED STEPS:

  1. Start a normal merge:

     git merge master
    
  2. Manually resolve conflicts in just the 5 files I want to do that in, and save each file. Then, MAKE COPIES OF THEM to a location outside of the repo (or at least in a folder that is ignored by the repo). I now have copies of those 5 files I manually resolved. Here is one way to accomplish this:

     mkdir -p temp  # create `temp` dir inside the repo.
    
     # Note: if you're not aware, the ".git/info/exclude" file in your repo is
     # an **untracked** form of a ".gitignore" file for the repo. We will use 
     # this file below.
    
     # Add a `/temp/` entry to the ".git/info/exclude" file to keep
     # the repo from tracking the /temp/ dir, but withOUT using the
     # shared ".gitignore" file since this is my own personal setting
     # not everyone else on the team necessarily wants.
     echo -e "\n# don't track this temporary folder for my arbitrary use\n/temp/" \
     >> .git/info/exclude
    
     # Now manually make copies of your 5 files you just resolved conflicts in:
     cp some/dir/file1.cpp temp
     cp some/dir/file2.cpp temp
     cp some/dir/file3.cpp temp
     cp some/dir/file4.cpp temp
     cp some/dir/file5.cpp temp
    
  3. Do git merge --abort to abort the merge, then do git merge -X theirs master to redo the merge, except this time automatically accepting all of master's changes for all 50 files (ie: for all conflicts).

  4. Now, manually copy my manually-resolved backup copies from above back into the repo on top of their respective files:

     cp temp/file1.cpp some/dir
     cp temp/file2.cpp some/dir
     cp temp/file3.cpp some/dir
     cp temp/file4.cpp some/dir
     cp temp/file5.cpp some/dir
    
  5. Finally, do git add -A and git commit --amend to amend them to the merge commit, and voila! I have the equivalent of an automatic resolution on the 45 files, and the manual resolution on the 5 files.

Is there a better way?

I think this way works pretty well and is pretty effective, but I'm open to learning alternatives, especially if they are faster or easier.

Update: here is the answer. The currently-most-upvoted answer is in fact not correct, and produces the wrong behavior under certain really important circumstances, despite being the most-upvoted.

Related, but not duplicates:

  1. Merging two branches, how do I accept one branch for all conflicts
  2. Simple tool to 'accept theirs' or 'accept mine' on a whole file using git

Solution

  • After upvoting and marking correct this answer 6 months ago, I realized today after a very large and very botched merge where this was needed that that answer is wrong. Let's say you did this, to update your feature_branch with the latest changes from master:

    git checkout feature_branch
    git merge master
    # conflicts result...
    

    ...and there are a ton of conflicts. You see like 25 of them are inside some/dir, and you want to keep all conflicting changes from feature_branch, so you do any of these 3 commands (all the same thing in this case):

    git checkout -- some/dir
    # OR
    git checkout HEAD -- some/dir
    # OR
    git checkout feature_branch -- some/dir
    

    Well, you just messed up! master (the --theirs side for git merge) had also added some changes and new files inside some/dir, many of which had ZERO CONFLICTS with your feature_branch changes (the --ours side for git merge), and all of which you want (this is the whole point of merging latest master into your feature_branch!) but now they are all gone because you just OVERWROTE THEM with your some/dir from feature_branch. That's not what we want at all!

    So, here's the correct answer:

    # RIGHT ANSWER
    # NB: you must be **in the middle of resolving conflicts in a `git merge`**
    # in order for this to have the behavior I describe here. 
    
    # **Keep conflicting changes** for all conflicts within files inside
    # some/dir, from the "--ours" (`feature_branch` in this case) side.
    # This does NOT delete files added by the `master` (`--theirs`) side which 
    # have **no conflicts** with your `feature_branch` (`--ours`) side. 
    git checkout --ours -- some/dir
    git add some/dir
    git merge --continue
    

    Again, that is NOT the same thing as this:

    # WRONG ANSWER
    
    # **Overwrite this entire `some/dir` directory** with everything from
    # the `feature_branch` (`--ours`) side. 
    # This DOES delete files added by the `master` (`--theirs`) side which 
    # have **no conflicts** with your `feature_branch` (`--ours`) side,
    # which is NOT what we want.
    git checkout feature_branch -- some/dir
    git add some/dir
    git merge --continue
    

    So, here's the right answer in full context:

    # merge latest master into feature_branch to get those upstream changes
    # from other people into your feature_branch
    git fetch origin master:master
    git checkout feature_branch
    git merge master 
    
    # conflicts result here...
    
    # Keep all changes from `feature_branch` for conflicts in some/dir
    git checkout --ours -- some/dir
    # OR, keep all changes from `master` for conflicts in some/dir
    git checkout --theirs -- some/dir
    
    git add some/dir
    git merge --continue
    

    Done!

    For details and clarity, see my answer here: Who is "us" and who is "them" according to Git?. This is covered in the "WARNING WARNING WARNING" section of that answer, although this answer is a more-thorough example of it, and better shows the problem.