|
36 | 36 | "from fastcore.script import *\n", |
37 | 37 | "from fastcore import shutil\n", |
38 | 38 | "\n", |
| 39 | + "import subprocess\n", |
39 | 40 | "from difflib import SequenceMatcher" |
40 | 41 | ] |
41 | 42 | }, |
|
405 | 406 | "outputs": [], |
406 | 407 | "source": [ |
407 | 408 | "#|export\n", |
408 | | - "@call_parse\n", |
409 | | - "def nbdev_fix(nbname:str, # Notebook filename to fix\n", |
| 409 | + "def _nbdev_fix(nbname:str, # Notebook filename to fix\n", |
410 | 410 | " outname:str=None, # Filename of output notebook (defaults to `nbname`)\n", |
411 | 411 | " nobackup:bool=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided\n", |
412 | 412 | " theirs:bool=False, # Use their outputs and metadata instead of ours\n", |
|
427 | 427 | " return conflict" |
428 | 428 | ] |
429 | 429 | }, |
| 430 | + { |
| 431 | + "cell_type": "code", |
| 432 | + "execution_count": null, |
| 433 | + "metadata": {}, |
| 434 | + "outputs": [], |
| 435 | + "source": [ |
| 436 | + "#|export\n", |
| 437 | + "nbdev_fix = call_parse(_nbdev_fix)" |
| 438 | + ] |
| 439 | + }, |
430 | 440 | { |
431 | 441 | "cell_type": "markdown", |
432 | 442 | "metadata": {}, |
|
456 | 466 | "os.unlink('tmp.ipynb')" |
457 | 467 | ] |
458 | 468 | }, |
| 469 | + { |
| 470 | + "cell_type": "markdown", |
| 471 | + "metadata": {}, |
| 472 | + "source": [ |
| 473 | + "## Git merge driver" |
| 474 | + ] |
| 475 | + }, |
| 476 | + { |
| 477 | + "cell_type": "code", |
| 478 | + "execution_count": null, |
| 479 | + "metadata": {}, |
| 480 | + "outputs": [], |
| 481 | + "source": [ |
| 482 | + "#|export\n", |
| 483 | + "def _only(o):\n", |
| 484 | + " \"Return the only item of `o`, raise if `o` doesn't have exactly one item\"\n", |
| 485 | + " it = iter(o)\n", |
| 486 | + " try: res = next(it)\n", |
| 487 | + " except StopIteration: raise ValueError('iterable has 0 items') from None\n", |
| 488 | + " try: next(it)\n", |
| 489 | + " except StopIteration: return res\n", |
| 490 | + " raise ValueError(f'iterable has more than 1 item')" |
| 491 | + ] |
| 492 | + }, |
| 493 | + { |
| 494 | + "cell_type": "code", |
| 495 | + "execution_count": null, |
| 496 | + "metadata": {}, |
| 497 | + "outputs": [], |
| 498 | + "source": [ |
| 499 | + "#|hide\n", |
| 500 | + "test_fail(lambda: _only([]), contains='iterable has 0 items')\n", |
| 501 | + "test_eq(_only([0]), 0)\n", |
| 502 | + "test_fail(lambda: _only([0,1]), contains='iterable has more than 1 item')" |
| 503 | + ] |
| 504 | + }, |
| 505 | + { |
| 506 | + "cell_type": "code", |
| 507 | + "execution_count": null, |
| 508 | + "metadata": {}, |
| 509 | + "outputs": [], |
| 510 | + "source": [ |
| 511 | + "#|export\n", |
| 512 | + "def _git_branch_merge(): return _only(v for k,v in os.environ.items() if k.startswith('GITHEAD'))" |
| 513 | + ] |
| 514 | + }, |
| 515 | + { |
| 516 | + "cell_type": "code", |
| 517 | + "execution_count": null, |
| 518 | + "metadata": {}, |
| 519 | + "outputs": [], |
| 520 | + "source": [ |
| 521 | + "#|export\n", |
| 522 | + "@call_parse\n", |
| 523 | + "def nbdev_merge(base:str, ours:str, theirs:str, path:str):\n", |
| 524 | + " \"Git merge driver for notebooks\"\n", |
| 525 | + " proc = subprocess.run(f'git merge-file -L HEAD -L BASE -L {_git_branch_merge()} {ours} {base} {theirs}',\n", |
| 526 | + " shell=True, capture_output=True, text=True)\n", |
| 527 | + " if proc.returncode == 0: return\n", |
| 528 | + " theirs = str2bool(os.environ.get('THEIRS', False))\n", |
| 529 | + " return _nbdev_fix(ours, theirs=theirs)" |
| 530 | + ] |
| 531 | + }, |
| 532 | + { |
| 533 | + "cell_type": "markdown", |
| 534 | + "metadata": {}, |
| 535 | + "source": [ |
| 536 | + "This implements a [git merge driver](https://git-scm.com/docs/gitattributes#_defining_a_custom_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`." |
| 537 | + ] |
| 538 | + }, |
| 539 | + { |
| 540 | + "cell_type": "markdown", |
| 541 | + "metadata": {}, |
| 542 | + "source": [ |
| 543 | + "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:\n", |
| 544 | + "\n", |
| 545 | + " THEIRS=True git merge branch" |
| 546 | + ] |
| 547 | + }, |
459 | 548 | { |
460 | 549 | "cell_type": "markdown", |
461 | 550 | "metadata": {}, |
|
0 commit comments