Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/git/diff.py: 27%
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", "INDEX", "Diffable", "DiffIndex", "Diff"]
8import enum
9import re
10import warnings
12from git.cmd import 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 Tuple,
27 TYPE_CHECKING,
28 TypeVar,
29 Union,
30 cast,
31)
32from git.types import Literal, PathLike
34if TYPE_CHECKING:
35 from subprocess import Popen
37 from git.cmd import Git
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"""
86INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
87"""Stand-in indicating you want to diff against the index.
89See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
91This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
92:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
93"""
95_octal_byte_re = re.compile(rb"\\([0-9]{3})")
98def _octal_repl(matchobj: Match) -> bytes:
99 value = matchobj.group(1)
100 value = int(value, 8)
101 value = bytes(bytearray((value,)))
102 return value
105def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
106 if path == b"/dev/null":
107 return None
109 if path.startswith(b'"') and path.endswith(b'"'):
110 path = path[1:-1].replace(b"\\n", b"\n").replace(b"\\t", b"\t").replace(b'\\"', b'"').replace(b"\\\\", b"\\")
112 path = _octal_byte_re.sub(_octal_repl, path)
114 if has_ab_prefix:
115 assert path.startswith(b"a/") or path.startswith(b"b/")
116 path = path[2:]
118 return path
121class Diffable:
122 """Common interface for all objects that can be diffed against another object of
123 compatible type.
125 :note:
126 Subclasses require a :attr:`repo` member, as it is the case for
127 :class:`~git.objects.base.Object` instances. For practical reasons we do not
128 derive from :class:`~git.objects.base.Object`.
129 """
131 __slots__ = ()
133 repo: "Repo"
134 """Repository to operate on. Must be provided by subclass or sibling class."""
136 NULL_TREE = NULL_TREE
137 """Stand-in indicating you want to compare against the empty tree in diffs.
139 See the :meth:`diff` method, which accepts this as a value of its ``other``
140 parameter.
142 This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
143 :const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
144 """
146 INDEX = INDEX
147 """Stand-in indicating you want to diff against the index.
149 See the :meth:`diff` method, which accepts this as a value of its ``other``
150 parameter.
152 This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
153 :const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
154 which is kept for backward compatibility (it is now defined an alias of this).
155 """
157 Index = INDEX
158 """Stand-in indicating you want to diff against the index
159 (same as :const:`~Diffable.INDEX`).
161 This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
162 :const:`~Diffable.INDEX` and :meth:`diff` for details.
164 :note:
165 Although always meant for use as an opaque constant, this was formerly defined
166 as a class. Its usage is unchanged, but static type annotations that attempt
167 to permit only this object must be changed to avoid new mypy errors. This was
168 previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
169 It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
170 """
172 def _process_diff_args(
173 self,
174 args: List[Union[PathLike, "Diffable"]],
175 ) -> List[Union[PathLike, "Diffable"]]:
176 """
177 :return:
178 Possibly altered version of the given args list.
179 This method is called right before git command execution.
180 Subclasses can use it to alter the behaviour of the superclass.
181 """
182 return args
184 def diff(
185 self,
186 other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
187 paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
188 create_patch: bool = False,
189 **kwargs: Any,
190 ) -> "DiffIndex[Diff]":
191 """Create diffs between two items being trees, trees and index or an index and
192 the working tree. Detects renames automatically.
194 :param other:
195 This the item to compare us with.
197 * If ``None``, we will be compared to the working tree.
199 * If a :class:`~git.types.Tree_ish` or string, it will be compared against
200 the respective tree.
202 * If :const:`INDEX`, it will be compared against the index.
204 * If :const:`NULL_TREE`, it will compare against the empty tree.
206 This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
207 method will not by default fail on bare repositories.
209 :param paths:
210 This a list of paths or a single path to limit the diff to. It will only
211 include at least one of the given path or paths.
213 :param create_patch:
214 If ``True``, the returned :class:`Diff` contains a detailed patch that if
215 applied makes the self to other. Patches are somewhat costly as blobs have
216 to be read and diffed.
218 :param kwargs:
219 Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
220 swap both sides of the diff.
222 :return:
223 A :class:`DiffIndex` representing the computed diff.
225 :note:
226 On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
227 an instance of :class:`~git.objects.tree.Tree` or
228 :class:`~git.objects.commit.Commit`, or a git command error will occur.
229 """
230 args: List[Union[PathLike, Diffable]] = []
231 args.append("--abbrev=40") # We need full shas.
232 args.append("--full-index") # Get full index paths, not only filenames.
234 # Remove default '-M' arg (check for renames) if user is overriding it.
235 if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
236 args.append("-M")
238 if create_patch:
239 args.append("-p")
240 args.append("--no-ext-diff")
241 else:
242 args.append("--raw")
243 args.append("-z")
245 # Ensure we never see colored output.
246 # Fixes: https://github.com/gitpython-developers/GitPython/issues/172
247 args.append("--no-color")
249 if paths is not None and not isinstance(paths, (tuple, list)):
250 paths = [paths]
252 diff_cmd = self.repo.git.diff
253 if other is INDEX:
254 args.insert(0, "--cached")
255 elif other is NULL_TREE:
256 args.insert(0, "-r") # Recursive diff-tree.
257 args.insert(0, "--root")
258 diff_cmd = self.repo.git.diff_tree
259 elif other is not None:
260 args.insert(0, "-r") # Recursive diff-tree.
261 args.insert(0, other)
262 diff_cmd = self.repo.git.diff_tree
264 args.insert(0, self)
266 # paths is a list or tuple here, or None.
267 if paths:
268 args.append("--")
269 args.extend(paths)
270 # END paths handling
272 kwargs["as_process"] = True
273 proc = diff_cmd(*self._process_diff_args(args), **kwargs)
275 diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
276 index = diff_method(self.repo, proc)
278 proc.wait()
279 return index
282T_Diff = TypeVar("T_Diff", bound="Diff")
285class DiffIndex(List[T_Diff]):
286 R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
287 properties.
289 The class improves the diff handling convenience.
290 """
292 change_type = ("A", "C", "D", "R", "M", "T")
293 """Change type invariant identifying possible ways a blob can have changed:
295 * ``A`` = Added
296 * ``D`` = Deleted
297 * ``R`` = Renamed
298 * ``M`` = Modified
299 * ``T`` = Changed in the type
300 """
302 def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]:
303 """
304 :return:
305 Iterator yielding :class:`Diff` instances that match the given `change_type`
307 :param change_type:
308 Member of :attr:`DiffIndex.change_type`, namely:
310 * 'A' for added paths
311 * 'D' for deleted paths
312 * 'R' for renamed paths
313 * 'M' for paths with modified data
314 * 'T' for changed in the type paths
315 """
316 if change_type not in self.change_type:
317 raise ValueError("Invalid change type: %s" % change_type)
319 for diffidx in self:
320 if diffidx.change_type == change_type:
321 yield diffidx
322 elif change_type == "A" and diffidx.new_file:
323 yield diffidx
324 elif change_type == "D" and diffidx.deleted_file:
325 yield diffidx
326 elif change_type == "C" and diffidx.copied_file:
327 yield diffidx
328 elif change_type == "R" and diffidx.renamed_file:
329 yield diffidx
330 elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
331 yield diffidx
332 # END for each diff
335class Diff:
336 """A Diff contains diff information between two Trees.
338 It contains two sides a and b of the diff. Members are prefixed with "a" and "b"
339 respectively to indicate that.
341 Diffs keep information about the changed blob objects, the file mode, renames,
342 deletions and new files.
344 There are a few cases where ``None`` has to be expected as member variable value:
346 New File::
348 a_mode is None
349 a_blob is None
350 a_path is None
352 Deleted File::
354 b_mode is None
355 b_blob is None
356 b_path is None
358 Working Tree Blobs:
360 When comparing to working trees, the working tree blob will have a null hexsha
361 as a corresponding object does not yet exist. The mode will be null as well. The
362 path will be available, though.
364 If it is listed in a diff, the working tree version of the file must differ from
365 the version in the index or tree, and hence has been modified.
366 """
368 # Precompiled regex.
369 re_header = re.compile(
370 rb"""
371 ^diff[ ]--git
372 [ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
373 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
374 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
375 (?:^similarity[ ]index[ ]\d+%\n
376 ^rename[ ]from[ ](?P<rename_from>.*)\n
377 ^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
378 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
379 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
380 (?:^similarity[ ]index[ ]\d+%\n
381 ^copy[ ]from[ ].*\n
382 ^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
383 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
384 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
385 (?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
386 (?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
387 """,
388 re.VERBOSE | re.MULTILINE,
389 )
391 # These can be used for comparisons.
392 NULL_HEX_SHA = "0" * 40
393 NULL_BIN_SHA = b"\0" * 20
395 __slots__ = (
396 "a_blob",
397 "b_blob",
398 "a_mode",
399 "b_mode",
400 "a_rawpath",
401 "b_rawpath",
402 "new_file",
403 "deleted_file",
404 "copied_file",
405 "raw_rename_from",
406 "raw_rename_to",
407 "diff",
408 "change_type",
409 "score",
410 )
412 def __init__(
413 self,
414 repo: "Repo",
415 a_rawpath: Optional[bytes],
416 b_rawpath: Optional[bytes],
417 a_blob_id: Union[str, bytes, None],
418 b_blob_id: Union[str, bytes, None],
419 a_mode: Union[bytes, str, None],
420 b_mode: Union[bytes, str, None],
421 new_file: bool,
422 deleted_file: bool,
423 copied_file: bool,
424 raw_rename_from: Optional[bytes],
425 raw_rename_to: Optional[bytes],
426 diff: Union[str, bytes, None],
427 change_type: Optional[Lit_change_type],
428 score: Optional[int],
429 ) -> None:
430 assert a_rawpath is None or isinstance(a_rawpath, bytes)
431 assert b_rawpath is None or isinstance(b_rawpath, bytes)
432 self.a_rawpath = a_rawpath
433 self.b_rawpath = b_rawpath
435 self.a_mode = mode_str_to_int(a_mode) if a_mode else None
436 self.b_mode = mode_str_to_int(b_mode) if b_mode else None
438 # Determine whether this diff references a submodule. If it does then
439 # we need to overwrite "repo" to the corresponding submodule's repo instead.
440 if repo and a_rawpath:
441 for submodule in repo.submodules:
442 if submodule.path == a_rawpath.decode(defenc, "replace"):
443 if submodule.module_exists():
444 repo = submodule.module()
445 break
447 self.a_blob: Union["IndexObject", None]
448 if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
449 self.a_blob = None
450 else:
451 self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
453 self.b_blob: Union["IndexObject", None]
454 if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
455 self.b_blob = None
456 else:
457 self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
459 self.new_file: bool = new_file
460 self.deleted_file: bool = deleted_file
461 self.copied_file: bool = copied_file
463 # Be clear and use None instead of empty strings.
464 assert raw_rename_from is None or isinstance(raw_rename_from, bytes)
465 assert raw_rename_to is None or isinstance(raw_rename_to, bytes)
466 self.raw_rename_from = raw_rename_from or None
467 self.raw_rename_to = raw_rename_to or None
469 self.diff = diff
470 self.change_type: Union[Lit_change_type, None] = change_type
471 self.score = score
473 def __eq__(self, other: object) -> bool:
474 for name in self.__slots__:
475 if getattr(self, name) != getattr(other, name):
476 return False
477 # END for each name
478 return True
480 def __ne__(self, other: object) -> bool:
481 return not (self == other)
483 def __hash__(self) -> int:
484 return hash(tuple(getattr(self, n) for n in self.__slots__))
486 def __str__(self) -> str:
487 h = "%s"
488 if self.a_blob:
489 h %= self.a_blob.path
490 elif self.b_blob:
491 h %= self.b_blob.path
493 msg = ""
494 line = None
495 line_length = 0
496 for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
497 if b:
498 line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
499 else:
500 line = "\n%s: None" % n
501 # END if blob is not None
502 line_length = max(len(line), line_length)
503 msg += line
504 # END for each blob
506 # Add headline.
507 h += "\n" + "=" * line_length
509 if self.deleted_file:
510 msg += "\nfile deleted in rhs"
511 if self.new_file:
512 msg += "\nfile added in rhs"
513 if self.copied_file:
514 msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
515 if self.rename_from:
516 msg += "\nfile renamed from %r" % self.rename_from
517 if self.rename_to:
518 msg += "\nfile renamed to %r" % self.rename_to
519 if self.diff:
520 msg += "\n---"
521 try:
522 msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
523 except UnicodeDecodeError:
524 msg += "OMITTED BINARY DATA"
525 # END handle encoding
526 msg += "\n---"
527 # END diff info
529 return h + msg
531 @property
532 def a_path(self) -> Optional[str]:
533 return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None
535 @property
536 def b_path(self) -> Optional[str]:
537 return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None
539 @property
540 def rename_from(self) -> Optional[str]:
541 return self.raw_rename_from.decode(defenc, "replace") if self.raw_rename_from else None
543 @property
544 def rename_to(self) -> Optional[str]:
545 return self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None
547 @property
548 def renamed(self) -> bool:
549 """Deprecated, use :attr:`renamed_file` instead.
551 :return:
552 ``True`` if the blob of our diff has been renamed
554 :note:
555 This property is deprecated.
556 Please use the :attr:`renamed_file` property instead.
557 """
558 warnings.warn(
559 "Diff.renamed is deprecated, use Diff.renamed_file instead",
560 DeprecationWarning,
561 stacklevel=2,
562 )
563 return self.renamed_file
565 @property
566 def renamed_file(self) -> bool:
567 """:return: ``True`` if the blob of our diff has been renamed"""
568 return self.rename_from != self.rename_to
570 @classmethod
571 def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
572 if path_match:
573 return decode_path(path_match)
575 if rename_match:
576 return decode_path(rename_match, has_ab_prefix=False)
578 if path_fallback_match:
579 return decode_path(path_fallback_match)
581 return None
583 @classmethod
584 def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
585 """Create a new :class:`DiffIndex` from the given process output which must be
586 in patch format.
588 :param repo:
589 The repository we are operating on.
591 :param proc:
592 :manpage:`git-diff(1)` process to read from
593 (supports :class:`Git.AutoInterrupt <git.cmd.Git.AutoInterrupt>` wrapper).
595 :return:
596 :class:`DiffIndex`
597 """
599 # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
600 text_list: List[bytes] = []
601 handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
603 # For now, we have to bake the stream.
604 text = b"".join(text_list)
605 index: "DiffIndex" = DiffIndex()
606 previous_header: Union[Match[bytes], None] = None
607 header: Union[Match[bytes], None] = None
608 a_path, b_path = None, None # For mypy.
609 a_mode, b_mode = None, None # For mypy.
610 for _header in cls.re_header.finditer(text):
611 (
612 a_path_fallback,
613 b_path_fallback,
614 old_mode,
615 new_mode,
616 rename_from,
617 rename_to,
618 new_file_mode,
619 deleted_file_mode,
620 copied_file_name,
621 a_blob_id,
622 b_blob_id,
623 b_mode,
624 a_path,
625 b_path,
626 ) = _header.groups()
628 new_file, deleted_file, copied_file = (
629 bool(new_file_mode),
630 bool(deleted_file_mode),
631 bool(copied_file_name),
632 )
634 a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
635 b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
637 # Our only means to find the actual text is to see what has not been matched
638 # by our regex, and then retro-actively assign it to our index.
639 if previous_header is not None:
640 index[-1].diff = text[previous_header.end() : _header.start()]
641 # END assign actual diff
643 # Make sure the mode is set if the path is set. Otherwise the resulting blob
644 # is invalid. We just use the one mode we should have parsed.
645 a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
646 b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
647 index.append(
648 Diff(
649 repo,
650 a_path,
651 b_path,
652 a_blob_id and a_blob_id.decode(defenc),
653 b_blob_id and b_blob_id.decode(defenc),
654 a_mode and a_mode.decode(defenc),
655 b_mode and b_mode.decode(defenc),
656 new_file,
657 deleted_file,
658 copied_file,
659 rename_from,
660 rename_to,
661 None,
662 None,
663 None,
664 )
665 )
667 previous_header = _header
668 header = _header
669 # END for each header we parse
670 if index and header:
671 index[-1].diff = text[header.end() :]
672 # END assign last diff
674 return index
676 @staticmethod
677 def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
678 lines = lines_bytes.decode(defenc)
680 # Discard everything before the first colon, and the colon itself.
681 _, _, lines = lines.partition(":")
683 for line in lines.split("\x00:"):
684 if not line:
685 # The line data is empty, skip.
686 continue
687 meta, _, path = line.partition("\x00")
688 path = path.rstrip("\x00")
689 a_blob_id: Optional[str]
690 b_blob_id: Optional[str]
691 old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
692 # Change type can be R100
693 # R: status letter
694 # 100: score (in case of copy and rename)
695 change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
696 score_str = "".join(_change_type[1:])
697 score = int(score_str) if score_str.isdigit() else None
698 path = path.strip("\n")
699 a_path = path.encode(defenc)
700 b_path = path.encode(defenc)
701 deleted_file = False
702 new_file = False
703 copied_file = False
704 rename_from = None
705 rename_to = None
707 # NOTE: We cannot conclude from the existence of a blob to change type,
708 # as diffs with the working do not have blobs yet.
709 if change_type == "D":
710 b_blob_id = None # Optional[str]
711 deleted_file = True
712 elif change_type == "A":
713 a_blob_id = None
714 new_file = True
715 elif change_type == "C":
716 copied_file = True
717 a_path_str, b_path_str = path.split("\x00", 1)
718 a_path = a_path_str.encode(defenc)
719 b_path = b_path_str.encode(defenc)
720 elif change_type == "R":
721 a_path_str, b_path_str = path.split("\x00", 1)
722 a_path = a_path_str.encode(defenc)
723 b_path = b_path_str.encode(defenc)
724 rename_from, rename_to = a_path, b_path
725 elif change_type == "T":
726 # Nothing to do.
727 pass
728 # END add/remove handling
730 diff = Diff(
731 repo,
732 a_path,
733 b_path,
734 a_blob_id,
735 b_blob_id,
736 old_mode,
737 new_mode,
738 new_file,
739 deleted_file,
740 copied_file,
741 rename_from,
742 rename_to,
743 "",
744 change_type,
745 score,
746 )
747 index.append(diff)
749 @classmethod
750 def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
751 """Create a new :class:`DiffIndex` from the given process output which must be
752 in raw format.
754 :param repo:
755 The repository we are operating on.
757 :param proc:
758 Process to read output from.
760 :return:
761 :class:`DiffIndex`
762 """
763 # handles
764 # :100644 100644 687099101... 37c5e30c8... M .gitignore
766 index: "DiffIndex" = DiffIndex()
767 handle_process_output(
768 proc,
769 lambda byt: cls._handle_diff_line(byt, repo, index),
770 None,
771 finalize_process,
772 decode_streams=False,
773 )
775 return index