Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-unidiff for openSUSE:Factory checked in at 2021-08-23 10:07:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-unidiff (Old) and /work/SRC/openSUSE:Factory/.python-unidiff.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-unidiff" Mon Aug 23 10:07:50 2021 rev:7 rq:912667 version:0.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-unidiff/python-unidiff.changes 2020-06-10 00:52:07.991527895 +0200 +++ /work/SRC/openSUSE:Factory/.python-unidiff.new.1899/python-unidiff.changes 2021-08-23 10:08:30.420222809 +0200 @@ -1,0 +2,9 @@ +Sun Aug 15 19:15:09 UTC 2021 - Martin Li??ka <mli...@suse.cz> + +- Update to version 0.7.0 + * Fixed issues handling multiple git renames. + * Renamed files return target filename as PatchedFile.path. + * Fixed error when first change is a binary file. + * Added source code type hints. + +------------------------------------------------------------------- Old: ---- v0.6.0.tar.gz New: ---- v0.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-unidiff.spec ++++++ --- /var/tmp/diff_new_pack.BOwgdd/_old 2021-08-23 10:08:31.560221479 +0200 +++ /var/tmp/diff_new_pack.BOwgdd/_new 2021-08-23 10:08:31.564221474 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-unidiff # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-unidiff -Version: 0.6.0 +Version: 0.7.0 Release: 0 Summary: Unified diff parsing/metadata extraction library License: MIT @@ -29,7 +29,7 @@ BuildRequires: fdupes BuildRequires: python-rpm-macros Requires(post): update-alternatives -Requires(postun): update-alternatives +Requires(postun):update-alternatives BuildArch: noarch # SECTION test requirements BuildRequires: %{python_module pytest} ++++++ v0.6.0.tar.gz -> v0.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/.travis.yml new/python-unidiff-0.7.0/.travis.yml --- old/python-unidiff-0.6.0/.travis.yml 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/.travis.yml 2021-08-15 20:46:23.000000000 +0200 @@ -1,9 +1,8 @@ language: python python: - "2.7" - - "3.4" - - "3.5" - "3.6" - "3.7" - "3.8" + - "3.9" script: ./run_tests.sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/AUTHORS new/python-unidiff-0.7.0/AUTHORS --- old/python-unidiff-0.6.0/AUTHORS 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/AUTHORS 2021-08-15 20:46:23.000000000 +0200 @@ -26,3 +26,4 @@ * Povilas Kanapickas (`@p12tic`_) * Snowhite (`@CirQ`_) * earonesty (`@earonesty`_) + * Ben Carlsson (`@glacials`_) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/HISTORY new/python-unidiff-0.7.0/HISTORY --- old/python-unidiff-0.6.0/HISTORY 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/HISTORY 2021-08-15 20:46:23.000000000 +0200 @@ -1,6 +1,14 @@ History ------- +0.7.0 - 2021-08-16 +------------------ + +* Fixed issues handling multiple git renames. +* Renamed files return target filename as PatchedFile.path. +* Fixed error when first change is a binary file. +* Added source code type hints. + 0.6.0 - 2020-05-07 ---------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/README.rst new/python-unidiff-0.7.0/README.rst --- old/python-unidiff-0.6.0/README.rst 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/README.rst 2021-08-15 20:46:23.000000000 +0200 @@ -3,8 +3,8 @@ Simple Python library to parse and interact with unified diff data. -.. image:: https://travis-ci.org/matiasb/python-unidiff.svg?branch=master - :target: https://travis-ci.org/matiasb/python-unidiff +.. image:: https://www.travis-ci.com/matiasb/python-unidiff.svg?branch=master + :target: https://travis-ci.com/matiasb/python-unidiff Installing unidiff ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/setup.py new/python-unidiff-0.7.0/setup.py --- old/python-unidiff-0.6.0/setup.py 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/setup.py 2021-08-15 20:46:23.000000000 +0200 @@ -48,7 +48,6 @@ "Programming Language :: Python :: 2", 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/tests/samples/git_rename.diff new/python-unidiff-0.7.0/tests/samples/git_rename.diff --- old/python-unidiff-0.6.0/tests/samples/git_rename.diff 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/tests/samples/git_rename.diff 2021-08-15 20:46:23.000000000 +0200 @@ -11,3 +11,22 @@ Some content -Some content +Some modified content + +diff --git a/oldfile b/newfile +similarity index 85% +rename from oldfile +rename to newfile +index a071991..4dbab21 100644 +--- a/oldfile ++++ b/newfile +@@ -9,4 +9,4 @@ Some content + Some content + Some content + Some content +-Some content ++Some modified content + +diff --git a/sub/onefile b/sub/otherfile +similarity index 100% +rename from onefile +rename to otherfile diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/tests/test_parser.py new/python-unidiff-0.7.0/tests/test_parser.py --- old/python-unidiff-0.6.0/tests/test_parser.py 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/tests/test_parser.py 2021-08-15 20:46:23.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) -# Copyright (c) 2014-2020 Matias Bordese +# Copyright (c) 2014-2021 Matias Bordese # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -246,7 +246,7 @@ self.assertTrue(res[0].is_added_file) self.assertTrue(res[0].is_binary_file) - # second file is added + # second file is modified self.assertTrue(res[1].is_modified_file) self.assertFalse(res[1].is_removed_file) self.assertFalse(res[1].is_added_file) @@ -411,16 +411,27 @@ with codecs.open(file_path, 'r', encoding='utf-8') as diff_file: res = PatchSet(diff_file) - self.assertEqual(len(res), 1) - - patch = res[0] - self.assertTrue(patch.is_rename) - self.assertEqual(patch.added, 1) - self.assertEqual(patch.removed, 1) - self.assertEqual(len(res.modified_files), 1) + self.assertEqual(len(res), 3) + self.assertEqual(len(res.modified_files), 3) self.assertEqual(len(res.added_files), 0) self.assertEqual(len(res.removed_files), 0) + # renamed and modified files + for patch in res[:2]: + self.assertTrue(patch.is_rename) + self.assertEqual(patch.added, 1) + self.assertEqual(patch.removed, 1) + # renamed file under sub-path + patch = res[2] + self.assertTrue(patch.is_rename) + self.assertEqual(patch.added, 0) + self.assertEqual(patch.removed, 0) + # confirm the full path is in source/target filenames + self.assertEqual(patch.source_file, 'a/sub/onefile') + self.assertEqual(patch.target_file, 'b/sub/otherfile') + # check path is the target path + self.assertEqual(patch.path, 'sub/otherfile') + # check that original diffs and those produced # by unidiff are the same with codecs.open(file_path, 'r', encoding='utf-8') as diff_file: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/unidiff/__version__.py new/python-unidiff-0.7.0/unidiff/__version__.py --- old/python-unidiff-0.6.0/unidiff/__version__.py 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/unidiff/__version__.py 2021-08-15 20:46:23.000000000 +0200 @@ -21,4 +21,4 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE # OR OTHER DEALINGS IN THE SOFTWARE. -__version__ = '0.6.0' +__version__ = '0.7.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/unidiff/constants.py new/python-unidiff-0.7.0/unidiff/constants.py --- old/python-unidiff-0.6.0/unidiff/constants.py 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/unidiff/constants.py 2021-08-15 20:46:23.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) -# Copyright (c) 2014-2020 Matias Bordese +# Copyright (c) 2014-2021 Matias Bordese # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -35,9 +35,9 @@ r'^\+\+\+ (?P<filename>[^\t\n]+)(?:\t(?P<timestamp>[^\n]+))?') -# git renamed files support -RE_RENAME_SOURCE_FILENAME = re.compile(r'^rename from (?P<filename>[^\t\n]+)') -RE_RENAME_TARGET_FILENAME = re.compile(r'^rename to (?P<filename>[^\t\n]+)') +# check diff git line for git renamed files support +RE_DIFF_GIT_HEADER = re.compile( + r'^diff --git (?P<source>a/[^\t\n]+) (?P<target>b/[^\t\n]+)') # @@ (source offset, length) (target offset, length) @@ (section header) @@ -63,6 +63,7 @@ DEFAULT_ENCODING = 'UTF-8' +DEV_NULL = '/dev/null' LINE_TYPE_ADDED = '+' LINE_TYPE_REMOVED = '-' LINE_TYPE_CONTEXT = ' ' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-unidiff-0.6.0/unidiff/patch.py new/python-unidiff-0.7.0/unidiff/patch.py --- old/python-unidiff-0.6.0/unidiff/patch.py 2020-05-08 00:16:37.000000000 +0200 +++ new/python-unidiff-0.7.0/unidiff/patch.py 2021-08-15 20:46:23.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # The MIT License (MIT) -# Copyright (c) 2014-2020 Matias Bordese +# Copyright (c) 2014-2021 Matias Bordese # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -31,17 +31,17 @@ from unidiff.constants import ( DEFAULT_ENCODING, + DEV_NULL, LINE_TYPE_ADDED, LINE_TYPE_CONTEXT, LINE_TYPE_EMPTY, LINE_TYPE_REMOVED, LINE_TYPE_NO_NEWLINE, LINE_VALUE_NO_NEWLINE, + RE_DIFF_GIT_HEADER, RE_HUNK_BODY_LINE, RE_HUNK_EMPTY_BODY_LINE, RE_HUNK_HEADER, - RE_RENAME_SOURCE_FILENAME, - RE_RENAME_TARGET_FILENAME, RE_SOURCE_FILENAME, RE_TARGET_FILENAME, RE_NO_NEWLINE_MARKER, @@ -62,6 +62,7 @@ return cls else: from io import StringIO + from typing import Iterable, Optional, Union open_file = open make_str = str implements_to_string = lambda x: x @@ -75,6 +76,7 @@ def __init__(self, value, line_type, source_line_no=None, target_line_no=None, diff_line_no=None): + # type: (str, str, Optional[int], Optional[int], Optional[int]) -> None super(Line, self).__init__() self.source_line_no = source_line_no self.target_line_no = target_line_no @@ -83,12 +85,15 @@ self.value = value def __repr__(self): + # type: () -> str return make_str("<Line: %s%s>") % (self.line_type, self.value) def __str__(self): + # type: () -> str return "%s%s" % (self.line_type, self.value) def __eq__(self, other): + # type: (Line) -> bool return (self.source_line_no == other.source_line_no and self.target_line_no == other.target_line_no and self.diff_line_no == other.diff_line_no and @@ -97,14 +102,17 @@ @property def is_added(self): + # type: () -> bool return self.line_type == LINE_TYPE_ADDED @property def is_removed(self): + # type: () -> bool return self.line_type == LINE_TYPE_REMOVED @property def is_context(self): + # type: () -> bool return self.line_type == LINE_TYPE_CONTEXT @@ -118,10 +126,12 @@ """ def __repr__(self): + # type: () -> str value = "<PatchInfo: %s>" % self[0].strip() return make_str(value) def __str__(self): + # type: () -> str return ''.join(unicode(line) for line in self) @@ -131,6 +141,7 @@ def __init__(self, src_start=0, src_len=0, tgt_start=0, tgt_len=0, section_header=''): + # type: (int, int, int, int, str) -> None super(Hunk, self).__init__() if src_len is None: src_len = 1 @@ -141,10 +152,11 @@ self.target_start = int(tgt_start) self.target_length = int(tgt_len) self.section_header = section_header - self._added = None - self._removed = None + self._added = None # Optional[int] + self._removed = None # Optional[int] def __repr__(self): + # type: () -> str value = "<Hunk: @@ %d,%d %d,%d @@ %s>" % (self.source_start, self.source_length, self.target_start, @@ -153,6 +165,7 @@ return make_str(value) def __str__(self): + # type: () -> str # section header is optional and thus we output it only if it's present head = "@@ -%d,%d +%d,%d @@%s\n" % ( self.source_start, self.source_length, @@ -162,6 +175,7 @@ return head + content def append(self, line): + # type: (Line) -> None """Append the line to hunk, and keep track of source/target lines.""" # Make sure the line is encoded correctly. This is a no-op except for # potentially raising a UnicodeDecodeError. @@ -170,6 +184,7 @@ @property def added(self): + # type: () -> Optional[int] if self._added is not None: return self._added # re-calculate each time to allow for hunk modifications @@ -178,6 +193,7 @@ @property def removed(self): + # type: () -> Optional[int] if self._removed is not None: return self._removed # re-calculate each time to allow for hunk modifications @@ -185,24 +201,29 @@ return sum(1 for line in self if line.is_removed) def is_valid(self): + # type: () -> bool """Check hunk header data matches entered lines info.""" return (len(self.source) == self.source_length and len(self.target) == self.target_length) def source_lines(self): + # type: () -> Iterable[Line] """Hunk lines from source file (generator).""" return (l for l in self if l.is_context or l.is_removed) @property def source(self): + # type: () -> Iterable[str] return [str(l) for l in self.source_lines()] def target_lines(self): + # type: () -> Iterable[Line] """Hunk lines from target file (generator).""" return (l for l in self if l.is_context or l.is_added) @property def target(self): + # type: () -> Iterable[str] return [str(l) for l in self.target_lines()] @@ -212,6 +233,7 @@ def __init__(self, patch_info=None, source='', target='', source_timestamp=None, target_timestamp=None, is_binary_file=False, is_rename=False): + # type: (Optional[PatchInfo], str, str, Optional[str], Optional[str], bool, bool) -> None super(PatchedFile, self).__init__() self.patch_info = patch_info self.source_file = source @@ -222,9 +244,11 @@ self.is_rename = is_rename def __repr__(self): + # type: () -> str return make_str("<PatchedFile: %s>") % make_str(self.path) def __str__(self): + # type: () -> str source = '' target = '' # patch info is optional @@ -240,6 +264,7 @@ return info + source + target + hunks def _parse_hunk(self, header, diff, encoding, metadata_only): + # type: (str, enumerate[str], Optional[str], bool) -> None """Parse hunk details.""" header_info = RE_HUNK_HEADER.match(header) hunk_info = header_info.groups() @@ -293,7 +318,7 @@ if line_type == LINE_TYPE_EMPTY: line_type = LINE_TYPE_CONTEXT - value = valid_line.group('value') + value = valid_line.group('value') # type: str original_line = Line(value, line_type=line_type) if line_type == LINE_TYPE_ADDED: @@ -339,6 +364,7 @@ self.append(hunk) def _add_no_newline_marker_to_last_hunk(self): + # type: () -> None if not self: raise UnidiffParseError( 'Unexpected marker:' + LINE_VALUE_NO_NEWLINE) @@ -347,6 +373,7 @@ Line(LINE_VALUE_NO_NEWLINE + '\n', line_type=LINE_TYPE_NO_NEWLINE)) def _append_trailing_empty_line(self): + # type: () -> None if not self: raise UnidiffParseError('Unexpected trailing newline character') last_hunk = self[-1] @@ -354,49 +381,52 @@ @property def path(self): + # type: () -> str """Return the file path abstracted from VCS.""" - if (self.source_file.startswith('a/') and - self.target_file.startswith('b/')): - filepath = self.source_file[2:] - elif (self.source_file.startswith('a/') and - self.target_file == '/dev/null'): - filepath = self.source_file[2:] - elif (self.target_file is not None and - self.target_file.startswith('b/') and - self.source_file == '/dev/null'): - filepath = self.target_file[2:] - else: - filepath = self.source_file + filepath = self.source_file + if filepath in (None, DEV_NULL) or ( + self.is_rename and self.target_file not in (None, DEV_NULL)): + # if this is a rename, prefer the target filename + filepath = self.target_file + + if filepath.startswith('a/') or filepath.startswith('b/'): + filepath = filepath[2:] + return filepath @property def added(self): + # type: () -> int """Return the file total added lines.""" return sum([hunk.added for hunk in self]) @property def removed(self): + # type: () -> int """Return the file total removed lines.""" return sum([hunk.removed for hunk in self]) @property def is_added_file(self): + # type: () -> bool """Return True if this patch adds the file.""" - if self.source_file == '/dev/null': + if self.source_file == DEV_NULL: return True return (len(self) == 1 and self[0].source_start == 0 and self[0].source_length == 0) @property def is_removed_file(self): + # type: () -> bool """Return True if this patch removes the file.""" - if self.target_file == '/dev/null': + if self.target_file == DEV_NULL: return True return (len(self) == 1 and self[0].target_start == 0 and self[0].target_length == 0) @property def is_modified_file(self): + # type: () -> bool """Return True if this patch modifies the file.""" return not (self.is_added_file or self.is_removed_file) @@ -406,11 +436,12 @@ """A list of PatchedFiles.""" def __init__(self, f, encoding=None, metadata_only=False): + # type: (Union[StringIO, str], Optional[str], bool) -> None super(PatchSet, self).__init__() # convert string inputs to StringIO objects if isinstance(f, basestring): - f = self._convert_string(f, encoding) + f = self._convert_string(f, encoding) # type: StringIO # make sure we pass an iterator object to parse data = iter(f) @@ -421,12 +452,15 @@ self._parse(data, encoding=encoding, metadata_only=metadata_only) def __repr__(self): + # type: () -> str return make_str('<PatchSet: %s>') % super(PatchSet, self).__repr__() def __str__(self): + # type: () -> str return ''.join(unicode(patched_file) for patched_file in self) def _parse(self, diff, encoding, metadata_only): + # type: (StringIO, Optional[str], bool) -> None current_file = None patch_info = None @@ -435,33 +469,22 @@ if encoding is not None: line = line.decode(encoding) - # check for a git rename, source file - is_rename_source_filename = RE_RENAME_SOURCE_FILENAME.match(line) - if is_rename_source_filename: - # prefix with 'a/' to match expected git source format - source_file = ( - 'a/' + is_rename_source_filename.group('filename')) - # keep line as patch_info - patch_info.append(line) - # reset current file - current_file = None - continue - - # check for a git rename, target file - is_rename_target_filename = RE_RENAME_TARGET_FILENAME.match(line) - if is_rename_target_filename: - if current_file is not None: - raise UnidiffParseError('Target without source: %s' % line) - # prefix with 'b/' to match expected git source format - target_file = ( - 'b/' + is_rename_target_filename.group('filename')) - # keep line as patch_info + # check for a git file rename + is_diff_git_header = RE_DIFF_GIT_HEADER.match(line) + if is_diff_git_header: + if patch_info is None: + patch_info = PatchInfo() + source_file = is_diff_git_header.group('source') + target_file = is_diff_git_header.group('target') + if (source_file != DEV_NULL + and target_file != DEV_NULL + and source_file[2:] != target_file[2:]): + # this is a renamed file + current_file = PatchedFile( + patch_info, source_file, target_file, None, None, + is_rename=True) + self.append(current_file) patch_info.append(line) - # add current file to PatchSet - current_file = PatchedFile( - patch_info, source_file, target_file, None, None, - is_rename=True) - self.append(current_file) continue # check for source file header @@ -495,6 +518,7 @@ # check for hunk header is_hunk_header = RE_HUNK_HEADER.match(line) if is_hunk_header: + patch_info = None if current_file is None: raise UnidiffParseError('Unexpected hunk found: %s' % line) current_file._parse_hunk(line, diff, encoding, metadata_only) @@ -513,6 +537,11 @@ current_file._append_trailing_empty_line() continue + # if nothing has matched above then this line is a patch info + if patch_info is None: + current_file = None + patch_info = PatchInfo() + is_binary_diff = RE_BINARY_DIFF.match(line) if is_binary_diff: source_file = is_binary_diff.group('source_filename') @@ -525,14 +554,11 @@ current_file = None continue - # if nothing has matched above then this line is a patch info - if patch_info is None: - current_file = None - patch_info = PatchInfo() patch_info.append(line) @classmethod def from_filename(cls, filename, encoding=DEFAULT_ENCODING, errors=None): + # type: (str, str, Optional[str]) -> PatchSet """Return a PatchSet instance given a diff filename.""" with open_file(filename, 'r', encoding=encoding, errors=errors) as f: instance = cls(f) @@ -540,6 +566,7 @@ @staticmethod def _convert_string(data, encoding=None, errors='strict'): + # type: (Union[str, bytes], str, str) -> StringIO if encoding is not None: # if encoding is given, assume bytes and decode data = unicode(data, encoding=encoding, errors=errors) @@ -547,30 +574,36 @@ @classmethod def from_string(cls, data, encoding=None, errors='strict'): + # type: (str, str, Optional[str]) -> PatchSet """Return a PatchSet instance given a diff string.""" return cls(cls._convert_string(data, encoding, errors)) @property def added_files(self): + # type: () -> list[PatchedFile] """Return patch added files as a list.""" return [f for f in self if f.is_added_file] @property def removed_files(self): + # type: () -> list[PatchedFile] """Return patch removed files as a list.""" return [f for f in self if f.is_removed_file] @property def modified_files(self): + # type: () -> list[PatchedFile] """Return patch modified files as a list.""" return [f for f in self if f.is_modified_file] @property def added(self): + # type: () -> int """Return the patch total added lines.""" return sum([f.added for f in self]) @property def removed(self): + # type: () -> int """Return the patch total removed lines.""" return sum([f.removed for f in self])