Rewrite last commit. Don’t use after pushing without --force-with-lease.
git commit --fixup <sha>
Marker commit auto-squashed by rebase -i --autosquash.
git stash push -m "wip"
Save working-tree to a stack.
git stash list / pop / drop
Show, restore, delete entries.
git stash push -k
Keep staged changes; stash only unstaged.
switch · restore · trackBranching
git branch
List local branches.
git branch -a
Include remote-tracking.
git branch -vv
Show upstream + last commit per branch.
git switch main
Preferred Change branch.
git checkout main
Legacy Same idea; also does paths + commits.
git switch -c feat/x
Create + switch in one step.
git switch -c feat/x origin/main
Branch from a specific ref.
git branch -d feat/x
Delete merged branch (safe).
git branch -D feat/x
Force-delete (loses unmerged work).
git branch -m old new
Rename a branch.
git push origin --delete feat/x
Delete remote branch.
git push -u origin feat/x
First push + set upstream.
log · diff · blameInspecting
git log --oneline -n 20
Last 20 commits, compact.
git log --graph --oneline --all --decorate
ASCII history graph. Save as alias lg.
git log -p <path>
History with diffs for a path.
git log --grep="regex"
Search commit messages.
git log -S"substr"
Find commits adding/removing a literal string (pickaxe).
git log --author=ada
Filter by author.
git log main..feat/x
Commits on feat/x not yet in main.
git diff
Unstaged changes.
git diff --staged
Staged changes (what will commit).
git diff main...feat/x
Branch-diff from common ancestor.
git diff --stat
Per-file summary.
git show <sha>
Commit metadata + diff.
git blame -L 42,60 -- file
Who touched lines 42–60 and when.
git shortlog -sn
Commit counts per author.
reset · revert · restoreUndoing
git restore <path>
Discard working-tree changes. Destructive.
git restore --staged <path>
Unstage. Keeps the working-tree edits.
git restore --source=main <path>
Copy a path from another ref.
git reset --soft HEAD~1
Uncommit; keep changes staged.
git reset --mixed HEAD~1
Uncommit; unstage. Working tree untouched.
git reset --hard HEAD~1
Uncommit + discard. Destructive.
git revert <sha>
Create a new commit that undoes <sha>. Safe on shared branches.
git clean -nd
Dry-run: list untracked files + dirs that would be deleted.
git clean -fd
Delete untracked files + dirs. Destructive.
Reset rewrites; revert appends. Use revert on anything you’ve
already pushed to a shared branch. Reserve reset for branches only you own.
replay · squash · reorderRebasing
git rebase main
Replay current branch on top of main.
git rebase origin/main
Replay onto the latest fetched trunk.
git rebase -i HEAD~5
Interactive: reword / squash / fixup / drop.
git rebase -i --autosquash HEAD~10
Auto-apply --fixup markers.
git rebase --onto base from to
Surgical re-anchor. Move commits between branches.
git rebase --continue / --skip / --abort
Drive a paused rebase.
git merge --no-ff feat/x
Merge with a merge commit even if fast-forward is possible.
git merge --abort
Back out of a conflicting merge.
git push --force-with-lease
Preferred Safe force-push. Refuses if remote moved.
git push --force
Legacy Overwrites unconditionally. Avoid.
bash
# Rebase your branch onto the latest main
git fetch origin
git rebase origin/main # replay your commits on top of origin/main
# If a conflict pops up:
# 1) fix files
# 2) git add
# 3) git rebase --continue
# or bail:
git rebase --abort
# Interactive rebase: squash/edit/reorder the last N commits
git rebase -i HEAD~5
# In the editor — change "pick" to:
# r reword – edit commit message
# s squash – fold into previous, keep both messages
# f fixup – fold into previous, discard message
# e edit – stop to amend
# d drop – delete this commit
# Force-push safely (refuses if remote moved since you pulled)
git push --force-with-lease origin my-branch
reflog · fsckRecovery
git reflog
Every HEAD move, last ~90 days. Your safety net.
git reflog show <ref>
Reflog for a specific ref.
git checkout HEAD@{2}
Time-travel via reflog index.
git branch rescue <sha>
Recreate a deleted branch.
git fsck --lost-found
Find dangling objects after a hard reset.
git cherry-pick <sha>
Replay a single commit onto your branch.
git bisect start / good / bad
Binary search for a regression.
bash
# I just blew away a branch / commit — can I get it back?
git reflog # every HEAD move, last ~90 days
git reflog show my-branch # ref-specific reflog
git checkout -b rescue HEAD@{2} # recreate from the 2-step-old HEAD
git branch my-branch # or restore by sha
# I committed to the wrong branch
git log --oneline -n 3 # note the sha
git reset --soft HEAD~1 # uncommit but keep changes staged
git switch other-branch
git commit -m "..." # commit there instead
# I committed secrets — purge from history (destructive)
git filter-repo --invert-paths --path secrets.env
# Then notify the team; everyone re-clones.
# I want to see who broke a line, when, and why
git blame -L 42,60 -- src/foo.py
git log -p -L 42,60:src/foo.py # line-range history with diffs
multiple checkouts, one repoWorktrees
git worktree add ../app-hotfix hotfix/x
Checkout a branch in a sibling dir.
git worktree list
All worktrees + their refs.
git worktree remove ../app-hotfix
Remove a worktree.
git worktree prune
Clean up after manual deletion.
Worktrees beat stash for context-switching. Spawn a separate dir for the hotfix, leave your
feature branch dirty. No stash juggling, no “wait, did I commit the right thing?”.
releases · markersTags
git tag
List tags.
git tag -a v1.0.0 -m "Release v1"
Annotated tag (preferred for releases).
git tag -s v1.0.0 -m "…"
Signed annotated tag.
git tag v1.0.0 <sha>
Tag a specific commit.
git push origin v1.0.0
Push one tag.
git push origin --tags
Push all local tags. Use sparingly.
git tag -d v1.0.0 && git push origin :v1.0.0
Delete local + remote.
git describe --tags --abbrev=0
Most recent tag reachable from HEAD.
repos inside reposSubmodules
git submodule add URL path
Attach a sub-repo.
git clone --recurse-submodules URL
Clone + initialise submodules.
git submodule update --init --recursive
After an existing clone.
git submodule update --remote
Move each submodule to its tracked branch tip.
git submodule foreach 'git pull'
Run a command in every submodule.
git config --global submodule.recurse true
Make pull / checkout submodule-aware by default.
Submodules pin a sub-repo to an exact commit. Updating means a new commit in the parent repo that records
the new sub-commit — reviewers see the SHA bump, not the contents.
Branch off, hack, tidy with an interactive rebase, sync, push, post-merge cleanup. The loop you’ll
repeat hundreds of times.
bash
# Full feature-branch loop with rebase + squash + safe push.
# 1 · Sync main, branch off
git switch main
git pull --rebase
git switch -c feat/cart-discounts
# 2 · Hack hack hack
$EDITOR src/cart.py
git add -p # stage hunks selectively
git commit -m "Add discount calc"
# 3 · Before opening the PR — tidy history + sync
git fetch origin
git rebase -i origin/main # squash WIP commits
# 4 · Push and open a PR
git push -u origin feat/cart-discounts --force-with-lease
# 5 · After review, sync once more before merge
git fetch origin
git rebase origin/main
git push --force-with-lease
# 6 · After merge, prune local state
git switch main
git pull --rebase
git branch -d feat/cart-discounts # safe delete (refuses if unmerged)
git fetch --prune # drop stale origin/* refs
Best practiceGood to know
Use --force-with-lease, never --force.
Plain --force overwrites whatever the remote has now — including a
colleague’s commit you didn’t pull. --force-with-lease refuses if the
remote moved.
Reflog is your seatbelt.
Almost nothing is truly lost for ~90 days. Before reaching for git reset --hard,
remember git reflog exists.
Commit small, then rewrite history before pushing.
Free-text the first pass, rebase -i the cleanup. Reviewers see clean
commits; you don’t lose the trail of your own thinking.
Common trapsWatch out for
Don’t rebase a branch others are based on.
Rewriting shared history forces everyone else to either re-clone or run a tricky
rebase --onto. Revert in place instead, or coordinate explicitly.
git pull defaults to a merge commit.
Set pull.rebase=true globally. Otherwise every sync leaves a noisy merge
bubble that distorts git log --graph.
Renaming files? Stage the rename in one commit.
Git detects renames via similarity index, but mixing a big rename with edits in the same commit fools
blame. Rename first, edit second.
What is the difference between git merge and git rebase?
git merge combines two branches and preserves all history with a merge commit. git rebase replays your commits on top of another branch, producing a linear history without merge commits. Rebase keeps the log clean but rewrites commit SHAs — never rebase public/shared branches.
How do I undo the last commit in Git?
git reset HEAD~1 undoes the last commit and puts the changes back into your working tree (unstaged). Add --soft to keep them staged instead. Use git reset --hard HEAD~1 to discard the changes entirely. If the commit is already pushed, prefer git revert HEAD to create an undo commit without rewriting shared history.
What is git stash used for?
git stash saves your uncommitted changes (both staged and unstaged) onto a stack and restores a clean working tree. Use it when you need to switch branches quickly without committing. Retrieve with git stash pop (removes from stack) or git stash apply (keeps on stack). Name stashes with git stash push -m 'description'.
What is git reflog and how does it help recover lost commits?
git reflog records every time HEAD moved — commits, resets, checkouts, rebases. Even after a hard reset or mistaken branch deletion, commits stay in the object database for at least 30 days. Find the lost SHA in reflog, then git checkout <sha> or git branch recover <sha> to bring it back.
What is the difference between git switch and git checkout?
git switch (Git ≥ 2.23) is the modern command for changing branches: git switch main or git switch -c new-branch. git restore handles discarding file changes. git checkout originally handled both, making it confusing. New workflows should prefer switch and restore; checkout still works for everything.
What is --force-with-lease and when should I use it?
git push --force-with-lease is a safer alternative to --force. It refuses to overwrite the remote if someone else has pushed since you last fetched, preventing you from silently discarding their commits. Always use --force-with-lease instead of --force when you need to push a rewritten branch.