Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git merge says Already up-to-date but there's a committed difference in the branches

Tags:

git

git-merge

I have two branches: master and develop. When the changes from develop are finalized they are merged into master.

At this point, the branches should be synchronized. However, I noticed that one file is missing from master but is on the develop branch. (It looks like it was deleted somehow but I cannot find any evidence of this using the git log -- myfile.txt command.

I tried to merge develop to master but it says that there is nothing to merge.

> git checkout master
> git merge --no-ff develop
Already up-to-date

How do I re-merge that one missing file that is in our develop branch into our master branch?

like image 748
whyceewhite Avatar asked Oct 21 '22 11:10

whyceewhite


1 Answers

You don't merge this at all; instead, you just extract the file you want from the develop branch and commit it on the master branch. (I'm assuming the "missing" file was never merged in, perhaps due to a mistake by whoever made the last however-any merges, or perhaps intentionally.) To extract a file from another branch, while on master:

# run "git status" here to make sure you're on master
# and have no other changes you want to commit first!
git checkout develop -- path/to/file

Because this kind of checkout writes "through" the index, the new file is now staged and ready to be committed to branch master.

Discussion below.


A merge does not actually synchronize branches (more specifically, it does not mean they will have the same work-trees, by any means). All git merge does is look through the commit histories to see what changes, if any, should be merged. This means it has to find the most recent common history. Let's draw some commit graph fragments to illustrate this.

git checkout master

picks up some specific commit, then:

git merge <sha1-or-other-identifier-like-develop>

tells git to start at the identified SHA-1 and see where that branch was last "together with" master. This is the SHA-1 of the "merge base":

...-o-*-o-o-o   <-- master
       \
        o-o     <-- develop

The merge-base is the commit marked * here, and there are commits after * on the line identified by develop that are not on the line identified by master, so this sequence needs a merge.

However:

...-o-*-o-o-o   <-- master
       `---___
              `---- develop

Here, develop points directly to commit * (the merge-base, again). There are no commits since then underneath develop that are not also found underneath master. So this is Already up-to-date. Likewise for:

...-o-o-o-M   <-- master
     \   /
      o-*     <-- develop

This is the kind of situation you will see if develop was merged fairly recently: there's a merge commit on master, that I have marked M. The merge-base is still commit *; develop points directly to that commit, as does the merge commit M. So again there is nothing to merge (no commits on develop that are not already on master, courtesy of merge-commit M).

If (git thinks) a merge is needed, git will diff the base (the * commit) against the newest commit on the current branch (the tip of master), and also diff the base against the commit you asked to merge-in (in this case, the tip of develop). It then (in essence) looks through the two diffs, taking any changes in the second one that are not already in the first, and applying them to the work-tree.

If all goes well, git generally makes a merge commit at this point (although you can suppress it). If not, it stops with a merge conflict and makes you resolve the problem yourself, after which you do your own "git commit" to commit the merge.

The working-tree of the merge commit consists of whatever git auto-resolved, plus whatever you staged after that, if you did the commit manually. Once that merge commit itself exists, the commit graph will be such that git will think a subsequent merge is not needed (until more commits are added to develop for instance).

If (git thinks) a merge is not needed it will of course do nothing.


For two last illustrations:

...-*-----M1--M2  <-- master
     \   /   /
      A-B-C-D     <-- develop

This pattern arises after doing two git merge --no-ff develops into master, with no intervening commits on master:

The first merge started with * as the common merge base (M1 and M2 did not exist yet), so it found the changes between * and B. There were no changes between * and itself so the merge was easy: git added those changes to * (giving the same work-tree as in B) and made merge commit M1.

The second merge started with commit B as the merge-base (M2 did not exist yet). So it took the difference between B and M1, which is none; and the difference between B and D, which is the new development on develop. It then combined those differences, applying them to the tree for M1, and making new merge commit M2. The tree for M2 is the same as that for D.

However, this is a bit different:

...-*-o---M1--M2  <-- master
     \   /   /
      A-B-C-D     <-- develop

In this case, the common base was * as before, but diffing * and o probably produced something. Diffing * and B showed what happened on branch develop. Git combined these two by applying the second set of changes to the work-tree for o, to make merge-commit M1. The work tree for M1 may well be different from that for B, because it also contains whatever happened at commit o. But it could be the same, because git "combines" the changes.

Specifically, if the diff from o to B includes the same change as the diff from * to o, the tree for M1 will match the tree for B. If not, it won't. That is, git keeps only one copy of an identical change. ("Identical" here means "as far as git can tell": it's not particularly smart. If one change was to add the words "shiny metal" and the other change was to add the words "metal that is shiny", git thinks these are different.)

In any case, the second merge repeats the pattern: compare work trees for B vs M1, and B vs D. Whatever changes are found in the second that aren't already contained in the first, apply those to the work-tree for M1, and use the result to make the work-tree for M2.

like image 91
torek Avatar answered Oct 27 '22 12:10

torek