Ship through dev. Keep main clean.
A lightweight Git workflow designed to keep the main branch stable while allowing completed work to integrate through a dedicated dev branch. Clean Flow is simpler than Git Flow and more structured than GitHub Flow.
Note: This is a documented personal workflow I've refined over years of practice. While I use the term "Clean Flow workflow" to describe this standardized approach, it may evolve into a broader convention as others adopt and adapt it.
Existing Git workflows are either too simple or too complex:
- GitHub Flow sends feature branches directly into main — no integration step
- Git Flow introduces release branches, hotfix branches, and ceremony that many teams don't need
Clean Flow is different:
- Structured: A dedicated
devbranch gates work before it reachesmain - Simple: Only three branch types —
main,dev, and feature branches - Clean: Squash merges keep history compact and scannable
- Flexible: Works for solo developers, small teams, and open source projects
feature/* ──squash merge──> dev ──merge commit──> main
| Branch | Role |
|---|---|
main |
Stable branch — production-ready or release-ready code |
dev |
Integration branch — collects completed feature work |
feature/* |
Short-lived branches for new work, created from dev |
git checkout dev
git pull origin devgit checkout -b feature/user-authenticationgit add .
git commit -m "add user authentication system"git fetch origin
git rebase origin/devgit push -u origin feature/user-authenticationThen open a pull request:
feature/user-authentication → dev
When approved, squash merge into dev. This turns multiple work-in-progress commits into one clean logical commit.
git checkout main
git pull origin main
git merge dev
git push origin mainUse a regular merge commit to preserve the boundary between integration and stable history.
Use short, descriptive branch names with prefixes:
| Prefix | Use For |
|---|---|
feature/ |
New features and functionality |
fix/ |
Bug fixes |
docs/ |
Documentation changes |
chore/ |
Maintenance and dependency updates |
test/ |
Test additions and changes |
refactor/ |
Code refactoring |
Examples:
feature/user-authentication
fix/cart-total-rounding
docs/installation-guide
chore/update-dependencies
test/payment-service
refactor/api-client
Feature branch commits: A → B → C
Squash merge on dev: S
Why squash merge?
- Keeps feature history compact
- Removes noisy work-in-progress commits
- Creates one logical commit per feature
- Keeps
deveasier to scan
Why merge commit?
- Preserves the boundary between integration work and stable release history
- Makes it clear when
devwas promoted tomain
Maintainers work against the primary repository.
origin/dev → local dev → feature branch → dev → main
- Pull the latest
dev - Create a feature branch from
dev - Work, commit, and push
- Open a pull request targeting
dev - Squash merge into
dev - When stable, merge
devintomain
Contributors work from a fork.
upstream/dev → local dev → feature branch → origin → PR into upstream/dev
- Sync from
upstream/dev - Create a feature branch from
dev - Work, commit, and push to your fork (
origin) - Open a pull request into
upstream/dev
Feature branches should always target dev.
Correct:
feature/user-authentication → dev
fix/login-validation → dev
docs/update-guide → dev
chore/update-dependencies → dev
Avoid:
feature/user-authentication → main
fix/login-validation → main
The main branch should receive changes from dev, not directly from feature branches.
Start from dev. Create a feature/ branch. Target the PR to dev. Squash merge.
feature/user-profile → dev
Start from dev. Create a fix/ branch. Target the PR to dev. Squash merge.
fix/login-validation → dev
Start from dev. Create a docs/ branch. Target the PR to dev. Squash merge.
docs/api-guide → dev
Verify dev is stable. Merge dev into main. Tag or publish the release if needed.
Delete feature branches after they are merged:
git branch -d feature/user-authentication
git push origin --delete feature/user-authentication- Require pull requests
- Require status checks
- Restrict direct pushes
- Require review before merge
- Require up-to-date branches when appropriate
- Require pull requests
- Require status checks
- Allow squash merges
- Restrict direct pushes when working with a team
| GitHub Flow | Clean Flow | |
|---|---|---|
| Branches | main + feature branches |
main + dev + feature branches |
| Feature target | Directly into main |
Into dev first |
| Integration step | None | dev collects completed work |
| Best for | Small projects, fast continuous deployment | Projects needing a stable integration step |
| Git Flow | Clean Flow | |
|---|---|---|
| Branches | main, develop, release/*, hotfix/*, feature branches |
main, dev, feature branches |
| Complexity | More formal and complex | Simpler and easier to maintain |
| Release process | Dedicated release and hotfix branches | No required release or hotfix ceremony |
| Best for | Complex versioned release processes | Projects wanting structure without overhead |
Use Clean Flow when:
- You want
mainto stay stable - You want feature work integrated into
devfirst - You want cleaner history through squash merges
- GitHub Flow feels too direct
- Git Flow feels too complex
- You work with contributors or forks
- You release from stable milestones
- You want simple branch rules that are easy to teach
Clean Flow may not be necessary when:
- You deploy every commit directly from
main - Your project is very small and has only one maintainer
- You need formal release and hotfix branches
- Your organization already uses strict Git Flow
- You require trunk-based development only
- Keep
mainproduction-ready — Only mergedevintomainwhen the project is stable - Use
devas the working integration branch — All completed feature work should land indevfirst - Squash feature branches — Turn work-in-progress commits into one clean logical commit
- Keep feature branches short-lived — Long-lived branches drift from
devand become harder to merge - Update before submitting — Always rebase your feature branch against the latest
devbefore submitting a PR - Delete merged branches — After a feature branch is merged, delete it locally and remotely
- Protect
mainanddev— Branch protection prevents accidental direct pushes
git checkout dev
git pull origin dev
git checkout -b feature/user-authentication
# ... do your work ...
git add .
git commit -m "add user authentication system"
git fetch origin
git rebase origin/dev
git push -u origin feature/user-authenticationThen open a pull request:
feature/user-authentication → dev
After approval: Squash merge into dev.
When stable: Merge dev into main.
Copy these templates to integrate Clean Flow with AI coding assistants.
Copy examples/copilot.instructions.md to your project:
mkdir -p .github/instructions
curl -o .github/instructions/copilot.instructions.md https://raw.githubusercontent.com/wgtechlabs/clean-flow/main/examples/copilot.instructions.mdCopy examples/AGENTS.md to your project root:
curl -o AGENTS.md https://raw.githubusercontent.com/wgtechlabs/clean-flow/main/examples/AGENTS.md- SPECIFICATION.md - Full technical specification with detailed guidelines
- QUICK-REFERENCE.md - Single-page cheatsheet for quick lookup
We welcome contributions! Please read our Contributing Guidelines to get started.
MIT License - see the LICENSE file for details.
Created with love by Waren Gonzaga / WG Tech Labs
Ship through dev. Keep main clean.