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):
- Instead of merging
main
into your feature branch, you could rebase your branch ontomain
to update it. Then you won't be adding "back merges" that could likely get fast-forwarded intomain
which flips the desired parent order. - 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.