A Comprehensive Reference for Version Control
COMPILED MMXXVIThe essential commands every practitioner should know by heart.
Laying the foundation. Every repository begins with proper configuration -- identity, preferences, and the tools of the trade.
# Set your identity (required for commits) git config --global user.name "Your Name" git config --global user.email "your.email@example.com" # Set default editor git config --global core.editor "code --wait" # VS Code git config --global core.editor "vim" # Vim git config --global core.editor "nano" # Nano # Configure line endings git config --global core.autocrlf true # Windows: CRLF on checkout, LF on commit git config --global core.autocrlf input # Mac/Linux: LF only # Set default branch name git config --global init.defaultBranch main # View all settings (and where they come from) git config --list git config --list --show-origin
# Store credentials permanently (Linux/Mac) git config --global credential.helper store # Cache credentials for 1 hour git config --global credential.helper 'cache --timeout=3600' # Windows credential manager git config --global credential.helper manager-core # Unset credential helper git config --global --unset credential.helper
# Initialize a new repository git init git init <directory> # Clone an existing repository git clone <url> git clone <url> <directory> git clone --depth 1 <url> # Shallow clone (latest only) git clone --branch <branch> <url> # Clone specific branch git clone --single-branch <url> # Clone only one branch
Git config has three scopes: --system (all users), --global (your user), and --local (current repo, the default). Local settings override global, which override system.
The daily rhythm of version control -- staging, committing, inspecting, and understanding the state of your work.
git status # Full working tree status git status -s # Short format git status -sb # Short format with branch info git status --ignored # Show ignored files too
git add <file> # Stage specific file git add <file1> <file2> # Stage multiple files git add -A # Stage all changes (new, modified, deleted) git add . # Stage all in current directory git add -u # Stage modified and deleted (not new files) git add -p # Patch mode -- stage chunks interactively
git commit -m "Commit message" git commit -m "Title" -m "Description" # Multi-line message git commit -am "message" # Stage tracked files + commit git commit --amend # Amend last commit git commit --amend -m "New message" # Amend with new message git commit --amend --no-edit # Amend without changing message git commit --author="Name <email>" # Commit with specific author
git diff # Unstaged changes (working tree vs index) git diff --staged # Staged changes (index vs HEAD) git diff --cached # Same as --staged git diff HEAD # All local changes (working tree vs HEAD) git diff <commit1> <commit2> # Between two commits git diff <file> # Changes in specific file git diff --word-diff # Word-level diff git diff --stat # Statistics only
git log # Full log git log -n 5 # Last 5 commits git log --oneline # Compact one-line view git log --graph --oneline --all # ASCII branch graph git log -p # Show patches (full diffs) git log --author="John" # Filter by author git log --since="2 weeks ago" # Filter by date git log --grep="keyword" # Search commit messages git log --follow <file> # File history (follow renames) git log --pretty=format:"%h - %an: %s" # Custom format # Show specific commit details git show <commit> git show <commit>:<file> # File at specific commit
The true power of Git lies in its branching model -- lightweight, fast, and designed for parallel lines of development.
# List branches git branch # Local branches git branch -a # All (local + remote) git branch -r # Remote branches only git branch -v # Show last commit per branch git branch -vv # Show tracking branches # Create branch git branch <name> git branch <name> <start-point> # Switch branches (modern) git switch <branch> git switch -c <new-branch> # Create and switch # Switch branches (classic) git checkout <branch> git checkout -b <new-branch> # Create and switch # Rename branch git branch -m <old> <new> git branch -m <new-name> # Rename current branch # Delete branch git branch -d <branch> # Safe delete (merged only) git branch -D <branch> # Force delete git push origin --delete <branch> # Delete remote branch
git merge <branch> # Merge into current branch git merge <branch> -m "message" # Merge with message git merge --no-ff <branch> # Force merge commit (no fast-forward) git merge --ff-only <branch> # Fast-forward only, fail otherwise git merge --squash <branch> # Squash all commits into one git merge --abort # Abort a conflicted merge
git rebase <branch> # Rebase current onto branch git rebase main # Rebase onto main git rebase -i HEAD~3 # Interactive rebase last 3 commits git rebase -i --autosquash <branch> # Auto-arrange fixup commits git rebase --continue # Continue after conflict resolution git rebase --skip # Skip current commit git rebase --abort # Abort rebase entirely
# View conflicts git status # See conflicted files git diff # View conflict markers # Choose a version git checkout --ours <file> # Keep current branch version git checkout --theirs <file> # Keep incoming branch version # After resolving manually git add <resolved-files> git merge --continue # or: git rebase --continue # Visual merge tool git mergetool
Merge preserves the full history with a merge commit. Rebase creates a linear history by replaying commits. Never rebase commits that have been pushed to a shared branch -- this rewrites public history and can cause serious problems for collaborators.
Collaboration requires coordination with remote repositories -- fetching, pulling, pushing, and tracking distributed branches.
git remote # List remotes git remote -v # Show remote URLs git remote add <name> <url> # Add new remote git remote remove <name> # Remove remote git remote rename <old> <new> # Rename remote git remote set-url <name> <url> # Change remote URL git remote show origin # Detailed remote info
# Fetch (download without merging) git fetch # Fetch all remotes git fetch <remote> # Fetch specific remote git fetch <remote> <branch> # Fetch specific branch git fetch --all # Fetch all remotes git fetch --prune # Remove stale remote-tracking branches # Pull (fetch + merge) git pull # Pull current branch git pull <remote> <branch> # Pull specific branch git pull --rebase # Pull with rebase instead of merge git pull --ff-only # Fast-forward only
git push # Push to tracked remote git push <remote> <branch> # Push specific branch git push -u origin <branch> # Push and set upstream tracking git push --all # Push all branches git push --tags # Push all tags git push --force-with-lease # Safer force push git push origin --delete <branch> # Delete remote branch
git branch -u <remote>/<branch> # Set upstream git branch --set-upstream-to=origin/main git branch -vv # View tracking info git branch --unset-upstream # Remove upstream tracking
A place to temporarily shelve changes -- like tucking a draft into a desk drawer to return to later.
git stash # Stash staged and unstaged changes git stash push -m "message" # Stash with description git stash -u # Include untracked files git stash --include-untracked # Same as -u git stash -a # Include ignored files too git stash push <file> # Stash specific file git stash -p # Interactive: choose hunks to stash git stash --keep-index # Stash but keep staged changes staged
git stash list # List all stashes git stash show # Show latest stash summary git stash show -p # Show full diff of latest stash git stash show stash@{2} -p # Show specific stash diff # Apply stash (keep in list) git stash apply # Apply latest git stash apply stash@{2} # Apply specific stash # Pop stash (apply and remove from list) git stash pop # Pop latest git stash pop stash@{2} # Pop specific stash # Remove stashes git stash drop # Drop latest git stash drop stash@{2} # Drop specific stash git stash clear # Remove all stashes # Create branch from stash git stash branch <branch-name> # New branch from latest stash
Stashes are local only -- they are never transferred to the server when you push. Use git stash push -m "description" to name your stashes so you can identify them later in git stash list.
The archaeological tools of version control -- tracing authorship, hunting bugs, and recovering from the unrecoverable.
# Graph views git log --graph --oneline --all git log --graph --decorate --oneline # Filter by author git log --author="John Doe" git log --author="john\|jane" # Multiple authors # Filter by date git log --since="2 weeks ago" git log --after="2024-01-01" --before="2024-12-31" # Search content git log --grep="fix" # Search commit messages git log -S "function_name" # Search for string in diffs (pickaxe) git log -G "regex" # Search diffs with regex # Merge-specific git log --merges # Show only merge commits git log --no-merges # Exclude merge commits # Statistics git log --stat # File change statistics git log --shortstat # Summary only git log --name-only # List changed files git log --name-status # Files with add/modify/delete status # Custom formatting git log --pretty=format:"%h %an %ad %s" --date=short git log --pretty=format:"%C(yellow)%h%Creset %C(blue)%an%Creset %s"
git blame <file> # Who changed each line git blame -L 10,20 <file> # Lines 10 through 20 git blame -L 10,+5 <file> # 5 lines starting at line 10 git blame -e <file> # Show email instead of name git blame -w <file> # Ignore whitespace changes
Binary search through commit history to find the commit that introduced a bug.
git bisect start # Begin bisect session git bisect bad # Current commit has the bug git bisect good <commit> # Known good commit # Git checks out middle commit. Test and mark: git bisect good # This commit is fine git bisect bad # This commit has the bug git bisect skip # Can't test this one, skip # Automate with a test script git bisect run <test-script> git bisect reset # End bisect, return to original HEAD
The safety net -- a log of every position HEAD has pointed to. Invaluable for recovering lost work.
git reflog # Show reflog for HEAD git reflog show <branch> # Reflog for specific branch git reflog --date=relative # Show with relative dates git reflog --date=iso # Show with ISO dates # Recover lost commits git reflog # Find the commit hash git branch <recovery-branch> <hash> # Create branch at lost commit
Almost nothing in Git is truly lost. If you committed it (or even just staged it), git reflog and git fsck --lost-found can help you recover it. The reflog keeps entries for about 90 days by default.
The art of reversal -- from a gentle unstage to a complete rewrite. Choose the right tool for the severity of the situation.
| Command | Scope | Rewrites History? | Safe for Shared? |
|---|---|---|---|
git restore |
Working tree / staging | No | Yes |
git revert |
Commits (creates inverse) | No | Yes |
git reset --soft |
Commit pointer only | Yes | Local only |
git reset --mixed |
Commit pointer + staging | Yes | Local only |
git reset --hard |
Everything | Yes | Dangerous |
git clean |
Untracked files | No | Permanent |
git restore <file> # Discard working tree changes git restore . # Discard all working tree changes git restore --staged <file> # Unstage file (keep changes) git restore --staged --worktree <file> # Unstage and discard changes git restore --source=HEAD~2 <file> # Restore file from 2 commits ago
# Soft: uncommit, keep changes staged git reset --soft HEAD~1 # Mixed (default): uncommit, unstage changes git reset HEAD~1 git reset --mixed HEAD~1 # Hard: discard everything (DANGEROUS) git reset --hard HEAD~1 git reset --hard origin/main # Match remote exactly # Unstage a file git reset HEAD <file> # Legacy way to unstage
git revert <commit> # Create inverse commit git revert -n <commit> # Revert without committing git revert -m 1 <merge-commit> # Revert a merge commit git revert <commit1>..<commit2> # Revert range of commits git revert --continue # Continue after conflict git revert --abort # Abort revert
git clean -n # Dry run -- preview what would be removed git clean -f # Remove untracked files git clean -fd # Remove untracked files and directories git clean -fx # Remove untracked + ignored files git clean -fdx # Remove everything untracked (NUCLEAR) git clean -i # Interactive mode
git reset --hard and git clean -f permanently destroy uncommitted work. Always run git stash or git clean -n (dry run) first. Committed work can almost always be recovered via git reflog.
The master craftsman's toolkit -- cherry-picking commits, managing multiple working trees, and composing repositories.
Apply specific commits from one branch to another, without merging the entire branch.
git cherry-pick <commit> # Apply commit to current branch git cherry-pick -n <commit> # Apply without committing git cherry-pick <c1> <c2> <c3> # Cherry-pick multiple commits git cherry-pick <c1>..<c2> # Cherry-pick range (exclusive of c1) git cherry-pick -e <commit> # Edit commit message git cherry-pick --continue # Continue after conflict git cherry-pick --abort # Abort cherry-pick git cherry-pick --skip # Skip current commit
Rewrite, reorder, squash, or drop commits before sharing your work.
git rebase -i HEAD~3 # Rebase last 3 commits interactively git rebase -i <commit> # Rebase from commit forward # Editor commands: # pick = use commit as-is # reword = edit commit message # edit = stop to amend commit # squash = meld into previous, keep message # fixup = meld into previous, discard message # drop = remove commit entirely # exec = run shell command # Auto-squash workflow git commit --fixup <commit> # Create fixup commit git rebase -i --autosquash <base> # Auto-arrange fixup commits
Work on multiple branches simultaneously without stashing or switching.
git worktree list # Show all worktrees git worktree add <path> <branch> # Check out branch in new directory git worktree add -b <new> <path> # Create new branch in new directory git worktree remove <path> # Remove worktree git worktree prune # Clean up stale worktrees git worktree lock <path> # Prevent worktree from being pruned
git submodule add <url> <path> # Add a submodule git submodule init # Initialize submodule config git submodule update # Fetch submodule content git submodule update --init --recursive # Init + update all nested git clone --recurse-submodules <url> # Clone with submodules git pull --recurse-submodules # Pull including submodules git submodule foreach <command> # Run command in each submodule # Remove submodule git submodule deinit <path> git rm <path>
# Subtree (alternative to submodules) git subtree add --prefix=<dir> <repo> <branch> --squash git subtree pull --prefix=<dir> <repo> <branch> --squash git subtree push --prefix=<dir> <repo> <branch> # Filter-Repo (modern history rewriting, replaces filter-branch) # Install: pip install git-filter-repo git filter-repo --path <file> --invert-paths # Remove file from all history git filter-repo --path <dir>/ --invert-paths # Remove directory from all history
Bookmarks in the timeline -- marking releases, milestones, and significant moments in a project's life.
# Lightweight tag (just a pointer) git tag v1.0.0 # Annotated tag (recommended -- includes metadata) git tag -a v1.0.0 -m "Release version 1.0.0" # Signed tag (GPG verified) git tag -s v1.0.0 -m "Signed release" # Tag a specific commit (not HEAD) git tag v1.0.0 <commit-hash> git tag -a v1.0.0 <commit-hash> -m "message"
git tag # List all tags git tag -l "v1.*" # Filter tags by pattern git show v1.0.0 # Show tag details git tag -v v1.0.0 # Verify signed tag git tag -d v1.0.0 # Delete local tag git push origin --delete v1.0.0 # Delete remote tag
git push origin v1.0.0 # Push specific tag git push --tags # Push all tags git push --follow-tags # Push annotated tags only git fetch --tags # Fetch all remote tags
Annotated tags (-a) store the tagger name, email, date, and message -- use these for releases. Lightweight tags are just pointers to a commit -- suitable for temporary or personal bookmarks. Signed tags (-s) add cryptographic verification.
A well-configured set of aliases transforms a lengthy command into a swift keystroke -- the mark of a seasoned practitioner.
Add these to your ~/.gitconfig under the [alias] section, or use git config --global alias.<name> <command>.
[alias]
st = status
s = status -sb
co = checkout
sw = switch
br = branch
ci = commit
cm = commit -m
ca = commit --amend
cane = commit --amend --no-edit
a = add
aa = add -A
ap = add -p
d = diff
ds = diff --staged
dw = diff --word-diff
[alias]
l = log --oneline
lg = log --graph --oneline --decorate --all
ll = log --pretty=format:'%h %ad %an: %s' --date=short
lol = log --graph --pretty=oneline --abbrev-commit
lola = log --graph --pretty=oneline --abbrev-commit --all
last = log -1 HEAD --stat
[alias]
pl = pull
ps = push
psu = push -u origin HEAD
pf = push --force-with-lease
ss = stash
sp = stash pop
sl = stash list
[alias]
undo = reset HEAD~1 --mixed
unstage = reset HEAD --
wip = commit -am "WIP"
unwip = reset HEAD~1
cont = rebase --continue
abort = rebase --abort
aliases = config --get-regexp alias
The accumulated wisdom of the trade -- patterns, hooks, performance tuning, and the finer points of the craft.
# Common patterns *.log # Ignore all .log files /todo.txt # Ignore file in root only build/ # Ignore entire directory doc/*.txt # Ignore .txt in doc/ (not subdirs) **/logs # Ignore logs dir anywhere in tree !important.log # Exception: do NOT ignore this file # OS files .DS_Store Thumbs.db # Dependencies node_modules/ vendor/ # Environment & secrets .env .env.local *.env # IDE .vscode/ .idea/ *.swp # Build output dist/ build/ *.min.js *.min.css
Set up a global gitignore for patterns common across all your projects:
git config --global core.excludesfile ~/.gitignore_global
Scripts that run automatically at key points in the Git workflow. Located in .git/hooks/.
| Hook | When It Runs | Common Use |
|---|---|---|
pre-commit |
Before commit is created | Lint, format, run tests |
prepare-commit-msg |
Before message editor opens | Template commit messages |
commit-msg |
After message is saved | Validate message format |
pre-push |
Before push executes | Run full test suite |
post-checkout |
After checkout completes | Install deps, rebuild |
post-merge |
After merge completes | Install deps, migrations |
Work with only a subset of files in large repositories.
git sparse-checkout init # Enable sparse checkout git sparse-checkout set <directory> # Set directories to include git sparse-checkout add <directory> # Add directory to checkout git sparse-checkout list # List included paths git sparse-checkout disable # Disable (restore full tree)
git clone --filter=blob:none <url> # Blobless clone (download blobs on demand) git clone --filter=tree:0 <url> # Treeless clone (most aggressive) git clone --depth=1 <url> # Shallow clone (latest commit only) git fetch --unshallow # Convert shallow to full clone
# Configure GPG signing git config --global user.signingkey <key-id> git config --global commit.gpgsign true # Always sign # Sign individual commits git commit -S -m "Signed commit" # Verify signatures git log --show-signature git verify-commit <commit>
git grep "search term" # Search in working tree git grep -n "search term" # With line numbers git grep -i "search term" # Case insensitive git grep "term" <commit> # Search in specific commit git grep -p "term" # Show function context git grep -E "regex.*pattern" # Extended regex search
# Repository maintenance git gc # Garbage collection git gc --aggressive # Aggressive optimization git prune # Remove unreachable objects git fsck # Verify repository integrity git count-objects -v # Count and size objects # Performance settings git config core.preloadindex true # Parallel index preload git config core.fsmonitor true # File system monitor git config core.commitGraph true # Enable commit graph git commit-graph write --reachable # Build commit graph
# Create archives git archive HEAD --format=zip > repo.zip git archive HEAD --prefix=project/ --format=zip > project.zip # Bundles (offline transfer) git bundle create repo.bundle --all # Create bundle git clone repo.bundle # Clone from bundle git bundle verify repo.bundle # Verify bundle
# Useful environment overrides export GIT_EDITOR=vim # Override editor export GIT_PAGER=less # Set pager export GIT_PAGER=cat # Disable pager export GIT_SSH_COMMAND="ssh -i ~/.ssh/custom_key"