GitPython 3.1.43__py3-none-any.whl → 3.1.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
git/__init__.py CHANGED
@@ -86,7 +86,7 @@ __all__ = [
86
86
  "to_hex_sha",
87
87
  ]
88
88
 
89
- __version__ = '3.1.43'
89
+ __version__ = '3.1.45'
90
90
 
91
91
  from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Tuple, Union
92
92
 
git/cmd.py CHANGED
@@ -60,6 +60,11 @@ from typing import (
60
60
  overload,
61
61
  )
62
62
 
63
+ if sys.version_info >= (3, 10):
64
+ from typing import TypeAlias
65
+ else:
66
+ from typing_extensions import TypeAlias
67
+
63
68
  from git.types import Literal, PathLike, TBD
64
69
 
65
70
  if TYPE_CHECKING:
@@ -207,7 +212,7 @@ def handle_process_output(
207
212
  )
208
213
  if stderr_handler:
209
214
  error_str: Union[str, bytes] = (
210
- "error: process killed because it timed out." f" kill_after_timeout={kill_after_timeout} seconds"
215
+ f"error: process killed because it timed out. kill_after_timeout={kill_after_timeout} seconds"
211
216
  )
212
217
  if not decode_streams and isinstance(p_stderr, BinaryIO):
213
218
  # Assume stderr_handler needs binary input.
@@ -268,12 +273,12 @@ if sys.platform == "win32":
268
273
  if shell:
269
274
  # The original may be immutable, or the caller may reuse it. Mutate a copy.
270
275
  env = {} if env is None else dict(env)
271
- env["NoDefaultCurrentDirectoryInExePath"] = "1" # The "1" can be an value.
276
+ env["NoDefaultCurrentDirectoryInExePath"] = "1" # The "1" can be any value.
272
277
 
273
278
  # When not using a shell, the current process does the search in a
274
279
  # CreateProcessW API call, so the variable must be set in our environment. With
275
280
  # a shell, that's unnecessary if https://github.com/python/cpython/issues/101283
276
- # is patched. In Python versions where it is unpatched, and in the rare case the
281
+ # is patched. In Python versions where it is unpatched, in the rare case the
277
282
  # ComSpec environment variable is unset, the search for the shell itself is
278
283
  # unsafe. Setting NoDefaultCurrentDirectoryInExePath in all cases, as done here,
279
284
  # is simpler and protects against that. (As above, the "1" can be any value.)
@@ -308,6 +313,230 @@ def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], exc
308
313
 
309
314
  ## -- End Utilities -- @}
310
315
 
316
+
317
+ class _AutoInterrupt:
318
+ """Process wrapper that terminates the wrapped process on finalization.
319
+
320
+ This kills/interrupts the stored process instance once this instance goes out of
321
+ scope. It is used to prevent processes piling up in case iterators stop reading.
322
+
323
+ All attributes are wired through to the contained process object.
324
+
325
+ The wait method is overridden to perform automatic status code checking and possibly
326
+ raise.
327
+ """
328
+
329
+ __slots__ = ("proc", "args", "status")
330
+
331
+ # If this is non-zero it will override any status code during _terminate, used
332
+ # to prevent race conditions in testing.
333
+ _status_code_if_terminate: int = 0
334
+
335
+ def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None:
336
+ self.proc = proc
337
+ self.args = args
338
+ self.status: Union[int, None] = None
339
+
340
+ def _terminate(self) -> None:
341
+ """Terminate the underlying process."""
342
+ if self.proc is None:
343
+ return
344
+
345
+ proc = self.proc
346
+ self.proc = None
347
+ if proc.stdin:
348
+ proc.stdin.close()
349
+ if proc.stdout:
350
+ proc.stdout.close()
351
+ if proc.stderr:
352
+ proc.stderr.close()
353
+ # Did the process finish already so we have a return code?
354
+ try:
355
+ if proc.poll() is not None:
356
+ self.status = self._status_code_if_terminate or proc.poll()
357
+ return
358
+ except OSError as ex:
359
+ _logger.info("Ignored error after process had died: %r", ex)
360
+
361
+ # It can be that nothing really exists anymore...
362
+ if os is None or getattr(os, "kill", None) is None:
363
+ return
364
+
365
+ # Try to kill it.
366
+ try:
367
+ proc.terminate()
368
+ status = proc.wait() # Ensure the process goes away.
369
+
370
+ self.status = self._status_code_if_terminate or status
371
+ except OSError as ex:
372
+ _logger.info("Ignored error after process had died: %r", ex)
373
+ # END exception handling
374
+
375
+ def __del__(self) -> None:
376
+ self._terminate()
377
+
378
+ def __getattr__(self, attr: str) -> Any:
379
+ return getattr(self.proc, attr)
380
+
381
+ # TODO: Bad choice to mimic `proc.wait()` but with different args.
382
+ def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
383
+ """Wait for the process and return its status code.
384
+
385
+ :param stderr:
386
+ Previously read value of stderr, in case stderr is already closed.
387
+
388
+ :warn:
389
+ May deadlock if output or error pipes are used and not handled separately.
390
+
391
+ :raise git.exc.GitCommandError:
392
+ If the return status is not 0.
393
+ """
394
+ if stderr is None:
395
+ stderr_b = b""
396
+ stderr_b = force_bytes(data=stderr, encoding="utf-8")
397
+ status: Union[int, None]
398
+ if self.proc is not None:
399
+ status = self.proc.wait()
400
+ p_stderr = self.proc.stderr
401
+ else: # Assume the underlying proc was killed earlier or never existed.
402
+ status = self.status
403
+ p_stderr = None
404
+
405
+ def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
406
+ if stream:
407
+ try:
408
+ return stderr_b + force_bytes(stream.read())
409
+ except (OSError, ValueError):
410
+ return stderr_b or b""
411
+ else:
412
+ return stderr_b or b""
413
+
414
+ # END status handling
415
+
416
+ if status != 0:
417
+ errstr = read_all_from_possibly_closed_stream(p_stderr)
418
+ _logger.debug("AutoInterrupt wait stderr: %r" % (errstr,))
419
+ raise GitCommandError(remove_password_if_present(self.args), status, errstr)
420
+ return status
421
+
422
+
423
+ _AutoInterrupt.__name__ = "AutoInterrupt"
424
+ _AutoInterrupt.__qualname__ = "Git.AutoInterrupt"
425
+
426
+
427
+ class _CatFileContentStream:
428
+ """Object representing a sized read-only stream returning the contents of
429
+ an object.
430
+
431
+ This behaves like a stream, but counts the data read and simulates an empty stream
432
+ once our sized content region is empty.
433
+
434
+ If not all data are read to the end of the object's lifetime, we read the rest to
435
+ ensure the underlying stream continues to work.
436
+ """
437
+
438
+ __slots__ = ("_stream", "_nbr", "_size")
439
+
440
+ def __init__(self, size: int, stream: IO[bytes]) -> None:
441
+ self._stream = stream
442
+ self._size = size
443
+ self._nbr = 0 # Number of bytes read.
444
+
445
+ # Special case: If the object is empty, has null bytes, get the final
446
+ # newline right away.
447
+ if size == 0:
448
+ stream.read(1)
449
+ # END handle empty streams
450
+
451
+ def read(self, size: int = -1) -> bytes:
452
+ bytes_left = self._size - self._nbr
453
+ if bytes_left == 0:
454
+ return b""
455
+ if size > -1:
456
+ # Ensure we don't try to read past our limit.
457
+ size = min(bytes_left, size)
458
+ else:
459
+ # They try to read all, make sure it's not more than what remains.
460
+ size = bytes_left
461
+ # END check early depletion
462
+ data = self._stream.read(size)
463
+ self._nbr += len(data)
464
+
465
+ # Check for depletion, read our final byte to make the stream usable by
466
+ # others.
467
+ if self._size - self._nbr == 0:
468
+ self._stream.read(1) # final newline
469
+ # END finish reading
470
+ return data
471
+
472
+ def readline(self, size: int = -1) -> bytes:
473
+ if self._nbr == self._size:
474
+ return b""
475
+
476
+ # Clamp size to lowest allowed value.
477
+ bytes_left = self._size - self._nbr
478
+ if size > -1:
479
+ size = min(bytes_left, size)
480
+ else:
481
+ size = bytes_left
482
+ # END handle size
483
+
484
+ data = self._stream.readline(size)
485
+ self._nbr += len(data)
486
+
487
+ # Handle final byte.
488
+ if self._size - self._nbr == 0:
489
+ self._stream.read(1)
490
+ # END finish reading
491
+
492
+ return data
493
+
494
+ def readlines(self, size: int = -1) -> List[bytes]:
495
+ if self._nbr == self._size:
496
+ return []
497
+
498
+ # Leave all additional logic to our readline method, we just check the size.
499
+ out = []
500
+ nbr = 0
501
+ while True:
502
+ line = self.readline()
503
+ if not line:
504
+ break
505
+ out.append(line)
506
+ if size > -1:
507
+ nbr += len(line)
508
+ if nbr > size:
509
+ break
510
+ # END handle size constraint
511
+ # END readline loop
512
+ return out
513
+
514
+ # skipcq: PYL-E0301
515
+ def __iter__(self) -> "Git.CatFileContentStream":
516
+ return self
517
+
518
+ def __next__(self) -> bytes:
519
+ line = self.readline()
520
+ if not line:
521
+ raise StopIteration
522
+
523
+ return line
524
+
525
+ next = __next__
526
+
527
+ def __del__(self) -> None:
528
+ bytes_left = self._size - self._nbr
529
+ if bytes_left:
530
+ # Read and discard - seeking is impossible within a stream.
531
+ # This includes any terminating newline.
532
+ self._stream.read(bytes_left + 1)
533
+ # END handle incomplete read
534
+
535
+
536
+ _CatFileContentStream.__name__ = "CatFileContentStream"
537
+ _CatFileContentStream.__qualname__ = "Git.CatFileContentStream"
538
+
539
+
311
540
  _USE_SHELL_DEFAULT_MESSAGE = (
312
541
  "Git.USE_SHELL is deprecated, because only its default value of False is safe. "
313
542
  "It will be removed in a future release."
@@ -321,7 +550,7 @@ _USE_SHELL_DANGER_MESSAGE = (
321
550
  )
322
551
 
323
552
 
324
- def _warn_use_shell(extra_danger: bool) -> None:
553
+ def _warn_use_shell(*, extra_danger: bool) -> None:
325
554
  warnings.warn(
326
555
  _USE_SHELL_DANGER_MESSAGE if extra_danger else _USE_SHELL_DEFAULT_MESSAGE,
327
556
  DeprecationWarning,
@@ -337,12 +566,12 @@ class _GitMeta(type):
337
566
 
338
567
  def __getattribute(cls, name: str) -> Any:
339
568
  if name == "USE_SHELL":
340
- _warn_use_shell(False)
569
+ _warn_use_shell(extra_danger=False)
341
570
  return super().__getattribute__(name)
342
571
 
343
572
  def __setattr(cls, name: str, value: Any) -> Any:
344
573
  if name == "USE_SHELL":
345
- _warn_use_shell(value)
574
+ _warn_use_shell(extra_danger=value)
346
575
  super().__setattr__(name, value)
347
576
 
348
577
  if not TYPE_CHECKING:
@@ -728,221 +957,9 @@ class Git(metaclass=_GitMeta):
728
957
  f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
729
958
  )
730
959
 
731
- class AutoInterrupt:
732
- """Process wrapper that terminates the wrapped process on finalization.
733
-
734
- This kills/interrupts the stored process instance once this instance goes out of
735
- scope. It is used to prevent processes piling up in case iterators stop reading.
736
-
737
- All attributes are wired through to the contained process object.
738
-
739
- The wait method is overridden to perform automatic status code checking and
740
- possibly raise.
741
- """
742
-
743
- __slots__ = ("proc", "args", "status")
744
-
745
- # If this is non-zero it will override any status code during _terminate, used
746
- # to prevent race conditions in testing.
747
- _status_code_if_terminate: int = 0
748
-
749
- def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None:
750
- self.proc = proc
751
- self.args = args
752
- self.status: Union[int, None] = None
753
-
754
- def _terminate(self) -> None:
755
- """Terminate the underlying process."""
756
- if self.proc is None:
757
- return
758
-
759
- proc = self.proc
760
- self.proc = None
761
- if proc.stdin:
762
- proc.stdin.close()
763
- if proc.stdout:
764
- proc.stdout.close()
765
- if proc.stderr:
766
- proc.stderr.close()
767
- # Did the process finish already so we have a return code?
768
- try:
769
- if proc.poll() is not None:
770
- self.status = self._status_code_if_terminate or proc.poll()
771
- return
772
- except OSError as ex:
773
- _logger.info("Ignored error after process had died: %r", ex)
774
-
775
- # It can be that nothing really exists anymore...
776
- if os is None or getattr(os, "kill", None) is None:
777
- return
778
-
779
- # Try to kill it.
780
- try:
781
- proc.terminate()
782
- status = proc.wait() # Ensure the process goes away.
783
-
784
- self.status = self._status_code_if_terminate or status
785
- except OSError as ex:
786
- _logger.info("Ignored error after process had died: %r", ex)
787
- # END exception handling
788
-
789
- def __del__(self) -> None:
790
- self._terminate()
791
-
792
- def __getattr__(self, attr: str) -> Any:
793
- return getattr(self.proc, attr)
794
-
795
- # TODO: Bad choice to mimic `proc.wait()` but with different args.
796
- def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
797
- """Wait for the process and return its status code.
798
-
799
- :param stderr:
800
- Previously read value of stderr, in case stderr is already closed.
801
-
802
- :warn:
803
- May deadlock if output or error pipes are used and not handled
804
- separately.
805
-
806
- :raise git.exc.GitCommandError:
807
- If the return status is not 0.
808
- """
809
- if stderr is None:
810
- stderr_b = b""
811
- stderr_b = force_bytes(data=stderr, encoding="utf-8")
812
- status: Union[int, None]
813
- if self.proc is not None:
814
- status = self.proc.wait()
815
- p_stderr = self.proc.stderr
816
- else: # Assume the underlying proc was killed earlier or never existed.
817
- status = self.status
818
- p_stderr = None
819
-
820
- def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
821
- if stream:
822
- try:
823
- return stderr_b + force_bytes(stream.read())
824
- except (OSError, ValueError):
825
- return stderr_b or b""
826
- else:
827
- return stderr_b or b""
828
-
829
- # END status handling
830
-
831
- if status != 0:
832
- errstr = read_all_from_possibly_closed_stream(p_stderr)
833
- _logger.debug("AutoInterrupt wait stderr: %r" % (errstr,))
834
- raise GitCommandError(remove_password_if_present(self.args), status, errstr)
835
- return status
836
-
837
- # END auto interrupt
838
-
839
- class CatFileContentStream:
840
- """Object representing a sized read-only stream returning the contents of
841
- an object.
842
-
843
- This behaves like a stream, but counts the data read and simulates an empty
844
- stream once our sized content region is empty.
845
-
846
- If not all data are read to the end of the object's lifetime, we read the
847
- rest to ensure the underlying stream continues to work.
848
- """
849
-
850
- __slots__ = ("_stream", "_nbr", "_size")
851
-
852
- def __init__(self, size: int, stream: IO[bytes]) -> None:
853
- self._stream = stream
854
- self._size = size
855
- self._nbr = 0 # Number of bytes read.
856
-
857
- # Special case: If the object is empty, has null bytes, get the final
858
- # newline right away.
859
- if size == 0:
860
- stream.read(1)
861
- # END handle empty streams
862
-
863
- def read(self, size: int = -1) -> bytes:
864
- bytes_left = self._size - self._nbr
865
- if bytes_left == 0:
866
- return b""
867
- if size > -1:
868
- # Ensure we don't try to read past our limit.
869
- size = min(bytes_left, size)
870
- else:
871
- # They try to read all, make sure it's not more than what remains.
872
- size = bytes_left
873
- # END check early depletion
874
- data = self._stream.read(size)
875
- self._nbr += len(data)
876
-
877
- # Check for depletion, read our final byte to make the stream usable by
878
- # others.
879
- if self._size - self._nbr == 0:
880
- self._stream.read(1) # final newline
881
- # END finish reading
882
- return data
883
-
884
- def readline(self, size: int = -1) -> bytes:
885
- if self._nbr == self._size:
886
- return b""
887
-
888
- # Clamp size to lowest allowed value.
889
- bytes_left = self._size - self._nbr
890
- if size > -1:
891
- size = min(bytes_left, size)
892
- else:
893
- size = bytes_left
894
- # END handle size
895
-
896
- data = self._stream.readline(size)
897
- self._nbr += len(data)
898
-
899
- # Handle final byte.
900
- if self._size - self._nbr == 0:
901
- self._stream.read(1)
902
- # END finish reading
903
-
904
- return data
905
-
906
- def readlines(self, size: int = -1) -> List[bytes]:
907
- if self._nbr == self._size:
908
- return []
909
-
910
- # Leave all additional logic to our readline method, we just check the size.
911
- out = []
912
- nbr = 0
913
- while True:
914
- line = self.readline()
915
- if not line:
916
- break
917
- out.append(line)
918
- if size > -1:
919
- nbr += len(line)
920
- if nbr > size:
921
- break
922
- # END handle size constraint
923
- # END readline loop
924
- return out
925
-
926
- # skipcq: PYL-E0301
927
- def __iter__(self) -> "Git.CatFileContentStream":
928
- return self
929
-
930
- def __next__(self) -> bytes:
931
- line = self.readline()
932
- if not line:
933
- raise StopIteration
934
-
935
- return line
936
-
937
- next = __next__
960
+ AutoInterrupt: TypeAlias = _AutoInterrupt
938
961
 
939
- def __del__(self) -> None:
940
- bytes_left = self._size - self._nbr
941
- if bytes_left:
942
- # Read and discard - seeking is impossible within a stream.
943
- # This includes any terminating newline.
944
- self._stream.read(bytes_left + 1)
945
- # END handle incomplete read
962
+ CatFileContentStream: TypeAlias = _CatFileContentStream
946
963
 
947
964
  def __init__(self, working_dir: Union[None, PathLike] = None) -> None:
948
965
  """Initialize this instance with:
@@ -971,7 +988,7 @@ class Git(metaclass=_GitMeta):
971
988
 
972
989
  def __getattribute__(self, name: str) -> Any:
973
990
  if name == "USE_SHELL":
974
- _warn_use_shell(False)
991
+ _warn_use_shell(extra_danger=False)
975
992
  return super().__getattribute__(name)
976
993
 
977
994
  def __getattr__(self, name: str) -> Any:
@@ -1269,6 +1286,7 @@ class Git(metaclass=_GitMeta):
1269
1286
  stdout=stdout_sink,
1270
1287
  shell=shell,
1271
1288
  universal_newlines=universal_newlines,
1289
+ encoding=defenc if universal_newlines else None,
1272
1290
  **subprocess_kwargs,
1273
1291
  )
1274
1292
  except cmd_not_found_exception as err:
@@ -1318,7 +1336,7 @@ class Git(metaclass=_GitMeta):
1318
1336
  out, err = proc.communicate()
1319
1337
  watchdog.cancel()
1320
1338
  if kill_check.is_set():
1321
- err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
1339
+ err = 'Timeout: the command "%s" did not complete in %d secs.' % (
1322
1340
  " ".join(redacted_command),
1323
1341
  timeout,
1324
1342
  )
git/config.py CHANGED
@@ -87,15 +87,15 @@ class MetaParserBuilder(abc.ABCMeta): # noqa: B024
87
87
  mutating_methods = clsdict[kmm]
88
88
  for base in bases:
89
89
  methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
90
- for name, method in methods:
91
- if name in clsdict:
90
+ for method_name, method in methods:
91
+ if method_name in clsdict:
92
92
  continue
93
93
  method_with_values = needs_values(method)
94
- if name in mutating_methods:
94
+ if method_name in mutating_methods:
95
95
  method_with_values = set_dirty_and_flush_changes(method_with_values)
96
96
  # END mutating methods handling
97
97
 
98
- clsdict[name] = method_with_values
98
+ clsdict[method_name] = method_with_values
99
99
  # END for each name/method pair
100
100
  # END for each base
101
101
  # END if mutating methods configuration is set
@@ -452,7 +452,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
452
452
  e = None # None, or an exception.
453
453
 
454
454
  def string_decode(v: str) -> str:
455
- if v[-1] == "\\":
455
+ if v and v.endswith("\\"):
456
456
  v = v[:-1]
457
457
  # END cut trailing escapes to prevent decode error
458
458
 
@@ -496,19 +496,26 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
496
496
  if mo:
497
497
  # We might just have handled the last line, which could contain a quotation we want to remove.
498
498
  optname, vi, optval = mo.group("option", "vi", "value")
499
+ optname = self.optionxform(optname.rstrip())
500
+
499
501
  if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'):
500
502
  pos = optval.find(";")
501
503
  if pos != -1 and optval[pos - 1].isspace():
502
504
  optval = optval[:pos]
503
505
  optval = optval.strip()
504
- if optval == '""':
505
- optval = ""
506
- # END handle empty string
507
- optname = self.optionxform(optname.rstrip())
508
- if len(optval) > 1 and optval[0] == '"' and optval[-1] != '"':
506
+
507
+ if len(optval) < 2 or optval[0] != '"':
508
+ # Does not open quoting.
509
+ pass
510
+ elif optval[-1] != '"':
511
+ # Opens quoting and does not close: appears to start multi-line quoting.
509
512
  is_multi_line = True
510
513
  optval = string_decode(optval[1:])
511
- # END handle multi-line
514
+ elif optval.find("\\", 1, -1) == -1 and optval.find('"', 1, -1) == -1:
515
+ # Opens and closes quoting. Single line, and all we need is quote removal.
516
+ optval = optval[1:-1]
517
+ # TODO: Handle other quoted content, especially well-formed backslash escapes.
518
+
512
519
  # Preserves multiple values for duplicate optnames.
513
520
  cursect.add(optname, optval)
514
521
  else:
git/diff.py CHANGED
@@ -187,7 +187,7 @@ class Diffable:
187
187
  paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
188
188
  create_patch: bool = False,
189
189
  **kwargs: Any,
190
- ) -> "DiffIndex":
190
+ ) -> "DiffIndex[Diff]":
191
191
  """Create diffs between two items being trees, trees and index or an index and
192
192
  the working tree. Detects renames automatically.
193
193
 
@@ -325,7 +325,7 @@ class DiffIndex(List[T_Diff]):
325
325
  yield diffidx
326
326
  elif change_type == "C" and diffidx.copied_file:
327
327
  yield diffidx
328
- elif change_type == "R" and diffidx.renamed:
328
+ elif change_type == "R" and diffidx.renamed_file:
329
329
  yield diffidx
330
330
  elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
331
331
  yield diffidx
@@ -581,7 +581,7 @@ class Diff:
581
581
  return None
582
582
 
583
583
  @classmethod
584
- def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex:
584
+ def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
585
585
  """Create a new :class:`DiffIndex` from the given process output which must be
586
586
  in patch format.
587
587
 
@@ -674,7 +674,7 @@ class Diff:
674
674
  return index
675
675
 
676
676
  @staticmethod
677
- def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> None:
677
+ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
678
678
  lines = lines_bytes.decode(defenc)
679
679
 
680
680
  # Discard everything before the first colon, and the colon itself.
@@ -695,7 +695,7 @@ class Diff:
695
695
  change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
696
696
  score_str = "".join(_change_type[1:])
697
697
  score = int(score_str) if score_str.isdigit() else None
698
- path = path.strip()
698
+ path = path.strip("\n")
699
699
  a_path = path.encode(defenc)
700
700
  b_path = path.encode(defenc)
701
701
  deleted_file = False
@@ -747,7 +747,7 @@ class Diff:
747
747
  index.append(diff)
748
748
 
749
749
  @classmethod
750
- def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex":
750
+ def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
751
751
  """Create a new :class:`DiffIndex` from the given process output which must be
752
752
  in raw format.
753
753