Foundations: Python, Git and toolsLoading progress…

Stash and recovery

Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.

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)

empty

Branch

8fb40b0 Add app8fb40b0Add appHEAD → main
Working treefiles on disk
Staging areathe next snapshot
Repositorythe last commit
git add →
git commit →
← restore --staged
app.py
8ea7593 · edited
def main():
    print('half done')
336e825
def main():
    pass
336e825
def main():
    pass

new or untracked differs from the area to its right in sync

Press a button. The commands run on a real (simulated) repository.
Shelve, commit a hotfix on the clean tree, bring the work back.

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.

5f48158 A5f48158A3a2d82a B3a2d82aB93bb972 C: important93bb972C: importa…77679b3 D: important too77679b3D: importa…HEAD → main

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.
Lose two commits with reset --hard, then click the reflog to get them back. Then try an uncommitted edit.

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.

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.