Git Workflows
A Git workflow is a recipe or guideline for how to use Git effectively. The right workflow depends on your team size, release cadence, and deployment strategy. Choosing poorly can slow development; choosing well enables rapid, safe shipping.
This guide covers the five most popular workflows, their trade-offs, and when each is appropriate — from solo development to large distributed teams.
Gitflow uses long-lived main and develop branches, with feature, release, and hotfix branches. It excels at scheduled releases with versioned software.
| 1 | # Gitflow setup |
| 2 | git flow init -d |
| 3 | |
| 4 | # Feature development |
| 5 | git flow feature start user-authentication |
| 6 | # ... work on feature ... |
| 7 | git flow feature finish user-authentication |
| 8 | |
| 9 | # Release preparation |
| 10 | git flow release start 2.0.0 |
| 11 | # ... bump version, update changelog ... |
| 12 | git flow release finish 2.0.0 |
| 13 | |
| 14 | # Hotfix for production bugs |
| 15 | git flow hotfix start critical-fix |
| 16 | # ... fix the bug ... |
| 17 | git flow hotfix finish critical-fix |
| 18 | |
| 19 | # Branch structure: |
| 20 | # main — production-ready code |
| 21 | # develop — integration branch |
| 22 | # feature/* — new features (branch from develop) |
| 23 | # release/* — release preparation (branch from develop) |
| 24 | # hotfix/* — production fixes (branch from main) |
best practice
GitHub Flow is a lightweight, branch-based workflow. Every change goes through: create a branch, make changes, open a PR, get reviewed, deploy to staging, merge to main. Simple and effective for continuous deployment.
| 1 | # GitHub Flow — simplified |
| 2 | # 1. Create a descriptive branch |
| 3 | git checkout main && git pull |
| 4 | git checkout -b feature/add-dark-mode |
| 5 | |
| 6 | # 2. Make changes and commit |
| 7 | git add -A |
| 8 | git commit -m "feat: add dark mode toggle" |
| 9 | |
| 10 | # 3. Push and create PR |
| 11 | git push -u origin feature/add-dark-mode |
| 12 | # Open PR on GitHub |
| 13 | |
| 14 | # 4. After review and CI passes, merge (squash) |
| 15 | # 5. Deploy main automatically |
| 16 | |
| 17 | # Naming conventions: |
| 18 | # feature/description — new features |
| 19 | # fix/description — bug fixes |
| 20 | # docs/description — documentation |
| 21 | # refactor/description — code refactoring |
| 22 | # chore/description — maintenance tasks |
info
GitLab Flow sits between Gitflow and GitHub Flow. It uses environment branches (staging, production) with downstream merging, giving you more control than GitHub Flow while staying simpler than Gitflow.
| 1 | # GitLab Flow — environment branches |
| 2 | # Feature branches merge into pre-production first |
| 3 | git checkout -b feature/new-api |
| 4 | # ... work ... |
| 5 | # Merge: feature -> pre-production -> production |
| 6 | |
| 7 | # Release branches for versioned software |
| 8 | git checkout -b release/2.0 |
| 9 | # Cherry-pick fixes back to main |
| 10 | |
| 11 | # Push rules enforce merge-down, cherry-pick-up: |
| 12 | # main -> pre-production -> production (forward) |
| 13 | # hotfix main -> cherry-pick to pre-production (backward) |
| 14 | |
| 15 | # GitLab CI/CD auto-deploys per environment: |
| 16 | # main -> dev environment |
| 17 | # pre-production -> staging |
| 18 | # production -> live |
Trunk-based development means everyone commits to main (the trunk) frequently — ideally multiple times per day. Feature flags gate incomplete work instead of long-lived branches.
| 1 | # Trunk-based: short-lived branches, frequent merges |
| 2 | git checkout main && git pull |
| 3 | git checkout -b feat/dark-mode |
| 4 | |
| 5 | # Small, frequent commits |
| 6 | git commit -m "feat: add theme provider" |
| 7 | git commit -m "feat: add dark mode toggle component" |
| 8 | git commit -m "test: add dark mode tests" |
| 9 | |
| 10 | # Merge quickly (hours, not days) |
| 11 | git push && gh pr create |
| 12 | |
| 13 | # Feature flags gate incomplete work |
| 14 | # In code: |
| 15 | if (featureFlags.isEnabled("dark-mode")) { |
| 16 | return <DarkMode />; |
| 17 | } |
| 18 | |
| 19 | # Release from main — no release branches |
| 20 | git tag v2.0.0 |
| 21 | git push origin v2.0.0 |
best practice
| 1 | # Keep branches short-lived (< 2 days ideal) |
| 2 | # Rebase before merging to keep history linear |
| 3 | git checkout feature/my-feature |
| 4 | git fetch origin |
| 5 | git rebase origin/main |
| 6 | # Resolve conflicts if any |
| 7 | git push --force-with-lease # Safe force push after rebase |
| 8 | |
| 9 | # Squash merge for clean history |
| 10 | git checkout main |
| 11 | git merge --squash feature/my-feature |
| 12 | git commit -m "feat: add user authentication system" |
| 13 | |
| 14 | # Or rebase merge for preserving individual commits |
| 15 | git checkout feature/my-feature |
| 16 | git rebase main |
| 17 | git checkout main |
| 18 | git merge --ff-only feature/my-feature |
| 19 | |
| 20 | # Clean up merged branches |
| 21 | git branch -d feature/my-feature |
| 22 | git push origin --delete feature/my-feature |
| 23 | |
| 24 | # Prune stale remote-tracking branches |
| 25 | git fetch --prune |
info
| 1 | # Semantic Versioning: MAJOR.MINOR.PATCH |
| 2 | # 1.0.0 -> 1.0.1 (patch: bug fix) |
| 3 | # 1.0.1 -> 1.1.0 (minor: new feature, backwards compatible) |
| 4 | # 1.1.0 -> 2.0.0 (major: breaking change) |
| 5 | |
| 6 | # Create a release |
| 7 | git tag -a v2.1.0 -m "Release 2.1.0: dark mode, performance improvements" |
| 8 | git push origin v2.1.0 |
| 9 | |
| 10 | # Generate changelog automatically |
| 11 | git log --pretty=format:"- %s" v2.0.0..v2.1.0 > CHANGELOG.md |
| 12 | |
| 13 | # Automated release with GitHub Actions |
| 14 | # .github/workflows/release.yml: |
| 15 | # on: |
| 16 | # push: |
| 17 | # tags: ['v*'] |
| 18 | # jobs: |
| 19 | # release: |
| 20 | # steps: |
| 21 | # - uses: actions/create-release@v1 |
| 22 | # with: |
| 23 | # tag_name: ${{ github.ref }} |
| 24 | # release_name: Release ${{ github.ref_name }} |
best practice