Why Git Workflow Matters
Git is powerful, but without a clear workflow, teams face merge conflicts, lost work, and confusion about what code is production-ready. A good Git workflow scales from solo projects to large teams.
Essential Git Commands
Before diving into workflows, master these:
# Configuration
git config --global user.name "Your Name"
git config --global user.email "your@email.com"
# Start tracking
git init
git clone <repo-url>
# Daily workflow
git status
git add <file>
git commit -m "message"
git push
git pull
# Branching
git branch <name>
git checkout <name>
git checkout -b <name> # create and switch
git switch <name> # modern alternative
# Viewing history
git log
git log --oneline --graph --all
git show <commit>
git diff
# Undoing changes
git restore <file> # discard changes
git reset HEAD~1 # undo last commit
git revert <commit> # create inverse commit
Commit Best Practices
Write Clear Commit Messages
# ❌ Bad: Vague, no context
git commit -m "fix"
git commit -m "update code"
git commit -m "changes"
# ✅ Good: Descriptive, actionable
git commit -m "fix: prevent duplicate user registration"
git commit -m "feat: add password reset email flow"
git commit -m "refactor: extract validation logic into utils"
Conventional Commits Format
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentationstyle: Formatting (no code change)refactor: Code restructuringtest: Adding testschore: Build/tooling
Examples:
git commit -m "feat(auth): add OAuth2 login support"
git commit -m "fix(api): handle null response in user endpoint
The /api/users endpoint was returning 500 when user not found.
Now returns 404 with proper error message.
Closes #123"
git commit -m "refactor(database): migrate from MongoDB to PostgreSQL
BREAKING CHANGE: Database schema incompatible with previous version.
See migration guide in docs/migration.md"
Atomic Commits
# ❌ Bad: Multiple unrelated changes
git add .
git commit -m "fix login, update README, refactor API"
# ✅ Good: One logical change per commit
git add src/auth/login.js
git commit -m "fix: prevent duplicate login attempts"
git add README.md
git commit -m "docs: update installation instructions"
git add src/api/
git commit -m "refactor: extract API logic into modules"
Branching Strategies
1. GitHub Flow (Simple, Continuous Deployment)
main (production)
↓
feature/add-search
↓
[Pull Request]
↓
main (deployed)
Workflow:
# 1. Create feature branch from main
git checkout main
git pull origin main
git checkout -b feature/add-search
# 2. Work on feature
git add .
git commit -m "feat: implement search functionality"
# 3. Push and create PR
git push -u origin feature/add-search
# Open PR on GitHub
# 4. After review and merge, clean up
git checkout main
git pull origin main
git branch -d feature/add-search
When to use: Small teams, continuous deployment, simple products.
2. GitFlow (Structured, Release-Based)
main (production)
↓
develop (integration)
↓
feature/new-feature
↓
release/1.0
↓
main (tagged v1.0)
Branches:
main: Production codedevelop: Integration branchfeature/*: New featuresrelease/*: Release preparationhotfix/*: Production fixes
Workflow:
# Start new feature
git checkout develop
git pull origin develop
git checkout -b feature/user-dashboard
# Work and commit
git commit -m "feat: add user statistics"
# Finish feature
git checkout develop
git merge feature/user-dashboard
git push origin develop
git branch -d feature/user-dashboard
# Create release
git checkout -b release/1.0 develop
# Fix bugs, update version
git commit -m "chore: bump version to 1.0"
# Finish release
git checkout main
git merge release/1.0
git tag -a v1.0 -m "Release version 1.0"
git checkout develop
git merge release/1.0
git branch -d release/1.0
# Hotfix
git checkout -b hotfix/critical-bug main
git commit -m "fix: resolve critical security issue"
git checkout main
git merge hotfix/critical-bug
git tag -a v1.0.1 -m "Hotfix 1.0.1"
git checkout develop
git merge hotfix/critical-bug
git branch -d hotfix/critical-bug
When to use: Scheduled releases, multiple versions in production.
3. Trunk-Based Development (Fast, Modern)
main
↓
[short-lived feature branch]
↓
main (continuously deployed)
Workflow:
# Short-lived branches (< 2 days)
git checkout -b short-feature
# Small, frequent commits
git commit -m "feat: add button component"
git commit -m "feat: wire up button click"
# Merge quickly
git checkout main
git pull origin main
git merge short-feature
git push origin main
Rules:
- Branches live < 2 days
- Merge to main multiple times per day
- Feature flags for incomplete features
- Automated testing required
When to use: High-velocity teams, strong CI/CD, continuous deployment.
Pull Request Best Practices
Creating Good PRs
## Description
Implements user search functionality with filters and pagination.
## Changes
- Added SearchBar component
- Implemented backend /api/search endpoint
- Added pagination with limit/offset
- Wrote tests for search logic
## Testing
- [ ] Unit tests pass
- [ ] Manual testing completed
- [ ] Tested on mobile
## Screenshots
[Include screenshots for UI changes]
## Related Issues
Closes #45
## Breaking Changes
None
PR Size Guidelines
# ❌ Too large: Hard to review
# 50 files changed, 2000+ lines
# ✅ Good size: Focused, reviewable
# 5-10 files changed, 200-400 lines
Split large changes:
# Instead of one massive PR
git checkout -b feature/complete-redesign
# Break into smaller PRs
git checkout -b refactor/extract-components
git checkout -b feat/new-layout
git checkout -b style/update-theme
Reviewing PRs
# Fetch PR locally
git fetch origin pull/123/head:pr-123
git checkout pr-123
# Test locally
npm install
npm test
npm start
# Leave review on GitHub
Handling Merge Conflicts
Prevention
# Keep your branch updated
git checkout feature-branch
git pull origin main # or git rebase origin/main
# Commit frequently
git commit -m "work in progress"
# Communicate with team about overlapping work
Resolution
# During merge/rebase
# <<<<<<< HEAD
# Your changes
# =======
# Their changes
# >>>>>>> branch-name
# 1. Open conflicted files
# 2. Choose or combine changes
# 3. Remove conflict markers
# 4. Test the result
# For merge
git add <resolved-files>
git commit
# For rebase
git add <resolved-files>
git rebase --continue
Tools
# Use merge tool
git mergetool
# Visual Studio Code
# Shows "Accept Current" | "Accept Incoming" | "Accept Both"
# Configure merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
Rebase vs Merge
When to Merge
# Preserves history exactly as it happened
git checkout main
git merge feature-branch
# Creates merge commit
# * Merge branch 'feature' into main
# |\
# | * feat: add feature
# * | fix: main branch fix
# |/
Use when:
- Working on public/shared branches
- Want to preserve feature branch history
- Merge commits are acceptable
When to Rebase
# Rewrites history linearly
git checkout feature-branch
git rebase main
# Linear history
# * feat: add feature
# * fix: main branch fix
Use when:
- Cleaning up feature branch before PR
- Want linear history
- Branch is not shared/published
Interactive Rebase (Cleaning History)
# Clean up last 3 commits
git rebase -i HEAD~3
# Editor opens:
pick abc1234 feat: add component
pick def5678 fix: typo
pick ghi9012 feat: add styles
# Squash commits:
pick abc1234 feat: add component
squash def5678 fix: typo
squash ghi9012 feat: add styles
# Result: Single clean commit
# feat: add component with styles
⚠️ Never rebase published/shared branches!
Tagging Releases
# Lightweight tag
git tag v1.0.0
# Annotated tag (recommended)
git tag -a v1.0.0 -m "Release version 1.0.0
Features:
- User authentication
- Dashboard
- Search functionality"
# Push tags
git push origin v1.0.0
git push origin --tags # all tags
# List tags
git tag
git tag -l "v1.*" # filter
# Checkout tag
git checkout v1.0.0
# Delete tag
git tag -d v1.0.0
git push origin --delete v1.0.0
Semantic Versioning:
v1.0.0: Major release (breaking changes)v1.1.0: Minor release (new features)v1.1.1: Patch release (bug fixes)
Git Hooks for Automation
Pre-commit Hook
# .git/hooks/pre-commit
#!/bin/sh
# Run linter
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Commit aborted."
exit 1
fi
# Run tests
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
Using Husky
npm install -D husky
npx husky install
# Add pre-commit hook
npx husky add .husky/pre-commit "npm run lint && npm test"
# Add commit-msg hook (enforce format)
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
Commitlint
npm install -D @commitlint/cli @commitlint/config-conventional
# commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'],
],
},
};
Advanced Git Techniques
Cherry-Pick
# Apply specific commit to current branch
git cherry-pick abc1234
# Multiple commits
git cherry-pick abc1234 def5678
# Range
git cherry-pick abc1234..def5678
Stash
# Save work in progress
git stash
git stash save "work on feature X"
# List stashes
git stash list
# Apply stash
git stash apply
git stash apply stash@{1}
# Apply and remove
git stash pop
# Clear all stashes
git stash clear
Bisect (Find Bug Introduction)
# Start bisect
git bisect start
git bisect bad # current commit is bad
git bisect good v1.0.0 # v1.0.0 was good
# Git checks out middle commit
# Test it
npm test
# Mark result
git bisect good # or git bisect bad
# Repeat until bug found
# Git shows the commit that introduced the bug
git bisect reset
Worktrees
# Work on multiple branches simultaneously
git worktree add ../project-hotfix hotfix/bug
# Now you have two working directories:
# ./project (main)
# ../project-hotfix (hotfix/bug)
cd ../project-hotfix
# Work on hotfix without affecting main
# List worktrees
git worktree list
# Remove worktree
git worktree remove ../project-hotfix
.gitignore Best Practices
# Dependencies
node_modules/
vendor/
# Build outputs
dist/
build/
*.min.js
*.min.css
# Environment
.env
.env.local
.env.production
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Testing
coverage/
.nyc_output/
# Temporary
*.tmp
*.temp
Global .gitignore
# ~/.gitignore_global
.DS_Store
*.swp
.idea/
.vscode/
git config --global core.excludesfile ~/.gitignore_global
Troubleshooting Common Issues
Undo Last Commit (Not Pushed)
# Keep changes, undo commit
git reset HEAD~1
# Discard changes too
git reset --hard HEAD~1
# Amend last commit
git add forgotten-file.js
git commit --amend --no-edit
Undo Pushed Commit
# Create inverse commit (safe for shared branches)
git revert abc1234
git push
# Force push (dangerous!)
git reset --hard HEAD~1
git push --force
Recover Deleted Branch
# Find commit
git reflog
# Recreate branch
git checkout -b recovered-branch abc1234
Clean Untracked Files
# Dry run
git clean -n
# Remove files
git clean -f
# Remove files and directories
git clean -fd
Team Collaboration Tips
1. Communicate Branch Intent
# Clear branch names
feature/user-authentication
fix/login-validation-bug
refactor/extract-api-layer
hotfix/security-patch
2. Write PR Descriptions
- Why the change is needed
- What was changed
- How to test
- Screenshots for UI changes
- Link to related issues
3. Review Code Promptly
- Aim for same-day reviews
- Use GitHub’s “Request Review” feature
- Block PRs requiring changes
- Approve and merge quickly
4. Keep Branches Short-Lived
- Merge within 2-3 days
- Smaller PRs are easier to review
- Reduces merge conflicts
CI/CD Integration
GitHub Actions
# .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
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm test
- run: npm run lint
Git Aliases for Productivity
# ~/.gitconfig
[alias]
st = status
co = checkout
br = branch
ci = commit
unstage = reset HEAD --
last = log -1 HEAD
visual = log --oneline --graph --all --decorate
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
uncommit = reset --mixed HEAD~1
branches = branch -a
remotes = remote -v
Use with:
git st
git co main
git visual
Best Practices Summary
- Commit often with clear messages
- Pull before push to avoid conflicts
- Branch from main, not from feature branches
- Keep PRs small (< 400 lines)
- Review code promptly
- Use conventional commits
- Never force push to shared branches
- Tag releases with semantic versioning
- Automate checks with hooks and CI
- Communicate with your team
