33# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/19_diff.ipynb.
44
55# %% auto 0
6- __all__ = ['source_diff' ]
6+ __all__ = ['read_nb_from_git' , 'nbs_pair' , 'changed_cells' , 'source_diff' , 'cell_diffs' ]
7+
8+ # %% ../nbs/api/19_diff.ipynb
9+ import json
10+ from fastcore .utils import *
11+ from fastcore .meta import delegates
12+ from difflib import unified_diff
13+ from fastgit import Git
14+ from execnb .nbio import *
15+
16+ # %% ../nbs/api/19_diff.ipynb
17+ def read_nb_from_git (
18+ g :Git , # The git object
19+ ref , # The git ref to read from (e.g. HEAD)
20+ path # The path to the notebook in git
21+ )-> AttrDict : # The notebook
22+ "Read notebook from git ref (e.g. HEAD) at path"
23+ raw = g .show (f'{ ref } :{ path } ' , split = False )
24+ return dict2nb (json .loads (raw ))
25+
26+ # %% ../nbs/api/19_diff.ipynb
27+ def nbs_pair (
28+ nb_path , # Path to the notebook
29+ ref_a = 'HEAD' , # First git ref (None for working dir)
30+ ref_b = None # Second git ref (None for working dir)
31+ ): # Tuple of two notebooks
32+ "NBs at two refs; None means working dir. By default provides HEAD and working dir"
33+ nb_path = Path (nb_path ).resolve ()
34+ g = Git (nb_path .parent )
35+ rel = nb_path .relative_to (g .top ())
36+ nb_a = read_nb_from_git (g , ref_a , str (rel )) if ref_a else read_nb (nb_path )
37+ nb_b = read_nb_from_git (g , ref_b , str (rel )) if ref_b else read_nb (nb_path )
38+ return nb_a , nb_b
39+
40+ # %% ../nbs/api/19_diff.ipynb
41+ def _cell_changes (
42+ nb_path , # Path to the notebook
43+ fn , # function to call to get dict values
44+ ref_a = 'HEAD' , # First git ref (None for working dir)
45+ ref_b = None , # Second git ref (None for working dir)
46+ adds = True , # Include cells in b but not in a
47+ changes = True , # Include cells with different content
48+ dels = False , # Include cells in a but not in b
49+ metadata = False , # Consider cell metadata when comparing
50+ outputs = False # Consider cell outputs when comparing
51+ ): # Dict of results
52+ "Apply fn(cell_id, old_content, new_content) to changed cells between two refs"
53+ nb_a , nb_b = nbs_pair (nb_path , ref_a , ref_b )
54+ def cell_content (c ):
55+ res = c .get ('source' , '' )
56+ if metadata : res += '\n # metadata: ' + json .dumps (c .get ('metadata' , {}), sort_keys = True )
57+ if outputs : res += '\n # outputs: ' + json .dumps (c .get ('outputs' , []), sort_keys = True )
58+ return res
59+ old = {c ['id' ]: cell_content (c ) for c in nb_a .cells }
60+ new = {c ['id' ]: cell_content (c ) for c in nb_b .cells }
61+ res = {}
62+ if adds : res |= {cid : fn (cid , '' , new [cid ]) for cid in new if cid not in old }
63+ if changes : res |= {cid : fn (cid , old [cid ], new [cid ]) for cid in new if cid in old and new [cid ] != old [cid ]}
64+ if dels : res |= {cid : fn (cid , old [cid ], '' ) for cid in old if cid not in new }
65+ return res
66+
67+ # %% ../nbs/api/19_diff.ipynb
68+ @delegates (_cell_changes )
69+ def changed_cells (nb_path , ** kwargs ):
70+ "Return set of cell IDs for changed/added/deleted cells between two refs"
71+ def f (cid ,o ,n ): return cid
72+ return set (_cell_changes (nb_path , f , ** kwargs ).keys ())
773
874# %% ../nbs/api/19_diff.ipynb
975def source_diff (
@@ -12,3 +78,10 @@ def source_diff(
1278): # Unified diff string
1379 "Return unified diff string for source change"
1480 return '\n ' .join (unified_diff (old_source .splitlines (), new_source .splitlines (), lineterm = '' ))
81+
82+ # %% ../nbs/api/19_diff.ipynb
83+ @delegates (_cell_changes )
84+ def cell_diffs (nb_path , ** kwargs ):
85+ "{cell_id:diff} for changed/added/deleted cells between two refs"
86+ def f (cid ,o ,n ): return source_diff (o ,n )
87+ return _cell_changes (nb_path , f , ** kwargs )
0 commit comments