Skip to content

Commit e14aa9e

Browse files
committed
git_patch_from_diff may return a NULL patch without an error
1 parent d88fa3d commit e14aa9e

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

pygit2/_pygit2.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,8 @@ class Diff:
432432
def from_c(diff, repo) -> Diff: ...
433433
@staticmethod
434434
def parse_diff(git_diff: str | bytes) -> Diff: ...
435-
def __getitem__(self, index: int) -> Patch: ... # Diff_getitem
436-
def __iter__(self) -> Iterator[Patch]: ... # -> DiffIter
435+
def __getitem__(self, index: int) -> Patch | None: ... # Diff_getitem
436+
def __iter__(self) -> Iterator[Patch | None]: ... # -> DiffIter
437437
def __len__(self) -> int: ...
438438

439439
class DiffDelta:

src/diff.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ diff_get_patch_byindex(git_diff *diff, size_t idx)
533533
if (err < 0)
534534
return Error_set(err);
535535

536+
/* libgit2 may decide not to create a patch if the file is
537+
"unchanged or binary", but this isn't an error case */
538+
if (patch == NULL)
539+
Py_RETURN_NONE;
540+
536541
return (PyObject*) wrap_patch(patch, NULL, NULL);
537542
}
538543

test/test_diff.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import textwrap
2929
from collections.abc import Iterator
3030
from itertools import chain
31+
from pathlib import Path
3132

3233
import pytest
3334

@@ -459,3 +460,33 @@ def test_diff_blobs(emptyrepo: Repository) -> None:
459460
assert diff_one_context_line.text == PATCH_BLOBS_ONE_CONTEXT_LINE
460461
diff_all_together = repo.diff(blob1, blob2, context_lines=1, interhunk_lines=1)
461462
assert diff_all_together.text == PATCH_BLOBS_DEFAULT
463+
464+
465+
def test_diff_unchanged_file_no_patch(testrepo) -> None:
466+
repo = testrepo
467+
468+
# Convert hello.txt line endings to CRLF
469+
path = Path(repo.workdir) / 'hello.txt'
470+
data = path.read_bytes()
471+
data = data.replace(b'\n', b'\r\n')
472+
path.write_bytes(data)
473+
474+
# Enable CRLF filter
475+
repo.config['core.autocrlf'] = 'input'
476+
477+
diff = repo.diff()
478+
assert len(diff) == 1
479+
480+
# Get patch #0 in the same diff several times.
481+
# git_patch_from_diff eventually decides that the file is "unchanged";
482+
# it returns a NULL patch in this case.
483+
# https://libgit2.org/docs/reference/main/patch/git_patch_from_diff
484+
for i in range(10): # loop typically exits in the third iteration
485+
patch = diff[0]
486+
if patch is None: # libgit2 decides the file is unchanged
487+
break
488+
assert patch.delta.new_file.path == path.name
489+
assert patch.text == '' # no content change (just line endings)
490+
else:
491+
# Didn't find the edge case that this test is supposed to exercise.
492+
assert False, 'libgit2 rebuilt a new patch every time'

0 commit comments

Comments
 (0)