Foxtrot Golf India Tango
What is a foxtrot merge, how to fix it and who is my parent #1.
Why
Inspired by 2016 blog post No Foxtrot Merges Allowed In My Git, repostet on Atlassian Developer Protect our Git Repos, Stop Foxtrots Now! by Sylvie Davies. I started to play around a bit and check hands-on what happens when I merge and rebase. Read the original post and do the foxtrot with me.
Do the foxtrot
Read the post. TL;DR always keep the main
branch as first parent in merges.
I created an example repo on codeberg foxtrot, you can clone it and do the foxtrot with me.
The initial state is:
git log main feat --oneline --graph
* 24d3250 (origin/main, origin/HEAD, main) 6
| * 707f11d (HEAD -> feat, origin/feat) f1
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
There is a feature branch, feat
, that has commit 5
as common ancestor with main
.
Very often developers merge main
into feat
to stay up to date with code changes.
git switch first-parent
, here feat
git merge seconde-parent
, here main
git switch feat
git merge main
git log main feat --oneline --graph
Here comes the foxtrot, commit f1
moved magically in the first parent lane, the one on the left, and commit 6
, from the main
branch, is in the second parent lane.
* xxxxxxx (HEAD -> feat) Merge branch 'main' into feat
|\
| * 24d3250 (origin/main, origin/HEAD, main) 6
* | 707f11d (origin/feat) f1
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
git log -1 <merge-hash>
shows you the parents of the merge commit.
commit xxxxxxxxxxxxxxxxxxxxxxxx (HEAD -> feat)
Merge: 707f11d 24d3250
Author: otherpaco <otherpaco@noreply.codeberg.org>
Date: Sun Jan 28 17:15:50 2024 +0100
Merge branch 'main' into feat
707f11d
first parent
24d3250
second parent
Merge the foxtrot into main
When you are done with your feature and merge feat
into main
, this will be done with a fast-forward merge and results in a foxtrot:
git switch main
git merge feat
git log main feat --oneline --graph
* xxxxxxx (HEAD -> main, feat) Merge branch 'main' into feat
|\
| * 24d3250 (origin/main, origin/HEAD) 6
* | 707f11d (origin/feat) f1
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
Right back where we started from - RESET
Whenever you play around a bit, this commands will bring you back to the initial state.
git switch feat
git reset --hard 707f11d
git switch main
git reset --hard 24d3250
git log main feat --oneline --graph
We should be in the initial state
* 24d3250 (HEAD -> main, origin/main, origin/HEAD) 6
| * 707f11d (origin/feat, feat) f1
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
Rebase to the rescue
When you are at the end of the ‘do the foxtrot’ part,even if you merged main
mulitple times into feat
, the rebase
command will straigthen out your history, and rewrite your histroy - see this great article git rebase: what can go wrong? by Julia Evans. If you want to push your feat
-branch you now have to use the power of the --force
(git push --force-with-lease
).
git switch feat
git rebase main
git log main feat --oneline --graph
* xxxxxxx (HEAD -> feat) f1
* 24d3250 (origin/main, origin/HEAD, main) 6
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
Everything looks just fine, let’s see what happens now:
git switch main
git merge feat
git log main feat --oneline --graph
This was a fast forward merge, too.
* xxxxxxx (HEAD -> main, feat) f1
* 24d3250 (origin/main, origin/HEAD) 6
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
Everything is fine.
Always rebase in feat-branches
Instead of merging main
, one could always use git rebase main
in feat-branches, if you don’t share the branch with others.
No fast-forward
As Sylvie Davies suggests, --no-ff
is another solution but an ugly one imho.
Back to the ‘do the foxtrot’ part.
The next command is very close to the one in merge the foxtrot into main but we add --no-ff
to prevent a fast-forward merge.
git switch main
git merge feat --no-ff
git log main feat --oneline --graph
* xxxxxxx (HEAD -> main) Merge branch 'feat'
|\
| * xxxxxxx (feat) Merge branch 'main' into feat
| |\
| |/
|/|
* | 24d3250 (origin/main, origin/HEAD) 6
| * 707f11d (origin/feat) f1
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
Commit 6
is back in the first parent lane but overall the history looks a bit wild.
No updates for me
Back to the ‘right back where we started from - RESET’ part.
You could merge feat
into main
without pulling the new code from main
into your feature branch via rebase
or merge
. This works when your branch is not to much behind main
and the changes in main
do not alter code your feature relies on.
git switch main
git merge feat
git log main feat --oneline --graph
This would be the history:
* xxxxxxx (HEAD -> main) Merge branch 'feat'
|\
| * 707f11d (origin/feat, feat) f1
* | 24d3250 (origin/main, origin/HEAD) 6
|/
* c350b81 5
* 6f9114d 4
* 3f9e923 3
* c27fadf 2
* 9057d20 1
* ccf0b92 0
All fine, commit 6
stays in the first parent lane.
Questions, comments, ideas?
Just open an issue on the repo or get in contact via mastodon