btay.io/wiki

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 treestaging (index)commit (HEAD)remote. Most of the "undo" confusion is just picking which of those four you want to move back to.

Branching

TaskCommand
List branchesgit branch · git branch -a (incl. remotes)
Create + switchgit switch -c feature (or git checkout -b feature)
Switchgit switch main
Rename currentgit branch -m new-name
Delete (merged)git branch -d feature
Force delete (unmerged)git branch -D feature
Push + set upstreamgit push -u origin feature
Track an existing remote branchgit switch feature (auto-tracks origin/feature)
Delete a remote branchgit push origin --delete feature
Last commit per branchgit 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.0

Undoing things

Pick by where you want the change to land:

GoalCommand
Unstage a file (keep edits)git restore --staged file
Discard working-tree editsgit restore file ⚠️ destructive
Discard all uncommitted editsgit restore . ⚠️ destructive
Amend last commit (message or files)git commit --amend
Undo last commit, keep changes stagedgit reset --soft HEAD~1
Undo last commit, keep changes unstagedgit reset HEAD~1 (mixed, default)
Undo last commit, throw away changesgit reset --hard HEAD~1 ⚠️ destructive
Revert a pushed commit (new commit)git revert <sha>
Restore a file from another commitgit 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 push

Rule of thumb: reset rewrites history (use on local, unpushed commits). revert adds history (use on anything already pushed/shared).

Stashing

TaskCommand
Stash tracked changesgit stash (or git stash push -m "msg")
Include untracked filesgit stash -u
List stashesgit stash list
Reapply + drop newestgit stash pop
Reapply, keep in stashgit stash apply
Apply a specific stashgit stash apply stash@{2}
Show a stash's diffgit stash show -p stash@{0}
Drop one / clear allgit stash drop stash@{0} · git stash clear
Stash only some filesgit 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 pop

Inspecting

TaskCommand
Status (short)git status -sb
Staged diffgit diff --staged
Compact loggit log --oneline --graph --decorate -20
Who changed a linegit blame -L 10,20 file
Search commit messagesgit log --grep="fix auth"
Search code history (pickaxe)git log -S "functionName" -p
Changes to one filegit log --oneline -- path/to/file
Find the commit that broke itgit bisect startgood/badreset
What's on the remote vs localgit log --oneline @{u}.. (ahead) · ..@{u} (behind)

Remote & sync

TaskCommand
Fetch without merginggit fetch --all --prune
Pull with rebase (linear)git pull --rebase
Show remotesgit remote -v
Change a remote URLgit remote set-url origin git@github.com:user/repo.git
Push tagsgit push --tags
Force push (safely)git push --force-with-lease

Prefer --force-with-lease over --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 verbEffect
pickKeep the commit as-is
rewordKeep changes, edit the message
squashMerge into previous, combine messages
fixupLike squash, but discard this message
dropRemove the commit entirely

Cleanup

TaskCommand
Preview untracked file removalgit clean -nd
Remove untracked filesgit clean -fd ⚠️ destructive
Remove untracked + ignoredgit clean -fdx ⚠️ very destructive
Prune deleted remote branchesgit fetch --prune
Delete merged local branchessee snippet
Garbage-collect / repackgit 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 -d

Recovery — 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 fetch

Gotchas

  • git checkout was overloaded (branches and files); git switch (branches) and git restore (files) are the modern, less-footgun replacements.
  • reset --hard and clean -fd discard work with no commit to recover from — reflog only helps with committed history, not uncommitted edits.
  • A git pull is fetch + merge by default, which can create merge commits on your feature branch — set pull.rebase true for a linear history.
  • Force-pushing a shared branch rewrites history others may have based work on; coordinate first, and always use --force-with-lease.

Related pages

On this page