Skip to content

Commit 295640e

Browse files
authored
Merge pull request #586 from jupyterlab/585-fix-staging-diff
Fix staging diff
2 parents 6ab7e37 + 24cad81 commit 295640e

19 files changed

+307
-161
lines changed

jupyterlab_git/git.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,36 @@ async def status(self, current_path):
281281
"message": my_error,
282282
}
283283

284+
# Add attribute `is_binary`
285+
command = [ # Compare stage to an empty tree see `_is_binary`
286+
"git",
287+
"diff",
288+
"--numstat",
289+
"-z",
290+
"--cached",
291+
"4b825dc642cb6eb9a060e54bf8d69288fbee4904"
292+
]
293+
text_code, text_output, _ = await execute(
294+
command, cwd=os.path.join(self.root_dir, current_path),
295+
)
296+
297+
are_binary = dict()
298+
if text_code == 0:
299+
for line in filter(lambda l: len(l) > 0, strip_and_split(text_output)):
300+
diff, name = line.rsplit("\t", maxsplit=1)
301+
are_binary[name] = diff.startswith("-\t-")
302+
284303
result = []
285304
line_iterable = (line for line in strip_and_split(my_output) if line)
286305
for line in line_iterable:
306+
name = line[3:]
287307
result.append({
288308
"x": line[0],
289309
"y": line[1],
290-
"to": line[3:],
310+
"to": name,
291311
# if file was renamed, next line contains original path
292-
"from": next(line_iterable) if line[0]=='R' else line[3:]
312+
"from": next(line_iterable) if line[0]=='R' else name,
313+
"is_binary": are_binary.get(name, None)
293314
})
294315
return {"code": code, "files": result}
295316

@@ -355,6 +376,7 @@ async def detailed_log(self, selected_hash, current_path):
355376
result = []
356377
line_iterable = iter(strip_and_split(my_output)[1:])
357378
for line in line_iterable:
379+
is_binary = line.startswith("-\t-\t")
358380
insertions, deletions, file = line.split('\t')
359381
insertions = insertions.replace('-', '0')
360382
deletions = deletions.replace('-', '0')
@@ -370,10 +392,11 @@ async def detailed_log(self, selected_hash, current_path):
370392
modified_file_path = file
371393

372394
result.append({
373-
"modified_file_path": modified_file_path,
374-
"modified_file_name": modified_file_name,
375-
"insertion": insertions,
376-
"deletion": deletions,
395+
"modified_file_path": modified_file_path,
396+
"modified_file_name": modified_file_name,
397+
"insertion": insertions,
398+
"deletion": deletions,
399+
"is_binary": is_binary
377400
})
378401
total_insertions += int(insertions)
379402
total_deletions += int(deletions)
@@ -1003,7 +1026,7 @@ async def diff_content(self, filename, prev_ref, curr_ref, top_repo_path):
10031026
if curr_ref["special"] == "WORKING":
10041027
curr_content = self.get_content(filename, top_repo_path)
10051028
elif curr_ref["special"] == "INDEX":
1006-
is_binary = await self._is_binary(filename, "", top_repo_path)
1029+
is_binary = await self._is_binary(filename, "INDEX", top_repo_path)
10071030
if is_binary:
10081031
raise tornado.web.HTTPError(log_message="Error occurred while executing command to retrieve plaintext diff as file is not UTF-8.")
10091032

@@ -1030,8 +1053,22 @@ async def _is_binary(self, filename, ref, top_repo_path):
10301053
- <https://stackoverflow.com/questions/6119956/how-to-determine-if-git-handles-a-file-as-binary-or-as-text/6134127#6134127>
10311054
- <https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---numstat>
10321055
- <https://git-scm.com/docs/git-diff#_other_diff_formats>
1056+
1057+
Args:
1058+
filename (str): Filename (relative to the git repository)
1059+
ref (str): Commit reference or "INDEX" if file is staged
1060+
top_repo_path (str): Git repository filepath
1061+
1062+
Returns:
1063+
bool: Is file binary?
1064+
1065+
Raises:
1066+
HTTPError: if git command failed
10331067
"""
1034-
command = ["git", "diff", "--numstat", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", ref, "--", filename] # where 4b825... is a magic SHA which represents the empty tree
1068+
if ref == "INDEX":
1069+
command = ["git", "diff", "--numstat", "--cached", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", "--", filename]
1070+
else:
1071+
command = ["git", "diff", "--numstat", "4b825dc642cb6eb9a060e54bf8d69288fbee4904", ref, "--", filename] # where 4b825... is a magic SHA which represents the empty tree
10351072
code, output, error = await execute(command, cwd=top_repo_path)
10361073

10371074
if code != 0:
@@ -1042,10 +1079,7 @@ async def _is_binary(self, filename, ref, top_repo_path):
10421079
raise tornado.web.HTTPError(log_message="Error while determining if file is binary or text '{}'.".format(error))
10431080

10441081
# For binary files, `--numstat` outputs two `-` characters separated by TABs:
1045-
if output.startswith('-\t-\t'):
1046-
return True
1047-
1048-
return False
1082+
return output.startswith('-\t-\t')
10491083

10501084
def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
10511085
"""Handle call to `git remote add` command.

jupyterlab_git/tests/test_branch.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# local lib
99
from jupyterlab_git.git import Git
1010

11-
from .testutils import FakeContentManager
11+
from .testutils import FakeContentManager, maybe_future
1212

1313

1414
def test_is_remote_branch():
@@ -33,7 +33,7 @@ async def test_get_current_branch_success():
3333

3434
with patch("jupyterlab_git.git.execute") as mock_execute:
3535
# Given
36-
mock_execute.return_value = tornado.gen.maybe_future((0, "feature-foo", ""))
36+
mock_execute.return_value = maybe_future((0, "feature-foo", ""))
3737

3838
# When
3939
actual_response = await (
@@ -59,10 +59,10 @@ async def test_checkout_branch_noref_success():
5959

6060
with patch("jupyterlab_git.git.execute") as mock_execute:
6161
with patch.object(
62-
Git, "_get_branch_reference", return_value=tornado.gen.maybe_future(None)
62+
Git, "_get_branch_reference", return_value=maybe_future(None)
6363
) as mock__get_branch_reference:
6464
# Given
65-
mock_execute.return_value = tornado.gen.maybe_future(
65+
mock_execute.return_value = maybe_future(
6666
(rc, stdout_message, stderr_message)
6767
)
6868

@@ -93,10 +93,10 @@ async def test_checkout_branch_noref_failure():
9393
rc = 1
9494
with patch("jupyterlab_git.git.execute") as mock_execute:
9595
with patch.object(
96-
Git, "_get_branch_reference", return_value=tornado.gen.maybe_future(None)
96+
Git, "_get_branch_reference", return_value=maybe_future(None)
9797
) as mock__get_branch_reference:
9898
# Given
99-
mock_execute.return_value = tornado.gen.maybe_future(
99+
mock_execute.return_value = maybe_future(
100100
(rc, stdout_message, stderr_message)
101101
)
102102

@@ -132,10 +132,10 @@ async def test_checkout_branch_remoteref_success():
132132
with patch.object(
133133
Git,
134134
"_get_branch_reference",
135-
return_value=tornado.gen.maybe_future("refs/remotes/remote_branch"),
135+
return_value=maybe_future("refs/remotes/remote_branch"),
136136
) as mock__get_branch_reference:
137137
# Given
138-
mock_execute.return_value = tornado.gen.maybe_future(
138+
mock_execute.return_value = maybe_future(
139139
(rc, stdout_message, stderr_message)
140140
)
141141

@@ -169,10 +169,10 @@ async def test_checkout_branch_headsref_failure():
169169
with patch.object(
170170
Git,
171171
"_get_branch_reference",
172-
return_value=tornado.gen.maybe_future("refs/heads/local_branch"),
172+
return_value=maybe_future("refs/heads/local_branch"),
173173
) as mock__get_branch_reference:
174174
# Given
175-
mock_execute.return_value = tornado.gen.maybe_future(
175+
mock_execute.return_value = maybe_future(
176176
(rc, stdout_message, stderr_message)
177177
)
178178

@@ -206,10 +206,10 @@ async def test_checkout_branch_headsref_success():
206206
with patch.object(
207207
Git,
208208
"_get_branch_reference",
209-
return_value=tornado.gen.maybe_future("refs/heads/local_branch"),
209+
return_value=maybe_future("refs/heads/local_branch"),
210210
):
211211
# Given
212-
mock_execute.return_value = tornado.gen.maybe_future(
212+
mock_execute.return_value = maybe_future(
213213
(rc, stdout_message, stderr_message)
214214
)
215215

@@ -239,10 +239,10 @@ async def test_checkout_branch_remoteref_failure():
239239
with patch.object(
240240
Git,
241241
"_get_branch_reference",
242-
return_value=tornado.gen.maybe_future("refs/remotes/remote_branch"),
242+
return_value=maybe_future("refs/remotes/remote_branch"),
243243
):
244244
# Given
245-
mock_execute.return_value = tornado.gen.maybe_future(
245+
mock_execute.return_value = maybe_future(
246246
(rc, stdout_message, stderr_message)
247247
)
248248

@@ -272,7 +272,7 @@ async def test_get_branch_reference_success():
272272
branch = "test-branch"
273273
reference = "refs/remotes/origin/test_branch"
274274

275-
mock_execute.return_value = tornado.gen.maybe_future((0, reference, ""))
275+
mock_execute.return_value = maybe_future((0, reference, ""))
276276

277277
# When
278278
actual_response = await Git(FakeContentManager("/bin"))._get_branch_reference(
@@ -294,7 +294,7 @@ async def test_get_branch_reference_failure():
294294
branch = "test-branch"
295295
reference = "test-branch"
296296
# Given
297-
mock_execute.return_value = tornado.gen.maybe_future(
297+
mock_execute.return_value = maybe_future(
298298
(
299299
128,
300300
reference,
@@ -321,7 +321,7 @@ async def test_get_branch_reference_failure():
321321
async def test_get_current_branch_failure():
322322
with patch("jupyterlab_git.git.execute") as mock_execute:
323323
# Given
324-
mock_execute.return_value = tornado.gen.maybe_future(
324+
mock_execute.return_value = maybe_future(
325325
(
326326
128,
327327
"",
@@ -356,7 +356,7 @@ async def test_get_current_branch_detached_success():
356356
" remotes/origin/feature-foo",
357357
" remotes/origin/HEAD",
358358
]
359-
mock_execute.return_value = tornado.gen.maybe_future(
359+
mock_execute.return_value = maybe_future(
360360
(0, "\n".join(process_output), "")
361361
)
362362

@@ -376,7 +376,7 @@ async def test_get_current_branch_detached_success():
376376
async def test_get_current_branch_detached_failure():
377377
with patch("jupyterlab_git.git.execute") as mock_execute:
378378
# Given
379-
mock_execute.return_value = tornado.gen.maybe_future(
379+
mock_execute.return_value = maybe_future(
380380
(
381381
128,
382382
"",
@@ -413,7 +413,7 @@ async def test_get_current_branch_detached_failure():
413413
async def test_get_upstream_branch_success(branch, upstream):
414414
with patch("jupyterlab_git.git.execute") as mock_execute:
415415
# Given
416-
mock_execute.return_value = tornado.gen.maybe_future((0, upstream, ""))
416+
mock_execute.return_value = maybe_future((0, upstream, ""))
417417

418418
# When
419419
actual_response = await Git(FakeContentManager("/bin")).get_upstream_branch(
@@ -451,7 +451,7 @@ async def test_get_upstream_branch_success(branch, upstream):
451451
async def test_get_upstream_branch_failure(outputs, message):
452452
with patch("jupyterlab_git.git.execute") as mock_execute:
453453
# Given
454-
mock_execute.return_value = tornado.gen.maybe_future(outputs)
454+
mock_execute.return_value = maybe_future(outputs)
455455

456456
# When
457457
if message:
@@ -482,7 +482,7 @@ async def test_get_upstream_branch_failure(outputs, message):
482482
async def test_get_tag_success():
483483
with patch("jupyterlab_git.git.execute") as mock_execute:
484484
# Given
485-
mock_execute.return_value = tornado.gen.maybe_future((0, "v0.3.0", ""))
485+
mock_execute.return_value = maybe_future((0, "v0.3.0", ""))
486486

487487
# When
488488
actual_response = await Git(FakeContentManager("/bin"))._get_tag(
@@ -503,8 +503,8 @@ async def test_get_tag_failure():
503503
with patch("jupyterlab_git.git.execute") as mock_execute:
504504
# Given
505505
mock_execute.side_effect = [
506-
tornado.gen.maybe_future((128, "", "fatal: Not a valid object name blah")),
507-
tornado.gen.maybe_future(
506+
maybe_future((128, "", "fatal: Not a valid object name blah")),
507+
maybe_future(
508508
(
509509
128,
510510
"",
@@ -557,7 +557,7 @@ async def test_get_tag_failure():
557557
async def test_no_tags():
558558
with patch("jupyterlab_git.git.execute") as mock_execute:
559559
# Given
560-
mock_execute.return_value = tornado.gen.maybe_future(
560+
mock_execute.return_value = maybe_future(
561561
(128, "", "fatal: No names found, cannot describe anything.\n")
562562
)
563563

@@ -590,9 +590,9 @@ async def test_branch_success():
590590

591591
mock_execute.side_effect = [
592592
# Response for get all refs/heads
593-
tornado.gen.maybe_future((0, "\n".join(process_output_heads), "")),
593+
maybe_future((0, "\n".join(process_output_heads), "")),
594594
# Response for get all refs/remotes
595-
tornado.gen.maybe_future((0, "\n".join(process_output_remotes), "")),
595+
maybe_future((0, "\n".join(process_output_remotes), "")),
596596
]
597597

598598
expected_response = {
@@ -694,7 +694,7 @@ async def test_branch_failure():
694694
"--format=%(refname:short)%09%(objectname)%09%(upstream:short)%09%(HEAD)",
695695
"refs/heads/",
696696
]
697-
mock_execute.return_value = tornado.gen.maybe_future(
697+
mock_execute.return_value = maybe_future(
698698
(
699699
128,
700700
"",
@@ -738,15 +738,15 @@ async def test_branch_success_detached_head():
738738

739739
mock_execute.side_effect = [
740740
# Response for get all refs/heads
741-
tornado.gen.maybe_future((0, "\n".join(process_output_heads), "")),
741+
maybe_future((0, "\n".join(process_output_heads), "")),
742742
# Response for get current branch
743-
tornado.gen.maybe_future(
743+
maybe_future(
744744
(128, "", "fatal: ref HEAD is not a symbolic ref")
745745
),
746746
# Response for get current branch detached
747-
tornado.gen.maybe_future((0, "\n".join(detached_head_output), "")),
747+
maybe_future((0, "\n".join(detached_head_output), "")),
748748
# Response for get all refs/remotes
749-
tornado.gen.maybe_future((0, "\n".join(process_output_remotes), "")),
749+
maybe_future((0, "\n".join(process_output_remotes), "")),
750750
]
751751

752752
expected_response = {

jupyterlab_git/tests/test_clone.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
from jupyterlab_git.git import Git
88

9-
from .testutils import FakeContentManager
9+
from .testutils import FakeContentManager, maybe_future
1010

1111

1212
@pytest.mark.asyncio
1313
async def test_git_clone_success():
1414
with patch("os.environ", {"TEST": "test"}):
1515
with patch("jupyterlab_git.git.execute") as mock_execute:
1616
# Given
17-
mock_execute.return_value = tornado.gen.maybe_future((0, "output", "error"))
17+
mock_execute.return_value = maybe_future((0, "output", "error"))
1818

1919
# When
2020
actual_response = await Git(FakeContentManager("/bin")).clone(
@@ -40,7 +40,7 @@ async def test_git_clone_failure_from_git():
4040
with patch("os.environ", {"TEST": "test"}):
4141
with patch("jupyterlab_git.git.execute") as mock_execute:
4242
# Given
43-
mock_execute.return_value = tornado.gen.maybe_future(
43+
mock_execute.return_value = maybe_future(
4444
(128, "test_output", "fatal: Not a git repository")
4545
)
4646

@@ -66,7 +66,7 @@ async def test_git_clone_with_auth_success():
6666
with patch("os.environ", {"TEST": "test"}):
6767
with patch("jupyterlab_git.git.execute") as mock_authentication:
6868
# Given
69-
mock_authentication.return_value = tornado.gen.maybe_future((0, "", ""))
69+
mock_authentication.return_value = maybe_future((0, "", ""))
7070

7171
# When
7272
auth = {"username": "asdf", "password": "qwerty"}
@@ -95,7 +95,7 @@ async def test_git_clone_with_auth_wrong_repo_url_failure_from_git():
9595
with patch("os.environ", {"TEST": "test"}):
9696
with patch("jupyterlab_git.git.execute") as mock_authentication:
9797
# Given
98-
mock_authentication.return_value = tornado.gen.maybe_future(
98+
mock_authentication.return_value = maybe_future(
9999
(128, "", "fatal: repository 'ghjkhjkl' does not exist")
100100
)
101101

@@ -129,7 +129,7 @@ async def test_git_clone_with_auth_auth_failure_from_git():
129129
with patch("os.environ", {"TEST": "test"}):
130130
with patch("jupyterlab_git.git.execute") as mock_authentication:
131131
# Given
132-
mock_authentication.return_value = tornado.gen.maybe_future(
132+
mock_authentication.return_value = maybe_future(
133133
(
134134
128,
135135
"",

0 commit comments

Comments
 (0)