Git commands & workflows
Everyday git — branching, undoing, stashing, and cleanup.
Updated 1 mo ago
Lookup for the git commands worth not re-googling. Mental model: changes move between the working tree → staging (index) → commit (HEAD) → remote. Most of the "undo" confusion is just picking which of those four you want to move back to.
Branching
| Task | Command |
|---|---|
| List branches | git branch · git branch -a (incl. remotes) |
| Create + switch | git switch -c feature (or git checkout -b feature) |
| Switch | git switch main |
| Rename current | git branch -m new-name |
| Delete (merged) | git branch -d feature |
| Force delete (unmerged) | git branch -D feature |
| Push + set upstream | git push -u origin feature |
| Track an existing remote branch | git switch feature (auto-tracks origin/feature) |
| Delete a remote branch | git push origin --delete feature |
| Last commit per branch | git branch -v |
# Switch back to the previous branch (like `cd -`)
git switch -
# Create a branch from a specific commit / tag, not HEAD
git switch -c hotfix v1.2.0Undoing things
Pick by where you want the change to land:
| Goal | Command |
|---|---|
| Unstage a file (keep edits) | git restore --staged file |
| Discard working-tree edits | git restore file ⚠️ destructive |
| Discard all uncommitted edits | git restore . ⚠️ destructive |
| Amend last commit (message or files) | git commit --amend |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, keep changes unstaged | git reset HEAD~1 (mixed, default) |
| Undo last commit, throw away changes | git reset --hard HEAD~1 ⚠️ destructive |
| Revert a pushed commit (new commit) | git revert <sha> |
| Restore a file from another commit | git restore --source=<sha> file |
# reset modes, in one picture (target = where HEAD moves to)
# --soft move HEAD (index + working tree untouched → changes staged)
# --mixed move HEAD + index (working tree untouched → changes unstaged) [default]
# --hard move HEAD + index + working tree (changes gone)
# Safely "undo a push": revert makes a new inverse commit instead of rewriting history
git revert <sha>
git pushRule of thumb:
resetrewrites history (use on local, unpushed commits).revertadds history (use on anything already pushed/shared).
Stashing
| Task | Command |
|---|---|
| Stash tracked changes | git stash (or git stash push -m "msg") |
| Include untracked files | git stash -u |
| List stashes | git stash list |
| Reapply + drop newest | git stash pop |
| Reapply, keep in stash | git stash apply |
| Apply a specific stash | git stash apply stash@{2} |
| Show a stash's diff | git stash show -p stash@{0} |
| Drop one / clear all | git stash drop stash@{0} · git stash clear |
| Stash only some files | git stash push -m "msg" path/to/file |
# Stash, switch to fix something urgent, come back
git stash -u
git switch main
# ... hotfix ...
git switch -
git stash popInspecting
| Task | Command |
|---|---|
| Status (short) | git status -sb |
| Staged diff | git diff --staged |
| Compact log | git log --oneline --graph --decorate -20 |
| Who changed a line | git blame -L 10,20 file |
| Search commit messages | git log --grep="fix auth" |
| Search code history (pickaxe) | git log -S "functionName" -p |
| Changes to one file | git log --oneline -- path/to/file |
| Find the commit that broke it | git bisect start → good/bad → reset |
| What's on the remote vs local | git log --oneline @{u}.. (ahead) · ..@{u} (behind) |
Remote & sync
| Task | Command |
|---|---|
| Fetch without merging | git fetch --all --prune |
| Pull with rebase (linear) | git pull --rebase |
| Show remotes | git remote -v |
| Change a remote URL | git remote set-url origin git@github.com:user/repo.git |
| Push tags | git push --tags |
| Force push (safely) | git push --force-with-lease |
Prefer
--force-with-leaseover--force: it refuses to clobber remote work you haven't seen, so you can't accidentally overwrite a teammate's push.
Rebase & history rewriting
# Replay your branch on top of latest main (linear history, no merge commit)
git switch feature
git fetch origin
git rebase origin/main
# Clean up your last 4 commits before opening a PR: squash, reword, reorder, drop
git rebase -i HEAD~4
# Mid-rebase
git rebase --continue # after resolving conflicts
git rebase --abort # bail out, restore pre-rebase state| Interactive verb | Effect |
|---|---|
pick | Keep the commit as-is |
reword | Keep changes, edit the message |
squash | Merge into previous, combine messages |
fixup | Like squash, but discard this message |
drop | Remove the commit entirely |
Cleanup
| Task | Command |
|---|---|
| Preview untracked file removal | git clean -nd |
| Remove untracked files | git clean -fd ⚠️ destructive |
| Remove untracked + ignored | git clean -fdx ⚠️ very destructive |
| Prune deleted remote branches | git fetch --prune |
| Delete merged local branches | see snippet |
| Garbage-collect / repack | git gc --prune=now |
# Delete every local branch already merged into main (skips main + current)
git branch --merged main | grep -vE '^\*|main' | xargs -r git branch -dRecovery — the safety net
Almost nothing is truly lost until gc runs. reflog records every place HEAD has been.
# See recent HEAD positions (after a bad reset/rebase/checkout)
git reflog
# Go back to where you were before the mistake
git reset --hard HEAD@{2}
# Recover a deleted branch: find its tip in the reflog, re-point a branch at it
git branch recovered <sha>Config worth setting once
git config --global init.defaultBranch main
git config --global pull.rebase true # rebase instead of merge on pull
git config --global push.autoSetupRemote true # `git push` works on new branches
git config --global rebase.autostash true # auto-stash/pop around a rebase
git config --global fetch.prune true # auto-prune on every fetchGotchas
git checkoutwas overloaded (branches and files);git switch(branches) andgit restore(files) are the modern, less-footgun replacements.reset --hardandclean -fddiscard work with no commit to recover from —reflogonly helps with committed history, not uncommitted edits.- A
git pullisfetch+mergeby default, which can create merge commits on your feature branch — setpull.rebase truefor a linear history. - Force-pushing a shared branch rewrites history others may have based work on; coordinate
first, and always use
--force-with-lease.