merge

修复 jupyter notebook 中的合并冲突

引言

当使用 jupyter notebook(它们在后台是 json 文件)和 GitHub 时,合并冲突(会在 notebook 源文件中添加新行)非常普遍,这会导致你正在处理的一些 notebook 损坏。此模块定义了函数 nbdev_fix 来为你修复这些 notebook,并尝试自动合并标准冲突。剩余的冲突将由 markdown 单元格界定,如下所示

<<<<<< HEAD

# local code here

======

# remote code here

>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35

下面是一个损坏的 notebook 示例。json 格式被 git 自动添加的行破坏了。这样的文件无法在 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
}

请注意,在这个例子中,第二个冲突很容易解决:它只涉及第二个单元格的执行计数,并且通过选择任一选项而不会真正影响你的 notebook 就可以解决。这类冲突我们将自动修复。第一个冲突则更复杂,因为它跨越了两个单元格,并且其中一个版本存在一个单元格,而另一个版本没有。这样的冲突(通常是单元格输入内容从一个版本变为另一个版本的冲突)不会自动修复,但我们会返回一个合适的 json 文件,其中 git 引入的注释将被放在 markdown 单元格中。

创建合并的 notebook

我们使用的方法是首先“unpatch”冲突的文件,重新生成它最初创建的两个文件。然后我们重新进行 diff 过程,但使用单元格而不是文本行。


source

unpatch

 unpatch (s:str)

接受带有冲突标记的字符串,并返回两个原始文件及其分支名称

对我们冲突的测试 notebook 进行“unpatch”的结果是它本应创建的两个原始 notebook。每个原始 notebook 都将包含有效的 JSON。

a,b,branch1,branch2 = unpatch(tst_nb)
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')

source

nbdev_fix

 nbdev_fix (nbname:str, outname:str=None, nobackup:<function
            bool_arg>=True, theirs:bool=False, noprint:bool=False)

从冲突的 notebook nbname 创建工作 notebook

类型 默认值 详情
nbname str 要修复的 Notebook 文件名
outname str None 输出 notebook 的文件名(默认为 nbname
nobackup bool_arg True 如果未提供 outname,则不将 nbname 备份到 nbname.bak
theirs bool False 使用他们的输出和元数据而非我们的
noprint bool False 不打印有关是否找到冲突的信息

首先可选地将 notebook fname 备份到 fname.bak,以防万一出错。然后它解析损坏的 json,解决单元格中的冲突。每个仅涉及单元格元数据或输出的冲突将通过使用本地分支 (theirs==False) 或远程分支 (theirs==True) 自动解决。否则,或对于涉及单元格输入内容的冲突,json 将通过包含冲突单元格的两个版本以及指示冲突的 markdown 单元格来修复。你将能够再次打开 notebook 并搜索冲突(查找 <<<<<<<),然后根据需要修复它们。

将打印一条消息,指示 notebook 是完全合并还是仍有冲突。

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 合并驱动


source

nbdev_merge

 nbdev_merge (base:str, ours:str, theirs:str, path:str)

notebook 的 Git 合并驱动

这为 notebook 实现了一个 git 合并驱动,它可以自动解决冲突的元数据和输出,并将剩余的冲突拆分为单独的单元格,以便在 Jupyter 中查看和修复 notebook。安装它的最简单方法是运行 nbdev_install_hooks

这通过首先运行 Git 的默认合并驱动,然后在仍有冲突时运行 nbdev_fix 来工作。你可以使用 THEIRS 环境变量设置 nbdev_fixtheirs 参数,例如

THEIRS=True git merge branch