# local code here
merge
Introduction
When working with jupyter notebooks (which are json files behind the scenes) and GitHub, it is very common that a merge conflict (that will add new lines in the notebook source file) will break some notebooks you are working on. This module defines the function nbdev_fix
to fix those notebooks for you, and attempt to automatically merge standard conflicts. The remaining ones will be delimited by markdown cells like this:
<<<<<< HEAD
======
# remote code here
>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35
Below is an example of broken notebook. The json format is broken by the lines automatically added by git. Such a file can’t be opened in jupyter notebook.
= Path('../../tests/example.ipynb.broken')
broken = broken.read_text(encoding='utf-8')
tst_nb print(tst_nb)
{
"cells": [
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
<<<<<<< HEAD
"z=3\n",
=======
"z=2\n",
>>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35
"z"
]
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6"
]
},
<<<<<<< HEAD
"execution_count": 7,
=======
"execution_count": 5,
>>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x=3\n",
"y=3\n",
"x+y"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Note that in this example, the second conflict is easily solved: it just concerns the execution count of the second cell and can be solved by choosing either option without really impacting your notebook. This is the kind of conflict we will fix automatically. The first conflict is more complicated as it spans across two cells and there is a cell present in one version, not the other. Such a conflict (and generally the ones where the inputs of the cells change form one version to the other) aren’t automatically fixed, but we will return a proper json file where the annotations introduced by git will be placed in markdown cells.
Creating a merged notebook
The approach we use is to first “unpatch” the conflicted file, regenerating the two files it was originally created from. Then we redo the diff process, but using cells instead of text lines.
unpatch
unpatch (s:str)
Takes a string with conflict markers and returns the two original files, and their branch names
The result of “unpatching” our conflicted test notebook is the two original notebooks it would have been created from. Each of these original notebooks will contain valid JSON:
= unpatch(tst_nb)
a,b,branch1,branch2 dict2nb(loads(a))
{ 'cells': [ { 'cell_type': 'code',
'execution_count': 6,
'idx_': 0,
'metadata': {},
'outputs': [ { 'data': {'text/plain': ['3']},
'execution_count': 6,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'z=3\nz'},
{ 'cell_type': 'code',
'execution_count': 5,
'idx_': 1,
'metadata': {},
'outputs': [ { 'data': {'text/plain': ['6']},
'execution_count': 7,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'x=3\ny=3\nx+y'},
{ 'cell_type': 'code',
'execution_count': None,
'idx_': 2,
'metadata': {},
'outputs': [],
'source': ''}],
'metadata': { 'kernelspec': { 'display_name': 'Python 3',
'language': 'python',
'name': 'python3'}},
'nbformat': 4,
'nbformat_minor': 2}
dict2nb(loads(b))
{ 'cells': [ { 'cell_type': 'code',
'execution_count': 6,
'idx_': 0,
'metadata': {},
'outputs': [ { 'data': {'text/plain': ['3']},
'execution_count': 6,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'z=2\nz'},
{ 'cell_type': 'code',
'execution_count': 5,
'idx_': 1,
'metadata': {},
'outputs': [ { 'data': {'text/plain': ['6']},
'execution_count': 5,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'x=3\ny=3\nx+y'},
{ 'cell_type': 'code',
'execution_count': None,
'idx_': 2,
'metadata': {},
'outputs': [],
'source': ''}],
'metadata': { 'kernelspec': { 'display_name': 'Python 3',
'language': 'python',
'name': 'python3'}},
'nbformat': 4,
'nbformat_minor': 2}
branch1,branch2
('HEAD', 'a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35')
nbdev_fix
nbdev_fix (nbname:str, outname:str=None, nobackup:<function bool_arg>=True, theirs:bool=False, noprint:bool=False)
Create working notebook from conflicted notebook nbname
Type | Default | Details | |
---|---|---|---|
nbname | str | Notebook filename to fix | |
outname | str | None | Filename of output notebook (defaults to nbname ) |
nobackup | bool_arg | True | Do not backup nbname to nbname .bak if outname not provided |
theirs | bool | False | Use their outputs and metadata instead of ours |
noprint | bool | False | Do not print info about whether conflicts are found |
This begins by optionally backing the notebook fname
to fname.bak
in case something goes wrong. Then it parses the broken json, solving conflicts in cells. Every conflict that only involves metadata or outputs of cells will be solved automatically by using the local (theirs==False
) or the remote (theirs==True
) branch. Otherwise, or for conflicts involving the inputs of cells, the json will be repaired by including the two version of the conflicted cell(s) with markdown cells indicating the conflicts. You will be able to open the notebook again and search for the conflicts (look for <<<<<<<
) then fix them as you wish.
A message will be printed indicating whether the notebook was fully merged or if conflicts remain.
='tmp.ipynb')
nbdev_fix(broken, outname= read_nb('tmp.ipynb')
chk len(chk.cells), 7)
test_eq('tmp.ipynb') os.unlink(
One or more conflict remains in the notebook, please inspect manually.
Git merge driver
nbdev_merge
nbdev_merge (base:str, ours:str, theirs:str, path:str)
Git merge driver for notebooks
This implements a git merge driver for notebooks that automatically resolves conflicting metadata and outputs, and splits remaining conflicts as separate cells so that the notebook can be viewed and fixed in Jupyter. The easiest way to install it is by running nbdev_install_hooks
.
This works by first running Git’s default merge driver, and then nbdev_fix
if there are still conflicts. You can set nbdev_fix
’s theirs
argument using the THEIRS
environment variable, for example:
THEIRS=True git merge branch