skip to Main Content

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.

2

Answers


  1. 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
    
    Login or Signup to reply.
  2. 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.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search