Testing a feature branch against the latest main Jump to heading

A feature branch’s tests pass against the main it was created from. That main is days or weeks old. Since then, others have changed shared code, renamed functions, updated dependencies. Whether your branch still works once merged depends on what main looks like now — and the only way to know is to test the combination. Merging main into the branch every day answers it but clutters the branch’s history and is irreversible if the result is confusing. A throwaway merge — build the combination, test it, throw it away — answers the question without touching the branch. CI can do the same automatically on every push. This page shows both, within feature branch isolation.

When to use this approach Jump to heading

  • Your branch has been open for more than a day or two while main kept moving.
  • You want to know whether the branch will work after merging, before asking for review.
  • You prefer to keep the branch free of “merge main into feature” commits until it is ready.
  • Your CI tests branch heads rather than merge results; the fix is described in catching semantic conflicts in CI.

Step 1 — Build a throwaway merge in a separate worktree Jump to heading

Use a temporary worktree so your working copy and branch are untouched. Merge the latest main into a detached HEAD there, and run the tests.

git fetch origin
git worktree add --detach ../merge-check feature/export-scheduling
cd ../merge-check
git merge --no-edit origin/main || { echo "conflicts — see git status"; exit 1; }
make test
cd - && git worktree remove --force ../merge-check
A throwaway merge leaves the branch untouchedThe feature branch has commits F1 to F3 on an old main. A detached worktree merges today's main tip into F3, producing a temporary merge commit T that is tested and discarded. The feature branch still ends at F3 with no merge commit added.test the combination, keep the branchmainBM1M2M3featureBF1F2F3throwawayT (tested)T exists only in the worktree and is gone when the worktree is removed A throwaway merge leaves the branch untouchedThe feature branch has commits F1 to F3 on an old main. A detached worktree merges today's main tip into F3, producing a temporary merge commit T that is tested and discarded. The feature branch still ends at F3 with no merge commit added.test the combination, keep the branchmainBM1M2M3featureBF1F2F3throwawayT (tested)T exists only in the worktree and is gone when the worktree is removed

Worktrees for this kind of side task are covered in running long builds in a separate worktree.

Step 2 — Check for conflicts without a build Jump to heading

Sometimes you only want to know whether the merge is clean. git merge-tree --write-tree answers that in memory, without any worktree.

git fetch origin
if git merge-tree --write-tree --name-only --no-messages origin/main feature/export-scheduling >/tmp/mt; then
  echo "merges cleanly with today's main"
else
  echo "conflicts in:"; tail -n +2 /tmp/mt
fi

The full set of previewing techniques is in previewing merges with git merge-tree.

Step 3 — Let CI test the merge result on every push Jump to heading

Forges compute a merge of each pull request into its base and expose it as a ref; CI can test that instead of the branch head. On GitHub, pull_request workflows do this by default; GitLab calls them merged results pipelines.

on: pull_request                     # actions/checkout uses refs/pull/<n>/merge by default
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: git log -1 --format='testing %h with parents %p'   # two parents: the merge
      - run: make test
Three ways to test against the latest mainMerging main into the branch regularly tests the combination but adds merge commits to the branch. A throwaway merge in a worktree tests it locally with no trace. CI on the forge's merge ref tests it automatically on every push, which is the best default.Branch historyEffortmerge main into branchmerge commits addedeach time, by handthrowaway mergeuntouchedone command, by handCI on merge refuntouchedautomaticCI covers every push; the throwaway merge is for checking before you push Three ways to test against the latest mainMerging main into the branch regularly tests the combination but adds merge commits to the branch. A throwaway merge in a worktree tests it locally with no trace. CI on the forge's merge ref tests it automatically on every push, which is the best default.Branch historyEffortmerge main into branchmerge commits addedeach time, by handthrowaway mergeuntouchedone command, by handCI on merge refuntouchedautomaticCI covers every push; the throwaway merge is for checking before you push

The merge ref is recomputed when the branch is pushed, not when main moves. A green check from yesterday may be stale today; re-run the workflow, or rely on a merge queue to test the final combination.

Step 4 — Decide when to actually integrate main Jump to heading

Testing the combination tells you whether integrating will hurt; it does not integrate. When the branch is ready for review, or when tests against main start failing in ways that need code changes on the branch, bring main in for real — by rebase or merge, depending on team policy.

# Rebase onto the latest main (rewrites the branch; force-push with lease)
git rebase origin/main && git push --force-with-lease
# Or merge main in (keeps history, adds a merge commit)
git merge origin/main && git push

The trade-offs are covered in rebasing a feature branch before merge.

Step 5 — Make the check a daily habit for long branches Jump to heading

For branches that stay open longer than a few days, run the throwaway merge on a schedule and post the result, so problems show up the day main introduces them.

# Nightly: test every open feature branch against main, report failures
for b in $(git for-each-ref --format='%(refname:lstrip=3)' 'refs/remotes/origin/feat/*' 'refs/remotes/origin/fix/*'); do
  git merge-tree --write-tree --no-messages origin/main "origin/$b" >/dev/null 2>&1 \
    || echo "CONFLICT  $b"
done
A daily check for long-lived branchesEach night a job fetches main, previews the merge of every open feature branch with it, builds and tests the clean ones in throwaway worktrees, and reports conflicts or failures to each branch's author before they grow.Fetch mainlatest tipPreviewmerge-tree per branchBuild + testclean ones onlyReportto branch authorsa problem found the day it appears is a one-line fix; found a month later it is an afternoon A daily check for long-lived branchesEach night a job fetches main, previews the merge of every open feature branch with it, builds and tests the clean ones in throwaway worktrees, and reports conflicts or failures to each branch's author before they grow.Fetch mainlatest tipPreviewmerge-tree per branchBuild + testclean ones onlyReportto branch authorsa problem found the day it appears is a one-line fix; found a month later it is an afternoon

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Does a throwaway merge leave anything behind? Jump to heading

The temporary merge commit exists only in the worktree and becomes unreachable when the worktree is removed; garbage collection cleans it up later. Your branch and working copy are unchanged.

Why not just rebase every morning? Jump to heading

Rebasing rewrites the branch, forcing collaborators to reset and invalidating review comments tied to commits. A throwaway merge gives the same information without those costs, so you rebase only when needed.

What if the throwaway merge conflicts? Jump to heading

That tells you integrating will need work. You can resolve it in the worktree to see how hard it is — with rerere enabled, the resolution is remembered for when you integrate for real, as described in rerere for long-lived topic branches.