Git: the commands that get asked about
Nobody is tested on git add. What gets probed is the handful of operations
where getting it wrong costs someone else their afternoon: rewriting shared
history, undoing a pushed commit, and recovering from either.
The three trees
Almost every confusing Git answer becomes obvious once this is explicit:
working tree ──add──▶ index (staging) ──commit──▶ HEADreset moves HEAD and optionally the other two, which is the entire
difference between its flags:
git reset --soft HEAD~1 # undo commit, keep it staged
git reset --mixed HEAD~1 # keep changes unstaged (default)
git reset --hard HEAD~1 # undo commit, discard the work--soft is the “I want to redo that commit message or split it” move.
--hard is the only one that destroys work, and it is why the next section
exists.
Undoing something already pushed
git revert <sha> # new commit that reverses <sha>Not reset --hard then force-push. Revert adds history; reset rewrites it,
and rewriting history someone else has pulled means their next pull produces a
merge between two versions of the same work. The recovery is manual and it is
their problem, not yours.
| Situation | Use |
|---|---|
| Pushed, others may have it | revert |
| Local only, not pushed | reset |
| Pushed to your own branch, no reviewers | push --force-with-lease |
--force-with-lease rather than --force: it refuses if the remote moved since
you last fetched, so you cannot silently overwrite a colleague’s push.
Merge or rebase
git switch feature
# replay my commits on top of main
git rebase mainRebase gives a linear history and makes git log readable. It also rewrites
every commit it touches — new SHAs — so the rule is: rebase your own
unpublished branch, merge anything shared.
Gotcha: “never rebase a branch others have pulled” is the rule people recite and then break with
git pull --rebaseon a shared branch. Rebasing during a pull rewrites your local commits, which is fine, but if you had already pushed them it produces duplicates on the next push.
Recovering from a mistake
git reflog
# 8a3c1f2 HEAD@{0}: reset: moving to HEAD~3
# 4f7b9e1 HEAD@{1}: commit: the work you thought was gone
git reset --hard 4f7b9e1reflog records every position HEAD has held locally, whether or not those
commits are reachable from any branch. A botched reset, a deleted branch, a
rebase that went wrong — all recoverable, typically for 90 days.
This is the single most valuable Git answer, because the honest version of “have you ever lost work in Git” is “no, because of reflog”.
Finding the commit that broke it
git bisect start
git bisect bad # current commit is broken
git bisect good v2.3.0 # this tag was fine
# git checks out the midpoint; test, then:
git bisect good # or: git bisect badBinary search over history — ten steps across a thousand commits. With a test script it runs unattended:
git bisect run pytest tests/test_regression.pyInterview angle 6
- “Merge or rebase?” - rebase your own unpublished branch to keep history linear before merging; merge anything shared. Rebasing rewrites commits, so doing it to a branch others have pulled forces everyone to recover by hand.
- “How do you undo a commit that’s already pushed?” -
git revert, which adds a commit reversing the change and preserves shared history.reset --hardplus a force-push rewrites history and is only safe on a branch nobody else has. - “What’s the difference between the reset modes?” - all three move
HEAD.--softleaves the changes staged,--mixedleaves them in the working tree,--harddiscards them. Only--hardloses work. - “How do you recover something you think you destroyed?” -
git reflog. It records every position HEAD has held locally, so a botched reset, a deleted branch or a bad rebase is usually recoverable for around 90 days. - “Why
--force-with-leaserather than--force?” - it refuses the push if the remote moved since your last fetch, so you cannot overwrite a colleague’s commit without noticing. Plain--forcewill happily discard it. - “A regression appeared somewhere in the last 200 commits. Find it.” -
git bisect, which binary-searches history in about eight steps. Withgit bisect run <test-command>it finds the commit unattended.