# Git History Flattening — First-Parent Cherry-Pick Flatten a merge-heavy git history into a linear sequence of first-parent commits — merge nodes become regular commits, no information lost. Produces a history where `git log --first-parent` IS the full log. Used when: repo has accumulated many merge nodes (e.g. PR-per-feature workflow with `--no-ff` merges) and you want a clean linear history. ## When to use - Merge noise is high (e.g. 38 merge nodes in 430 commits) - History should be linear but content must be identical - Repo pack is small enough that flattening is cosmetic, not space-driven ## Technique: orphan branch + first-parent cherry-pick This is safer than `git rebase -i` because it doesn't touch the original branch until the new history is fully built and verified. ```bash cd /path/to/repo # 1. Backup current main git branch main-pre-flatten HEAD # 2. Create orphan branch (empty, no history) git checkout --orphan flat-main git rm -rf --quiet . 2>/dev/null || true # 3. Cherry-pick all first-parent commits in chronological order. # Merge commits get -m 1 (take main-line side). # Regular commits cherry-pick normally. git log --reverse --format='%H' --first-parent main-pre-flatten | while read hash; do parents=$(git rev-list --parents -n 1 "$hash" | wc -w) # parents count includes the commit itself: 2=regular, 3+=merge if [ "$parents" -ge 3 ]; then git cherry-pick -m 1 "$hash" --allow-empty else git cherry-pick "$hash" --allow-empty fi done # 4. Verify tree is identical git diff main-pre-flatten flat-main --stat # should be empty # 5. Swap branches git branch -D main git branch -m flat-main main ``` ## Result | Before | After | | ---------------------------- | ---------------------------- | --- | | 430 commits, 38 merge nodes | 194 linear commits, 0 merges | | `git log --oneline --merges | wc -l` = 38 | = 0 | | `git diff old-main new-main` | empty | ## Pitfalls - **Remote has branch protection**: must temporarily remove protection, force push, re-create. See `references/branch-protection-api.md`. - **Other remotes**: push to all remotes (`forgejo` + `origin`) to keep tracking refs in sync. - **Worktrees on the FreeBSD build host**: they may be detached at the old HEAD. After force push they can `git fetch && git reset --hard origin/main` — the new HEAD is the same tree, just different commit SHA. - **Orphan branch must be truly empty**: `git checkout --orphan` creates a branch with no commits, but the index still has staged files. `git rm -rf --quiet .` clears them before cherry-picking. - **First-parent view is the target**: 38 original merge commits vs 31 on first-parent — the 7 missing are "merge main back into feature branch" type merges that don't appear on the first-parent line and are correctly excluded.