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", "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 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.cmd import Git
39 from git.objects.base import IndexObject
40 from git.objects.commit import Commit
41 from git.objects.tree import Tree
42 from git.repo.base import Repo
44Lit_change_type = Literal["A", "D", "C", "M", "R", "T", "U"]
46# ------------------------------------------------------------------------
49@enum.unique
50class DiffConstants(enum.Enum):
51 """Special objects for :meth:`Diffable.diff`.
53 See the :meth:`Diffable.diff` method's ``other`` parameter, which accepts various
54 values including these.
56 :note:
57 These constants are also available as attributes of the :mod:`git.diff` module,
58 the :class:`Diffable` class and its subclasses and instances, and the top-level
59 :mod:`git` module.
60 """
62 NULL_TREE = enum.auto()
63 """Stand-in indicating you want to compare against the empty tree in diffs.
65 Also accessible as :const:`git.NULL_TREE`, :const:`git.diff.NULL_TREE`, and
66 :const:`Diffable.NULL_TREE`.
67 """
69 INDEX = enum.auto()
70 """Stand-in indicating you want to diff against the index.
72 Also accessible as :const:`git.INDEX`, :const:`git.diff.INDEX`, and
73 :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`. The latter has been
74 kept for backward compatibility and made an alias of this, so it may still be used.
75 """
78NULL_TREE: Literal[DiffConstants.NULL_TREE] = DiffConstants.NULL_TREE
79"""Stand-in indicating you want to compare against the empty tree in diffs.
81See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
83This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed as
84:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
85"""
87INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
88"""Stand-in indicating you want to diff against the index.
90See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
92This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
93:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
94"""
96_octal_byte_re = re.compile(rb"\\([0-9]{3})")
99def _octal_repl(matchobj: Match) -> bytes:
100 value = matchobj.group(1)
101 value = int(value, 8)
102 value = bytes(bytearray((value,)))
103 return value
106def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
107 if path == b"/dev/null":
108 return None
110 if path.startswith(b'"') and path.endswith(b'"'):
111 path = path[1:-1].replace(b"\\n", b"\n").replace(b"\\t", b"\t").replace(b'\\"', b'"').replace(b"\\\\", b"\\")
113 path = _octal_byte_re.sub(_octal_repl, path)
115 if has_ab_prefix:
116 assert path.startswith(b"a/") or path.startswith(b"b/")
117 path = path[2:]
119 return path
122class Diffable:
123 """Common interface for all objects that can be diffed against another object of
124 compatible type.
126 :note:
127 Subclasses require a :attr:`repo` member, as it is the case for
128 :class:`~git.objects.base.Object` instances. For practical reasons we do not
129 derive from :class:`~git.objects.base.Object`.
130 """
132 __slots__ = ()
134 repo: "Repo"
135 """Repository to operate on. Must be provided by subclass or sibling class."""
137 NULL_TREE = NULL_TREE
138 """Stand-in indicating you want to compare against the empty tree in diffs.
140 See the :meth:`diff` method, which accepts this as a value of its ``other``
141 parameter.
143 This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
144 :const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
145 """
147 INDEX = INDEX
148 """Stand-in indicating you want to diff against the index.
150 See the :meth:`diff` method, which accepts this as a value of its ``other``
151 parameter.
153 This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
154 :const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
155 which is kept for backward compatibility (it is now defined an alias of this).
156 """
158 Index = INDEX
159 """Stand-in indicating you want to diff against the index
160 (same as :const:`~Diffable.INDEX`).
162 This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
163 :const:`~Diffable.INDEX` and :meth:`diff` for details.
165 :note:
166 Although always meant for use as an opaque constant, this was formerly defined
167 as a class. Its usage is unchanged, but static type annotations that attempt
168 to permit only this object must be changed to avoid new mypy errors. This was
169 previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
170 It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
171 """
173 def _process_diff_args(
174 self,
175 args: List[Union[PathLike, "Diffable"]],
176 ) -> List[Union[PathLike, "Diffable"]]:
177 """
178 :return:
179 Possibly altered version of the given args list.
180 This method is called right before git command execution.
181 Subclasses can use it to alter the behaviour of the superclass.
182 """
183 return args
185 def diff(
186 self,
187 other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
188 paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
189 create_patch: bool = False,
190 **kwargs: Any,
191 ) -> "DiffIndex[Diff]":
192 """Create diffs between two items being trees, trees and index or an index and
193 the working tree. Detects renames automatically.
195 :param other:
196 This the item to compare us with.
198 * If ``None``, we will be compared to the working tree.
200 * If a :class:`~git.types.Tree_ish` or string, it will be compared against
201 the respective tree.
203 * If :const:`INDEX`, it will be compared against the index.
205 * If :const:`NULL_TREE`, it will compare against the empty tree.
207 This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
208 method will not by default fail on bare repositories.
210 :param paths:
211 This a list of paths or a single path to limit the diff to. It will only
212 include at least one of the given path or paths.
214 :param create_patch:
215 If ``True``, the returned :class:`Diff` contains a detailed patch that if
216 applied makes the self to other. Patches are somewhat costly as blobs have
217 to be read and diffed.
219 :param kwargs:
220 Additional arguments passed to :manpage:`git-diff(1)`, such as ``R=True`` to
221 swap both sides of the diff.
223 :return:
224 A :class:`DiffIndex` representing the computed diff.
226 :note:
227 On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
228 an instance of :class:`~git.objects.tree.Tree` or
229 :class:`~git.objects.commit.Commit`, or a git command error will occur.
230 """
231 args: List[Union[PathLike, Diffable]] = []
232 args.append("--abbrev=40") # We need full shas.
233 args.append("--full-index") # Get full index paths, not only filenames.
235 # Remove default '-M' arg (check for renames) if user is overriding it.
236 if not any(x in kwargs for x in ("find_renames", "no_renames", "M")):
237 args.append("-M")
239 if create_patch:
240 args.append("-p")
241 args.append("--no-ext-diff")
242 else:
243 args.append("--raw")
244 args.append("-z")
246 # Ensure we never see colored output.
247 # Fixes: https://github.com/gitpython-developers/GitPython/issues/172
248 args.append("--no-color")
250 if paths is not None and not isinstance(paths, (tuple, list)):
251 paths = [paths]
253 diff_cmd = self.repo.git.diff
254 if other is INDEX:
255 args.insert(0, "--cached")
256 elif other is NULL_TREE:
257 args.insert(0, "-r") # Recursive diff-tree.
258 args.insert(0, "--root")
259 diff_cmd = self.repo.git.diff_tree
260 elif other is not None:
261 args.insert(0, "-r") # Recursive diff-tree.
262 args.insert(0, other)
263 diff_cmd = self.repo.git.diff_tree
265 args.insert(0, self)
267 # paths is a list or tuple here, or None.
268 if paths:
269 args.append("--")
270 args.extend(paths)
271 # END paths handling
273 kwargs["as_process"] = True
274 proc = diff_cmd(*self._process_diff_args(args), **kwargs)
276 diff_method = Diff._index_from_patch_format if create_patch else Diff._index_from_raw_format
277 index = diff_method(self.repo, proc)
279 proc.wait()
280 return index
283T_Diff = TypeVar("T_Diff", bound="Diff")
286class DiffIndex(List[T_Diff]):
287 R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
288 properties.
290 The class improves the diff handling convenience.
291 """
293 change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") # noqa: F821
294 """Change type invariant identifying possible ways a blob can have changed:
296 * ``A`` = Added
297 * ``D`` = Deleted
298 * ``R`` = Renamed
299 * ``M`` = Modified
300 * ``T`` = Changed in the type
301 """
303 def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]:
304 """
305 :return:
306 Iterator yielding :class:`Diff` instances that match the given `change_type`
308 :param change_type:
309 Member of :attr:`DiffIndex.change_type`, namely:
311 * 'A' for added paths
312 * 'D' for deleted paths
313 * 'R' for renamed paths
314 * 'M' for paths with modified data
315 * 'T' for changed in the type paths
316 """
317 if change_type not in self.change_type:
318 raise ValueError("Invalid change type: %s" % change_type)
320 for diffidx in self:
321 if diffidx.change_type == change_type:
322 yield diffidx
323 elif change_type == "A" and diffidx.new_file:
324 yield diffidx
325 elif change_type == "D" and diffidx.deleted_file:
326 yield diffidx
327 elif change_type == "C" and diffidx.copied_file:
328 yield diffidx
329 elif change_type == "R" and diffidx.renamed_file:
330 yield diffidx
331 elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
332 yield diffidx
333 # END for each diff
336class Diff:
337 """A Diff contains diff information between two Trees.
339 It contains two sides a and b of the diff. Members are prefixed with "a" and "b"
340 respectively to indicate that.
342 Diffs keep information about the changed blob objects, the file mode, renames,
343 deletions and new files.
345 There are a few cases where ``None`` has to be expected as member variable value:
347 New File::
349 a_mode is None
350 a_blob is None
351 a_path is None
353 Deleted File::
355 b_mode is None
356 b_blob is None
357 b_path is None
359 Working Tree Blobs:
361 When comparing to working trees, the working tree blob will have a null hexsha
362 as a corresponding object does not yet exist. The mode will be null as well. The
363 path will be available, though.
365 If it is listed in a diff, the working tree version of the file must differ from
366 the version in the index or tree, and hence has been modified.
367 """
369 # Precompiled regex.
370 re_header = re.compile(
371 rb"""
372 ^diff[ ]--git
373 [ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
374 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
375 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
376 (?:^similarity[ ]index[ ]\d+%\n
377 ^rename[ ]from[ ](?P<rename_from>.*)\n
378 ^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))?
379 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
380 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
381 (?:^similarity[ ]index[ ]\d+%\n
382 ^copy[ ]from[ ].*\n
383 ^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))?
384 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
385 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
386 (?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
387 (?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
388 """,
389 re.VERBOSE | re.MULTILINE,
390 )
392 # These can be used for comparisons.
393 NULL_HEX_SHA = "0" * 40
394 NULL_BIN_SHA = b"\0" * 20
396 __slots__ = (
397 "a_blob",
398 "b_blob",
399 "a_mode",
400 "b_mode",
401 "a_rawpath",
402 "b_rawpath",
403 "new_file",
404 "deleted_file",
405 "copied_file",
406 "raw_rename_from",
407 "raw_rename_to",
408 "diff",
409 "change_type",
410 "score",
411 )
413 def __init__(
414 self,
415 repo: "Repo",
416 a_rawpath: Optional[bytes],
417 b_rawpath: Optional[bytes],
418 a_blob_id: Union[str, bytes, None],
419 b_blob_id: Union[str, bytes, None],
420 a_mode: Union[bytes, str, None],
421 b_mode: Union[bytes, str, None],
422 new_file: bool,
423 deleted_file: bool,
424 copied_file: bool,
425 raw_rename_from: Optional[bytes],
426 raw_rename_to: Optional[bytes],
427 diff: Union[str, bytes, None],
428 change_type: Optional[Lit_change_type],
429 score: Optional[int],
430 ) -> None:
431 assert a_rawpath is None or isinstance(a_rawpath, bytes)
432 assert b_rawpath is None or isinstance(b_rawpath, bytes)
433 self.a_rawpath = a_rawpath
434 self.b_rawpath = b_rawpath
436 self.a_mode = mode_str_to_int(a_mode) if a_mode else None
437 self.b_mode = mode_str_to_int(b_mode) if b_mode else None
439 # Determine whether this diff references a submodule. If it does then
440 # we need to overwrite "repo" to the corresponding submodule's repo instead.
441 if repo and a_rawpath:
442 for submodule in repo.submodules:
443 if submodule.path == a_rawpath.decode(defenc, "replace"):
444 if submodule.module_exists():
445 repo = submodule.module()
446 break
448 self.a_blob: Union["IndexObject", None]
449 if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
450 self.a_blob = None
451 else:
452 self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
454 self.b_blob: Union["IndexObject", None]
455 if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
456 self.b_blob = None
457 else:
458 self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
460 self.new_file: bool = new_file
461 self.deleted_file: bool = deleted_file
462 self.copied_file: bool = copied_file
464 # Be clear and use None instead of empty strings.
465 assert raw_rename_from is None or isinstance(raw_rename_from, bytes)
466 assert raw_rename_to is None or isinstance(raw_rename_to, bytes)
467 self.raw_rename_from = raw_rename_from or None
468 self.raw_rename_to = raw_rename_to or None
470 self.diff = diff
471 self.change_type: Union[Lit_change_type, None] = change_type
472 self.score = score
474 def __eq__(self, other: object) -> bool:
475 for name in self.__slots__:
476 if getattr(self, name) != getattr(other, name):
477 return False
478 # END for each name
479 return True
481 def __ne__(self, other: object) -> bool:
482 return not (self == other)
484 def __hash__(self) -> int:
485 return hash(tuple(getattr(self, n) for n in self.__slots__))
487 def __str__(self) -> str:
488 h = "%s"
489 if self.a_blob:
490 h %= self.a_blob.path
491 elif self.b_blob:
492 h %= self.b_blob.path
494 msg = ""
495 line = None
496 line_length = 0
497 for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
498 if b:
499 line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
500 else:
501 line = "\n%s: None" % n
502 # END if blob is not None
503 line_length = max(len(line), line_length)
504 msg += line
505 # END for each blob
507 # Add headline.
508 h += "\n" + "=" * line_length
510 if self.deleted_file:
511 msg += "\nfile deleted in rhs"
512 if self.new_file:
513 msg += "\nfile added in rhs"
514 if self.copied_file:
515 msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
516 if self.rename_from:
517 msg += "\nfile renamed from %r" % self.rename_from
518 if self.rename_to:
519 msg += "\nfile renamed to %r" % self.rename_to
520 if self.diff:
521 msg += "\n---"
522 try:
523 msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
524 except UnicodeDecodeError:
525 msg += "OMITTED BINARY DATA"
526 # END handle encoding
527 msg += "\n---"
528 # END diff info
530 return h + msg
532 @property
533 def a_path(self) -> Optional[str]:
534 return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None
536 @property
537 def b_path(self) -> Optional[str]:
538 return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None
540 @property
541 def rename_from(self) -> Optional[str]:
542 return self.raw_rename_from.decode(defenc, "replace") if self.raw_rename_from else None
544 @property
545 def rename_to(self) -> Optional[str]:
546 return self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None
548 @property
549 def renamed(self) -> bool:
550 """Deprecated, use :attr:`renamed_file` instead.
552 :return:
553 ``True`` if the blob of our diff has been renamed
555 :note:
556 This property is deprecated.
557 Please use the :attr:`renamed_file` property instead.
558 """
559 warnings.warn(
560 "Diff.renamed is deprecated, use Diff.renamed_file instead",
561 DeprecationWarning,
562 stacklevel=2,
563 )
564 return self.renamed_file
566 @property
567 def renamed_file(self) -> bool:
568 """:return: ``True`` if the blob of our diff has been renamed"""
569 return self.rename_from != self.rename_to
571 @classmethod
572 def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
573 if path_match:
574 return decode_path(path_match)
576 if rename_match:
577 return decode_path(rename_match, has_ab_prefix=False)
579 if path_fallback_match:
580 return decode_path(path_fallback_match)
582 return None
584 @classmethod
585 def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
586 """Create a new :class:`DiffIndex` from the given process output which must be
587 in patch format.
589 :param repo:
590 The repository we are operating on.
592 :param proc:
593 :manpage:`git-diff(1)` process to read from
594 (supports :class:`Git.AutoInterrupt <git.cmd.Git.AutoInterrupt>` wrapper).
596 :return:
597 :class:`DiffIndex`
598 """
600 # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
601 text_list: List[bytes] = []
602 handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
604 # For now, we have to bake the stream.
605 text = b"".join(text_list)
606 index: "DiffIndex" = DiffIndex()
607 previous_header: Union[Match[bytes], None] = None
608 header: Union[Match[bytes], None] = None
609 a_path, b_path = None, None # For mypy.
610 a_mode, b_mode = None, None # For mypy.
611 for _header in cls.re_header.finditer(text):
612 (
613 a_path_fallback,
614 b_path_fallback,
615 old_mode,
616 new_mode,
617 rename_from,
618 rename_to,
619 new_file_mode,
620 deleted_file_mode,
621 copied_file_name,
622 a_blob_id,
623 b_blob_id,
624 b_mode,
625 a_path,
626 b_path,
627 ) = _header.groups()
629 new_file, deleted_file, copied_file = (
630 bool(new_file_mode),
631 bool(deleted_file_mode),
632 bool(copied_file_name),
633 )
635 a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
636 b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
638 # Our only means to find the actual text is to see what has not been matched
639 # by our regex, and then retro-actively assign it to our index.
640 if previous_header is not None:
641 index[-1].diff = text[previous_header.end() : _header.start()]
642 # END assign actual diff
644 # Make sure the mode is set if the path is set. Otherwise the resulting blob
645 # is invalid. We just use the one mode we should have parsed.
646 a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
647 b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
648 index.append(
649 Diff(
650 repo,
651 a_path,
652 b_path,
653 a_blob_id and a_blob_id.decode(defenc),
654 b_blob_id and b_blob_id.decode(defenc),
655 a_mode and a_mode.decode(defenc),
656 b_mode and b_mode.decode(defenc),
657 new_file,
658 deleted_file,
659 copied_file,
660 rename_from,
661 rename_to,
662 None,
663 None,
664 None,
665 )
666 )
668 previous_header = _header
669 header = _header
670 # END for each header we parse
671 if index and header:
672 index[-1].diff = text[header.end() :]
673 # END assign last diff
675 return index
677 @staticmethod
678 def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
679 lines = lines_bytes.decode(defenc)
681 # Discard everything before the first colon, and the colon itself.
682 _, _, lines = lines.partition(":")
684 for line in lines.split("\x00:"):
685 if not line:
686 # The line data is empty, skip.
687 continue
688 meta, _, path = line.partition("\x00")
689 path = path.rstrip("\x00")
690 a_blob_id: Optional[str]
691 b_blob_id: Optional[str]
692 old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
693 # Change type can be R100
694 # R: status letter
695 # 100: score (in case of copy and rename)
696 change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
697 score_str = "".join(_change_type[1:])
698 score = int(score_str) if score_str.isdigit() else None
699 path = path.strip("\n")
700 a_path = path.encode(defenc)
701 b_path = path.encode(defenc)
702 deleted_file = False
703 new_file = False
704 copied_file = False
705 rename_from = None
706 rename_to = None
708 # NOTE: We cannot conclude from the existence of a blob to change type,
709 # as diffs with the working do not have blobs yet.
710 if change_type == "D":
711 b_blob_id = None # Optional[str]
712 deleted_file = True
713 elif change_type == "A":
714 a_blob_id = None
715 new_file = True
716 elif change_type == "C":
717 copied_file = True
718 a_path_str, b_path_str = path.split("\x00", 1)
719 a_path = a_path_str.encode(defenc)
720 b_path = b_path_str.encode(defenc)
721 elif change_type == "R":
722 a_path_str, b_path_str = path.split("\x00", 1)
723 a_path = a_path_str.encode(defenc)
724 b_path = b_path_str.encode(defenc)
725 rename_from, rename_to = a_path, b_path
726 elif change_type == "T":
727 # Nothing to do.
728 pass
729 # END add/remove handling
731 diff = Diff(
732 repo,
733 a_path,
734 b_path,
735 a_blob_id,
736 b_blob_id,
737 old_mode,
738 new_mode,
739 new_file,
740 deleted_file,
741 copied_file,
742 rename_from,
743 rename_to,
744 "",
745 change_type,
746 score,
747 )
748 index.append(diff)
750 @classmethod
751 def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
752 """Create a new :class:`DiffIndex` from the given process output which must be
753 in raw format.
755 :param repo:
756 The repository we are operating on.
758 :param proc:
759 Process to read output from.
761 :return:
762 :class:`DiffIndex`
763 """
764 # handles
765 # :100644 100644 687099101... 37c5e30c8... M .gitignore
767 index: "DiffIndex" = DiffIndex()
768 handle_process_output(
769 proc,
770 lambda byt: cls._handle_diff_line(byt, repo, index),
771 None,
772 finalize_process,
773 decode_streams=False,
774 )
776 return index