Home > database >  Which commit is HEAD^ after a merge and why isn't it the right one
Which commit is HEAD^ after a merge and why isn't it the right one

Time:10-02

I have main:

* 38f4e95 - (HEAD -> main, origin/main) …
* 2abcc91 - nginx configs blue and green …

From main, I merge a branch and now I have:

*   461eb66 - (HEAD -> main, 
|\  
| * 38f4e95 - (origin/main) .
| * 2abcc91 - nginx configs b
| *   320c676 - Merge branch 
| |\  
| * | 46d0cbd - fix filter (3
* | | 7ea521f - EF Migration 

I expect HEAD^ after a commit to be “the HEAD from just before I commited anything” i.e. 38f4e95. But it isn't, it's a commit from days ago with a whole bunch of work missing. Why?

  • Instead HEAD^2 is what I expect to be HEAD^1 (aka HEAD^).

  • My habit before merging into main is to first merge from main into branch (so issues are found and fixed in the branch). Does this make a difference?

  • Of course the question springs from wanting to undo the merge. It seems like git reset HEAD^ --hard should be just the thing for undo last commit, but instead I have to either be really careful or first note down what the “previous” commit was, or else hope that reset from a remote will neatly solves it.

CodePudding user response:

Both HEAD^1 and HEAD^2 are parents of your merge commit. There's a nice illustration in man gitrevisions:

       Here is an illustration, by Jon Loeliger. Both commit nodes B and C are
       parents of commit node A. Parent commits are ordered left-to-right.

           G   H   I   J
            \ /     \ /
             D   E   F
              \  |  / \
               \ | /   |
                \|/    |
                 B     C
                  \   /
                   \ /
                    A

           A =      = A^0
           B = A^   = A^1     = A~1
           C =      = A^2
           D = A^^  = A^1^1   = A~2
           E = B^2  = A^^2
           F = B^3  = A^^3
           G = A^^^ = A^1^1^1 = A~3
           H = D^2  = B^^2    = A^^^2  = A~2^2
           I = F^   = B^3^    = A^^3^
           J = F^2  = B^3^2   = A^^3^2

CodePudding user response:

It's because the workflow you describe results in fast-forward merges:

My habit before merging into main is to first merge from main into branch (so issues are found and fixed in the branch). Does this make a difference?

This is exactly why it's happening! If you have my-branch checked out and merge from main, then main (commit 38f4e95) is the second parent of that merge commit. When you then switch over to main and merge my-branch, it's a fast-forward merge so the branches are equivalent at that moment.

Note the first and second parents can't switch based on the point of view- they are "baked into" the commit.

So, yes, if you need to revert, you'll want to double-check the first and second parents to make sure you revert the right direction. You can use git log --first-parent to see what -m1 would yield. (And even just git log will display the parent commits for the merge in the proper order.)

Suggestion: Here are 2 possible tweaks you could make to your workflow to prevent this from happening in the future (either of these would prevent it, but I personally prefer both):

  1. Instead of merging main into your feature branch, you could rebase your branch onto main to update it. Then you won't be adding "back merges" that could likely get fast-forwarded into main which flips the desired parent order.
  2. When merging into main, choose the --no-ff option to force creating a new merge commit. Then the first parent will always be what you expect.
  •  Tags:  
  • git
  • Related