Lesson 8 of 9 · about 12 minutes

Notebooks and branches

Your notebook is a JSON file in disguise. Teach Git to see the code inside it.

You will set up penguins-lab so that a notebook change on a feature branch shows up as a readable diff, and you'll know how to keep notebook merge conflicts rare.

Why notebooks fight with branches

A .ipynb file is not a script. Its format "is defined with this JSON schema," and every code cell stores "a list of outputs associated with executing that code" plus an execution_count.1 Here's a trimmed example of one cell:

{
  "cell_type": "code",
  "execution_count": 7,
  "source": ["df = drop_missing(df, [\"bill_length_mm\"])\n", "df.shape"],
  "outputs": [
    { "output_type": "execute_result", "execution_count": 7,
      "data": { "text/plain": ["(342, 8)"] } }
  ]
}

Re-run that cell and execution_count and outputs change, even if you never touched the code. Plots are stored inside outputs as base64-encoded image data,1 so a re-run plot rewrites a large block of the file. Three things follow:

The nbdime project says it plainly: "primitive line-based diff and merge tools do not handle well the logical structure of notebook documents."3

Fix 1 — nbdime: notebook-aware diff and merge

nbdime "provides tools for diffing and merging of Jupyter Notebooks."3 Once you register it with Git, git diff gives "the standard diff for any non-notebook files, but use[s] nbdime's command-line diff for all .ipynb files," and "Nbdime will also be used for all merges on notebook files."4

$ pip install nbdime
$ nbdime config-git --enable        # this repository only; add --global for all repos
$ git diff main...add-plot          # notebooks now diff cell by cell
$ nbdiff-web                        # rich side-by-side diff in the browser
$ git mergetool --tool nbdime -- *.ipynb   # resolve a notebook conflict visually

The setup writes a .gitattributes entry (*.ipynb diff=jupyternotebook).4 Commit that file so everyone who clones the repo, including CI, uses the same rule. Not every command uses the driver: "git add --patch will not use the driver."4

Fix 2 — Jupytext: version the code, not the JSON

Jupytext asks: "Have you always wished Jupyter notebooks were plain text documents?… And get clear and meaningful diffs when doing version control?"5 It pairs your notebook with a py:percent script that stores "Only the notebook inputs (and optionally, the metadata)." Such text notebooks "are well suited for version control."5

$ pip install jupytext
$ jupytext --set-formats ipynb,py:percent analysis.ipynb   # creates analysis.py, paired
$ jupytext --sync analysis.py                              # bring the pair back in step

When a paired notebook opens, "the inputs are loaded from the most recent file in the pair, while the outputs are loaded from the .ipynb file."6 So you can commit analysis.py and, as Jupytext suggests, "exclude .ipynb files from version control (unless you want to see the outputs versioned!)."5 Your teammates' diff then "is nothing else than a standard diff on a Python script."5 To pair every notebook in a project, put formats = "ipynb,py:percent" in a jupytext.toml.5

Branch habits for data scientists

The tools help. These habits (course advice, built on the lessons so far) keep conflicts rare to begin with:

Do it: make penguins-lab notebook-friendly

Check yourself

You re-run a notebook without editing any code. Why does git diff still show changes?

Code cells store their outputs and an execution_count in the JSON, so running a cell rewrites the file.

What does nbdime config-git --enable change?

It registers nbdime as Git's diff and merge driver for .ipynb files; other files still use the standard diff.

With Jupytext pairing, which file gives reviewers the cleanest diff?

The py:percent file stores only the inputs, so its diff is a standard Python diff.

Read nbdime's Git integration guide (5 minutes), then skim the Jupytext "Notebooks under version control" section to choose between the two.
Ask Claude: "Look at my repo's notebooks and suggest which logic should move into clean.py so it can be tested," or "Walk me through resolving a conflict in analysis.ipynb with nbdime's mergetool."

Sources

  1. nbformat, The Notebook file format.
  2. GitHub Docs, About merge conflicts.
  3. nbdime documentation (overview).
  4. nbdime, Version control integration — Git.
  5. Jupytext README (text notebooks, command line, notebooks under version control).
  6. Jupytext, Paired notebooks.