GitPython 3.1.44__py3-none-any.whl → 3.1.46__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.44'
89
+ __version__ = '3.1.46'
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:
@@ -1319,7 +1336,7 @@ class Git(metaclass=_GitMeta):
1319
1336
  out, err = proc.communicate()
1320
1337
  watchdog.cancel()
1321
1338
  if kill_check.is_set():
1322
- err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
1339
+ err = 'Timeout: the command "%s" did not complete in %d secs.' % (
1323
1340
  " ".join(redacted_command),
1324
1341
  timeout,
1325
1342
  )
git/config.py CHANGED
@@ -66,7 +66,7 @@ _logger = logging.getLogger(__name__)
66
66
  CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository")
67
67
  """The configuration level of a configuration file."""
68
68
 
69
- CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"")
69
+ CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"")
70
70
  """Section pattern to detect conditional includes.
71
71
 
72
72
  See: https://git-scm.com/docs/git-config#_conditional_includes
@@ -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
@@ -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:
@@ -567,11 +574,11 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
567
574
  if keyword.endswith("/i"):
568
575
  value = re.sub(
569
576
  r"[a-zA-Z]",
570
- lambda m: "[{}{}]".format(m.group().lower(), m.group().upper()),
577
+ lambda m: f"[{m.group().lower()!r}{m.group().upper()!r}]",
571
578
  value,
572
579
  )
573
580
  if self._repo.git_dir:
574
- if fnmatch.fnmatchcase(str(self._repo.git_dir), value):
581
+ if fnmatch.fnmatchcase(os.fspath(self._repo.git_dir), value):
575
582
  paths += self.items(section)
576
583
 
577
584
  elif keyword == "onbranch":
@@ -583,7 +590,11 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
583
590
 
584
591
  if fnmatch.fnmatchcase(branch_name, value):
585
592
  paths += self.items(section)
586
-
593
+ elif keyword == "hasconfig:remote.*.url":
594
+ for remote in self._repo.remotes:
595
+ if fnmatch.fnmatchcase(remote.url, value):
596
+ paths += self.items(section)
597
+ break
587
598
  return paths
588
599
 
589
600
  def read(self) -> None: # type: ignore[override]
@@ -622,8 +633,6 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
622
633
  file_path = cast(IO[bytes], file_path)
623
634
  self._read(file_path, file_path.name)
624
635
  else:
625
- # Assume a path if it is not a file-object.
626
- file_path = cast(PathLike, file_path)
627
636
  try:
628
637
  with open(file_path, "rb") as fp:
629
638
  file_ok = True
@@ -757,7 +766,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
757
766
  if self.read_only:
758
767
  raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
759
768
 
760
- def add_section(self, section: str) -> None:
769
+ def add_section(self, section: "cp._SectionName") -> None:
761
770
  """Assures added options will stay in order."""
762
771
  return super().add_section(section)
763
772
 
git/diff.py CHANGED
@@ -23,13 +23,14 @@ from typing import (
23
23
  List,
24
24
  Match,
25
25
  Optional,
26
+ Sequence,
26
27
  Tuple,
27
28
  TYPE_CHECKING,
28
29
  TypeVar,
29
30
  Union,
30
31
  cast,
31
32
  )
32
- from git.types import Literal, PathLike
33
+ from git.types import PathLike, Literal
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from subprocess import Popen
@@ -289,7 +290,7 @@ class DiffIndex(List[T_Diff]):
289
290
  The class improves the diff handling convenience.
290
291
  """
291
292
 
292
- change_type = ("A", "C", "D", "R", "M", "T")
293
+ change_type: Sequence[Literal["A", "C", "D", "R", "M", "T"]] = ("A", "C", "D", "R", "M", "T") # noqa: F821
293
294
  """Change type invariant identifying possible ways a blob can have changed:
294
295
 
295
296
  * ``A`` = Added