Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/git/diff.py: 29%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
2#
3# This module is part of GitPython and is released under the
4# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
6__all__ = ["DiffConstants", "NULL_TREE", "NULL_TREE_SHA", "INDEX", "Diffable", "DiffIndex", "Diff"]
8import enum
9import re
10import warnings
12from git.cmd import Git, handle_process_output
13from git.compat import defenc
14from git.objects.blob import Blob
15from git.objects.util import mode_str_to_int
16from git.util import finalize_process, hex_to_bin
18# typing ------------------------------------------------------------------
20from typing import (
21 Any,
22 Iterator,
23 List,
24 Match,
25 Optional,
26 Sequence,
27 Tuple,
28 TYPE_CHECKING,
29 TypeVar,
30 Union,
31 cast,
32)
33from git.types import PathLike, Literal
35if TYPE_CHECKING:
36 from subprocess import Popen
38 from git.objects.base import IndexObject
39 from git.objects.commit import Commit
40 from git.objects.tree import Tree
41 from git.repo.base import Repo
43Lit_change_type = Literal["A", "D", "C", "M", "R", "T", "U"]
45# ------------------------------------------------------------------------
48@enum.unique
49class DiffConstants(enum.Enum):
50 """Special objects for :meth:`Diffable.diff`.
52 See the :meth:`Diffable.diff` method's ``other`` parameter, which accepts various
53 values including these.
55 :note:
56 These constants are also available as attributes of the :mod:`git.diff` module,
57 the :class:`Diffable` class and its subclasses and instances, and the top-level
58 :mod:`git` module.
59 """
61 NULL_TREE = enum.auto()
62 """Stand-in indicating you want to compare against the empty tree in diffs.
64 Also accessible as :const:`git.NULL_TREE`, :const:`git.diff.NULL_TREE`, and
65 :const:`Diffable.NULL_TREE`.
66 """
68 INDEX = enum.auto()
69 """Stand-in indicating you want to diff against the index.
71 Also accessible as :const:`git.INDEX`, :const:`git.diff.INDEX`, and
72 :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`. The latter has been
73 kept for backward compatibility and made an alias of this, so it may still be used.
74 """
77NULL_TREE: Literal[DiffConstants.NULL_TREE] = DiffConstants.NULL_TREE
78"""Stand-in indicating you want to compare against the empty tree in diffs.
80See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
82This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed as
83:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
84"""
86NULL_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
87"""SHA of Git's canonical empty tree object."""
89INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
90"""Stand-in indicating you want to diff against the index.
92See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
94This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
95:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
96"""
98_octal_byte_re = re.compile(rb"\\([0-9]{3})")
101def _octal_repl(matchobj: Match) -> bytes:
102 value = matchobj.group(1)
103 value = int(value, 8)
104 value = bytes(bytearray((value,)))
105 return value
108def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
109 if path == b"/dev/null":
110 return None
112 if path.startswith(b'"') and path.endswith(b'"'):
113 path = path[1:-1].replace(b"\\n", b"\n").replace(b"\\t", b"\t").replace(b'\\"', b'"').replace(b"\\\\", b"\\")
115 path = _octal_byte_re.sub(_octal_repl, path)
117 if has_ab_prefix:
118 assert path.startswith(b"a/") or path.startswith(b"b/")
119 path = path[2:]
121 return path
124class Diffable:
125 """Common interface for all objects that can be diffed against another object of
126 compatible type.
128 :note:
129 Subclasses require a :attr:`repo` member, as it is the case for
130 :class:`~git.objects.base.Object` instances. For practical reasons we do not
131 derive from :class:`~git.objects.base.Object`.
132 """
134 __slots__ = ()
136 repo: "Repo"
137 """Repository to operate on. Must be provided by subclass or sibling class."""
139 NULL_TREE = NULL_TREE
140 """Stand-in indicating you want to compare against the empty tree in diffs.
142 See the :meth:`diff` method, which accepts this as a value of its ``other``
143 parameter.
145 This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
146 :const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
147 """
149 INDEX = INDEX
150 """Stand-in indicating you want to diff against the index.
152 See the :meth:`diff` method, which accepts this as a value of its ``other``
153 parameter.
155 This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
156 :const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
157 which is kept for backward compatibility (it is now defined an alias of this).
158 """
160 Index = INDEX
161 """Stand-in indicating you want to diff against the index
162 (same as :const:`~Diffable.INDEX`).
164 This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
165 :const:`~Diffable.INDEX` and :meth:`diff` for details.
167 :note:
168 Although always meant for use as an opaque constant, this was formerly defined
169 as a class. Its usage is unchanged, but static type annotations that attempt
170 to permit only this object must be changed to avoid new mypy errors. This was
171 previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
172 It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
173 """
175 def _process_diff_args(
176 self,
177 args: List[Union[PathLike, "Diffable"]],
178 ) -> List[Union[PathLike, "Diffable"]]:
179 """
180 :return:
181 Possibly altered version of the given args list.
182 This method is called right before git command execution.
183 Subclasses can use it to alter the behaviour of the superclass.
184 """
185 return args
187 def diff(
188 self,
189 other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
190 paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
191 create_patch: bool = False,
192 allow_unsafe_options: bool = False,
193 **kwargs: Any,
194 ) -> "DiffIndex[Diff]":
195 """Create diffs between two items being trees, trees and index or an index and
196 the working tree. Detects renames automatically.
198 :param other:
199 This the item to compare us with.
201 * If ``None``, we will be compared to the working tree.
203 * If a :class:`~git.types.Tree_ish` or string, it will be compared against
204 the respective tree.
206 * If :const:`INDEX`, it will be compared against the index.
208 * If :const:`NULL_TREE`, it will compare against the empty tree.
210 This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
211 method will not by default fail on bare repositories.
213 :param paths:
214 This a list of paths or a single path to limit the diff to. It will only
215 include at least one of the given path or paths.
217 :param create_patch:
218 If ``True``, the returned :class:`Diff` contains a detailed patch that if
219 applied makes the self to other. Patches are somewhat costly as blobs have
220 to be read and diffed.
222 :param allow_unsafe_options:
223 If ``True``, allow options such as ``--output`` that can write to arbitrary
224 filesystem paths.
226 :param kwargs:
227 Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
228 swap both sides of the diff.
230 :return:
231 A :class:`DiffIndex` representing the computed diff.
233 :note:
234 On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
235 an instance of :class:`~git.objects.tree.Tree` or
236 :class:`~git.objects.commit.Commit`, or a git command error will occur.
237 """
238 if not allow_unsafe_options:
239 Git.check_unsafe_options(
240 options=Git._option_candidates([other], kwargs),
241 unsafe_options=self.repo.unsafe_git_revision_options,
242 )
244 args: List[Union[PathLike, Diffable]] = []
245 args.append("--abbrev=40") # We need full shas.
246 args.append("--full-index") # Get full index paths, not only filenames.
248 # Remove default '-M' arg (check for renames) if user is overriding it.
249 if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
250 args.append("-M")
252 if create_patch:
253 args.append("-p")
254 args.append("--no-ext-diff")
255 else:
256 args.append("--raw")
257 args.append("-z")
259 # Ensure we never see colored output.
260 # Fixes: https://github.com/gitpython-developers/GitPython/issues/172
261 args.append("--no-color")
263 if paths is not None and not isinstance(paths, (tuple, list)):
264 paths = [paths]
266 diff_cmd = self.repo.git.diff
267 if other is INDEX:
268 args.insert(0, "--cached")
269 elif other is NULL_TREE:
270 args.insert(0, "-r") # Recursive diff-tree.
271 args.insert(0, "--root")
272 diff_cmd = self.repo.git.diff_tree
273 elif other is not None:
274 args.insert(0, "-r") # Recursive diff-tree.
275 args.insert(0, other)
276 diff_cmd = self.repo.git.diff_tree
278 args.insert(0, self)
280 # paths is a list or tuple here, or None.
281 if paths:
282 args.append("--")
283 args.extend(paths)
284 # END paths handling
286 kwargs["as_process"] = True
287 proc = diff_cmd(*self._process_diff_args(args), **kwargs)
289 diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
290 index = diff_method(self.repo, proc)
292 proc.wait()
293 return index
296T_Diff = TypeVar("T_Diff", bound="Diff")
299class DiffIndex(List[T_Diff]):
300 R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
301 properties.
303 The class improves the diff handling convenience.
304 """
306 change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") # noqa: F821
307 """Change type invariant identifying possible ways a blob can have changed:
309 * ``A`` = Added
310 * ``D`` = Deleted
311 * ``R`` = Renamed
312 * ``M`` = Modified
313 * ``T`` = Changed in the type
314 """
316 def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]:
317 """
318 :return:
319 Iterator yielding :class:`Diff` instances that match the given `change_type`
321 :param change_type:
322 Member of :attr:`DiffIndex.change_type`, namely:
324 * 'A' for added paths
325 * 'D' for deleted paths
326 * 'R' for renamed paths
327 * 'M' for paths with modified data
328 * 'T' for changed in the type paths
329 """
330 if change_type not in self.change_type:
331 raise ValueError("Invalid change type: %s" % change_type)
333 for diffidx in self:
334 if diffidx.change_type == change_type:
335 yield diffidx
336 elif change_type == "A" and diffidx.new_file:
337 yield diffidx
338 elif change_type == "D" and diffidx.deleted_file:
339 yield diffidx
340 elif change_type == "C" and diffidx.copied_file:
341 yield diffidx
342 elif change_type == "R" and diffidx.renamed_file:
343 yield diffidx
344 elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
345 yield diffidx
346 # END for each diff
349class Diff:
350 """A Diff contains diff information between two Trees.
352 It contains two sides a and b of the diff. Members are prefixed with "a" and "b"
353 respectively to indicate that.
355 Diffs keep information about the changed blob objects, the file mode, renames,
356 deletions and new files.
358 There are a few cases where ``None`` has to be expected as member variable value:
360 New File::
362 a_mode is None
363 a_blob is None
364 a_path is None
366 Deleted File::
368 b_mode is None
369 b_blob is None
370 b_path is None
372 Working Tree Blobs:
374 When comparing to working trees, the working tree blob will have a null hexsha
375 as a corresponding object does not yet exist. The mode will be null as well. The
376 path will be available, though.
378 If it is listed in a diff, the working tree version of the file must differ from
379 the version in the index or tree, and hence has been modified.
380 """
382 # Precompiled regex.
383 re_header = re.compile(
384 rb"""
385 ^diff[ ]--git
386 [ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
387 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
388 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
389 (?:^similarity[ ]index[ ]\d+%\n
390 ^rename[ ]from[ ](?P<rename_from>.*)\n
391 ^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
392 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
393 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
394 (?:^similarity[ ]index[ ]\d+%\n
395 ^copy[ ]from[ ].*\n
396 ^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
397 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
398 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
399 (?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
400 (?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
401 """,
402 re.VERBOSE | re.MULTILINE,
403 )
405 # These can be used for comparisons.
406 NULL_HEX_SHA = "0" * 40
407 NULL_BIN_SHA = b"\0" * 20
409 __slots__ = (
410 "a_blob",
411 "b_blob",
412 "a_mode",
413 "b_mode",
414 "a_rawpath",
415 "b_rawpath",
416 "new_file",
417 "deleted_file",
418 "copied_file",
419 "raw_rename_from",
420 "raw_rename_to",
421 "diff",
422 "change_type",
423 "score",
424 )
426 def __init__(
427 self,
428 repo: "Repo",
429 a_rawpath: Optional[bytes],
430 b_rawpath: Optional[bytes],
431 a_blob_id: Union[str, bytes, None],
432 b_blob_id: Union[str, bytes, None],
433 a_mode: Union[bytes, str, None],
434 b_mode: Union[bytes, str, None],
435 new_file: bool,
436 deleted_file: bool,
437 copied_file: bool,
438 raw_rename_from: Optional[bytes],
439 raw_rename_to: Optional[bytes],
440 diff: Union[str, bytes, None],
441 change_type: Optional[Lit_change_type],
442 score: Optional[int],
443 ) -> None:
444 assert a_rawpath is None or isinstance(a_rawpath, bytes)
445 assert b_rawpath is None or isinstance(b_rawpath, bytes)
446 self.a_rawpath = a_rawpath
447 self.b_rawpath = b_rawpath
449 self.a_mode = mode_str_to_int(a_mode) if a_mode else None
450 self.b_mode = mode_str_to_int(b_mode) if b_mode else None
452 # Determine whether this diff references a submodule. If it does then
453 # we need to overwrite "repo" to the corresponding submodule's repo instead.
454 if repo and a_rawpath:
455 for submodule in repo.submodules:
456 if submodule.path == a_rawpath.decode(defenc, "replace"):
457 if submodule.module_exists():
458 repo = submodule.module()
459 break
461 self.a_blob: Union["IndexObject", None]
462 if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
463 self.a_blob = None
464 else:
465 self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
467 self.b_blob: Union["IndexObject", None]
468 if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
469 self.b_blob = None
470 else:
471 self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
473 self.new_file: bool = new_file
474 self.deleted_file: bool = deleted_file
475 self.copied_file: bool = copied_file
477 # Be clear and use None instead of empty strings.
478 assert raw_rename_from is None or isinstance(raw_rename_from, bytes)
479 assert raw_rename_to is None or isinstance(raw_rename_to, bytes)
480 self.raw_rename_from = raw_rename_from or None
481 self.raw_rename_to = raw_rename_to or None
483 self.diff = diff
484 self.change_type: Union[Lit_change_type, None] = change_type
485 self.score = score
487 def __eq__(self, other: object) -> bool:
488 for name in self.__slots__:
489 if getattr(self, name) != getattr(other, name):
490 return False
491 # END for each name
492 return True
494 def __ne__(self, other: object) -> bool:
495 return not (self == other)
497 def __hash__(self) -> int:
498 return hash(tuple(getattr(self, n) for n in self.__slots__))
500 def __str__(self) -> str:
501 h = "%s"
502 if self.a_blob:
503 h %= self.a_blob.path
504 elif self.b_blob:
505 h %= self.b_blob.path
507 msg = ""
508 line = None
509 line_length = 0
510 for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
511 if b:
512 line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
513 else:
514 line = "\n%s: None" % n
515 # END if blob is not None
516 line_length = max(len(line), line_length)
517 msg += line
518 # END for each blob
520 # Add headline.
521 h += "\n" + "=" * line_length
523 if self.deleted_file:
524 msg += "\nfile deleted in rhs"
525 if self.new_file:
526 msg += "\nfile added in rhs"
527 if self.copied_file:
528 msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
529 if self.rename_from:
530 msg += "\nfile renamed from %r" % self.rename_from
531 if self.rename_to:
532 msg += "\nfile renamed to %r" % self.rename_to
533 if self.diff:
534 msg += "\n---"
535 try:
536 msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
537 except UnicodeDecodeError:
538 msg += "OMITTED BINARY DATA"
539 # END handle encoding
540 msg += "\n---"
541 # END diff info
543 return h + msg
545 @property
546 def a_path(self) -> Optional[str]:
547 return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None
549 @property
550 def b_path(self) -> Optional[str]:
551 return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None
553 @property
554 def rename_from(self) -> Optional[str]:
555 return self.raw_rename_from.decode(defenc, "replace") if self.raw_rename_from else None
557 @property
558 def rename_to(self) -> Optional[str]:
559 return self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None
561 @property
562 def renamed(self) -> bool:
563 """Deprecated, use :attr:`renamed_file` instead.
565 :return:
566 ``True`` if the blob of our diff has been renamed
568 :note:
569 This property is deprecated.
570 Please use the :attr:`renamed_file` property instead.
571 """
572 warnings.warn(
573 "Diff.renamed is deprecated, use Diff.renamed_file instead",
574 DeprecationWarning,
575 stacklevel=2,
576 )
577 return self.renamed_file
579 @property
580 def renamed_file(self) -> bool:
581 """:return: ``True`` if the blob of our diff has been renamed"""
582 return self.rename_from != self.rename_to
584 @classmethod
585 def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
586 if path_match:
587 return decode_path(path_match)
589 if rename_match:
590 return decode_path(rename_match, has_ab_prefix=False)
592 if path_fallback_match:
593 return decode_path(path_fallback_match)
595 return None
597 @classmethod
598 def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
599 """Create a new :class:`DiffIndex` from the given process output which must be
600 in patch format.
602 :param repo:
603 The repository we are operating on.
605 :param proc:
606 :manpage:`git-diff(1)` process to read from
607 (supports :class:`Git.AutoInterrupt <git.cmd.Git.AutoInterrupt>` wrapper).
609 :return:
610 :class:`DiffIndex`
611 """
613 # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
614 text_list: List[bytes] = []
615 stderr_list: List[bytes] = []
617 def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
618 finalize_process(proc, stderr=b"".join(stderr_list))
620 handle_process_output(
621 proc, text_list.append, stderr_list.append, finalize_process_with_stderr, decode_streams=False
622 )
624 # For now, we have to bake the stream.
625 text = b"".join(text_list)
626 index: "DiffIndex" = DiffIndex()
627 previous_header: Union[Match[bytes], None] = None
628 header: Union[Match[bytes], None] = None
629 a_path, b_path = None, None # For mypy.
630 a_mode, b_mode = None, None # For mypy.
631 for _header in cls.re_header.finditer(text):
632 (
633 a_path_fallback,
634 b_path_fallback,
635 old_mode,
636 new_mode,
637 rename_from,
638 rename_to,
639 new_file_mode,
640 deleted_file_mode,
641 copied_file_name,
642 a_blob_id,
643 b_blob_id,
644 b_mode,
645 a_path,
646 b_path,
647 ) = _header.groups()
649 new_file, deleted_file, copied_file = (
650 bool(new_file_mode),
651 bool(deleted_file_mode),
652 bool(copied_file_name),
653 )
655 a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
656 b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
658 # Our only means to find the actual text is to see what has not been matched
659 # by our regex, and then retro-actively assign it to our index.
660 if previous_header is not None:
661 index[-1].diff = text[previous_header.end() : _header.start()]
662 # END assign actual diff
664 # Make sure the mode is set if the path is set. Otherwise the resulting blob
665 # is invalid. We just use the one mode we should have parsed.
666 a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
667 b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
668 index.append(
669 Diff(
670 repo,
671 a_path,
672 b_path,
673 a_blob_id and a_blob_id.decode(defenc),
674 b_blob_id and b_blob_id.decode(defenc),
675 a_mode and a_mode.decode(defenc),
676 b_mode and b_mode.decode(defenc),
677 new_file,
678 deleted_file,
679 copied_file,
680 rename_from,
681 rename_to,
682 None,
683 None,
684 None,
685 )
686 )
688 previous_header = _header
689 header = _header
690 # END for each header we parse
691 if index and header:
692 index[-1].diff = text[header.end() :]
693 # END assign last diff
695 return index
697 @staticmethod
698 def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
699 lines = lines_bytes.decode(defenc)
701 # Discard everything before the first colon, and the colon itself.
702 _, _, lines = lines.partition(":")
704 for line in lines.split("\x00:"):
705 if not line:
706 # The line data is empty, skip.
707 continue
708 meta, _, path = line.partition("\x00")
709 path = path.rstrip("\x00")
710 a_blob_id: Optional[str]
711 b_blob_id: Optional[str]
712 old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
713 # Change type can be R100
714 # R: status letter
715 # 100: score (in case of copy and rename)
716 change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
717 score_str = "".join(_change_type[1:])
718 score = int(score_str) if score_str.isdigit() else None
719 path = path.strip("\n")
720 a_path = path.encode(defenc)
721 b_path = path.encode(defenc)
722 deleted_file = False
723 new_file = False
724 copied_file = False
725 rename_from = None
726 rename_to = None
728 # NOTE: We cannot conclude from the existence of a blob to change type,
729 # as diffs with the working do not have blobs yet.
730 if change_type == "D":
731 b_blob_id = None # Optional[str]
732 deleted_file = True
733 elif change_type == "A":
734 a_blob_id = None
735 new_file = True
736 elif change_type == "C":
737 copied_file = True
738 a_path_str, b_path_str = path.split("\x00", 1)
739 a_path = a_path_str.encode(defenc)
740 b_path = b_path_str.encode(defenc)
741 elif change_type == "R":
742 a_path_str, b_path_str = path.split("\x00", 1)
743 a_path = a_path_str.encode(defenc)
744 b_path = b_path_str.encode(defenc)
745 rename_from, rename_to = a_path, b_path
746 elif change_type == "T":
747 # Nothing to do.
748 pass
749 # END add/remove handling
751 diff = Diff(
752 repo,
753 a_path,
754 b_path,
755 a_blob_id,
756 b_blob_id,
757 old_mode,
758 new_mode,
759 new_file,
760 deleted_file,
761 copied_file,
762 rename_from,
763 rename_to,
764 "",
765 change_type,
766 score,
767 )
768 index.append(diff)
770 @classmethod
771 def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
772 """Create a new :class:`DiffIndex` from the given process output which must be
773 in raw format.
775 :param repo:
776 The repository we are operating on.
778 :param proc:
779 Process to read output from.
781 :return:
782 :class:`DiffIndex`
783 """
784 # handles
785 # :100644 100644 687099101... 37c5e30c8... M .gitignore
787 index: "DiffIndex" = DiffIndex()
788 stderr_list: List[bytes] = []
790 def finalize_process_with_stderr(proc: Union["Popen", "Git.AutoInterrupt"]) -> None:
791 finalize_process(proc, stderr=b"".join(stderr_list))
793 handle_process_output(
794 proc,
795 lambda byt: cls._handle_diff_line(byt, repo, index),
796 stderr_list.append,
797 finalize_process_with_stderr,
798 decode_streams=False,
799 )
801 return index