Stash and recovery
Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.
THE IDEA
Stash: shelve work in progress
Sometimes you must switch context with edits half done. git stash saves your uncommitted changes to tracked files on a stack and restores a clean tree. git stash pop reapplies the newest entry and removes it; git stash list shows the stack. Add -u to include untracked files.
Stash stack (newest on top)
Branch
def main():
print('half done')def main():
passdef main():
passnew or untracked differs from the area to its right in sync
Press a button. The commands run on a real (simulated) repository.
Reflog: the undo history of your pointers
Git keeps a local log of where HEAD has pointed: every commit, switch, reset and merge. git reflog shows it newest first, and HEAD@{2} means "where HEAD was two moves ago". Commits that no branch names still exist for a while, so the reflog is how you find them again.
git reflog (click a line to return there)
Working file a.txt: "a\n". The reflog tracks where HEAD pointed. It cannot hold an edit that was never committed or stashed.
Press a button. The commands run on a real (simulated) repository.
Predict first. You edited a file, never staged or committed it, and ran git reset --hard. Can the reflog bring the edit back?
Chapter context
Git records snapshots in a graph of commits. Branch names are movable references to commits. Treat the working tree, staging area and recorded history as distinct states, and many apparently mysterious commands become predictable.
TRY IT YOURSELF
You have unfinished edits in app.py. A bug report arrives. Later you will deliberately lose a commit and find it again.
- You have unfinished edits in app.py and an urgent fix to make. Stash your work so the tree is clean
- On the clean tree, commit hotfix.txt as Hotfix login bug (the stash must still be there)
- Bring your unfinished edits back and drop the stash entry
- Commit the finished app change so the tree is clean again
- Commit parser.py as Experiment: new parser, then discard it with reset --hard HEAD~1. It vanishes from git log
- Ask the reflog where HEAD has been, and spot the lost commit
- Rescue the commit: point a branch at it (or move main back to it)
Commit graph (live)
Working tree, staging area, repository
def main():
print('wip')def main():
passdef main():
pass- ship it
- ship it
- ship it
new or untracked differs from the area to its right in sync
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Which edits can the reflog never bring back?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
Model history as a directed acyclic graph. A commit is reachable from a branch when following parent edges reaches it. A merge preserves both ancestry paths; rebasing creates a different path with new commit identities.
Worked example
Let B have parent A, C have parent B, and D have parent B. A merge M with parents C and D makes both C and D reachable. Moving a branch from C to M is different from copying D's files over C: the graph also records why those histories meet.
Reference code
The following example uses the Python standard library and does not call a remote service.
parents = {'A': [], 'B': ['A'], 'C': ['B'], 'D': ['B'], 'M': ['C', 'D']}
def ancestors(commit):
seen, todo = set(), [commit]
while todo:
node = todo.pop()
if node not in seen:
seen.add(node)
todo.extend(parents[node])
return seen
assert ancestors('M') == {'A', 'B', 'C', 'D', 'M'}
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
Git commit does not automatically upload data. Rebase changes commit identities. Reset modes have different effects on uncommitted work.