nbdime (Jupyter Notebook Diff & Merge)
nbdime provides tools for diffing and merging Jupyter Notebooks. It offers both a command-line interface for integration with version control systems (like Git) and a visual, interactive web interface for richer diffs and merges. The library is currently at version 4.0.4 and is actively maintained by the Project Jupyter team, with releases often coinciding with major JupyterLab updates.
Common errors
-
nbdime: command not found
cause The `nbdime` executable is not in your system's PATH, or the `nbdime` Python package was not installed correctly.fixEnsure `nbdime` is installed (`pip install nbdime`) and that your Python environment's script directory (where executables are placed) is included in your system's PATH. Restart your terminal after installation. -
No module named 'nbdime.nbdime'
cause You are attempting an incorrect programmatic import. There is no top-level `nbdime.nbdime` submodule directly.fixImport specific functions from submodules, for example: `from nbdime.diff_files import diff_notebooks` or `from nbdime.merge_files import merge_notebooks`. -
jupyter labextension install @jupyterlab/nbdime-extension failed with a compatibility error or 'Package not found'
cause There is a version mismatch between your installed JupyterLab and the `@jupyterlab/nbdime-extension` you are trying to install. This is common when upgrading JupyterLab or nbdime independently.fixFor `nbdime>=4.0.0` and JupyterLab 4, `pip install nbdime` should automatically handle the frontend. For JupyterLab 3, explicitly install a compatible extension version, e.g., `jupyter labextension install @jupyterlab/nbdime-extension@3`. Always consult the official `nbdime` documentation for the latest compatibility matrix. -
TraitError: The 'log' trait of a ServerApp instance must be a Logger, but a value of <nbdime.notebook_patch.PatchLogger object at 0x...> was specified.
cause This error can occur due to conflicts with `nbdime`'s monkey-patching of Jupyter server components and outdated `notebook` or `jupyter_server` dependencies.fixUpgrade your `notebook` and `jupyter_server` packages to their latest compatible versions. Then, reinstall `nbdime`. For example: `pip install --upgrade notebook jupyter_server nbdime`.
Warnings
- breaking nbdime 3.x and newer require Python 3.6+ for installation and execution. Older Python environments (e.g., Python 2.7, Python 3.5) are no longer supported and will lead to installation failures or runtime errors.
- breaking With `nbdime` version 4.0.0 and above, the JupyterLab frontend extension installation process changed. For JupyterLab 4, `pip install nbdime` should automatically handle the frontend extension. However, for JupyterLab 3 and below, you still need to explicitly run `jupyter labextension install @jupyterlab/nbdime-extension` (and possibly specify a compatible version like `@3`) after installing the Python package.
- gotcha By default, `nbdime diff` and `nbdime merge` commands launch a web browser to display the visual diff/merge interface. Users expecting immediate terminal output may be surprised. For command-line diff output, use the `--diff-alg=terminal` flag.
- deprecated The `ipython_genutils` dependency was removed in `nbdime` version 3.2.0. While this is an improvement, environments with other packages that still explicitly rely on or bundle `ipython_genutils` might experience minor dependency conflicts or warnings during upgrades.
Install
-
pip install nbdime -
jupyter nbextension install --py nbdime --sys-prefix --symlink -
jupyter labextension install @jupyterlab/nbdime-extension@3
Imports
- diff_notebooks
from nbdime.diff_files import diff_notebooks
- merge_notebooks
from nbdime.merge_files import merge_notebooks
- main
import nbdime
from nbdime.webapp import main
Quickstart
import nbformat
from pathlib import Path
import subprocess
# Create two dummy notebooks for demonstration
nb1 = nbformat.v4.new_notebook()
nb1.cells.append(nbformat.v4.new_code_cell("print('Hello from A')"))
nb1_path = Path("notebook_a.ipynb")
with open(nb1_path, "w", encoding="utf-8") as f:
nbformat.write(nb1, f)
nb2 = nbformat.v4.new_notebook()
nb2.cells.append(nbformat.v4.new_code_cell("print('Hello from B')"))
nb2.cells.append(nbformat.v4.new_markdown_cell("## A new section\nThis is a new section in notebook B."))
nb2_path = Path("notebook_b.ipynb")
with open(nb2_path, "w", encoding="utf-8") as f:
nbformat.write(nb2, f)
print(f"Created {nb1_path} and {nb2_path}")
# Run nbdime diff from the command line
print("\n--- Running nbdime diff in terminal mode ---")
try:
# Use --diff-alg=terminal to get output directly in the console
result = subprocess.run(
["nbdime", "diff", str(nb1_path), str(nb2_path), "--diff-alg=terminal"],
capture_output=True, text=True, check=True
)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Error running nbdime diff: {e.stderr}")
# To open in a browser (default behavior if --diff-alg is not specified)
# print("\n--- Running nbdime diff in browser (opens new tab) ---")
# subprocess.Popen(["nbdime", "diff", str(nb1_path), str(nb2_path)])
# print("Check your browser for the visual diff. Press Ctrl+C to stop this script after viewing.")
# import time; time.sleep(10) # Give time to view, then uncomment cleanup
# Clean up
nb1_path.unlink()
nb2_path.unlink()
print(f"\nCleaned up {nb1_path} and {nb2_path}")