← All writings ES

How to Merge a Git Feature Branch with a Squash Commit

Keep your main branch history clean by squashing a feature branch's commits into one before merging — step by step with a reusable shell script.

After a week of work on a feature branch, your commit history looks something like this:

fix typo
WIP
fix test
WIP again
fix linter
actually done now

These commits are useful while you’re working — but they’re noise in the main branch’s history. A squash merge collapses all of them into a single, descriptive commit before they land on main.

What squash merging actually does

A squash merge takes all the changes from a feature branch and stages them as a single commit on the target branch. The original branch’s commit history is discarded — only the diff survives.

The result: main stays readable, and every entry in git log represents a complete, shippable unit of work.

main
├── feat: add user authentication (#42)
├── feat: payment flow redesign (#38)
└── chore: update dependencies (#35)

Instead of:

main
├── fix test
├── WIP
├── fix typo
├── WIP
├── initial auth implementation
└── ...

The manual approach

Git supports squash merging natively. The standard workflow:

# Ensure main is up to date
git checkout main
git pull origin main

# Merge with squash — stages all changes, does not commit
git merge --squash feature/my-branch

# Write a single, descriptive commit message
git commit -m "feat: implement user authentication"

# Clean up the feature branch
git branch -d feature/my-branch
git push origin --delete feature/my-branch

The --squash flag is the key: it stages everything without creating a merge commit, giving you full control over the final commit message.

Automating it with a shell script

Doing this repeatedly across dozens of feature branches becomes tedious. A reusable script handles the mechanics:

#!/bin/bash
# git_squash.sh — merge a feature branch into a target branch with a squash commit

set -e

FEATURE_BRANCH=$1
TARGET_BRANCH=$2

if [ -z "$FEATURE_BRANCH" ] || [ -z "$TARGET_BRANCH" ]; then
  echo "Usage: git-squash <feature-branch> <target-branch>"
  exit 1
fi

echo "→ Squash merging $FEATURE_BRANCH into $TARGET_BRANCH"

# Backup the feature branch
git branch "${FEATURE_BRANCH}_backup"

# Switch to target and update
git checkout "$TARGET_BRANCH"
git pull origin "$TARGET_BRANCH"

# Squash merge
git merge --squash "$FEATURE_BRANCH"

# Commit (opens editor for the message)
git commit

# Push to remote
git push origin "$TARGET_BRANCH"

# Clean up
git branch -d "$FEATURE_BRANCH"
git branch -d "${FEATURE_BRANCH}_backup"

echo "✓ Done. $FEATURE_BRANCH merged into $TARGET_BRANCH."

Setting up the alias

Save the script somewhere permanent and make it executable:

chmod +x ~/scripts/git_squash.sh

Add an alias to your shell profile (.zshrc, .bashrc, or .profile):

alias git-squash="~/scripts/git_squash.sh"

Reload the profile:

source ~/.zshrc

Now you can squash-merge any branch with:

git-squash feature-123 main

When to use squash merges

Squash merging is the right default when:

  • The feature branch has many small or fixup commits that don’t add historical value
  • The team has agreed on a linear history strategy
  • You’re enforcing conventional commits on main and want full control over the message

It’s the wrong choice when:

  • You want to preserve the individual commit history for debugging (git bisect benefits from fine-grained commits)
  • Multiple people collaborated on the branch and attribution matters
  • The commits are already clean and descriptive

The alternative: interactive rebase

If you want to selectively squash some commits but keep others, git rebase -i is more surgical:

git rebase -i main

This opens an editor where you mark commits as pick, squash, or fixup. More control, slightly more friction.

Key takeaways

  • git merge --squash collapses all branch commits into a single staged diff — you write the final message
  • A clean main history makes git log, code review, and incident debugging significantly easier
  • The shell script adds a --backup branch as a safety net before destructive operations
  • Squash merging is a team convention — agree on it upfront, and enforce it via branch protection rules in GitHub/GitLab