# local code heremerge
Introduction
Git conflict markers make notebook JSON invalid. nbdev-fix creates a notebook you can open in Jupyter. It resolves metadata and output conflicts automatically. For conflicting cell contents, it keeps both versions between Markdown markers:
<<<<<< 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.
broken = Path('../../tests/example.ipynb.broken')
tst_nb = broken.read_text(encoding='utf-8')
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
}
The second conflict affects an execution count. Either value works, so nbdev-fix can resolve it automatically. The first conflict spans two cells, including one missing from the other version. It needs a manual decision. nbdev-fix preserves both versions in a valid notebook.
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
def 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:
a,b,branch1,branch2 = unpatch(tst_nb)
dict2nb(loads(a)){ 'cells': [ { 'cell_type': 'code',
'execution_count': 6,
'id': '54333cb4',
'idx_': 0,
'lang_': 'python',
'metadata': {},
'outputs': [ { 'data': {'text/plain': '3'},
'execution_count': 6,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'z=3\nz'},
{ 'cell_type': 'code',
'execution_count': 5,
'id': 'e275789e',
'idx_': 1,
'lang_': 'python',
'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,
'id': '15113038',
'idx_': 2,
'lang_': 'python',
'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,
'id': 'd6c69e35',
'idx_': 0,
'lang_': 'python',
'metadata': {},
'outputs': [ { 'data': {'text/plain': '3'},
'execution_count': 6,
'metadata': {},
'output_type': 'execute_result'}],
'source': 'z=2\nz'},
{ 'cell_type': 'code',
'execution_count': 5,
'id': '9f6fc608',
'idx_': 1,
'lang_': 'python',
'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,
'id': 'c8dcd223',
'idx_': 2,
'lang_': 'python',
'metadata': {},
'outputs': [],
'source': ''}],
'metadata': { 'kernelspec': { 'display_name': 'Python 3',
'language': 'python',
'name': 'python3'}},
'nbformat': 4,
'nbformat_minor': 2}branch1,branch2('HEAD', 'a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35')
nbdev_fix
def nbdev_fix(
nbname:str, # Notebook filename to fix
outname:str=None, # Filename of output notebook (defaults to `nbname`)
nobackup:<function bool_arg at 0x7fa89fbd5120>=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
):Create working notebook from conflicted notebook nbname
By default, nbdev-fix keeps local outputs and metadata. Pass theirs=True to use the other branch’s values. Conflicting source remains in separate cells. Open the repaired notebook and search for <<<<<<< to resolve these conflicts.
Without outname, the command overwrites the input notebook. Set nobackup=False to keep a .ipynb.bak copy. The completion message says whether any conflicts remain.
nbdev_fix(broken, outname='tmp.ipynb')
chk = read_nb('tmp.ipynb')
test_eq(len(chk.cells), 7)
os.unlink('tmp.ipynb')One or more conflict remains in the notebook, please inspect manually.
Git merge driver
nbdev_merge
def nbdev_merge(
base:str, ours:str, theirs:str, path:str
):Git merge driver for notebooks
Run nbdev-install-hooks to install the notebook Git merge driver. It first runs Git’s standard merge, then calls nbdev-fix if conflicts remain.
Set the THEIRS environment variable to choose the other branch’s outputs and metadata:
THEIRS=True git merge branch