Skip to content

Commit 9fdbb29

Browse files
committed
Start setup for notebook merge diffs
1 parent 1f26e0d commit 9fdbb29

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

jupyterlab_git/git.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import pexpect
1515
import tornado
1616
import tornado.locks
17-
from nbdime import diff_notebooks
17+
from nbdime import diff_notebooks, merge_notebooks
1818
from jupyter_server.utils import ensure_async
1919

2020
from .log import get_logger
@@ -320,14 +320,20 @@ async def fetch(self, path):
320320

321321
return result
322322

323-
async def get_nbdiff(self, prev_content: str, curr_content: str) -> dict:
323+
async def get_nbdiff(
324+
self, prev_content: str, curr_content: str, base_content=None
325+
) -> dict:
324326
"""Compute the diff between two notebooks.
325327
326328
Args:
327329
prev_content: Notebook previous content
328330
curr_content: Notebook current content
331+
base_content: Notebook base content - only passed during a merge conflict
329332
Returns:
330-
{"base": Dict, "diff": Dict}
333+
if not base_content:
334+
{"base": Dict, "diff": Dict}
335+
else:
336+
{"base": Dict, "merge_decisions": Dict}
331337
"""
332338

333339
def read_notebook(content):
@@ -348,11 +354,21 @@ def read_notebook(content):
348354
current_loop = tornado.ioloop.IOLoop.current()
349355
prev_nb = await current_loop.run_in_executor(None, read_notebook, prev_content)
350356
curr_nb = await current_loop.run_in_executor(None, read_notebook, curr_content)
351-
thediff = await current_loop.run_in_executor(
352-
None, diff_notebooks, prev_nb, curr_nb
353-
)
357+
if base_content:
358+
base_nb = await current_loop.run_in_executor(
359+
None, read_notebook, base_content
360+
)
361+
_, merge_decisions = await current_loop.run_in_executor(
362+
None, merge_notebooks, base_nb, prev_nb, curr_nb
363+
)
364+
365+
return {"base": base_nb, "merge_decisions": merge_decisions}
366+
else:
367+
thediff = await current_loop.run_in_executor(
368+
None, diff_notebooks, prev_nb, curr_nb
369+
)
354370

355-
return {"base": prev_nb, "diff": thediff}
371+
return {"base": prev_nb, "diff": thediff}
356372

357373
async def status(self, path):
358374
"""

jupyterlab_git/handlers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,10 @@ async def post(self):
705705
status_code=400, reason=f"Missing POST key: {e}"
706706
)
707707
try:
708-
content = await self.git.get_nbdiff(prev_content, curr_content)
708+
base_content = data.get("baseContent")
709+
content = await self.git.get_nbdiff(
710+
prev_content, curr_content, base_content
711+
)
709712
except Exception as e:
710713
get_logger().error(f"Error computing notebook diff.", exc_info=e)
711714
raise tornado.web.HTTPError(

src/components/diff/NotebookDiff.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { PromiseDelegate } from '@lumino/coreutils';
1212
import { Message } from '@lumino/messaging';
1313
import { Panel, Widget } from '@lumino/widgets';
1414
import { IDiffEntry } from 'nbdime/lib/diff/diffentries';
15+
import { IMergeDecision } from 'nbdime/lib/merge/decisions';
16+
import { NotebookMergeModel } from 'nbdime/lib/merge/model';
17+
import { NotebookMergeWidget } from 'nbdime/lib/merge/widget';
1518
import { NotebookDiffModel } from 'nbdime/lib/diff/model';
1619
import { CELLDIFF_CLASS, NotebookDiffWidget } from 'nbdime/lib/diff/widget';
1720
import {
@@ -50,6 +53,18 @@ interface INbdimeDiff {
5053
diff: IDiffEntry[];
5154
}
5255

56+
interface INbdimeMergeDiff {
57+
/**
58+
* Base notebook content
59+
*/
60+
base: INotebookContent;
61+
/**
62+
* Set of decisions made by comparing
63+
* reference, challenger, and base notebooks
64+
*/
65+
merge_decisions: IMergeDecision[];
66+
}
67+
5368
/**
5469
* Diff callback to be registered for notebook files.
5570
*
@@ -142,8 +157,13 @@ export class NotebookDiff
142157
return this._isReady;
143158
}
144159

160+
/**
161+
* Gets the file contents of a resolved merge conflict,
162+
* and rejects if unable to retrieve.
163+
*/
145164
async getResolvedFile(): Promise<string> {
146-
return Promise.resolve('');
165+
// TODO: Implement
166+
return Promise.reject('TODO');
147167
}
148168

149169
/**
@@ -159,6 +179,7 @@ export class NotebookDiff
159179

160180
const header = Private.diffHeader(
161181
this._model.reference.label,
182+
this._model.base?.label,
162183
this._model.challenger.label
163184
);
164185
this.addWidget(header);
@@ -173,10 +194,16 @@ export class NotebookDiff
173194
// ENH request content only if it changed
174195
const referenceContent = await this._model.reference.content();
175196
const challengerContent = await this._model.challenger.content();
197+
const baseContent = await this._model.base?.content();
176198

177-
const nbdWidget = await this.createDiffView(
199+
const createView = baseContent
200+
? this.createMergeView.bind(this)
201+
: this.createDiffView.bind(this);
202+
203+
const nbdWidget = await createView(
178204
challengerContent,
179-
referenceContent
205+
referenceContent,
206+
baseContent
180207
);
181208

182209
while (this._scroller.widgets.length > 0) {
@@ -215,6 +242,24 @@ export class NotebookDiff
215242
return new NotebookDiffWidget(model, this._renderMime);
216243
}
217244

245+
protected async createMergeView(
246+
challengerContent: string,
247+
referenceContent: string,
248+
baseContent: string
249+
): Promise<NotebookMergeWidget> {
250+
const data = await requestAPI<INbdimeMergeDiff>('diffnotebook', 'POST', {
251+
currentContent: challengerContent,
252+
previousContent: referenceContent,
253+
baseContent
254+
});
255+
256+
const model = new NotebookMergeModel(data.base, data.merge_decisions);
257+
258+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
259+
// @ts-ignore
260+
return new NotebookMergeWidget(model, this._renderMime);
261+
}
262+
218263
/**
219264
* Handle `'activate-request'` messages.
220265
*/
@@ -258,14 +303,16 @@ namespace Private {
258303
/**
259304
* Create a header widget for the diff view.
260305
*/
261-
export function diffHeader(baseLabel: string, remoteLabel: string): Widget {
306+
export function diffHeader(...labels: string[]): Widget {
262307
const node = document.createElement('div');
263308
node.className = 'jp-git-diff-header';
264-
node.innerHTML = `<div class="jp-git-diff-banner">
265-
<span>${baseLabel}</span>
266-
<span class="jp-spacer"></span>
267-
<span>${remoteLabel}</span>
268-
</div>`;
309+
node.innerHTML = `
310+
<div class="jp-git-diff-banner">
311+
${labels
312+
.filter(label => !!label)
313+
.map(label => `<span>${label}</span>`)
314+
.join('<span class="jp-spacer"></span>')}
315+
</div>`;
269316

270317
return new Widget({ node });
271318
}

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"rootDir": "src",
1919
"sourceMap": true,
2020
"sourceRoot": "./@jupyterlab/git/src",
21+
"skipLibCheck": true,
2122
"strict": true,
2223
"strictNullChecks": false,
2324
"target": "es2017",

0 commit comments

Comments
 (0)