Version control is the foundation of modern software development. Git enables teams to work in parallel, track changes, and maintain code history. But Git’s flexibility can lead to chaos without clear workflows. Messy commit histories, conflicting branches, and unclear processes slow teams down.
Professional teams follow established Git workflows that balance flexibility with structure. These workflows define how branches are created, how code is reviewed, and how changes reach production. The right workflow reduces conflicts, improves code quality, and makes collaboration smoother.
This guide covers Git workflows used by successful teams. You will learn branching strategies, commit conventions, code review processes, and automation techniques that make version control work for your team.
Choosing a Workflow
Different teams need different workflows. The right choice depends on team size, release frequency, and deployment model.
Git Flow
Git Flow uses multiple long-lived branches for different purposes. Main branches include main (production), develop (integration), and temporary branches for features, releases, and hotfixes.
Best for: Teams with scheduled releases, multiple versions in production, or complex release processes.
Pros: Clear separation between development and production. Supports parallel development of multiple versions. Well-documented and widely understood.
Cons: Complex branch structure. Merge conflicts increase with long-lived feature branches. Overhead for teams that deploy frequently.
GitHub Flow
GitHub Flow uses a single main branch with short-lived feature branches. Developers create branches, open pull requests, and merge to main after review. Main is always deployable.
Best for: Teams that deploy frequently, web applications, continuous deployment environments.
Pros: Simple and easy to understand. Encourages small, frequent changes. Main branch always reflects production state.
Cons: Requires good CI/CD infrastructure. Less suitable for maintaining multiple versions. Feature flags needed for incomplete features.
GitLab Flow
GitLab Flow combines elements of Git Flow and GitHub Flow. Uses environment branches (main, staging, production) with feature branches merging to main first.
Best for: Teams with multiple environments, staged deployments, or regulatory requirements.
Pros: Clear path from development to production. Supports environment-specific configurations. Balances simplicity with control.
Cons: More complex than GitHub Flow. Requires discipline to maintain environment branches. Can lead to drift between environments.
Trunk-Based Development
Trunk-Based Development keeps all developers working on a single branch (trunk/main) with short-lived feature branches or direct commits. Changes are integrated continuously.
Best for: Experienced teams, high-frequency deployments, microservices architectures.
Pros: Minimizes merge conflicts. Encourages continuous integration. Simplifies branch management.
Cons: Requires strong testing infrastructure. Feature flags essential for incomplete work. Higher risk without proper safeguards.
Implementing GitHub Flow
GitHub Flow provides a good balance of simplicity and structure for most teams. Here’s how to implement it effectively.
Branch Naming Conventions
# Feature branches
feature/user-authentication
feature/payment-integration
feature/dashboard-redesign
# Bug fixes
fix/login-error
fix/memory-leak
fix/broken-link
# Hotfixes (urgent production fixes)
hotfix/security-patch
hotfix/critical-bug
# Refactoring
refactor/database-queries
refactor/api-structure
# Documentation
docs/api-guide
docs/setup-instructions
Creating Feature Branches
# Update main branch
git checkout main
git pull origin main
# Create feature branch
git checkout -b feature/user-profile
# Make changes and commit
git add .
git commit -m "Add user profile page"
# Push to remote
git push -u origin feature/user-profile
Opening Pull Requests
Pull requests are where code review happens. Write clear descriptions that help reviewers understand your changes.
Good PR description:
## What
Adds user profile page with avatar upload and bio editing.
## Why
Users requested ability to customize their profiles. This addresses
issues #123 and #145.
## How
- Created ProfilePage component with form validation
- Added avatar upload to S3 with image resizing
- Implemented bio editor with character limit
- Added profile update API endpoint
## Testing
- Unit tests for ProfilePage component
- Integration tests for API endpoint
- Manual testing on staging environment
## Screenshots
[Include relevant screenshots]
## Checklist
- [x] Tests added and passing
- [x] Documentation updated
- [x] No breaking changes
- [x] Reviewed own code
Code Review Process
For reviewers:
Check for correctness, readability, and maintainability. Look for bugs, security issues, and performance problems. Suggest improvements but distinguish between blocking issues and nice-to-haves.
# Review locally
git fetch origin
git checkout feature/user-profile
git pull origin feature/user-profile
# Run tests
npm test
# Check changes
git diff main...feature/user-profile
For authors:
Respond to all comments. Make requested changes in new commits (don’t force-push during review). Mark conversations as resolved when addressed.
# Make requested changes
git add .
git commit -m "Address review comments"
git push origin feature/user-profile
Merging Strategies
Merge commit preserves complete history with a merge commit. Use when you want to keep feature branch history visible.
git checkout main
git merge --no-ff feature/user-profile
git push origin main
Squash and merge combines all feature branch commits into one. Use for cleaner history when individual commits aren’t important.
git checkout main
git merge --squash feature/user-profile
git commit -m "Add user profile page"
git push origin main
Rebase and merge replays feature commits on top of main. Use for linear history without merge commits.
git checkout feature/user-profile
git rebase main
git checkout main
git merge feature/user-profile
git push origin main
Commit Message Conventions
Good commit messages make history searchable and understandable. Follow a consistent format.
Conventional Commits
# Format: <type>(<scope>): <subject>
feat(auth): add OAuth2 login
fix(api): handle null response from database
docs(readme): update installation instructions
style(button): fix spacing and alignment
refactor(utils): simplify date formatting
test(user): add profile update tests
chore(deps): update dependencies
Types
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Code style changes (formatting, no logic change)
- refactor: Code restructuring without changing behavior
- test: Adding or updating tests
- chore: Maintenance tasks, dependency updates
Writing Good Commit Messages
Bad:
git commit -m "fix bug"
git commit -m "update code"
git commit -m "changes"
Good:
git commit -m "fix(auth): prevent token expiration during active sessions"
git commit -m "feat(api): add pagination to user list endpoint"
git commit -m "refactor(database): extract query logic into repository pattern"
Multi-line commits for complex changes:
git commit -m "feat(payment): integrate Stripe payment processing
- Add Stripe SDK and configuration
- Create payment intent endpoint
- Implement webhook handler for payment events
- Add payment status tracking to database
- Include error handling for failed payments
Closes #234"
Branch Protection Rules
Protect important branches from accidental changes or force-pushes.
GitHub Branch Protection
# Settings → Branches → Branch protection rules
main:
- Require pull request reviews before merging
- Require status checks to pass before merging
- CI tests
- Code coverage
- Linting
- Require branches to be up to date before merging
- Require conversation resolution before merging
- Require signed commits
- Include administrators
- Restrict who can push to matching branches
Pre-commit Hooks
Enforce standards before code is committed.
# Install pre-commit
pip install pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
- id: flake8
# Install hooks
pre-commit install
Handling Merge Conflicts
Conflicts happen when changes overlap. Resolve them carefully to avoid losing work.
Resolving Conflicts
# Update your branch
git checkout feature/my-feature
git fetch origin
git merge origin/main
# Git shows conflicts
# CONFLICT (content): Merge conflict in src/app.py
# Open conflicted files
# Look for conflict markers:
<<<<<<< HEAD
# Your changes
=======
# Changes from main
>>>>>>> origin/main
# Edit file to resolve conflict
# Remove conflict markers
# Keep the correct code
# Mark as resolved
git add src/app.py
git commit -m "Merge main and resolve conflicts"
git push origin feature/my-feature
Preventing Conflicts
Keep feature branches short-lived. Merge main into your branch regularly. Communicate with team about overlapping work. Use feature flags to isolate incomplete features.
# Regularly update feature branch
git checkout feature/my-feature
git fetch origin
git merge origin/main
Git Aliases for Productivity
Create shortcuts for common commands.
# ~/.gitconfig
[alias]
st = status
co = checkout
br = branch
ci = commit
unstage = reset HEAD --
last = log -1 HEAD
visual = log --graph --oneline --all
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
cleanup = !git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d
# Usage
git st
git co -b feature/new-feature
git visual
git cleanup
Automation with GitHub Actions
Automate testing, linting, and deployment with CI/CD.
Basic CI Workflow
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: pytest --cov=src tests/
- name: Check code coverage
run: |
coverage report --fail-under=80
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install linters
run: |
pip install black flake8 mypy
- name: Run Black
run: black --check src/
- name: Run Flake8
run: flake8 src/
- name: Run MyPy
run: mypy src/
Automatic Deployment
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
./scripts/deploy.sh
Advanced Git Techniques
Interactive Rebase
Clean up commit history before merging.
# Rebase last 3 commits
git rebase -i HEAD~3
# Editor opens with commits:
pick abc123 Add feature A
pick def456 Fix typo
pick ghi789 Add feature B
# Change to:
pick abc123 Add feature A
squash def456 Fix typo
pick ghi789 Add feature B
# Save and close
# Git combines first two commits
Cherry-Picking
Apply specific commits to another branch.
# Find commit hash
git log
# Apply commit to current branch
git cherry-pick abc123
# Cherry-pick multiple commits
git cherry-pick abc123 def456 ghi789
Bisect for Bug Hunting
Find which commit introduced a bug.
# Start bisect
git bisect start
# Mark current commit as bad
git bisect bad
# Mark known good commit
git bisect good abc123
# Git checks out middle commit
# Test if bug exists
git bisect bad # or git bisect good
# Repeat until Git finds the problematic commit
git bisect reset # Return to original state
Stashing Changes
Save work in progress without committing.
# Stash current changes
git stash
# Stash with message
git stash save "Work in progress on feature X"
# List stashes
git stash list
# Apply most recent stash
git stash apply
# Apply specific stash
git stash apply stash@{1}
# Apply and remove stash
git stash pop
# Clear all stashes
git stash clear
Team Collaboration Patterns
Feature Flags
Deploy incomplete features without affecting users.
# feature_flags.py
FEATURE_FLAGS = {
"new_dashboard": False,
"payment_v2": True,
"beta_features": False
}
def is_enabled(feature_name):
return FEATURE_FLAGS.get(feature_name, False)
# Usage in code
if is_enabled("new_dashboard"):
render_new_dashboard()
else:
render_old_dashboard()
Release Branches
Prepare releases while development continues.
# Create release branch from develop
git checkout -b release/v1.2.0 develop
# Make release-specific changes
# Update version numbers
# Fix last-minute bugs
# Merge to main
git checkout main
git merge --no-ff release/v1.2.0
git tag -a v1.2.0 -m "Release version 1.2.0"
# Merge back to develop
git checkout develop
git merge --no-ff release/v1.2.0
# Delete release branch
git branch -d release/v1.2.0
Hotfix Workflow
Fix critical production bugs quickly.
# Create hotfix branch from main
git checkout -b hotfix/security-patch main
# Fix the issue
git commit -m "fix(security): patch XSS vulnerability"
# Merge to main
git checkout main
git merge --no-ff hotfix/security-patch
git tag -a v1.2.1 -m "Hotfix: security patch"
# Merge to develop
git checkout develop
git merge --no-ff hotfix/security-patch
# Delete hotfix branch
git branch -d hotfix/security-patch
Troubleshooting Common Issues
Accidentally Committed to Wrong Branch
# Move commits to new branch
git branch feature/correct-branch
git reset --hard HEAD~3 # Remove last 3 commits from current branch
git checkout feature/correct-branch
Undo Last Commit (Keep Changes)
git reset --soft HEAD~1
Undo Last Commit (Discard Changes)
git reset --hard HEAD~1
Recover Deleted Branch
# Find commit hash
git reflog
# Recreate branch
git checkout -b recovered-branch abc123
Fix Commit Message
# Last commit
git commit --amend -m "New message"
# Older commit
git rebase -i HEAD~3
# Change 'pick' to 'reword' for commit to edit
Summary
Git workflows provide structure for team collaboration. The right workflow depends on your team size, release frequency, and deployment model. GitHub Flow works well for most teams with its simple branch-and-merge approach.
Good commit messages make history searchable. Follow conventional commit format with clear types and descriptions. Write messages that explain why changes were made, not just what changed.
Branch protection rules prevent mistakes. Require pull request reviews, passing tests, and up-to-date branches before merging. Use pre-commit hooks to enforce standards automatically.
Automation reduces manual work. GitHub Actions can run tests, check code quality, and deploy automatically. Set up CI/CD pipelines that catch problems before they reach production.
Advanced techniques like interactive rebase, cherry-picking, and bisect help manage complex histories. Learn these tools but use them carefully to avoid confusing teammates.
For more development best practices, check our guides on Python coding standards and testing with pytest.
Sources:
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.