9

I am having a real tough time using git.

I have a branch A and a branch B. I want to merge branch A into branch B. I am currently on branch B and I used the following command:

git merge A.

Now when I run git status, I see that I am ahead by two commits and when I look at the files that were altered during the git merge I noticed only two files being altered. However, there are at least a dozen files that I know was changed in branch A that didn't get picked up during the git merge.

Why is git merge only merging those two files when there are clearly other files that need to get merged?

Robin
  • 575
  • 2
  • 10
  • 26

1 Answers1

11

Comparing the two branch tip commits is the wrong thing to do, because that's not what git merge does.

To see what files each of you changed, you must first find the commit that branches A and B most recently shared:

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

(where each round o represents a commit, in the chain of commits people have added over time).

The common commit, which I marked * here, is what Git calls the merge base of the two branches. Git will find this commit automatically, on its own, when you sit at the tip of one of the two branches (such as B) and run git merge A.

Git will then compare, not B vs A, nor A vs B, but rather:

git diff hash-of-* hash-of-tip-of-B    # what did *we* change?

and:

git diff hash-of-* hash-of-tip-of-A    # what did *they* change?

The goal of git merge is to locate and extract commit *, apply all the changes from both branches, and commit that as a new merge commit on the current branch (B), with two parent commits, to make it a merge commit so that Git knows what to use as the merge base the next time too.

If you want to see the hash ID of commit *, you can run git merge-base:

git merge-base --all A B

You can then run git diff, giving it that hash ID for the merge base, and the name A to select the tip of branch A to see what they changed. You can run a second, separate, git diff, giving it that same hash ID for the merge base, and the name B to select the tip of branch B to see what you changed (you might remember what you changed, but it's a good idea to see if Git agrees with your memory, because Git is going to use what it finds, not what you remember!).

There is a short-hand that will run git merge-base for you, that works only with git diff:

git diff A...B   # note the three dots

will compare the merge base of A and B against the commit identified by B, to find what you changed. A subsequent:

git diff B...A   # note the three dots again

will compare the merge base of B and A1 against the commit identified by A, to find what they changed.

(You can add options like --name-status or --name-only to the git diff to affect how git diff shows the changes it calculates / finds. Note that whenever there are renames involved, the merge uses the equivalent of git diff -M50 to find the renames. Modern Git, since 2.9, enables rename detection in git diff as well by default.)


1"Merge base" is a symmetric graph operation, so "merge base of A and B" is by definition equal to "merge base of B and A".

torek
  • 448,244
  • 59
  • 642
  • 775
  • 7
    This doesn't provide a resolution as to how to make the two branches merge all the files. It just tells me that git is a convoluted unintutitive mess at times. – Ian Smith Apr 01 '20 at 18:35
  • @IanSmith: `git merge` *will* merge *from the merge base*, as described. If you want to merge from something other than the merge base, don't use `git merge`. (Consider `git merge-file`, which takes three input *files*, rather than three input *commits*, if you want to specifically take three files and combine them.) – torek Apr 01 '20 at 18:53
  • 1
    Say you revert branch A (mainline) to its very first commit and you want to bring everything from branch B (newMainline) to be the next new commit on branch A. Common sense says `git merge` should be the first tool to try but that isn't what should be used? What would be the right tool for this scenario? – Ian Smith Apr 01 '20 at 19:11
  • If you mean that you did the equivalent of: `git checkout master; git merge --ff-only $(git commit-tree -p HEAD -m urk first-commit-hash^{tree})` so that `master`'s tree now matches the root commit's tree, it's not at all clear what you'd like as the result of "merging" some other commit in the graph. If you have a plan, though, you can make any arbitrary graph you like using `git commit-tree` and then run `git merge` on the resulting commits, knowing how `git merge` works; the resulting *tree* is presumably the set of files you want. – torek Apr 01 '20 at 19:28
  • Note that `git merge` would do nothing in the above case. It's helpful to think of Git as a graph-manipulation tool with a lot of file-updating work attached to the graph operations: see [Think Like (a) Git](http://think-like-a-git.net/). – torek Apr 01 '20 at 19:38
  • @torek Hi, I have a file _foo.py_ that caused a _merge conflict_ at _merge base_. I have a branch _main_ and _other_. I merged _main_ into _other_ by keeping file from _other_. After some work on _main_, I am merging again _main_ into _other_, the conflict between the _foo.py_ does not triggers anymore (but still the files are branch-wise different, but no respective modification where applied on both versions of _foo.py_). If I understand correctly, it is due to the fact no resp. modification where applied on both versions of _foo.py_ that the merge does not warn on the _foo.py_ diffs? – Rémy Hosseinkhan Boucher Jun 09 '23 at 10:48
  • @torek, Note the first merge has a _merge base_ (A) which is different from the later _merge base_ (B), hence it would explain the behaviour. However, even if technically it makes sense, it is a bit confusing and which _git_ tool does the job of purely merging any modifications ? I mean, I'd like _git merge_ to track the oldest _merge base_ rather than the newest ? EDIT: Ok, now If I understand correctly the purpose of _git merge_ is to join **development histories**, then everything make sense I am asking this because the contents of `main...other` and `other...main` differed – Rémy Hosseinkhan Boucher Jun 09 '23 at 11:18