Skip to content

Commit a402291

Browse files
basokantDamans227fcollonval
authored
Adding support for standalone diffs of images (jupyterlab#1223)
* add ImageDiff component with stub methods * register image diff and add git widget * handle image files in the backend * convert binary file to base64 * return content from binary file condition * Load image from git db * add react-compare-image widget for viewing image diff * remove commented out diff images * fix formatting * migrate to react-image-diff (not working) * basic image diff view logic * use mui tabs/tab components for diff modes * change switch image diff mode logic * add image size constraints in 2-up image diff mode * finish minimum working onion skin image diff view * add image width and height to the 2-up image diff view * add file sizes to 2-up image diff view * fix label and image border colours * finish working swipe image diff view * add reference and challenger indicators to the slider component, and fix styling for image and MUI components * make sliders have the same width as the image for the swipe and onion skin image diff views * fix order of slider labels for swipe image diff view * fix git.py formatting * fix ref and chall having different aspect ratios styling and behaviour, and match slider and image widths responsively * add support for jpg and jpeg images * fix 3 failing tests to reflect binary file support * fix test_content_binary to reflect support for binary files * handle empty base64 image with placeholder * remove react-compare-image and react-image-diff dependencies * fix image diffs from the initial commit * Some clean ups * Add integration test * Lint the code * Fix tests --------- Co-authored-by: Damans227 <[email protected]> Co-authored-by: Frédéric Collonval <[email protected]> Co-authored-by: Frédéric Collonval <[email protected]>
1 parent 8666a36 commit a402291

File tree

12 files changed

+863
-21
lines changed

12 files changed

+863
-21
lines changed

jupyterlab_git/git.py

+22-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Module for executing git commands, sending results back to the handlers
33
"""
4+
import base64
45
import datetime
56
import os
67
import pathlib
@@ -51,6 +52,7 @@ async def execute(
5152
env: "Optional[Dict[str, str]]" = None,
5253
username: "Optional[str]" = None,
5354
password: "Optional[str]" = None,
55+
is_binary=False,
5456
) -> "Tuple[int, str, str]":
5557
"""Asynchronously execute a command.
5658
@@ -110,12 +112,20 @@ def call_subprocess(
110112
cmdline: "List[str]",
111113
cwd: "Optional[str]" = None,
112114
env: "Optional[Dict[str, str]]" = None,
115+
is_binary=is_binary,
113116
) -> "Tuple[int, str, str]":
114117
process = subprocess.Popen(
115118
cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env
116119
)
117120
output, error = process.communicate()
118-
return (process.returncode, output.decode("utf-8"), error.decode("utf-8"))
121+
if is_binary:
122+
return (
123+
process.returncode,
124+
base64.encodebytes(output).decode("ascii"),
125+
error.decode("utf-8"),
126+
)
127+
else:
128+
return (process.returncode, output.decode("utf-8"), error.decode("utf-8"))
119129

120130
try:
121131
await execution_lock.acquire(
@@ -1325,7 +1335,7 @@ async def _get_base_ref(self, path, filename):
13251335

13261336
return split_line[1] if len(split_line) > 1 else None
13271337

1328-
async def show(self, path, ref, filename=None):
1338+
async def show(self, path, ref, filename=None, is_binary=False):
13291339
"""
13301340
Execute
13311341
git show <ref:filename>
@@ -1341,7 +1351,7 @@ async def show(self, path, ref, filename=None):
13411351
else:
13421352
command.append(f"{ref}:{filename}")
13431353

1344-
code, output, error = await execute(command, cwd=path)
1354+
code, output, error = await execute(command, cwd=path, is_binary=is_binary)
13451355

13461356
error_messages = map(
13471357
lambda n: n.lower(),
@@ -1402,11 +1412,11 @@ async def get_content_at_reference(
14021412
elif reference["special"] == "INDEX":
14031413
is_binary = await self._is_binary(filename, "INDEX", path)
14041414
if is_binary:
1405-
raise tornado.web.HTTPError(
1406-
log_message="Error occurred while executing command to retrieve plaintext content as file is not UTF-8."
1415+
content = await self.show(
1416+
path, reference["git"], filename, is_binary=True
14071417
)
1408-
1409-
content = await self.show(path, "", filename)
1418+
else:
1419+
content = await self.show(path, "", filename)
14101420
elif reference["special"] == "BASE":
14111421
# Special case of file in merge conflict for which we want the base (aka common ancestor) version
14121422
ref = await self._get_base_ref(path, filename)
@@ -1417,14 +1427,14 @@ async def get_content_at_reference(
14171427
reference["special"]
14181428
)
14191429
)
1420-
elif reference["git"]:
1430+
elif "git" in reference:
14211431
is_binary = await self._is_binary(filename, reference["git"], path)
14221432
if is_binary:
1423-
raise tornado.web.HTTPError(
1424-
log_message="Error occurred while executing command to retrieve plaintext content as file is not UTF-8."
1433+
content = await self.show(
1434+
path, reference["git"], filename, is_binary=True
14251435
)
1426-
1427-
content = await self.show(path, reference["git"], filename)
1436+
else:
1437+
content = await self.show(path, reference["git"], filename)
14281438
else:
14291439
content = ""
14301440

jupyterlab_git/tests/test_handlers.py

+59-8
Original file line numberDiff line numberDiff line change
@@ -678,11 +678,24 @@ async def test_content(mock_execute, jp_fetch, jp_root_dir):
678678
assert payload["content"] == content
679679
mock_execute.assert_has_calls(
680680
[
681+
call(
682+
[
683+
"git",
684+
"diff",
685+
"--numstat",
686+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
687+
"previous",
688+
"--",
689+
filename,
690+
],
691+
cwd=str(local_path),
692+
),
681693
call(
682694
["git", "show", "{}:{}".format("previous", filename)],
683695
cwd=str(local_path),
696+
is_binary=False,
684697
),
685-
],
698+
]
686699
)
687700

688701

@@ -779,9 +792,22 @@ async def test_content_index(mock_execute, jp_fetch, jp_root_dir):
779792
assert payload["content"] == content
780793
mock_execute.assert_has_calls(
781794
[
795+
call(
796+
[
797+
"git",
798+
"diff",
799+
"--numstat",
800+
"--cached",
801+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
802+
"--",
803+
filename,
804+
],
805+
cwd=str(local_path),
806+
),
782807
call(
783808
["git", "show", "{}:{}".format("", filename)],
784809
cwd=str(local_path),
810+
is_binary=False,
785811
),
786812
],
787813
)
@@ -824,10 +850,15 @@ async def test_content_base(mock_execute, jp_fetch, jp_root_dir):
824850
mock_execute.assert_has_calls(
825851
[
826852
call(
827-
["git", "show", obj_ref],
853+
["git", "ls-files", "-u", "-z", filename],
828854
cwd=str(local_path),
829855
),
830-
],
856+
call(
857+
["git", "show", "915bb14609daab65e5304e59d89c626283ae49fc"],
858+
cwd=str(local_path),
859+
is_binary=False,
860+
),
861+
]
831862
)
832863

833864

@@ -902,11 +933,31 @@ async def test_content_binary(mock_execute, jp_fetch, jp_root_dir):
902933
}
903934

904935
# Then
905-
with pytest.raises(tornado.httpclient.HTTPClientError) as e:
906-
await jp_fetch(
907-
NAMESPACE, local_path.name, "content", body=json.dumps(body), method="POST"
908-
)
909-
assert_http_error(e, 500, expected_message="file is not UTF-8")
936+
response = await jp_fetch(
937+
NAMESPACE, local_path.name, "content", body=json.dumps(body), method="POST"
938+
)
939+
940+
mock_execute.assert_has_calls(
941+
[
942+
call(
943+
[
944+
"git",
945+
"diff",
946+
"--numstat",
947+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
948+
"current",
949+
"--",
950+
filename,
951+
],
952+
cwd=str(local_path),
953+
),
954+
call(
955+
["git", "show", "{}:{}".format("current", filename)],
956+
cwd=str(local_path),
957+
is_binary=True,
958+
),
959+
]
960+
)
910961

911962

912963
@patch("jupyterlab_git.git.execute")

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"@material-ui/lab": "^4.0.0-alpha.54",
7878
"@playwright/test": "^1.32.1",
7979
"diff-match-patch": "^1.0.4",
80+
"filesize": "^10.0.7",
8081
"nbdime": "^6.1.1",
8182
"nbdime-jupyterlab": "^2.1.0",
8283
"react": "^17.0.1",

0 commit comments

Comments
 (0)