Click a level to toggle visibility. All levels shown by default.
Branching Strategies
Your branching strategy defines how your team collaborates. There is no universal best choice — the right strategy depends on team size, release cadence, and deployment model.
Which Strategy Should You Use? Intermediate
Strategy Comparison Beginner
| Strategy | Branches | Best For | Complexity | Used By |
|---|---|---|---|---|
| GitFlow | main, develop, feature/*, release/*, hotfix/* | Versioned software, scheduled releases | High | Enterprise, mobile apps |
| GitHub Flow | main + short-lived feature branches | Web apps, continuous deployment | Low | Startups, SaaS teams |
| Trunk-Based | main only (branches < 1 day) | Mature teams, strong CI/CD | Medium | Google, Meta, Netflix |
| Release Flow | main + release branches | Large-scale services | Medium | Microsoft |
GitHub Flow in Action Beginner
GitFlow Branch Model Intermediate
Trunk-Based Development (TBD) is the gold standard for high-performing teams, but it demands infrastructure maturity:
- Feature flags: Every incomplete feature must be toggleable at runtime. Tools: LaunchDarkly, Unleash, Flipt, or homegrown.
- Comprehensive CI: Every commit to main must pass the full test suite in under 10 minutes. Flaky tests are blockers, not nuisances.
- Short-lived branches: If you use branches at all, they must merge within 24 hours. Long-lived branches defeat the purpose.
- Code review speed: PRs must be reviewed within hours, not days. Consider pair programming as an alternative to async review.
- Observability: You need instant rollback capability and real-time monitoring to catch regressions deployed to production.
Commit Message Conventions
Good commit messages are a love letter to your future self. They make git log, git blame, and git bisect infinitely more useful.
The Seven Rules Beginner
Separate subject from body
Leave a blank line between the subject and body. This makes git log --oneline work beautifully.
Limit subject to 50 chars
Forces clarity. GitHub truncates at 72, so 50 keeps you safely visible everywhere.
Capitalize the subject
Start with an uppercase letter. "Fix bug" not "fix bug".
No trailing period
The subject line is a title, not a sentence. Drop the period.
Imperative mood
"Add feature" not "Added feature" or "Adds feature". Match Git's own style: "Merge branch..."
Wrap body at 72 chars
Git doesn't auto-wrap. You must do it. Keeps git log readable in any terminal.
Explain what and why
The body should explain what changed and why, not how. The diff shows how.
Conventional Commits Intermediate
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
| Type | Purpose | Example |
|---|---|---|
feat | New feature | feat(auth): add OAuth2 login flow |
fix | Bug fix | fix(api): handle null response from /users |
docs | Documentation only | docs: update API migration guide |
style | Formatting, whitespace | style: fix indentation in auth module |
refactor | Code restructuring | refactor(db): extract query builder |
perf | Performance improvement | perf: cache user lookups in memory |
test | Adding/fixing tests | test(auth): add edge cases for token refresh |
build | Build system changes | build: upgrade webpack to v6 |
ci | CI configuration | ci: add Node 22 to test matrix |
chore | Maintenance tasks | chore: update dependencies |
revert | Revert a previous commit | revert: feat(auth): add OAuth2 login flow |
Anatomy of a Great Commit Message Beginner
commitlint + a commit-msg hook. This automates CHANGELOG generation and semantic versioning via tools like semantic-release or standard-version.
Merge vs Rebase
The merge vs rebase debate is the "tabs vs spaces" of Git. Both have valid use cases. The key is understanding when each shines.
git merge
- Preserves complete history
- Non-destructive operation
- Creates clear merge points
- Safe for shared branches
- Easy to understand
- Clutters history with merge commits
- Harder to read
git log - Can obscure feature boundaries
git rebase
- Clean, linear history
- Easy to follow with
git log - No unnecessary merge commits
- Cleaner
git bisect - Makes cherry-pick easier
- Rewrites commit history
- Dangerous on shared branches
- Harder to resolve conflicts
The Golden Rule Beginner
The Recommended Workflow Intermediate
# 1. Work on your feature branch
git checkout -b feat/new-widget
# 2. Make commits...
git commit -m "feat(widget): add base component"
git commit -m "feat(widget): add click handler"
# 3. Before merging, rebase onto latest main
git fetch origin
git rebase origin/main
# 4. Resolve any conflicts, then force-push your branch
git push --force-with-lease
# 5. Create PR and merge (squash or merge commit)
Squash Merge Intermediate
Squash merge combines all commits from a feature branch into a single commit on main. Popular with GitHub Flow because it keeps main history ultra-clean.
# Squash merge a feature branch
git merge --squash feat/new-widget
git commit # Write a single summary commit message
# On GitHub: use the "Squash and merge" button on PRs
--force-with-lease instead of --force when pushing after a rebase. It refuses to overwrite the remote if someone else pushed to the same branch, preventing you from destroying their work.
Git Hooks
Hooks automate quality checks at key points in your workflow. They catch issues before they reach CI, saving time and keeping your repository clean.
Hook Lifecycle Beginner
Tool Comparison Intermediate
| Tool | Language | Speed | Parallel | Config Format | Best For |
|---|---|---|---|---|---|
| Husky | Node.js | Medium | No | Shell scripts in .husky/ | JS/TS projects |
| lint-staged | Node.js | Fast | Yes | JSON/JS in package.json | Running linters on staged files only |
| Lefthook | Go | Fast | Yes | YAML (lefthook.yml) | Polyglot projects, speed-critical |
| pre-commit | Python | Medium | Yes | YAML (.pre-commit-config.yaml) | Python projects, polyglot |
Husky + lint-staged Setup Beginner
# Install Husky and lint-staged
npm install --save-dev husky lint-staged
# Initialize Husky
npx husky init
# Create pre-commit hook
echo "npx lint-staged" > .husky/pre-commit
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yml}": ["prettier --write"],
"*.css": ["stylelint --fix", "prettier --write"]
}
}
Lefthook Alternative Advanced
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts,tsx}"
run: npx eslint --fix {staged_files}
format:
glob: "*.{js,ts,tsx,json,md}"
run: npx prettier --write {staged_files}
typecheck:
run: npx tsc --noEmit
commit-msg:
commands:
validate:
run: npx commitlint --edit {1}
Code Review Best Practices
Code review is the highest-leverage activity in a development team. Done well, it catches bugs, shares knowledge, and improves design. Done poorly, it blocks progress and breeds resentment.
Google's Standard Beginner
The Magic Number: 400 LOC Beginner
Small PRs (<400 LOC)
Higher review quality. Faster merge time. Fewer bugs escape. Easier to revert if something breaks. Reviewers actually read the code.
Large PRs (>400 LOC)
Reviewers skim or rubber-stamp. Merge conflicts increase. Harder to understand context. Higher cognitive load leads to missed issues.
Review Checklist Intermediate
| Category | Check | Priority |
|---|---|---|
| Correctness | Does the code do what it claims? | Critical |
| Design | Is the architecture appropriate? Any over-engineering? | Critical |
| Tests | Are edge cases covered? Do tests actually assert behavior? | Critical |
| Security | Any injection vectors, leaked secrets, or auth bypasses? | Critical |
| Complexity | Can a reader understand it quickly? Could it be simpler? | High |
| Naming | Are variables, functions, classes clearly named? | High |
| Comments | Do comments explain why, not what? | Medium |
| Edge Cases | Null checks, empty arrays, boundary conditions? | High |
| Performance | Any N+1 queries, unnecessary re-renders, memory leaks? | Medium |
- CODEOWNERS file: Automatically assign reviewers based on file paths. Ensures domain experts review their area.
- Stacked PRs: Break large features into a chain of small, dependent PRs. Each can be reviewed and merged independently. Tools:
ghstack,graphite,spr. - AI-assisted review: Use AI tools (GitHub Copilot, Claude) for first-pass review — catching typos, style issues, simple bugs. Human reviewers focus on design and business logic.
- Review SLAs: Set team expectations. E.g., "First review within 4 hours." Track metrics. Review latency kills velocity.
- Praise: Call out good code, not just problems. "Nice abstraction here" builds culture.
Git Security
Security in Git goes beyond just keeping secrets out of commits. It encompasses identity verification, access control, and automated scanning.
Essential .gitignore Beginner
# Environment & secrets
.env
.env.*
!.env.example
*.pem
*.key
credentials.json
service-account.json
# Dependencies
node_modules/
vendor/
.venv/
# Build output
dist/
build/
.next/
out/
# IDE & OS
.DS_Store
.idea/
.vscode/settings.json
*.swp
Commit Signing with SSH Intermediate
Since Git 2.34, you can sign commits with SSH keys — no GPG setup required. This proves commits come from the key owner, not just someone who configured user.name.
# Configure SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
# Verify a signed commit
git log --show-signature -1
Secret Scanning Advanced
# Install gitleaks
brew install gitleaks
# Scan current state
gitleaks detect --source .
# Scan full history
gitleaks detect --source . --log-opts="--all"
# Use as pre-commit hook
gitleaks protect --staged
Branch Protection Rules Intermediate
Required Reviews
Minimum 1-2 approving reviews before merge. Dismiss stale reviews on new pushes.
Status Checks
Require CI to pass. Block merge on test failure, lint errors, or type check violations.
Signed Commits
Require commit signing to verify author identity. Prevents impersonation.
Linear History
Require squash or rebase merges. No merge commits = cleaner history.
Git Worktrees
Worktrees let you check out multiple branches simultaneously in separate directories, all sharing the same .git database. Essential for parallel development, especially with AI coding assistants.
Basic Usage Intermediate
# Create a new worktree for a feature branch
git worktree add ../my-project-feat feat/new-feature
# Create worktree with a new branch
git worktree add -b feat/auth ../my-project-auth
# List all worktrees
git worktree list
# Remove a worktree when done
git worktree remove ../my-project-feat
# Prune stale worktree metadata
git worktree prune
AI-Assisted Development with Worktrees Advanced
.worktreeinclude to copy .env and secrets into new worktrees automatically.
# Set up parallel worktrees for multiple features
git worktree add -b feat/auth ../project-auth
git worktree add -b feat/search ../project-search
git worktree add -b fix/perf ../project-perf
# Launch separate Claude Code sessions in each
cd ../project-auth && claude "implement OAuth2 flow" &
cd ../project-search && claude "add full-text search" &
cd ../project-perf && claude "optimize database queries" &
# Each agent works in isolation, no merge conflicts
Shared .git
All worktrees share the same objects, refs, and config. No duplicate clones eating disk space.
Branch Isolation
Each worktree checks out a different branch. You can't have two worktrees on the same branch.
.worktreeinclude
List files (like .env) to copy into new worktrees. Keeps secrets available without committing them.
Interactive Rebase Mastery
Interactive rebase (git rebase -i) is the power tool for crafting clean, logical commit histories before merging.
Rebase Operations Intermediate
| Command | Short | Effect |
|---|---|---|
pick | p | Use commit as-is |
reword | r | Use commit but edit the message |
edit | e | Stop at commit for amending |
squash | s | Meld into previous commit, combine messages |
fixup | f | Meld into previous commit, discard this message |
drop | d | Remove commit entirely |
The Autosquash Workflow Advanced
Autosquash is the cleanest way to fix up commits during development. Create fixup commits as you go, then squash them all at once before merging.
# Enable autosquash globally (recommended)
git config --global rebase.autosquash true
# Make your initial commits
git commit -m "feat: add user profile page" # abc1234
git commit -m "feat: add avatar upload" # def5678
# Later, realize you need to fix the profile page commit
git commit --fixup=abc1234 # Creates "fixup! feat: add user profile page"
# When ready to clean up, autosquash reorders and fixups
git rebase -i --autosquash main
# The fixup commit is auto-placed after its target
Monorepo Strategies
Managing multiple packages, apps, or services in a single repository requires specialized Git strategies to keep things manageable.
Approach Comparison Intermediate
| Approach | Pros | Cons | Verdict |
|---|---|---|---|
| True Monorepo | Atomic commits, shared tooling, easy refactoring | Requires build orchestration (Nx, Turborepo) | Recommended |
| Git Submodules | Independent version control per module | Complex workflow, nested repos, easy to break | Avoid |
| Git Subtrees | Simpler than submodules, no nested .git | History gets messy, push/pull syntax complex | Situational |
| Sparse Checkout | Clone only what you need from huge repos | Requires Git 2.25+, team coordination | Recommended |
Sparse Checkout for Large Monorepos Advanced
# Clone with sparse checkout (only root files)
git clone --sparse https://github.com/org/monorepo.git
cd monorepo
# Add only the directories you need
git sparse-checkout add packages/auth
git sparse-checkout add packages/shared
git sparse-checkout add infra/terraform
# List current sparse-checkout patterns
git sparse-checkout list
# Combine with partial clone for maximum speed
git clone --sparse --filter=blob:none https://github.com/org/monorepo.git
Git Performance Optimization
As repositories grow, Git operations slow down. These techniques keep things fast even for large codebases.
Clone Strategies Intermediate
| Strategy | Command | What It Skips | Use Case |
|---|---|---|---|
| Full Clone | git clone <url> |
Nothing | Primary development |
| Shallow Clone | git clone --depth=1 |
All history except latest commit | CI/CD pipelines |
| Partial Clone (blobless) | git clone --filter=blob:none |
File contents until checkout | Large repos, fast initial clone |
| Partial Clone (treeless) | git clone --filter=tree:0 |
Trees and blobs until needed | Fastest clone, limited local ops |
| Sparse + Partial | git clone --sparse --filter=blob:none |
Files outside sparse-checkout set | Monorepo focused development |
Background Maintenance Advanced
# Enable background maintenance (Git 2.31+)
git maintenance start
# Runs hourly: prefetch, commit-graph, loose-objects, incremental-repack
# Manual optimization
git gc --aggressive # Deep garbage collection
git repack -a -d --depth=250 # Optimal pack file
git commit-graph write --reachable # Speed up log/rev-list
# Check repo stats
git count-objects -vH # Size and object counts
For repos with millions of files, even git status can take seconds. Two features help:
# Sparse index: shrink the index to only tracked paths
git config core.sparseCheckoutCone true
git sparse-checkout init --cone
git config index.sparse true
# Filesystem monitor (macOS/Windows/Linux)
git config core.fsmonitor true
git config core.untrackedcache true
# Uses OS-level file system events instead of scanning
With both enabled, git status on a 3M-file repo drops from ~10s to <200ms.
Large File Handling
Git was designed for text files. Binary assets (images, videos, datasets, models) require specialized handling to keep your repository lean.
Solution Comparison Intermediate
| Tool | How It Works | Max File Size | Best For |
|---|---|---|---|
| Git LFS | Pointer files in repo, content on LFS server | ~5 GB (GitHub limit) | Binary assets, design files, media |
| git-annex | Decentralized, multiple backends (S3, rsync) | Unlimited | Research datasets, scientific data |
| DVC | Data version control, ML-focused | Unlimited | ML models, training datasets |
| External storage | Don't track in Git at all | Unlimited | Large media, build artifacts |
Git LFS Setup Beginner
# Install and initialize LFS
brew install git-lfs
git lfs install
# Track file patterns
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.zip"
git lfs track "models/**"
# Verify what's tracked
git lfs ls-files
# Don't forget to commit .gitattributes
git add .gitattributes
git commit -m "chore: configure LFS tracking"
Git Recovery & Debugging
Git almost never loses data. If you can find the SHA, you can recover the commit. The reflog is your safety net.
The Reflog: Your Safety Net Intermediate
# View the reflog (all HEAD movements)
git reflog
# abc1234 HEAD@{0}: commit: feat: add search
# def5678 HEAD@{1}: rebase: squash
# 789abcd HEAD@{2}: checkout: moving from main to feat/search
# Recover a "lost" commit after bad rebase
git reset --hard HEAD@{2}
# Cherry-pick a specific lost commit
git cherry-pick abc1234
# Create a branch from a reflog entry
git branch recovery-branch HEAD@{5}
Git Bisect: Binary Search for Bugs Advanced
# Start bisecting
git bisect start
# Mark current commit as bad (has the bug)
git bisect bad
# Mark a known good commit (before the bug)
git bisect good v2.1.0
# Git checks out the midpoint. Test it, then:
git bisect good # if this commit is OK
git bisect bad # if this commit has the bug
# Repeat until Git identifies the exact commit
# Automate with a test script
git bisect run npm test
# Clean up when done
git bisect reset
Emergency Recovery Cheat Sheet Intermediate
git reset --soft HEAD~1
Undo last commit, keep changes staged
git reset --mixed HEAD~1
Undo last commit, keep changes unstaged
git checkout -- <file>
Discard unstaged changes to a file
git stash
Temporarily shelve working directory changes
git reflog && git reset --hard <SHA>
Recover from bad rebase/reset
git fsck --unreachable
Find orphaned/dangling objects
CI/CD Integration
Git and CI/CD are inseparable in modern development. Branch protection, automated testing, and deployment pipelines all hinge on Git events.
CI Pipeline Stages Beginner
GitHub Actions Workflow Intermediate
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
Reusable Workflows Advanced
jobs:
lint-and-test:
uses: ./.github/workflows/shared-ci.yml
with:
node-version: '22'
secrets: inherit
deploy:
needs: lint-and-test
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/deploy.yml
with:
environment: production
actions/cache for node_modules, .next/cache, Docker layers, and package manager lock files. A cache hit can cut CI time by 60-80%.
Git Aliases & Productivity
Git aliases eliminate repetitive typing and encode best practices into muscle memory. A well-configured .gitconfig is a productivity multiplier.
Essential Aliases Beginner
[alias]
# Status & logging
st = status -sb
lg = log --oneline --graph --decorate --all
last = log -1 HEAD --stat
# Branching
co = checkout
br = branch
brd = branch -d
sw = switch
# Committing
ci = commit
amend = commit --amend --no-edit
wip = !git add -A && git commit -m 'WIP'
# Undoing
undo = reset --soft HEAD~1
unstage = reset HEAD --
# Syncing
sync = !git fetch origin && git rebase origin/main
pf = push --force-with-lease
Power Aliases Advanced
[alias]
# Show branches sorted by last commit date
recent = branch --sort=-committerdate --format='%(committerdate:relative)\t%(refname:short)'
# Delete all merged branches (except main/develop)
cleanup = !git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d
# Show which files changed between two branches
changed = diff --name-only
# Interactive fixup (select commit to fix with fzf)
fixup = !git log --oneline -20 | fzf | awk '{print $1}' | xargs git commit --fixup
# Who changed this file the most?
authors = !git log --format='%aN' -- \"$1\" | sort | uniq -c | sort -rn
# Today's commits
today = log --since=midnight --oneline --no-merges
Useful Git Config Settings Intermediate
[init]
defaultBranch = main
[pull]
rebase = true # Rebase instead of merge on pull
[push]
autoSetupRemote = true # Auto-create upstream on first push
default = current # Push current branch by default
[fetch]
prune = true # Auto-remove dead remote branches
[rebase]
autosquash = true # Auto-fixup on interactive rebase
autostash = true # Auto-stash before rebase
[merge]
conflictstyle = zdiff3 # Show base in conflict markers
[diff]
algorithm = histogram # Better diff output
colorMoved = default # Highlight moved lines
[rerere]
enabled = true # Remember conflict resolutions
fzf Integration Expert
# Interactive branch switcher
fbr() {
local branch
branch=$(git branch --all --sort=-committerdate | \
fzf --height 40% --preview 'git log --oneline -10 {1}' | \
sed 's/remotes\/origin\///' | xargs)
git checkout "$branch"
}
# Interactive log browser with diff preview
flog() {
git log --oneline --color=always | \
fzf --ansi --preview 'git show --stat --color=always {1}'
}
# Interactive stash manager
fstash() {
local stash
stash=$(git stash list | fzf --preview 'git stash show -p {1}' | \
cut -d: -f1)
git stash apply "$stash"
}
AI-Assisted Git Workflows
AI coding assistants are reshaping how developers interact with Git. The key insight: AI generates, humans verify. Never auto-push AI-generated code without review.
The AI + Git Workflow Intermediate
Claude Code Patterns Advanced
Worktree Isolation
Each Claude agent gets its own worktree and branch. Parallel development without merge conflicts. Use --worktree flag.
Commit Generation
Claude writes conventional commit messages from diffs. Add Co-Authored-By trailers to track AI contributions.
PR Creation
Claude drafts PR descriptions from commit history. Human reviews and approves. Never auto-merge.
Code Review
AI handles first-pass review: style, typos, simple bugs. Humans focus on design, business logic, and architecture.
GitClear Findings (2025) Intermediate
# Always attribute AI contributions in commits
git commit -m "feat(search): add full-text search API
Implements Elasticsearch-backed search with fuzzy matching
and relevance scoring. Includes rate limiting and caching.
Co-Authored-By: Claude <noreply@anthropic.com>"
Quick Reference Cheat Sheet
Daily Operations Beginner
git status -sb
Short status with branch info
git add -p
Stage changes interactively, hunk by hunk
git diff --cached
Show what's staged for the next commit
git log --oneline --graph -20
Visual commit history, last 20 commits
git stash push -m "description"
Stash changes with a descriptive name
git switch -c feat/my-feature
Create and switch to a new branch
Branch Management Intermediate
git branch --sort=-committerdate
List branches by most recent commit
git push -u origin HEAD
Push current branch and set upstream
git fetch --prune
Fetch and remove dead remote tracking branches
git branch --merged | xargs git branch -d
Delete all locally merged branches
git cherry-pick <SHA>
Apply a specific commit to current branch
git rebase --onto main feat/old feat/new
Transplant a branch to a new base
Investigation Advanced
git log -S "functionName" --oneline
Find commits that added/removed a string
git log --all --follow -- path/to/file
Full history of a file, including renames
git blame -w -C -C -C <file>
Blame ignoring whitespace and detecting moves
git diff main...HEAD --stat
Files changed on this branch vs main
git shortlog -sn --no-merges
Commit count by author (leaderboard)
git rev-list --count main..HEAD
Number of commits ahead of main
The Complete .gitconfig Expert
[user]
name = Your Name
email = you@example.com
[init]
defaultBranch = main
[core]
editor = code --wait
autocrlf = input
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
side-by-side = true
line-numbers = true
[pull]
rebase = true
[push]
autoSetupRemote = true
default = current
[fetch]
prune = true
pruneTags = true
[rebase]
autosquash = true
autostash = true
updateRefs = true
[merge]
conflictstyle = zdiff3
[diff]
algorithm = histogram
colorMoved = default
mnemonicPrefix = true
[rerere]
enabled = true
[branch]
sort = -committerdate
[tag]
sort = -version:refname
[gpg]
format = ssh
[commit]
gpgsign = true
[alias]
st = status -sb
lg = log --oneline --graph --decorate --all
last = log -1 HEAD --stat
co = checkout
sw = switch
br = branch
ci = commit
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
unstage = reset HEAD --
wip = !git add -A && git commit -m 'WIP'
sync = !git fetch origin && git rebase origin/main
pf = push --force-with-lease
recent = branch --sort=-committerdate --format='%(committerdate:relative)\t%(refname:short)'
cleanup = !git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d
today = log --since=midnight --oneline --no-merges
fixup = !git log --oneline -20 | fzf | awk '{print $1}' | xargs git commit --fixup