Skip to content

Commit 0cbfa4f

Browse files
authored
Merge pull request #1 from ianhi/non-ascii-z
Non ascii z
2 parents 6f4d215 + 9772317 commit 0cbfa4f

File tree

4 files changed

+110
-48
lines changed

4 files changed

+110
-48
lines changed

jupyterlab_git/git.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import re
66
import subprocess
77
from urllib.parse import unquote
8-
from codecs import decode, escape_decode
98

109
import pexpect
1110
import tornado
@@ -169,14 +168,14 @@ async def changed_files(self, base=None, remote=None, single_commit=None):
169168
}
170169
"""
171170
if single_commit:
172-
cmd = ["git", "diff", "{}^!".format(single_commit), "--name-only"]
171+
cmd = ["git", "diff", "{}^!".format(single_commit), "--name-only", "-z"]
173172
elif base and remote:
174173
if base == "WORKING":
175-
cmd = ["git", "diff", remote, "--name-only"]
174+
cmd = ["git", "diff", remote, "--name-only", "-z"]
176175
elif base == "INDEX":
177-
cmd = ["git", "diff", "--staged", remote, "--name-only"]
176+
cmd = ["git", "diff", "--staged", remote, "--name-only", "-z"]
178177
else:
179-
cmd = ["git", "diff", base, remote, "--name-only"]
178+
cmd = ["git", "diff", base, remote, "--name-only", "-z"]
180179
else:
181180
raise tornado.web.HTTPError(
182181
400, "Either single_commit or (base and remote) must be provided"
@@ -194,7 +193,7 @@ async def changed_files(self, base=None, remote=None, single_commit=None):
194193
response["command"] = " ".join(cmd)
195194
response["message"] = error
196195
else:
197-
response["files"] = output.strip().split("\n")
196+
response["files"] = output.strip("\x00").split("\x00")
198197

199198
return response
200199

@@ -237,7 +236,7 @@ async def status(self, current_path):
237236
"""
238237
Execute git status command & return the result.
239238
"""
240-
cmd = ["git", "status", "--porcelain", "-u"]
239+
cmd = ["git", "status", "--porcelain", "-u", "-z"]
241240
code, my_output, my_error = await execute(
242241
cmd, cwd=os.path.join(self.root_dir, current_path),
243242
)
@@ -250,22 +249,24 @@ async def status(self, current_path):
250249
}
251250

252251
result = []
253-
line_array = my_output.splitlines()
254-
for line in line_array:
255-
to1 = None
256-
from_path = line[3:]
257-
if line[0] == "R":
258-
to0 = line[3:].split(" -> ")
259-
to1 = to0[len(to0) - 1]
252+
line_iterable = iter(my_output.strip("\x00").split('\x00'))
253+
result = []
254+
for line in line_iterable:
255+
x = line[0]
256+
y = line[1]
257+
if line[0]=='R':
258+
#If file was renamed then we need both this line
259+
#and the next line, then we want to move onto the subsequent
260+
#line. We can accomplish this by calling next on the iterable
261+
to = line[3:]
262+
from_path = next(line_iterable)
260263
else:
261-
to1 = line[3:]
262-
if to1.startswith('"'):
263-
to1 = to1[1:]
264-
if to1.endswith('"'):
265-
to1 = to1[:-1]
266-
to1 = decode(escape_decode(to1)[0],'utf-8')
267-
from_path = decode(escape_decode(from_path)[0],'utf-8')
268-
result.append({"x": line[0], "y": line[1], "to": to1, "from": from_path})
264+
#to and from_path are the same
265+
from_path = line[3:]
266+
to = line[3:]
267+
result.append({"x": x, "y": y, "to": to, "from": from_path})
268+
269+
269270
return {"code": code, "files": result}
270271

271272
async def log(self, current_path, history_count=10):
@@ -314,10 +315,10 @@ async def log(self, current_path, history_count=10):
314315

315316
async def detailed_log(self, selected_hash, current_path):
316317
"""
317-
Execute git log -1 --stat --numstat --oneline command (used to get
318+
Execute git log -1 --stat --numstat --oneline -z command (used to get
318319
insertions & deletions per file) & return the result.
319320
"""
320-
cmd = ["git", "log", "-1", "--stat", "--numstat", "--oneline", selected_hash]
321+
cmd = ["git", "log", "-1", "--stat", "--numstat", "--oneline", "-z", selected_hash]
321322
code, my_output, my_error = await execute(
322323
cmd, cwd=os.path.join(self.root_dir, current_path),
323324
)
@@ -329,7 +330,7 @@ async def detailed_log(self, selected_hash, current_path):
329330
note = [0] * 3
330331
count = 0
331332
temp = ""
332-
line_array = my_output.splitlines()
333+
line_array = re.split("\x00|\n|\r\n|\r", my_output)
333334
length = len(line_array)
334335
INSERTION_INDEX = 0
335336
DELETION_INDEX = 1
@@ -342,7 +343,7 @@ async def detailed_log(self, selected_hash, current_path):
342343
note[count] = words[i]
343344
count += 1
344345
for num in range(1, int(length / 2)):
345-
line_info = line_array[num].split(maxsplit=2)
346+
line_info = line_array[num].split('\t', maxsplit=2)
346347
words = line_info[2].split("/")
347348
length = len(words)
348349
result.append(
@@ -373,14 +374,14 @@ async def diff(self, top_repo_path):
373374
"""
374375
Execute git diff command & return the result.
375376
"""
376-
cmd = ["git", "diff", "--numstat"]
377+
cmd = ["git", "diff", "--numstat", "-z"]
377378
code, my_output, my_error = await execute(cmd, cwd=top_repo_path)
378379

379380
if code != 0:
380381
return {"code": code, "command": " ".join(cmd), "message": my_error}
381382

382383
result = []
383-
line_array = my_output.splitlines()
384+
line_array = my_output.strip('\x00').split('\x00')
384385
for line in line_array:
385386
linesplit = line.split()
386387
result.append(

jupyterlab_git/tests/test_detailed_log.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,35 @@
1515
async def test_detailed_log():
1616
with patch("jupyterlab_git.git.execute") as mock_execute:
1717
# Given
18-
process_output = [
18+
process_output_first_half = [
1919
"f29660a (HEAD, origin/feature) Commit message",
20-
"10 3 notebook_without_spaces.ipynb",
21-
"11 4 Notebook with spaces.ipynb",
22-
"12 5 path/notebook_without_spaces.ipynb",
23-
"13 6 path/Notebook with spaces.ipynb",
20+
"10\t3\tnotebook_without_spaces.ipynb",
21+
"11\t4\tNotebook with spaces.ipynb",
22+
"12\t5\tpath/notebook_without_spaces.ipynb",
23+
"13\t6\tpath/Notebook with spaces.ipynb",
24+
"14\t1\tpath/Notebook with λ.ipynb",
25+
]
26+
process_output_second_half = [
2427
" notebook_without_spaces.ipynb | 13 ++++++++---",
2528
" Notebook with spaces.ipynb | 15 +++++++++----",
2629
" path/notebook_without_spaces.ipynb | 17 ++++++++++-----",
2730
" path/Notebook with spaces.ipynb | 19 +++++++++++------",
28-
" 4 files changed, 46 insertions(+), 18 deletions(-)",
31+
" path/Notebook with \\316\\273.ipynb | 15 +++++++++++-",
32+
" 5 files changed, 50 insertions(+), 19 deletions(-)",
2933
]
30-
mock_execute.return_value = tornado.gen.maybe_future(
31-
(0, "\n".join(process_output), "")
34+
process_output_first_half = "\x00".join(process_output_first_half)
35+
process_output_second_half = "\n".join(process_output_second_half)
36+
process_output = process_output_first_half + "\x00" + process_output_second_half
37+
mock_execute._mock_return_value = tornado.gen.maybe_future(
38+
(0, process_output, "")
3239
)
3340

3441
expected_response = {
3542
"code": 0,
36-
"modified_file_note": " 4 files changed, 46 insertions(+), 18 deletions(-)",
37-
"modified_files_count": "4",
38-
"number_of_insertions": "46",
39-
"number_of_deletions": "18",
43+
"modified_file_note": " 5 files changed, 50 insertions(+), 19 deletions(-)",
44+
"modified_files_count": "5",
45+
"number_of_insertions": "50",
46+
"number_of_deletions": "19",
4047
"modified_files": [
4148
{
4249
"modified_file_path": "notebook_without_spaces.ipynb",
@@ -62,6 +69,12 @@ async def test_detailed_log():
6269
"insertion": "13",
6370
"deletion": "6",
6471
},
72+
{
73+
"modified_file_path": "path/Notebook with λ.ipynb",
74+
"modified_file_name": "Notebook with λ.ipynb",
75+
"insertion": "14",
76+
"deletion": "1",
77+
},
6578
],
6679
}
6780

@@ -83,6 +96,7 @@ async def test_detailed_log():
8396
"--stat",
8497
"--numstat",
8598
"--oneline",
99+
"-z",
86100
"f29660a2472e24164906af8653babeb48e4bf2ab",
87101
],
88102
cwd=os.path.join("/bin", "test_curr_path"),

jupyterlab_git/tests/test_diff.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async def test_changed_files_single_commit():
2424
with patch("jupyterlab_git.git.execute") as mock_execute:
2525
# Given
2626
mock_execute.return_value = tornado.gen.maybe_future(
27-
(0, "file1.ipynb\nfile2.py", "")
27+
(0, "file1.ipynb\x00file2.py", "")
2828
)
2929

3030
# When
@@ -39,6 +39,7 @@ async def test_changed_files_single_commit():
3939
"diff",
4040
"64950a634cd11d1a01ddfedaeffed67b531cb11e^!",
4141
"--name-only",
42+
"-z",
4243
],
4344
cwd="/bin",
4445
)
@@ -50,7 +51,7 @@ async def test_changed_files_working_tree():
5051
with patch("jupyterlab_git.git.execute") as mock_execute:
5152
# Given
5253
mock_execute.return_value = tornado.gen.maybe_future(
53-
(0, "file1.ipynb\nfile2.py", "")
54+
(0, "file1.ipynb\x00file2.py", "")
5455
)
5556

5657
# When
@@ -60,7 +61,7 @@ async def test_changed_files_working_tree():
6061

6162
# Then
6263
mock_execute.assert_called_once_with(
63-
["git", "diff", "HEAD", "--name-only"], cwd="/bin"
64+
["git", "diff", "HEAD", "--name-only", "-z"], cwd="/bin"
6465
)
6566
assert {"code": 0, "files": ["file1.ipynb", "file2.py"]} == actual_response
6667

@@ -70,7 +71,7 @@ async def test_changed_files_index():
7071
with patch("jupyterlab_git.git.execute") as mock_execute:
7172
# Given
7273
mock_execute.return_value = tornado.gen.maybe_future(
73-
(0, "file1.ipynb\nfile2.py", "")
74+
(0, "file1.ipynb\x00file2.py", "")
7475
)
7576

7677
# When
@@ -80,7 +81,7 @@ async def test_changed_files_index():
8081

8182
# Then
8283
mock_execute.assert_called_once_with(
83-
["git", "diff", "--staged", "HEAD", "--name-only"], cwd="/bin"
84+
["git", "diff", "--staged", "HEAD", "--name-only", "-z"], cwd="/bin"
8485
)
8586
assert {"code": 0, "files": ["file1.ipynb", "file2.py"]} == actual_response
8687

@@ -90,7 +91,7 @@ async def test_changed_files_two_commits():
9091
with patch("jupyterlab_git.git.execute") as mock_execute:
9192
# Given
9293
mock_execute.return_value = tornado.gen.maybe_future(
93-
(0, "file1.ipynb\nfile2.py", "")
94+
(0, "file1.ipynb\x00file2.py", "")
9495
)
9596

9697
# When
@@ -100,7 +101,7 @@ async def test_changed_files_two_commits():
100101

101102
# Then
102103
mock_execute.assert_called_once_with(
103-
["git", "diff", "HEAD", "origin/HEAD", "--name-only"], cwd="/bin"
104+
["git", "diff", "HEAD", "origin/HEAD", "--name-only", "-z"], cwd="/bin"
104105
)
105106
assert {"code": 0, "files": ["file1.ipynb", "file2.py"]} == actual_response
106107

@@ -118,6 +119,6 @@ async def test_changed_files_git_diff_error():
118119

119120
# Then
120121
mock_execute.assert_called_once_with(
121-
["git", "diff", "HEAD", "origin/HEAD", "--name-only"], cwd="/bin"
122+
["git", "diff", "HEAD", "origin/HEAD", "--name-only", "-z"], cwd="/bin"
122123
)
123124
assert {"code": 128, "message": "error message"} == actual_response

jupyterlab_git/tests/test_status.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# python lib
2+
import os
3+
from unittest.mock import Mock, call, patch
4+
5+
import pytest
6+
import tornado
7+
8+
# local lib
9+
from jupyterlab_git.git import Git
10+
11+
from .testutils import FakeContentManager
12+
13+
@pytest.mark.asyncio
14+
async def test_status():
15+
with patch("jupyterlab_git.git.execute") as mock_execute:
16+
# Given
17+
process_output = (
18+
"A notebook with spaces.ipynb",
19+
"M notebook with λ.ipynb",
20+
"R renamed_to_θ.py",
21+
"originally_named_π.py",
22+
"?? untracked.ipynb",
23+
)
24+
25+
expected_resonse = [
26+
{"x": "A", "y": " ", "to": "notebook with spaces.ipynb", "from": "notebook with spaces.ipynb"},
27+
{"x": "M", "y": " ", "to": "notebook with λ.ipynb", "from": "notebook with λ.ipynb"},
28+
{"x": "R", "y": " ", "to": "renamed_to_θ.py", "from": "originally_named_π.py"},
29+
{"x": "?", "y": "?", "to": "untracked.ipynb", "from": "untracked.ipynb"},
30+
]
31+
mock_execute.return_value = tornado.gen.maybe_future(
32+
(0, "\x00".join(process_output), "")
33+
34+
)
35+
36+
# When
37+
actual_response = await Git(FakeContentManager("/bin")).status(
38+
current_path="test_curr_path"
39+
)
40+
41+
# Then
42+
mock_execute.assert_called_once_with(
43+
["git", "status", "--porcelain" , "-u", "-z"], cwd="/bin/test_curr_path"
44+
)
45+
46+
assert {"code": 0, "files": expected_resonse} == actual_response

0 commit comments

Comments
 (0)