How to Version Control Jupyter Notebooks

Galaxy Glossary

How do I version control Jupyter notebooks without unreadable diffs or merge conflicts?

Version-controlling Jupyter notebooks means tracking every meaningful change to the notebook’s code, narrative, and outputs in a reproducible, collaborative, and reviewable way—usually with Git plus specialized diff or text-conversion tools.

Sign up for the latest in SQL knowledge from the Galaxy Team!
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Description

What Is Version Control for Jupyter Notebooks?

Version control is the practice of tracking and managing changes to files over time so that multiple people can collaborate, review history, and restore previous states when needed. In software engineering this normally involves storing source code in a line-oriented text file and using Git to manage commits, branches, and pull requests.

Jupyter notebooks (.ipynb) complicate this process because they are JSON documents that bundle executable code, rich markdown, cell metadata, and often bulky cell outputs such as images or dataframes. A single character typed in a cell can re-write a large chunk of the underlying JSON, making a standard Git diff almost unreadable and merge conflicts nearly impossible to resolve manually.

Why Does It Matter?

  • Reproducibility — Data science and analytics work must be repeatable. A clean commit history documents exactly how a result was produced.
  • Collaboration — Without proper version control, analysts send notebooks back and forth by email or Slack. That leads to “notebook sprawl” and forgotten business logic.
  • Code Review — Peer review improves quality and enforces standards, but only if the diff is human-readable.
  • Experiment Tracking — Branches provide isolated sandboxes for exploratory work, letting you revert or merge only the valuable experiments.

Key Challenges

1. Noisy JSON Diff

Because notebook cells are stored in an array, re-ordering or re-executing cells changes the numeric IDs in the JSON structure, which produces huge diffs unrelated to the actual change.

2. Merge Conflicts

Two collaborators who execute the notebook in different orders or have different output sizes are likely to generate conflicting JSON fragments. Resolving these by hand is error-prone.

3. Binary Output Bloat

Embedded images, dataframes, and widgets can balloon a commit from KBs to MBs. Repository size and clone times grow rapidly.

Core Approaches to Notebook Versioning

Plain Git on .ipynb

Commit the raw notebook. Pros: zero setup, standard Git hosting services. Cons: unreadable diffs, high chance of merge conflicts.

Notebook-Aware Diff Tools

nbdime, ReviewNB, and GitHub’s built-in notebook viewer render visual, cell-level diffs that ignore irrelevant metadata. They integrate with Git hooks or your pull-request UI:

# Install nbdime globally
pip install nbdime
nbdime config-git --enable # sets up git difftool & mergetool

Pros: rich, cell-aware diff/merge; minimal workflow changes. Cons: collaborators must adopt the same tooling.

Conversion to Plain Text With Jupytext

Jupytext synchronizes a notebook with one or more text representations—Markdown, R Markdown, or a .py/.R script containing special # %% cell markers. Your repo stores the text file; the .ipynb is regenerated locally on open.

# One-time setup
pip install jupytext
jupytext --set-formats ipynb,py:percent my_notebook.ipynb

Pros: fully readable diff, easy conflict resolution, smaller repo size. Cons: Higher cognitive load (two files), need to educate team.

Track the Runtime Environment

Notebook code may depend on library versions, environment variables, or data stores. Commit one or more of the following:

  • requirements.txt or environment.yml
  • Dockerfile or conda-lock file
  • Data snapshots via DVC or lakehouse table versions

Recommended Best Practices

  1. Strip large or irrelevant outputs. Configure jupyter nbconvert --ClearOutputPreprocessor.enabled=True before commit, or use Git pre-commit hooks like nbstripout.
  2. Use Jupytext or nbdime for readable diffs. Mandate one method in your team’s contributing guide.
  3. Keep each notebook focused. One analytical question per notebook prevents spaghetti dependencies.
  4. Run cells top-to-bottom before commit. This guarantees deterministic state and lowers merge-conflict risk.
  5. Automate checks in CI. Lint notebooks with black-nb or flake8-nb, and validate execution with pytest --nbval.

End-to-End Workflow Example

The following opinionated workflow balances readability with minimal friction:

  1. Create a feature branch: git checkout -b feature/customer-churn-model
  2. Launch JupyterLab with the jupytext extension installed.
  3. Pair the notebook to a .py text representation (jupytext --set-formats ipynb,py:percent).
  4. Work primarily inside the notebook UI, but commit only the .py partner file and a stripped .ipynb (if needed). The nbstripout pre-commit hook clears outputs automatically.
  5. Push and create a pull request. Reviewers see a clean diff of the .py file inline, or a rendered notebook via ReviewNB.
  6. Merge once approved; main branch stays conflict-free and reproducible.

Where Does Galaxy Fit In?

Galaxy focuses on writing, sharing, and reviewing SQL rather than Python notebooks. If your analytics workflow relies primarily on SQL—notebooks for data science—you would author and version your queries directly inside Galaxy’s fast editor, leaving notebooks for exploratory or visualization tasks. For Git-based review, you could store exported SQL from Galaxy alongside Jupytext-managed notebooks in the same repository, maintaining a single source of truth for both query logic and ad-hoc analysis.

Common Pitfalls and How to Avoid Them

1. Committing Heavy Outputs by Accident

Why it happens: Analysts forget to clear outputs; Git LFS is not enabled. Fix it by automating nbstripout or using Jupytext to keep outputs separate.

2. Ignoring Execution Order

Out-of-order cell execution creates hidden state. Always Restart & Run All before commit and consider pytest --nbval in CI to enforce deterministic execution.

3. Mixing Too Many Concerns in One Notebook

Long, multipurpose notebooks cause merge headaches and confuse reviewers. Split ETL, EDA, and modeling into separate logically named notebooks or convert stable logic into Python modules or SQL queries managed in Galaxy.

Conclusion

Effective version control turns notebooks from disposable scratchpads into maintainable, collaborative artifacts. Whether you adopt Jupytext, nbdime, or another strategy, the essential steps are the same: store a diff-friendly representation, strip bloat, document the environment, and automate validation. With these guardrails in place, data teams can iterate quickly without sacrificing reproducibility or code quality.

Why How to Version Control Jupyter Notebooks is important

Notebooks dominate exploratory data science, yet their JSON format breaks traditional diff-and-merge workflows. Without proper version control, teams lose reproducibility, slow code reviews, and risk shipping incorrect analyses. Mastering notebook-aware versioning aligns data science with software-engineering rigor, enabling faster collaboration and more reliable insights.

How to Version Control Jupyter Notebooks Example Usage


# View a clean diff between two notebook commits
git difftool HEAD~1 HEAD -- my_notebook.ipynb

Common Mistakes

Frequently Asked Questions (FAQs)

Should I store the .ipynb file in Git at all?

Yes, but strip outputs and ensure a stable cell order. The JSON is still useful for rendering in viewers like GitHub or VS Code, but the text partner file (via Jupytext) should be the primary diff target.

How do I resolve merge conflicts in notebooks?

Enable nbdime as your Git mergetool. It performs cell-aware merges and opens a web UI for manual resolution. If you use Jupytext, you can merge the plain-text script first and regenerate the notebook.

What’s the difference between ReviewNB and nbdime?

Both provide cell-level diffs. nbdime is an open-source CLI/UI tool integrated with Git, whereas ReviewNB is a SaaS platform that plugs directly into GitHub pull requests and supports comments on individual cells.

Can Galaxy handle notebook version control?

Galaxy is optimized for SQL workflows rather than Jupyter notebooks. However, you can keep notebooks and Galaxy-authored SQL side-by-side in the same Git repository, applying the notebook practices discussed here alongside Galaxy’s built-in query history.

Want to learn about other SQL terms?