Git Workflow Best Practices for Modern Development Teams
Git is powerful, but with great power comes... the potential for a messy repository. Here's how to use Git effectively in a team environment without losing your mind.
Branching Strategies
Git Flow
Good for projects with scheduled releases:
main (production)
├── develop (integration)
├── feature/user-auth
├── feature/payment
└── release/v1.2.0
# Start a feature
git checkout develop
git checkout -b feature/user-auth
# Finish feature
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth
# Create release
git checkout -b release/v1.2.0 develop
# Make release-specific changes
git checkout main
git merge --no-ff release/v1.2.0
git tag -a v1.2.0
GitHub Flow
Simpler, great for continuous deployment:
main (always deployable)
├── feature/add-search
├── fix/login-bug
└── feature/dark-mode
# Create feature branch
git checkout -b feature/add-search
# Make changes, commit often
git add .
git commit -m "Add search functionality"
# Push and create PR
git push origin feature/add-search
# Create PR on GitHub
# After PR approval
# Merge and delete branch
Trunk-Based Development
For high-velocity teams:
main (trunk)
├── short-lived feature branches (< 1 day)
└── feature flags for incomplete features
Commit Best Practices
Write Good Commit Messages
Follow the Conventional Commits standard:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentationstyle: Formatting, no code changerefactor: Code restructuringtest: Adding testschore: Maintenance
Examples:
# Good
git commit -m "feat(auth): add Google OAuth login"
git commit -m "fix(api): handle null response in user endpoint"
git commit -m "docs: update installation instructions"
# Bad
git commit -m "fixed stuff"
git commit -m "updates"
git commit -m "asdfasdf"
Commit Early, Commit Often
# Make small, logical commits
git add src/auth/login.js
git commit -m "feat(auth): add login form validation"
git add src/auth/oauth.js
git commit -m "feat(auth): integrate Google OAuth"
git add tests/auth.test.js
git commit -m "test(auth): add OAuth integration tests"
Atomic Commits
Each commit should be a single logical change:
# ❌ Bad: Multiple unrelated changes
git add .
git commit -m "Add login, fix bug, update docs"
# ✅ Good: Separate commits
git add src/auth/
git commit -m "feat(auth): add login functionality"
git add src/api/user.js
git commit -m "fix(api): handle null user response"
git add README.md
git commit -m "docs: update API documentation"
Branch Management
Naming Conventions
feature/description # New features
fix/description # Bug fixes
hotfix/description # Urgent production fixes
refactor/description # Code refactoring
docs/description # Documentation changes
test/description # Test updates
# Examples
feature/user-authentication
fix/login-redirect-bug
hotfix/payment-gateway-timeout
refactor/database-queries
docs/api-endpoints
test/integration-tests
Keep Branches Short-Lived
# Update feature branch with latest main
git checkout feature/my-feature
git fetch origin
git rebase origin/main
# Interactive rebase to clean up
git rebase -i HEAD~5
Delete Merged Branches
# Delete local branch
git branch -d feature/my-feature
# Delete remote branch
git push origin --delete feature/my-feature
# Prune deleted remote branches
git remote prune origin
# List all merged branches
git branch --merged main | grep -v "main"
Pull Request Best Practices
Before Creating a PR
# 1. Update your branch
git fetch origin
git rebase origin/main
# 2. Run tests
npm test
# 3. Check for conflicts
# Resolve any conflicts
# 4. Push
git push origin feature/my-feature
PR Description Template
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
Describe how you tested this:
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Screenshots (if applicable)
Add screenshots for UI changes.
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review
- [ ] I have commented my code where necessary
- [ ] I have updated the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective
- [ ] New and existing unit tests pass locally
Review Your Own PR First
# Before submitting, review your changes
git diff main...feature/my-feature
# Check file changes
git diff --name-only main...feature/my-feature
Code Review Guidelines
As Author
- Keep PRs small (< 400 lines)
- Provide context in description
- Respond to feedback promptly
- Don't take feedback personally
As Reviewer
- Be kind and constructive
- Ask questions, don't make demands
- Approve when satisfied, not perfect
- Review promptly (within 24 hours)
# Checkout PR locally for testing
git fetch origin pull/123/head:pr-123
git checkout pr-123
npm install
npm test
Useful Git Commands
Stash Changes
# Save work in progress
git stash save "WIP: working on feature"
# List stashes
git stash list
# Apply stash
git stash apply stash@{0}
# Apply and delete stash
git stash pop
# Delete stash
git stash drop stash@{0}
Cherry Pick
# Apply specific commit to current branch
git cherry-pick abc123def
Revert
# Undo a commit (creates new commit)
git revert abc123def
# Undo multiple commits
git revert HEAD~3..HEAD
Reset
# Undo commits (destructive!)
git reset --soft HEAD~1 # Keep changes staged
git reset --mixed HEAD~1 # Keep changes unstaged (default)
git reset --hard HEAD~1 # Discard changes completely
Clean Up History
# Interactive rebase
git rebase -i HEAD~5
# Squash commits in interactive rebase:
# pick abc123 First commit
# squash def456 Second commit
# squash ghi789 Third commit
Find Bugs
# Binary search to find bad commit
git bisect start
git bisect bad HEAD
git bisect good abc123
# Test each commit
# Mark as good or bad
git bisect good
# or
git bisect bad
# When found
git bisect reset
Search
# Search in commit messages
git log --grep="bug fix"
# Search in code
git grep "function login"
# Search in history
git log -S "login" --source --all
Git Hooks
Automate checks:
# .git/hooks/pre-commit
#!/bin/sh
npm test
npm run lint
# Make executable
chmod +x .git/hooks/pre-commit
Use Husky to share hooks:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
.gitignore Best Practices
# Dependencies
node_modules/
vendor/
# Build outputs
dist/
build/
*.min.js
# Environment
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Testing
coverage/
.nyc_output/
Merge vs. Rebase
Use Merge When
- Working on public/shared branches
- Want to preserve exact history
- Following Git Flow
git merge feature/my-feature
Use Rebase When
- Updating feature branch with main
- Want clean, linear history
- Following GitHub Flow
git rebase main
Git Aliases
Make your life easier:
# Add to ~/.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
Pro Tips
- Commit often - Easy to squash later
- Pull with rebase -
git pull --rebase - Sign commits - Use GPG for verified commits
- Use
.gitattributes- Normalize line endings - Keep main deployable - Always
- Delete merged branches - Regularly
- Use tags - Mark releases
- Write good docs - In README and CONTRIBUTING
Common Pitfalls
Force Push Safely
# Never force push to shared branches
# If you must, use --force-with-lease
git push --force-with-lease
Merge Conflicts
# Accept theirs
git checkout --theirs path/to/file
# Accept ours
git checkout --ours path/to/file
# Use merge tool
git mergetool
Keep Learning
Need help setting up a Git workflow for your team? Get in touch.