Git Cheatsheet#
"You're not using Git — Git is using you." — every senior dev, eventually
🔍 Inspection#
# Status (every command's salvation)
git status
git status -sb # short branch info
git status --ignored # includes gitignored files
# Log
git log --oneline --graph --decorate --all
git log --since='2 weeks ago' --author='alice'
git log -p path/to/file # all changes made to a file
git log -L :function_name:file.py # history of a function
git log -S 'searchString' # commits that added/removed this string
git log --grep='fix.*auth' # commit message regex
# Show
git show <COMMIT>
git show <COMMIT>:path/to/file # file at a specific version
# Diff
git diff # working vs staged
git diff --staged # staged vs HEAD
git diff main..feature # between two branches
git diff --stat # summary (per file)
git diff --word-diff # word-level
git diff <COMMIT> -- path/ # a specific path
# Blame
git blame file.py
git blame -L 10,20 file.py
git log --follow -p -- file.py # follow renames
🌿 Branches#
# List
git branch # local
git branch -r # remote
git branch -a # all
git branch --merged main # branches merged into main
git branch --no-merged # not merged (gold for pre-cleanup)
# Create + switch
git switch -c feature/auth # create + checkout (modern)
git checkout -b feature/auth # old way
# Switch
git switch main
git switch - # to the previous branch
# Delete (cleanup)
git branch -d feature/auth # safe (warns if not merged)
git branch -D feature/auth # force delete
git push origin --delete feature/auth # delete from remote
# Clean up locals deleted on the remote
git fetch -p
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d
# Rename
git branch -m old-name new-name
git push origin :old-name new-name
💾 Commit#
# Stage + commit
git add .
git commit -m "feat: add user auth"
# Patch mode (stage by selecting chunks)
git add -p
# Amend (add to last commit / change message)
git commit --amend
git commit --amend --no-edit # keep the message
git commit --amend -m "new message"
# Empty commit (useful for triggering a deploy)
git commit --allow-empty -m "trigger: redeploy"
# Conventional commit examples
git commit -m "feat(auth): add OAuth2 support"
git commit -m "fix(api): handle null in user lookup"
git commit -m "chore(deps): bump axios from 1.4.0 to 1.5.0"
git commit -m "docs: update README quick-start"
git commit -m "refactor(payments): extract validation logic"
git commit -m "perf(query): add index on user_email"
git commit -m "test(auth): cover token expiration paths"
git commit -m "ci: cache npm install in pipeline"
🔄 Rewrite History (GREAT but use carefully)#
# Interactive rebase (last 5 commits)
git rebase -i HEAD~5
# In the editor that opens: pick/squash/fixup/reword/drop
# Rebase (branch onto the tip of main)
git switch feature/auth
git rebase main
# Rebase + force-push (for a shared branch)
git push --force-with-lease # safer, won't clobber someone else's commit
git push --force # dangerous — clobbers someone else's changes
# Rebase as the default instead of merge
git config --global pull.rebase true
# Squash merge (clean history after a PR)
git merge --squash feature/auth
git commit -m "feat: add auth flow"
🎯 Cherry-pick#
# Bring a single commit onto this branch
git cherry-pick <COMMIT>
# Multiple
git cherry-pick <COMMIT1> <COMMIT2>
git cherry-pick <COMMIT1>..<COMMIT2>
# If there's a conflict
git cherry-pick --continue
git cherry-pick --abort
# Pick the commit but don't commit yet (leave it staged)
git cherry-pick -n <COMMIT>
🔍 Bisect (when did the bug appear?)#
# Start
git bisect start
git bisect bad # current commit is broken
git bisect good <KNOWN_GOOD> # known good commit
# Git gives you the midpoint, test it:
./run-tests.sh
# Based on the result:
git bisect good # OK
git bisect bad # broken
# Continue, git finds the culprit commit
git bisect reset # when done
# Automatic (with a script)
git bisect run ./run-tests.sh
🧹 Stash (stash temporarily)#
git stash # stash the working tree
git stash -u # including untracked files
git stash push -m "WIP: refactor" # with a message
git stash push path/to/file # only a single path
git stash list
git stash show
git stash show -p stash@{0} # show diff
git stash apply # apply, keep it in stash
git stash pop # apply + delete
git stash drop stash@{0} # delete a specific stash
git stash clear # delete all
🆘 Reflog (everything is recorded)#
# History of every HEAD movement (90 days)
git reflog
# Find a lost commit
git reflog | grep 'feature/auth'
git checkout HEAD@{5} # to HEAD 5 movements ago
# Restore a branch you deleted
git reflog # find the commit hash
git checkout -b restored <COMMIT>
# Undo a hard reset
git reset --hard HEAD@{1} # 1 movement before the hard reset
↩️ Undo#
# Discard a change in the working dir
git restore file.py # modern
git checkout -- file.py # old
# Unstage staged changes (keep them in the working tree)
git restore --staged file.py # modern
git reset HEAD file.py # old
# Undo the last commit (keep changes in the working tree)
git reset HEAD~ # default = mixed
git reset --soft HEAD~ # keep them staged
git reset --hard HEAD~ # discard everything (CAUTION)
# Undo a published commit (with a new commit)
git revert <COMMIT> # safe, doesn't change history
🌊 Worktree (parallel checkout)#
# Open a different branch of the same repo in a separate folder (no full reload)
git worktree add ../app-hotfix hotfix/critical
cd ../app-hotfix
# List
git worktree list
# Remove
git worktree remove ../app-hotfix
🔑 Submodule (extra careful)#
git submodule add <URL> path/to/sub
git submodule update --init --recursive
git submodule update --remote --merge # pull the latest commits
# Clone with submodules
git clone --recurse-submodules <URL>
🛠️ Config#
# Global identity
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
# Editor
git config --global core.editor vim # or nano, code --wait
# Default branch (in new repos)
git config --global init.defaultBranch main
# Auto-prune on all remotes
git config --global fetch.prune true
# Rebase for pull (avoid merge commit spam)
git config --global pull.rebase true
# Safe force push
git config --global alias.fpush 'push --force-with-lease'
# Useful aliases
git config --global alias.st 'status -sb'
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD --stat'
git config --global alias.amend 'commit --amend --no-edit'
# List
git config --global --list
🚨 Emergency scenarios#
| Issue | Solution |
|---|---|
| Committed to the wrong branch | git reset --soft HEAD~, switch to the right branch, commit again |
Deleted someone else's commit with force push --force | Find the remote ref with git reflog, git push origin <COMMIT>:main |
| Committed the wrong file | git rm --cached <FILE>, git commit --amend, --force-with-lease push |
| A merge conflict got me | git mergetool (vimdiff/meld) or git checkout --theirs/--ours <FILE> |
| Committed in detached HEAD | Create a branch with git switch -c rescue and normalize |
Accidental git reset --hard | git reflog → git reset --hard HEAD@{1} |
| Large file accidentally committed | Rewrite with git filter-repo (more modern than BFG) |
| Changed origin, wrong URL | git remote set-url origin <NEW_URL> |
Added to .gitignore but still tracked | git rm --cached <FILE>, commit, push |