Git Workflow Best Practices: From Solo Dev to Team Collaboration

Master Git with practical workflows, branching strategies, commit conventions, and collaboration patterns. Learn GitFlow, GitHub Flow, and trunk-based development.

Developer Workflow: Git Workflow Best Practices: From Solo Dev to Team Collaboration

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 feature
  • fix: Bug fix
  • docs: Documentation
  • style: Formatting (no code change)
  • refactor: Code restructuring
  • test: Adding tests
  • chore: 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 code
  • develop: Integration branch
  • feature/*: New features
  • release/*: Release preparation
  • hotfix/*: 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

  1. Commit often with clear messages
  2. Pull before push to avoid conflicts
  3. Branch from main, not from feature branches
  4. Keep PRs small (< 400 lines)
  5. Review code promptly
  6. Use conventional commits
  7. Never force push to shared branches
  8. Tag releases with semantic versioning
  9. Automate checks with hooks and CI
  10. Communicate with your team

Resources