Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git will not merge - says 'Already up-to-date.' when it's not

Tags:

git

I have a git issue I've never encountered before.

I have 2 branches that have 2 files that differ.
If I merge one into the other (in either direction) it says 'Already up-to-date.'

Why does it think they are up to date, and how can I get it to merge?

I read this post git merge does not merge (and others) and they all say "If it says up to date then you have merged correctly."

I realize git thinks they are merged, but they are not - I have files in the 2 branches that differ.

Even if I go in and make changes and I merge it still says 'Already up-to-date.'

Can I get git to "forget" I have merged them and it to merge again?

I don't know what happened to get in this state, but it's been a nightmare. There must be some way to fix this.

Update with what I did:

I am the only one working on this. I was asked to add some new features so I made a branch off master, new_features, and started work on them. Then I was told to put that aside and work on higher priority new features, so I made another branch off master, new_features2, and I started working on those. Then I was told we needed some bug_fixes, and I made another branch off master, bug_fixes. I fixed the bugs and merged bug_fixes into master. Then I went back to working new_features2, got that stuff working, merged master into new_features2, tested, then merged new_features2 into master. All good so far.

Now I'm back to working in new_features. I get that stuff done, and merge master into new_features. It does merge some stuff. I test, and I find that neither the new_features2 stuff nor the bug_fixes are working quite right. I look at the merged code and it seems some of the changes from new_features2 and bug_fixes are in new_features, but not all of them. I try to merge again - it says "Already up-to-date."

like image 639
Larry Martell Avatar asked Apr 22 '16 21:04

Larry Martell


1 Answers

Large edit: you probably want rebase.

I'm putting the rebase section at the top, but you may want to read the rest first.

Rebasing

One of the most useful things in git is to cherry-pick an entire "branchlet" (a string of commits ending in a branch tip) to, in effect, move them. That is, assuming you are now comfortable with cherry-picking (see below if needed), let's say you have something like this in your commit graph:

             o--o         <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o    <-- develop
      \
       o--o--o--o         <-- feature

You started work on feature a while ago, then had to make some emergency changes on develop and an emergency bug fix that is merged back in now; and now it is time to get back to working on feature.

It sure would be nice, though, to work on a version of feature that starts from the latest good stuff in develop.

So let's do just that. We start by checking out a new feature branch based on the tip of develop:

git checkout -b new-feature develop

giving us this:

             o--o         <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o    <-- develop, HEAD->new-feature
      \
       o--o--o--o         <-- feature

Now we simply cherry-pick in all four feature commits. We could write feature~4..feature but that requires that we count up the commits, so since we know all about git set notation,1 we use develop..feature to find that very same set of commits. This allows us to use as our second git command:

git cherry-pick develop..feature

which copies those four commits:

             o--o                   <-- fix-bug-1234
            /    \
...--o--o--o---o--o--o              <-- develop
      \               \
       \               o--o--o--o   <-- HEAD->new-feature
        \
         o--o--o--o                 <-- feature

Now we rename feature to old-feature and then rename new-feature to feature:

git branch -m feature old-feature
git branch -m new-feature feature

Or we do all of this the easy way, using git rebase. We don't have to start with a git checkout -b, then a git cherry-pick and two rename steps, we just do one git checkout followed by one git rebase:

git checkout feature
git rebase develop

The rebase command uses our one argument, develop, to:

  • figure out which commits to cherry-pick: develop..feature
  • see where to copy them: after the tip of develop

There are fancier forms of rebase that let us separate the list-of-commits-to-cherry-pick from the place-they-go-after, but for the typical case, we do not need these.

Note that when we do a rebase, the rebase command normally omits merge commits from the copying, but copies commits "behind" the merge commits unless they are excluded by the set notation (the develop..feature stuff). When there are merge commits in the sequence this can produce surprising results.

In addition, since rebasing copies commits—it really is a series of cherry pick operations—you are only 100% safe to do this if you are the only person with the originals of those commits. If someone sharing your work (via fetch or push) has the originals as well, they may be using them and they may re-introduce those originals alongside your rebased copies. If you are the only one with the originals, this is not a problem. If those others who do have copies know all about your rebased copies, they can deal with the issue themselves (although they may not want to).


1If you don't know about this set notation, you are missing something that makes life much easier in git.


You are missing the point of a merge—or perhaps it would be more accurate to say that the point of a merge is missing what you would like done.

Let's take a look at the point of merge in the first place.

Suppose you and Alice are working on two different things. You are supposed to get widgets to slice both left and right, rather than slicing rightwards only. Alice is supposed to make crossbars hold up better.

Both of you start from a common base, where widgets only slice rightwards, and crossbars break.

This week you managed to make the widgets slice leftwards, and Alice fixed crossbars. So you (or Alice) now want to combine your work and her work:

$ git checkout larry-widget-fixes
$ git merge alice-crossbar-fixes

If this made all of your files match Alice's files exactly, that would undo all your work from this week.

Therefore, git merge does not make things match up. Instead, it goes back to the common base to see (1) what you changed, and (2) what Alice changed. It then tries to combine these two sets of changes into one combined (merged) change.

In this case, it does so by adding her changes to yours (because we merged her work into yours, rather than your work into hers).

Note that the merge base—the common starting point—is one of the key items here.

What if you don't really want a merge?

Let's say you don't want to combine some changes. You want instead to make some file(s) match.

There are a lot of ways to do this, depending on just what results you want. Let's look at three of these ways.

  1. Just make one file match, by extracting the other version.

    Let's say that while fixing widgets, you accidentally broke shims. Alice's branch has a good copy of shims.data (as does the merge base, for that matter, but let's just use Alice's here):

    git checkout alice-crossbar-fixes -- shims.data
    

    This extracts the version of file shims.data from the tip of Alice's branch, and now you have the one you want.

  2. Just un-do one commit, by commit ID.

    Let's say that you realize you broke the shims in commit badf00d, and all the other changes you made there should be undone too, or it's OK to undo them. You can then run:

    git revert badf00d
    

    The git revert command turns the given commit into a patch, then reverse-applies the patch so that the effect of that commit is un-done, and makes a new commit out of the result.

  3. You want to give up on everything entirely. You realize that everything you did this week, while it makes widgets slice leftwards as well as rightwards, is a mistake because widgets should not slice rightwards at all. Here you can use git reset.

    I won't give an example since this (a) this is probably not what you want; (b) git reset is overly complicated (the git folks stuffed about five to eight logically-different operations into one command); and (c) there are plenty of good examples on StackOverflow already. (This is true for git revert in step 2 as well, but git revert is a lot simpler so I left it in.)

What if you have made a bad merge?

This is more complicated.

Remember that when we did the merge in the first example, we had a common starting point, at the beginning of the week before you and Alice started different tasks:

...--o--*--o--o--o    <-- larry-widget-fixes
         \
          o-o-o-o-o   <-- alice-crossbar-fixes

The common merge base is commit *.

Once you finish your merge, though, we have this (I labeled the merge commit M1 to make it easy to see):

...--o--o--o--o--o--M1   <-- larry-widget-fixes
         \         /
          o-o-o-o-o      <-- alice-crossbar-fixes

Now suppose Alice makes some more fixes, and let's have you make one more change too:

...--o--o--o--o--o--M1-o     <-- larry-widget-fixes
         \         /
          o-o-o-o-*--o--o    <-- alice-crossbar-fixes

Now you go to pick these up by merging again:

git merge alice-crossbar-fixes

and git finds the common merge base. This is the nearest commit that is on both branches, which I marked with * again. Git will find out what Alice changed since *, and what you changed since *, and try to combine those changes. You already picked up all of Alice's earlier changes—they are in your branch already, in M1—so it is a good thing that git does not look at those again. Git will just try to add Alice's new changes since * to your changes since * (your version of Alice's changes).

But what if you got them wrong? What if your version of Alice's changes, in M1, are a bunch of wrong files?

At this point you must choose whether to retry the merge, or to use git's many other tools to fix things up.

Retrying a merge

To retry the merge, you must back up to a point before the merge happened. For instance, suppose we draw a new arrow to the commit just before M1, like so:

                   ..........<-- retry
                  .
...--o--o--o--o--o--M1-o     <-- larry-widget-fixes
         \         /
          o-o-o-o-o--o--o    <-- alice-crossbar-fixes

(imagine it's an arrow rather than just some crappy ASCII dots, anyway). We can leave the existing branch labels in there, pointing where they do, we just add this new retry, by doing:

git branch retry <commit-id> && git checkout retry

or:

git checkout -b retry <commit-id>

(these both essentially do the same thing, giving us retry pointing to the desired commit). Now if we do:

git merge alice-crossbar-fixes

git will try to merge the commit-before-M1 that retry points to, with the (new, current) tip of alice-crossbar-fixes. Git will once again find a merge base, which is the nearest commit on both of these branches, which is our original merge base again.

In other words, we will re-do the merge, ignoring the bad merge M1, but also ignoring new work done since then.

Cherry picking

We can git checkout -b <commit> on any particular commit to get back to that state, and then start doing things like git cherry-pick to re-add specific commits.

Let's say we like the commit-before-M1 a lot (it looks really close), so we make our new branch—let's call it something other than retry—that points here:

git checkout -b rework <commit-ID>

Now we can pick and choose from other commits, extracting just the changes they made, using git cherry-pick. We give it more commit IDs, more of those 197a3c2... strings, and it compares the state just before that commit to the state at that commit. The result is a patch, but in fact, it's somewhat better than a regular diff-only patch, for two reasons:

  • it has a commit message attached, which git can re-use, and
  • it has a particular place in the commit graph, so git can (if necessary) find a merge base and do a 3-way merge, when applying it! Normally this is not a big deal but it's pretty handy for some cases.

Leaving out the second advantage and just thinking about it as a patch, what git cherry-pick does is to apply the patch to the current commit, then make a new commit, re-using the original commit message.

If you would like to re-apply many commits this way, you can give git cherry-pick multiple commits and it will do them all:

git cherry-pick xbranch~3..xbranch

This applies every commit that is on xbranch except for commits starting at xbranch~3 and earlier, i.e., we count back three steps along xbranch (assuming no merges...) and apply the remaining three commits:

    N N  Y Y Y   [do we take it?]

    | |  | | |
    v v  v v v

...-o-o--o-o-o   <-- xbranch
...-o--o         <-- larry-widget-fixes (before cherry pick)

(and see rebasing, at the top)

like image 191
torek Avatar answered Sep 20 '22 02:09

torek