Skip to content

Commit daa6adc

Browse files
authored
Backport PR #900 on branch jlab-2
2 parents 2601a97 + 1fcc23a commit daa6adc

39 files changed

+3930
-3294
lines changed

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
indent_style = space
8+
indent_size = 2
9+
end_of_line = lf
10+
charset = utf-8
11+
trim_trailing_whitespace = false
12+
insert_final_newline = false
13+
14+
[*.py]
15+
indent_size = 4

jupyterlab_git/git.py

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
"""
22
Module for executing git commands, sending results back to the handlers
33
"""
4+
import datetime
45
import os
6+
import pathlib
57
import re
68
import shlex
79
import subprocess
810
from urllib.parse import unquote
911

10-
import pathlib
12+
import nbformat
1113
import pexpect
1214
import tornado
1315
import tornado.locks
14-
import datetime
16+
from nbdime import diff_notebooks
1517

1618
from .log import get_logger
1719

18-
1920
# Regex pattern to capture (key, value) of Git configuration options.
2021
# See https://git-scm.com/docs/git-config#_syntax for git var syntax
2122
CONFIG_PATTERN = re.compile(r"(?:^|\n)([\w\-\.]+)\=")
@@ -322,6 +323,40 @@ async def fetch(self, current_path):
322323

323324
return result
324325

326+
async def get_nbdiff(self, prev_content: str, curr_content: str) -> dict:
327+
"""Compute the diff between two notebooks.
328+
329+
Args:
330+
prev_content: Notebook previous content
331+
curr_content: Notebook current content
332+
Returns:
333+
{"base": Dict, "diff": Dict}
334+
"""
335+
336+
def read_notebook(content):
337+
if not content:
338+
return nbformat.versions[nbformat.current_nbformat].new_notebook()
339+
if isinstance(content, dict):
340+
# Content may come from model as a dict directly
341+
return (
342+
nbformat.versions[
343+
content.get("nbformat", nbformat.current_nbformat)
344+
]
345+
.nbjson.JSONReader()
346+
.to_notebook(content)
347+
)
348+
else:
349+
return nbformat.reads(content, as_version=4)
350+
351+
current_loop = tornado.ioloop.IOLoop.current()
352+
prev_nb = await current_loop.run_in_executor(None, read_notebook, prev_content)
353+
curr_nb = await current_loop.run_in_executor(None, read_notebook, curr_content)
354+
thediff = await current_loop.run_in_executor(
355+
None, diff_notebooks, prev_nb, curr_nb
356+
)
357+
358+
return {"base": prev_nb, "diff": thediff}
359+
325360
async def status(self, current_path):
326361
"""
327362
Execute git status command & return the result.
@@ -1156,6 +1191,7 @@ async def show(self, filename, ref, top_repo_path):
11561191
filename
11571192
),
11581193
"fatal: Path '{}' does not exist in '{}'".format(filename, ref),
1194+
"fatal: Invalid object name 'HEAD'",
11591195
],
11601196
)
11611197
lower_error = error.lower()
@@ -1188,48 +1224,39 @@ def get_content(self, filename, top_repo_path):
11881224
raise error
11891225
return model["content"]
11901226

1191-
async def diff_content(self, filename, prev_ref, curr_ref, top_repo_path):
1227+
async def get_content_at_reference(self, filename, reference, top_repo_path):
11921228
"""
1193-
Collect get content of prev and curr and return.
1229+
Collect get content of the file at the git reference.
11941230
"""
1195-
if prev_ref["git"]:
1196-
is_binary = await self._is_binary(filename, prev_ref["git"], top_repo_path)
1197-
if is_binary:
1198-
raise tornado.web.HTTPError(
1199-
log_message="Error occurred while executing command to retrieve plaintext diff as file is not UTF-8."
1200-
)
1201-
1202-
prev_content = await self.show(filename, prev_ref["git"], top_repo_path)
1203-
else:
1204-
prev_content = ""
1205-
1206-
if "special" in curr_ref:
1207-
if curr_ref["special"] == "WORKING":
1208-
curr_content = self.get_content(filename, top_repo_path)
1209-
elif curr_ref["special"] == "INDEX":
1231+
if "special" in reference:
1232+
if reference["special"] == "WORKING":
1233+
content = self.get_content(filename, top_repo_path)
1234+
elif reference["special"] == "INDEX":
12101235
is_binary = await self._is_binary(filename, "INDEX", top_repo_path)
12111236
if is_binary:
12121237
raise tornado.web.HTTPError(
1213-
log_message="Error occurred while executing command to retrieve plaintext diff as file is not UTF-8."
1238+
log_message="Error occurred while executing command to retrieve plaintext content as file is not UTF-8."
12141239
)
12151240

1216-
curr_content = await self.show(filename, "", top_repo_path)
1241+
content = await self.show(filename, "", top_repo_path)
12171242
else:
12181243
raise tornado.web.HTTPError(
1219-
log_message="Error while retrieving plaintext diff, unknown special ref '{}'.".format(
1220-
curr_ref["special"]
1244+
log_message="Error while retrieving plaintext content, unknown special ref '{}'.".format(
1245+
reference["special"]
12211246
)
12221247
)
1223-
else:
1224-
is_binary = await self._is_binary(filename, curr_ref["git"], top_repo_path)
1248+
elif reference["git"]:
1249+
is_binary = await self._is_binary(filename, reference["git"], top_repo_path)
12251250
if is_binary:
12261251
raise tornado.web.HTTPError(
1227-
log_message="Error occurred while executing command to retrieve plaintext diff as file is not UTF-8."
1252+
log_message="Error occurred while executing command to retrieve plaintext content as file is not UTF-8."
12281253
)
12291254

1230-
curr_content = await self.show(filename, curr_ref["git"], top_repo_path)
1255+
content = await self.show(filename, reference["git"], top_repo_path)
1256+
else:
1257+
content = ""
12311258

1232-
return {"prev_content": prev_content, "curr_content": curr_content}
1259+
return {"content": content}
12331260

12341261
async def _is_binary(self, filename, ref, top_repo_path):
12351262
"""
@@ -1275,10 +1302,17 @@ async def _is_binary(self, filename, ref, top_repo_path):
12751302
code, output, error = await execute(command, cwd=top_repo_path)
12761303

12771304
if code != 0:
1278-
err_msg = "fatal: Path '{}' does not exist (neither on disk nor in the index)".format(
1279-
filename
1280-
).lower()
1281-
if err_msg in error.lower():
1305+
error_messages = map(
1306+
lambda n: n.lower(),
1307+
[
1308+
"fatal: Path '{}' does not exist (neither on disk nor in the index)".format(
1309+
filename
1310+
),
1311+
"fatal: bad revision 'HEAD'",
1312+
],
1313+
)
1314+
lower_error = error.lower()
1315+
if any([msg in lower_error for msg in error_messages]):
12821316
return False
12831317

12841318
raise tornado.web.HTTPError(

0 commit comments

Comments
 (0)