Git Workflow

Version Control Best Practices

2026-02-01

Explanation

Why Git Workflows Matter

A consistent Git workflow helps teams collaborate effectively. It prevents conflicts, enables code review, and maintains a clean history.

Key Concepts

  • Branching: Isolated development streams
  • Commits: Atomic, logical changes
  • Pull Requests: Code review process
  • Merging: Combining branches

Common Workflows

| Workflow | Best For | |----------|----------| | GitHub Flow | Small teams, continuous deployment | | GitFlow | Release-based products | | Trunk-Based | Large teams, frequent deploys |


Demonstration

Example 1: GitHub Flow

# 1. Create feature branch from main
git checkout main
git pull origin main
git checkout -b feature/user-authentication

# 2. Make changes and commit
git add src/auth/
git commit -m "Add user authentication module

- Implement login/logout endpoints
- Add JWT token generation
- Create auth middleware

Co-Authored-By: Claude <noreply@anthropic.com>"

# 3. Push branch
git push -u origin feature/user-authentication

# 4. Create Pull Request (via GitHub CLI)
gh pr create --title "Add user authentication" --body "## Summary
- Implements login/logout functionality
- Uses JWT for stateless auth
- Adds middleware for protected routes

## Test Plan
- [ ] Test login with valid credentials
- [ ] Test login with invalid credentials
- [ ] Test protected route access"

# 5. After review, merge
gh pr merge --squash

# 6. Delete branch
git checkout main
git pull origin main
git branch -d feature/user-authentication

Example 2: Commit Best Practices

# Good commit messages
git commit -m "Add user registration endpoint

Implements POST /api/users endpoint that:
- Validates email and password
- Hashes password with bcrypt
- Returns JWT token on success

Closes #123"

git commit -m "Fix login rate limiting bug

The rate limiter was using IP address from X-Forwarded-For
without proper parsing, causing all users behind a proxy
to share the same limit.

Now correctly parses the first IP in the chain."

git commit -m "Refactor auth middleware for clarity

No functional changes. Extracts token validation
into separate function and adds JSDoc comments."

# Bad commit messages
# git commit -m "fix"
# git commit -m "WIP"
# git commit -m "changes"
# git commit -m "asdfasdf"

# Atomic commits - one logical change per commit
# Instead of one big commit with everything:
git add src/models/user.js
git commit -m "Add User model with validation"

git add src/routes/users.js
git commit -m "Add user CRUD routes"

git add tests/users.test.js
git commit -m "Add user route tests"

Example 3: Branch Management

# Naming conventions
# feature/   - new features
# fix/       - bug fixes
# hotfix/    - urgent production fixes
# refactor/  - code improvements
# docs/      - documentation
# test/      - test additions

git checkout -b feature/shopping-cart
git checkout -b fix/login-validation
git checkout -b hotfix/security-patch
git checkout -b refactor/auth-module

# Keep branches up to date with main
git checkout feature/my-feature
git fetch origin
git rebase origin/main

# Or merge (creates merge commit)
git merge origin/main

# Interactive rebase to clean up commits
git rebase -i HEAD~3
# Opens editor to squash, reword, reorder commits

# Stash changes temporarily
git stash
git checkout other-branch
# Do work...
git checkout -
git stash pop

# Cherry-pick specific commits
git cherry-pick abc123

# View branch relationships
git log --oneline --graph --all

Example 4: Handling Conflicts

# When merge conflict occurs
git merge feature-branch
# CONFLICT (content): Merge conflict in src/app.js

# View conflicting files
git status

# Edit file to resolve conflicts
# <<<<<<< HEAD
# const port = 3000;
# =======
# const port = process.env.PORT || 3000;
# >>>>>>> feature-branch

# After resolving:
git add src/app.js
git commit -m "Merge feature-branch, resolve port config conflict"

# Abort if needed
git merge --abort

# Using a merge tool
git mergetool

# Prevent conflicts with regular rebasing
git checkout feature-branch
git fetch origin
git rebase origin/main
# Resolve any conflicts one commit at a time
git add .
git rebase --continue

Example 5: Advanced Operations

# Undo last commit (keep changes)
git reset --soft HEAD~1

# Undo last commit (discard changes)
git reset --hard HEAD~1

# Amend last commit
git add forgotten-file.js
git commit --amend --no-edit

# Revert a pushed commit (safe for shared branches)
git revert abc123

# Find when bug was introduced
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Git will checkout commits for you to test
git bisect good  # or bad
# Continue until found
git bisect reset

# View file history
git log --follow -p -- src/app.js

# Find who changed a line
git blame src/app.js

# Clean up untracked files
git clean -fd

# Create patch
git diff > changes.patch
git apply changes.patch

# Squash all commits on branch into one
git checkout feature-branch
git reset --soft main
git commit -m "Feature: complete implementation"

Example 6: Git Hooks

#!/bin/sh
# .git/hooks/pre-commit

# Run linter
npm run lint
if [ $? -ne 0 ]; then
    echo "Linting failed. Fix errors before committing."
    exit 1
fi

# Run tests
npm test
if [ $? -ne 0 ]; then
    echo "Tests failed. Fix tests before committing."
    exit 1
fi

# Check for console.log
if git diff --cached | grep -E '^\+.*console\.log'; then
    echo "Warning: console.log found in changes"
    read -p "Continue anyway? (y/n) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        exit 1
    fi
fi
#!/bin/sh
# .git/hooks/commit-msg

# Enforce commit message format
commit_msg=$(cat "$1")

# Check for conventional commits format
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}"; then
    echo "Error: Commit message must follow conventional commits format"
    echo "Example: feat(auth): add login endpoint"
    exit 1
fi

Key Takeaways:

  • Write meaningful commit messages
  • Keep branches short-lived
  • Rebase to keep history clean
  • Use PRs for code review
  • Automate with hooks

Imitation

Challenge 1: Set Up Git Hooks

Task: Create pre-commit hooks that lint and test code.

Solution

# Install husky (npm)
npm install husky lint-staged --save-dev
npx husky install

# package.json
{
  "scripts": {
    "prepare": "husky install"
  },
  "lint-staged": {
    "*.{js,ts}": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

# Create pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"

# Create commit-msg hook for conventional commits
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'subject-case': [2, 'always', 'lower-case'],
    'subject-max-length': [2, 'always', 72]
  }
};

Challenge 2: Create Release Script

Task: Automate version bumping and changelog generation.

Solution

#!/bin/bash
# release.sh

set -e

# Get version type (major, minor, patch)
VERSION_TYPE=${1:-patch}

# Ensure clean working directory
if [[ -n $(git status --porcelain) ]]; then
    echo "Working directory not clean. Commit or stash changes first."
    exit 1
fi

# Ensure on main branch
CURRENT_BRANCH=$(git branch --show-current)
if [[ "$CURRENT_BRANCH" != "main" ]]; then
    echo "Must be on main branch to release"
    exit 1
fi

# Pull latest
git pull origin main

# Run tests
npm test

# Bump version
NEW_VERSION=$(npm version $VERSION_TYPE --no-git-tag-version)
echo "Bumping to $NEW_VERSION"

# Generate changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s

# Commit and tag
git add package.json package-lock.json CHANGELOG.md
git commit -m "chore(release): $NEW_VERSION"
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"

# Push
git push origin main --tags

echo "Released $NEW_VERSION!"


Practice

Exercise 1: Branch Strategy

Difficulty: Intermediate

Set up a GitFlow workflow:

  • main, develop branches
  • Feature branches
  • Release process

Exercise 2: Monorepo Management

Difficulty: Advanced

Configure Git for a monorepo:

  • Sparse checkout
  • Subtree management
  • Selective CI triggers

Summary

What you learned:

  • GitHub Flow for simple projects
  • Commit message best practices
  • Branch management strategies
  • Conflict resolution
  • Git hooks for automation

Next Steps:

  • Read: CI/CD Pipelines
  • Practice: Contribute to open source
  • Explore: Git internals

Resources