0

I have a special case of ignoring files during merge.

Suppose I have two branches: A and B.
A contains files a.txt and b.txt.
B contains file a.txt but not b.txt. And that's the way it should be.

Now I want to merge the changes in A into B, i.e., update a.txt. However, during the merge I always get a conflict:

CONFLICT (modify/delete): b.txt deleted in HEAD and modified in development. Version development of b.txt left in tree. Automatic merge failed; fix conflicts and then commit the result.

How can I set up .gitattributes so that B's version of the file (i.e., deleted) is being kept, i.e., b.txt ignored during merge?

s.d
  • 4,017
  • 5
  • 35
  • 65

1 Answers1

0

You can't. The attributes you can set in .gitattributes apply only to what I call low level conflicts, but "modify/delete" is what I call a high level conflict.

It's worth mentioning, though, that just because A has b.txt and B does not, does not guarantee you will get always get a modify/delete conflict. What kind of conflict you get—if any—depends as well on the merge base commit. The branch A resolves to the tip commit of that branch, and likewise for B, but git merge does not use only those inputs. It finds a third input:

          o--o--...--o   <-- A
         /
...--o--*
         \
          o--...--o   <-- B

Each round o represents some commit. The commit marked *, which is where the two branches split apart originally—and hence, right now, the first place they come back togther—is the current merge base of the two branch tips.

You now run git checkout B && git merge A. Git compares commit * to the tip of A and finds b.txt modified since *. Git compares * to the tip of B and finds b.txt removed. This is what produces the high level conflict, about which .gitattributes does nothing. So you must merge this file manually and then commit the result:

          o--...--o   <-- A
         /         \
...--o--o           \
         \           \
          o--...--o---o   <-- B

Because this is a true merge, the new commit has two parents. The first parent is the previous tip of B (the straight line across the bottom) and the second parent is the previous and still current tip of A (going up and left). No big deal so far; but now let's make new commits on both A and B:

          o--...--o--o--o   <-- A
         /         \
...--o--o           \
         \           \
          o--...--o---o--o   <-- B

If we now do that git checkout B && git merge A again, Git will find a new merge base, by searching backwards from both tips as before. The first commit that's reachable from both branch tips will be the merge base—but this time, that's a commit on the top row, specifically the one just before the last merge:

          o--...--*--o--o   <-- A
         /         \
...--o--o           \
         \           \
          o--...--o---o--o   <-- B

Git will now diff commit * against the current tips of A and B.

Since presumably B still lacks b.txt, Git will conclude that *-to-ours removes the file. If you haven't touched b.txt in the two new commits on A, though, *-to-theirs doesn't change it. There will be no high level conflict: "unchanged in A, removed in B" resolves to "remove in merge".

torek
  • 448,244
  • 59
  • 642
  • 775