Git Handbook
Git is a distributed version control system created by Linus Torvalds in 2005. Every developer has a full copy of the repository history — no central server required for local operations. Git tracks snapshots, not diffs, and uses a content-addressable store (SHA-1 / SHA-256) to ensure integrity. It is the universal standard for source control, used from solo projects to multi-thousand-engineer monorepos.
This handbook is organised around what you want to do, not around commands. Each section covers a real-world use case with the commands you actually need.
Resources
- git-scm.com — official Git website and downloads
- Pro Git — the definitive free Git book by Scott Chacon
- GitHub Docs — GitHub-specific workflows, Actions, and CLI
- Atlassian Git tutorials — visual, workflow-focused guides
- Oh Shit, Git!? — quick fixes for common Git mistakes
Setup & Config
Identity
Git embeds your name and email in every commit. Set these before your first commit.
# Set globally (stored in ~/.gitconfig)
git config --global user.name "Ada Lovelace"
git config --global user.email "ada@example.com"
# Set per-repo (overrides global)
git config user.email "work@company.com"
# Show effective config
git config --list --show-originUseful Aliases
Aliases shorten repetitive commands. They live in ~/.gitconfig under [alias].
# Compact log graph
git config --global alias.lg "log --oneline --graph --decorate --all"
# Shorter status
git config --global alias.st "status -sb"
# Undo last commit, keep changes staged
git config --global alias.undo "reset --soft HEAD~1"
# List all aliases
git config --global alias.aliases "config --get-regexp alias"Editor & Diff Tool
# Use VS Code as commit editor and diff tool
git config --global core.editor "code --wait"
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
# Use VS Code as merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'Daily Work
What changed?
# What is staged, modified, untracked?
git status
git status -sb # compact output
# Diff working tree vs last commit
git diff # unstaged changes
git diff --staged # staged changes (will go into next commit)
git diff HEAD # all changes (staged + unstaged) vs last commitStage & Unstage
The staging area (index) lets you compose commits precisely. You can stage individual hunks with -p to keep each commit focused.
# Stage specific file / directory
git add src/app.js
git add src/
# Stage only parts of a file (interactive hunk picker)
git add -p src/app.js
# Stage everything (tracked + untracked)
git add .
# Unstage a file (keep changes in working tree)
git restore --staged src/app.js
# Discard changes in working tree (DESTRUCTIVE — changes are gone)
git restore src/app.jsCommit
# Commit staged changes
git commit -m "feat: add login endpoint"
# Stage all tracked files and commit in one step
git commit -am "fix: correct off-by-one in pagination"
# Open editor for a multi-line message
git commit
# Commit with a body
git commit -m "feat: rate limiting" -m "Adds a token-bucket per IP.
Configurable via RATE_LIMIT_RPS env var.
Closes #42"Amend Last Commit
Fix a mistake in the most recent commit — before it is pushed to a shared branch.
# Fix the last commit message (not yet pushed)
git commit --amend -m "correct message"
# Add a forgotten file to the last commit
git add forgotten.js
git commit --amend --no-edit
# Change the author of the last commit
git commit --amend --author="Ada Lovelace <ada@example.com>" --no-edit
# ⚠️ Never amend commits that have already been pushed to a shared branchStash Work-in-Progress
Need to switch context urgently? Stash saves your dirty working tree so you can come back to it later.
# Save WIP (tracked files only)
git stash
git stash push -m "half-done feature X"
# Include untracked files
git stash push -u -m "with new files"
# List stashes
git stash list
# Apply latest stash and keep it in the list
git stash apply
# Apply specific stash
git stash apply stash@{2}
# Apply and remove from list
git stash pop
# Drop a stash
git stash drop stash@{0}
# Show diff of a stash
git stash show -p stash@{0}Branches
Create & Switch
Branches in Git are cheap — just a pointer to a commit. Create them freely.
# Create and switch in one step
git switch -c feature/login
# Create from a specific point (commit / tag / branch)
git switch -c hotfix/typo origin/main
# Switch to existing branch
git switch main
# Legacy syntax (still works)
git checkout -b feature/loginMerge
# Fast-forward merge (linear history, no merge commit)
git switch main
git merge feature/login
# Always create a merge commit (record that branches diverged)
git merge --no-ff feature/login
# Abort a conflicting merge
git merge --abort
# After resolving conflicts manually:
git add resolved-file.js
git merge --continue
# Merge only specific file from another branch
git checkout feature/ui -- src/styles/theme.cssRebase
Rebase moves your commits on top of another branch, creating a linear history. Use it on private branches; avoid it on shared ones.
# Replay feature branch on top of main
git switch feature/login
git rebase main
# Abort if conflicts are too messy
git rebase --abort
# After resolving each conflict:
git add resolved-file.js
git rebase --continue
# Rebase onto a specific commit
git rebase --onto main feature/old feature/new
# ⚠️ Never rebase commits shared with others (force-push required after)Delete & Rename
# Delete a fully-merged branch
git branch -d feature/login
# Force delete (even if not merged)
git branch -D spike/experiment
# Delete remote tracking branch
git push origin --delete feature/login
git remote prune origin # remove stale remote-tracking refs
# Rename current branch
git branch -m new-name
# Rename any branch
git branch -m old-name new-nameTrack Remote
# See tracking relationships
git branch -vv
# Set upstream for current branch
git branch --set-upstream-to=origin/main
# Push and set upstream at the same time
git push -u origin feature/login
# Fetch all remotes and prune deleted ones
git fetch --all --pruneRemote & Sync
Manage Remotes
# List remotes
git remote -v
# Add a remote
git remote add origin https://github.com/user/repo.git
git remote add upstream https://github.com/original/repo.git
# Change remote URL (e.g. switch to SSH)
git remote set-url origin git@github.com:user/repo.git
# Rename / remove
git remote rename origin old-origin
git remote remove old-originFetch & Pull
fetch downloads without touching your working tree. pull fetches then merges.
# Download new commits, don't merge
git fetch origin
git fetch --all --prune
# Pull = fetch + merge (default)
git pull
# Pull and rebase instead of merge (cleaner history)
git pull --rebase
# Pull a specific branch into current
git pull origin main
# Update a local branch without switching to it
git fetch origin main:mainPush
# Push current branch to its upstream
git push
# Push and set upstream in one go
git push -u origin feature/login
# Push a tag
git push origin v1.2.0
git push --tags # push all tags
# Force push (after rebase / amend — ⚠️ not on shared branches)
git push --force-with-lease # safe: fails if someone else pushed
git push --force # ⚠️ overwrites without checking
# Delete a remote branch
git push origin --delete feature/oldInspect History
Browse Log
# All commits, one line each
git log --oneline
# Graph of all branches
git log --oneline --graph --decorate --all
# Commits by author
git log --author="Ada"
# Commits in date range
git log --since="2024-01-01" --until="2024-06-01"
# Commits that touched a file
git log --follow -- src/auth.js
# Last 5 commits
git log -5
# Show stat (files changed)
git log --stat
# Full diff for each commit
git log -pDiff Anything
# Working tree vs staging area
git diff
# Staged vs last commit
git diff --staged
# Compare two branches
git diff main..feature/login
git diff main...feature/login # changes since they diverged
# Compare two commits
git diff abc1234 def5678
# Only file names that changed
git diff --name-only main..feature
# Diff a specific file between branches
git diff main -- src/auth.js
# Word-level diff (good for prose)
git diff --word-diffWho Changed What?
# Who last touched each line?
git blame src/auth.js
# Show only lines 10-30
git blame -L 10,30 src/auth.js
# Ignore whitespace changes
git blame -w src/auth.js
# Show the commit that deleted a line (requires -S and pickaxe)
git log -S "deleted_function_name" --source --allSearch Commits
The pickaxe (-S / -G) finds commits that added, removed, or changed a string — essential for tracking when a bug was introduced or a function was renamed.
# Find all commits whose message matches
git log --grep="fix: login"
# Find commits that added/removed a string (pickaxe)
git log -S "password_hash" --source --all
# Find commits that changed a regex pattern
git log -G "function login"
# Search file contents across history
git grep "TODO" HEAD
git grep "TODO" $(git rev-list --all) # all commits (slow)Find a Regression (bisect)
Binary-search the commit history to find exactly which commit introduced a bug. Git checks out the midpoint; you test and mark good/bad; repeat until Git pinpoints the culprit.
# Start bisect session
git bisect start
# Mark current commit as bad (has the bug)
git bisect bad
# Mark a known-good commit
git bisect good v1.0.0
# Git checks out a midpoint — test it, then mark:
git bisect good # or: git bisect bad
# Repeat until Git identifies the first bad commit.
# Automate with a test script (exit 0 = good, exit 1 = bad)
git bisect run npm test
# End the session
git bisect resetUndo & Fix
Discard Local Changes
# Discard all unstaged changes (DESTRUCTIVE)
git restore .
# Discard changes in one file
git restore src/app.js
# Remove untracked files (dry run first!)
git clean -n # show what would be deleted
git clean -f # delete untracked files
git clean -fd # delete untracked files + dirs
git clean -fdx # also delete ignored files (⚠️ node_modules etc.)Reset to a State
reset moves the HEAD pointer (and optionally the index and working tree) to any commit. --soft is safe; --hard is destructive.
# Move HEAD back N commits, keep changes staged
git reset --soft HEAD~1
git reset --soft HEAD~3
# Move HEAD back, unstage changes (default)
git reset HEAD~1
git reset --mixed HEAD~1
# Move HEAD back and DISCARD all changes (DESTRUCTIVE)
git reset --hard HEAD~1
git reset --hard origin/main # match remote exactly
# Unstage a specific file (without moving HEAD)
git reset HEAD -- src/app.jsRevert a Commit
revert creates a new commit that undoes the effect of an old one. Unlike reset, it is safe to use on shared/public branches.
# Create a new commit that undoes a previous one (safe for shared branches)
git revert abc1234
# Revert without opening the editor
git revert abc1234 --no-edit
# Revert a merge commit (specify which parent to keep)
git revert -m 1 abc1234
# Revert a range of commits (oldest first)
git revert oldest^..newestRecover Anything (reflog)
The reflog records every position HEAD has been in, including after hard resets, rebases, and deleted branches. It is your last resort for recovering "lost" work.
# See every HEAD position (including resets, rebases)
git reflog
# Recover a branch you accidentally deleted
git checkout -b recovered-branch HEAD@{3}
# Undo a hard reset
git reset --hard HEAD@{1}
# Reflog for a specific branch
git reflog show feature/login
# Reflog entries expire after 90 days by defaultRewrite History
These tools rewrite commits and change SHAs. Only use them on commits not yet pushed to a shared branch, or when you have team agreement and plan a coordinated force-push.
Interactive Rebase
The most powerful tool for cleaning up a branch before merging — reorder, rename, squash, or drop commits.
# Rewrite the last 4 commits
git rebase -i HEAD~4
# Rewrite since diverging from main
git rebase -i main
# Actions in the editor:
# pick — keep as-is
# reword — keep but edit message
# edit — pause to amend the commit
# squash — merge into previous commit, combine messages
# fixup — merge into previous commit, discard message
# drop — delete the commit entirely
# exec — run a shell command after the commitFixup & Squash
# Create a fixup commit targeting an earlier commit by message
git commit --fixup abc1234
# Then auto-squash during rebase
git rebase -i --autosquash HEAD~5
# Squash all commits on feature branch into one
git switch main
git merge --squash feature/login
git commit -m "feat: login feature" # single tidy commitCherry-pick
Apply a commit from any branch onto the current one — useful for backporting fixes.
# Apply a single commit from another branch
git cherry-pick abc1234
# Apply a range of commits
git cherry-pick abc1234^..def5678
# Apply but don't commit yet (stage the changes)
git cherry-pick --no-commit abc1234
# If conflicts:
git cherry-pick --continue
git cherry-pick --abortTags & Releases
Create & Push Tags
Use annotated tags for releases — they store a message, tagger, and date.
# Lightweight tag (just a pointer)
git tag v1.0.0
# Annotated tag (recommended for releases — stores author + message)
git tag -a v1.0.0 -m "First stable release"
# Tag a specific commit
git tag -a v1.0.0 abc1234 -m "Release"
# Push a single tag
git push origin v1.0.0
# Push all tags
git push --tags
# List tags matching a pattern
git tag -l "v1.*"Delete Tags
# Delete local tag
git tag -d v1.0.0-beta
# Delete remote tag
git push origin --delete v1.0.0-beta
# Move a tag to current HEAD (re-tag)
git tag -f v1.0.0 # move local
git push --force origin v1.0.0 # ⚠️ update remoteSubmodules
Add & Init
# Add a submodule
git submodule add https://github.com/org/lib.git libs/lib
# Clone a repo that has submodules
git clone --recurse-submodules https://github.com/org/project.git
# If you already cloned without --recurse-submodules
git submodule init
git submodule updateUpdate
# Pull latest for all submodules
git submodule update --remote --merge
# Run a command in every submodule
git submodule foreach 'git checkout main && git pull'
# Remove a submodule completely
git submodule deinit libs/lib
git rm libs/lib
rm -rf .git/modules/libs/libAdvanced
Worktrees
Check out multiple branches simultaneously in separate directories — no stashing required. Ideal when you need to test a hotfix while keeping your feature branch intact.
# Check out a second branch in a separate directory (no stash needed)
git worktree add ../project-hotfix hotfix/critical
# List all worktrees
git worktree list
# Remove a worktree (after you're done)
git worktree remove ../project-hotfix
# Useful pattern: keep main checked out while working on a feature
git worktree add ../project-main mainSparse Checkout
Download only part of a large monorepo, keeping disk usage and clone time low.
# Only download / check out a subdirectory of a large monorepo
git clone --filter=blob:none --sparse https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout set packages/ui packages/api
# Add more paths later
git sparse-checkout add packages/sharedHooks
Git hooks are shell scripts that run automatically at key points in the workflow.
# Hooks live in .git/hooks/ (not committed) or in a shared hooks dir.
# To share hooks, configure:
git config core.hooksPath .githooks
# Common hook files (make them executable: chmod +x):
# pre-commit — run lints / tests before commit
# commit-msg — validate commit message format
# pre-push — run tests before push
# post-merge — e.g. run npm install after pull
# Example pre-commit hook (.githooks/pre-commit):
# #!/bin/sh
# npm run lint || exit 1Patches by Email
# Create a patch file for the last 3 commits
git format-patch HEAD~3
# Create a single patch from a diff
git diff > my-changes.patch
# Apply a patch (tries to apply cleanly)
git apply my-changes.patch
# Apply a format-patch (preserves commit metadata)
git am 0001-feat-login.patch
# Send patches by email (git send-email)
git send-email --to=maintainer@project.org HEAD~3Useful Config Flags
Performance and workflow improvements worth enabling globally.
# Speed up large repos
git config core.fsmonitor true # OS file-system watcher
git config core.untrackedCache true # cache untracked file lookup
# Always rebase on pull (cleaner history)
git config --global pull.rebase true
# Prune stale remote refs on every fetch
git config --global fetch.prune true
# Auto-setup tracking when pushing a new branch
git config --global push.autoSetupRemote true
# Use patience diff algorithm (better for large refactors)
git config --global diff.algorithm patience
# Show moved lines differently in diffs
git config --global diff.colorMoved zebra
# Store credentials (macOS keychain / Windows Credential Manager)
git config --global credential.helper osxkeychain # macOS
git config --global credential.helper manager # Windows