Branching & Merging
Feature branches, merge, and rebase strategies for Python services shipping pyproject.toml, lockfiles, and Alembic migrations together.
Recipe
git checkout main && git pull --rebase origin main
git checkout -b feat/api-91-refunds
# work ...
git fetch origin && git rebase origin/main
git push -u origin feat/api-91-refunds
# open PR -> squash merge to main after CI greenWhen to reach for this:
- Starting ticket work isolated from
main - Integrating latest
mainbefore PR review - Cutting
release/x.ytrains with Django/FastAPI apps - Backporting hotfixes with cherry-pick
Working Example
# Feature branch lifecycle
git switch -c feat/worker-retries main
uv run pytest -q
git commit -am "feat(worker): retry smtp errors [WRK-12]"
git fetch origin
git rebase origin/main
# conflict in uv.lock -> regenerate lock after resolving pyproject
uv lock
git add pyproject.toml uv.lock
git rebase --continue
git push --force-with-lease origin feat/worker-retriesWhat this demonstrates:
- Branch from updated
main - Rebase integrates upstream without merge commits on feature branch
- Lockfile conflicts resolved by regeneration not manual edit
- Force-with-lease safe force push after rebase
Deep Dive
How It Works
- Fast-forward - Branch pointer moves when no divergent commits.
- Merge commit - Combines two histories with merge node.
- Rebase - Replays commits on new base - rewrites SHAs.
- Squash merge - PR becomes one commit on
main.
Branch Types
| Branch | Purpose |
|---|---|
main | Always shippable integration |
feat/* | Ticket work; rebased often |
release/x.y | Stabilization; fixes only |
hotfix/* | Production patch from tag |
Python Notes
# Cherry-pick migration fix to release branch
git switch release/2.3
git cherry-pick abc1234 # commit with Alembic revision
uv run alembic upgrade head
uv run pytest -qGotchas
- Long-lived feature branches - Lockfile and migration conflicts explode. Fix: merge or rebase
maindaily. - Squash losing revert info - Hard to bisect. Fix: meaningful PR title becomes squash message.
- Rebase after PR review started - Confuses reviewers with force push. Fix: communicate or merge instead.
- Merging
mainintorelease/*- Pulls unvetted features. Fix: cherry-pick only. - Alembic revision conflicts - Duplicate heads after parallel branches. Fix: merge heads migration before deploy.
- Binary wheel conflicts - Rare in git; large artifacts should not be in repo. Fix: Git LFS or package index.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Trunk-based + flags | Mature CI and feature flags | Long release trains with schema migrations |
GitFlow develop | Legacy org mandate | Team prefers trunk + release/* |
| Merge commit on PR | Preserve PR boundary metadata | History noise is a concern |
| Rebase merge | Linear history with PR grouping | Policy forbids any rewrite |
FAQs
Squash or merge commit for PRs?
Squash for feature PRs on main; merge commit when preserving multi-commit feature narrative is required.
Rebase conflicts on uv.lock?
Accept one side's pyproject.toml, run uv lock, stage regenerated lock - never hand-merge lock JSON.
How long should feat branches live?
Target 1-3 days; if longer, split ticket or integrate behind feature flag.
Delete branch after merge?
Yes - enable auto-delete on GitHub; local git branch -d after merge.
Multiple PRs one migration?
Coordinate Alembic revision chain - second PR bases on first until first merges.
Protected main rules?
Require PR, ruff, pytest, reviews - no direct push except break-glass role.
Monorepo branching?
Same rules per service folder; path filters in CI run only affected uv projects.
Backport hotfix?
Cherry-pick to main and active release/*; tag patch after staging validation.
Rebase vs merge for dependabot PRs?
Merge dependabot PRs as-is or rebase via GitHub button - keep lockfile consistent with uv sync --frozen CI.
Conflict markers in Jupyter?
See Git for Data/Notebooks - nbstripout reduces noise.
Related
- Essential Git Commands - daily commands
- Merge Conflict Resolution Checklist - calm process
- Git Worktrees - parallel checkouts
- CI/CD Pipelines - branch protections
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.