Git from First PrinciplesPost 14 of 16 · 16 of 16 written
- How to use this guide
- 01Why version control exists
- 02Git's data model: a content-addressed object database
- 03References: branches, HEAD and tags are pointers
- 04The three areas: working tree, index, repository
- 05Daily commands, mapped onto the model
- 06Branching and integration: merge, rebase, cherry-pick
- 07Undoing things safely
- 08Remotes and collaboration
- 09Workflows and conventions
- 10Git for AI and ML work
- 11Git with AI coding agents
- 12Power tools
- 13Under the hood: build a commit by hand
- 14Debugging mindset and recovery playbook
- 15Practice path, fluency checklist and glossary
- 16Sources
14. Debugging mindset and recovery playbook
When Git confuses you, stop typing commands and answer four questions. Where is HEAD? Where do the relevant refs point? What is in the index and the working tree? Is an operation in progress? Almost every situation becomes clear from those four answers.
The diagnostic kit
git status # branch, any operation in progress, the three areas
git lg # the graph with every ref (log --oneline --graph --decorate --all)
git branch -vv # upstreams, ahead and behind
git reflog -n 20 # HEAD's recent moves
git stash list
git remote -v # where fetch and push go
git config --list --show-origin # a surprising setting, and its sourceIf status says you are in the middle of a merge, rebase, cherry-pick or bisect, that operation has --continue and --abort (rebase also has --skip). Finishing or aborting it is usually step one.
Asking the right question
Translate the symptom into model terms before you search or ask. “My commit disappeared” becomes “my branch no longer reaches commit X”. The model-level phrasing finds better answers and often answers itself.
When you ask a person or an AI for help, give them what they need to diagnose:
I ran: git rebase main
I expected: my 3 commits on top of main
Instead: <paste the exact output>
git status: <paste>
git log --oneline --graph --all -15: <paste>Git’s own messages often include hint: lines with the exact next command. Read them before searching.
The zero-cost safety habit
Branches are free, so before anything risky - an interactive rebase, a reset, a history rewrite - drop a label:
git branch backup/before-rebaseIf it goes wrong, git reset --hard backup/before-rebase puts everything back. Delete the label when you are happy.
Recovery playbook
| Situation | What happened, in model terms | Fix |
|---|---|---|
Committed to main instead of a branch, not pushed |
main moved forward by mistake |
git branch feat → git reset --hard origin/main → git switch feat |
| Want to undo the last commit but keep its changes | The branch should move back one | git reset --soft HEAD~1 |
| Commits “lost” after a reset or rebase | The ref moved; commits are unreachable but still in the reflog | git reflog → git branch rescue <sha> |
| Committed in detached HEAD, then switched away | No ref ever pointed at those commits | Find them in git reflog → git branch rescue <sha> |
| Deleted a branch by mistake | Only the label is gone | The delete message or git reflog gives the hash → git branch <name> <sha> |
| Dropped a stash | The stash commit is unreachable | git fsck --unreachable | grep commit → git stash apply <sha> |
| Push rejected as non-fast-forward | The remote has commits you lack | git fetch → rebase onto or merge origin/<branch> → push |
| “Your branch and origin have diverged” | Both sides have commits the other lacks | Decide: rebase your commits onto origin/<branch>, or merge it |
| A merge or rebase went badly halfway | An operation is in progress | git merge --abort or git rebase --abort |
| Wrong message on a commit already shared | Rewriting would break others | Leave it; if it matters, add a follow-up commit or a note |
| Committed a large file, not pushed | Your commits reach a huge blob | Last commit: git reset --soft HEAD~1, unstage it, ignore or LFS it, recommit. Older: git filter-repo --path <file> --invert-paths |
| Committed a secret | The blob is in history, possibly public | Rotate the key first, then git filter-repo, force-push, and have collaborators re-clone |
| Files show as modified but you changed nothing | Line endings or file-mode bits differ | Inspect with git diff; fix with .gitattributes or core.fileMode false |
.gitignore does not ignore a file |
The file is already tracked | git rm --cached <file>, then commit |
| Conflict in a lockfile | A text merge of a generated file | Take one side (git restore --theirs uv.lock), then regenerate it (uv lock) and git add it |
When to stop and think
Stop if you are about to force-push to a shared branch, delete .git, or re-clone “to fix it”. Re-cloning throws away your local branches, stashes and reflog - the very things that make recovery possible. Diagnose first with the kit above.
Check yourself
- A branch “disappeared”. Which two commands tell you what happened?
- What is the zero-cost step before an interactive rebase?
- You committed to
mainby mistake and have not pushed. Write the three commands that move the work to a branch.