Git Workflow Strategies That Actually Work
Last month, a junior developer on my team accidentally pushed to main and broke production. It wasn't his fault—we hadn't set up proper branch protection. That incident cost us 4 hours of downtime and one very stressful night.
The Branching Strategy We Use Now
After trying GitFlow (too complex), GitHub Flow (too simple), and everything in between, here's what works for teams of 5-20 developers:
main (production)
└── develop (staging)
├── feature/user-authentication
├── feature/payment-gateway
└── bugfix/login-redirect
Branch Naming Conventions
Consistent naming prevents chaos:
feature/JIRA-123-add-razorpay-integration
bugfix/JIRA-456-fix-otp-validation
hotfix/critical-payment-failure
release/v2.1.0
Commit Messages That Make Sense
We follow Conventional Commits. Here's our template:
<type>(<scope>): <description>
[optional body]
[optional footer]
Examples:
feat(auth): add Google OAuth login
fix(payment): handle UPI timeout gracefully
docs(api): update endpoint documentation
refactor(user): simplify profile update logic
The Pull Request Process
Every PR must:
- Have at least 2 reviewers
- Pass all CI checks
- Have no merge conflicts
- Include relevant tests
Here's our PR template:
## What does this PR do?
Brief description of changes
## How to test?
Steps for reviewers to verify
## Screenshots (if UI changes)
## Checklist
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] No console.logs left
Handling Merge Conflicts
When conflicts happen (and they will):
# Update your branch with latest develop
git checkout feature/your-branch
git fetch origin
git rebase origin/develop
# If conflicts occur, resolve them file by file
# After resolving each file:
git add <resolved-file>
git rebase --continue
# Force push (only to your feature branch!)
git push --force-with-lease
The --force-with-lease flag is safer than --force. It won't overwrite changes if someone else pushed to the branch.
Useful Git Aliases
Add these to your .gitconfig:
[alias]
st = status
co = checkout
br = branch
cm = commit -m
lg = log --oneline --graph --all
undo = reset HEAD~1 --soft
amend = commit --amend --no-edit
Protecting Important Branches
In GitHub/GitLab settings:
- Require PR reviews before merging
- Require status checks to pass
- Prevent force pushes to main
- Require linear history (optional but clean)
When Things Go Wrong
Accidentally committed to wrong branch:
git stash
git checkout correct-branch
git stash pop
Need to undo last commit but keep changes:
git reset HEAD~1 --soft
Completely messed up and need to start fresh:
git reflog # Find the commit hash before the mess
git reset --hard <commit-hash>
Team Agreements
Document these and share with your team:
- Never push directly to main or develop
- Squash commits before merging (keeps history clean)
- Delete branches after merging
- Update Jira/Linear ticket status when PR is raised
Good Git hygiene is like good code—it takes discipline but saves endless debugging sessions later.