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 0There 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 --graphHere 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 0git 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 feat707f11d 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 0Right 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 --graphWe 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 0Rebase 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 0Everything looks just fine, let’s see what happens now:
git switch main
git merge feat
git log main feat --oneline --graphThis 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 0Everything 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 0Commit 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 --graphThis 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 0All 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