GitPython 3.1.44__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.44'
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:
@@ -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
@@ -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:
git/index/base.py CHANGED
@@ -508,7 +508,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
508
508
 
509
509
  :param predicate:
510
510
  Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by
511
- the iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you
511
+ the iterator. A default filter, the :class:`~git.index.typ.BlobFilter`, allows you
512
512
  to yield blobs only if they match a given list of paths.
513
513
  """
514
514
  for entry in self.entries.values():
@@ -530,7 +530,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
530
530
  stage. That is, a file removed on the 'other' branch whose entries are at
531
531
  stage 3 will not have a stage 3 entry.
532
532
  """
533
- is_unmerged_blob = lambda t: t[0] != 0
533
+
534
+ def is_unmerged_blob(t: Tuple[StageType, Blob]) -> bool:
535
+ return t[0] != 0
536
+
534
537
  path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {}
535
538
  for stage, blob in self.iter_blobs(is_unmerged_blob):
536
539
  path_map.setdefault(blob.path, []).append((stage, blob))
@@ -655,7 +658,10 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
655
658
  raise InvalidGitRepositoryError("require non-bare repository")
656
659
  if not osp.normpath(str(path)).startswith(str(self.repo.working_tree_dir)):
657
660
  raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
658
- return os.path.relpath(path, self.repo.working_tree_dir)
661
+ result = os.path.relpath(path, self.repo.working_tree_dir)
662
+ if str(path).endswith(os.sep) and not result.endswith(os.sep):
663
+ result += os.sep
664
+ return result
659
665
 
660
666
  def _preprocess_add_items(
661
667
  self, items: Union[PathLike, Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]]
@@ -687,12 +693,17 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
687
693
  This must be ensured in the calling code.
688
694
  """
689
695
  st = os.lstat(filepath) # Handles non-symlinks as well.
696
+
690
697
  if S_ISLNK(st.st_mode):
691
698
  # In PY3, readlink is a string, but we need bytes.
692
699
  # In PY2, it was just OS encoded bytes, we assumed UTF-8.
693
- open_stream: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath), encoding=defenc))
700
+ def open_stream() -> BinaryIO:
701
+ return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc))
694
702
  else:
695
- open_stream = lambda: open(filepath, "rb")
703
+
704
+ def open_stream() -> BinaryIO:
705
+ return open(filepath, "rb")
706
+
696
707
  with open_stream() as stream:
697
708
  fprogress(filepath, False, filepath)
698
709
  istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
@@ -767,7 +778,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
767
778
  - path string
768
779
 
769
780
  Strings denote a relative or absolute path into the repository pointing
770
- to an existing file, e.g., ``CHANGES``, `lib/myfile.ext``,
781
+ to an existing file, e.g., ``CHANGES``, ``lib/myfile.ext``,
771
782
  ``/home/gitrepo/lib/myfile.ext``.
772
783
 
773
784
  Absolute paths must start with working tree directory of this index's
@@ -786,7 +797,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
786
797
 
787
798
  They are added at stage 0.
788
799
 
789
- - :class:~`git.objects.blob.Blob` or
800
+ - :class:`~git.objects.blob.Blob` or
790
801
  :class:`~git.objects.submodule.base.Submodule` object
791
802
 
792
803
  Blobs are added as they are assuming a valid mode is set.
@@ -812,7 +823,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
812
823
 
813
824
  - :class:`~git.index.typ.BaseIndexEntry` or type
814
825
 
815
- Handling equals the one of :class:~`git.objects.blob.Blob` objects, but
826
+ Handling equals the one of :class:`~git.objects.blob.Blob` objects, but
816
827
  the stage may be explicitly set. Please note that Index Entries require
817
828
  binary sha's.
818
829
 
@@ -995,7 +1006,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
995
1006
 
996
1007
  The path string may include globs, such as ``*.c``.
997
1008
 
998
- - :class:~`git.objects.blob.Blob` object
1009
+ - :class:`~git.objects.blob.Blob` object
999
1010
 
1000
1011
  Only the path portion is used in this case.
1001
1012
 
@@ -1333,8 +1344,11 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
1333
1344
  kwargs["as_process"] = True
1334
1345
  kwargs["istream"] = subprocess.PIPE
1335
1346
  proc = self.repo.git.checkout_index(args, **kwargs)
1347
+
1336
1348
  # FIXME: Reading from GIL!
1337
- make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read())
1349
+ def make_exc() -> GitCommandError:
1350
+ return GitCommandError(("git-checkout-index", *args), 128, proc.stderr.read())
1351
+
1338
1352
  checked_out_files: List[PathLike] = []
1339
1353
 
1340
1354
  for path in paths:
git/index/fun.py CHANGED
@@ -207,7 +207,7 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]:
207
207
  version, num_entries = unpacked
208
208
 
209
209
  # TODO: Handle version 3: extended data, see read-cache.c.
210
- assert version in (1, 2)
210
+ assert version in (1, 2), "Unsupported git index version %i, only 1 and 2 are supported" % version
211
211
  return version, num_entries
212
212
 
213
213
 
git/objects/base.py CHANGED
@@ -122,7 +122,7 @@ class Object(LazyMixin):
122
122
  :return:
123
123
  New :class:`Object` instance of a type appropriate to the object type behind
124
124
  `id`. The id of the newly created object will be a binsha even though the
125
- input id may have been a `~git.refs.reference.Reference` or rev-spec.
125
+ input id may have been a :class:`~git.refs.reference.Reference` or rev-spec.
126
126
 
127
127
  :param id:
128
128
  :class:`~git.refs.reference.Reference`, rev-spec, or hexsha.
@@ -218,7 +218,7 @@ class IndexObject(Object):
218
218
  """Base for all objects that can be part of the index file.
219
219
 
220
220
  The classes representing git object types that can be part of the index file are
221
- :class:`~git.objects.tree.Tree and :class:`~git.objects.blob.Blob`. In addition,
221
+ :class:`~git.objects.tree.Tree` and :class:`~git.objects.blob.Blob`. In addition,
222
222
  :class:`~git.objects.submodule.base.Submodule`, which is not really a git object
223
223
  type but can be part of an index file, is also a subclass.
224
224
  """
git/objects/commit.py CHANGED
@@ -289,7 +289,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
289
289
  """
290
290
  :return:
291
291
  String describing the commits hex sha based on the closest
292
- `~git.refs.reference.Reference`.
292
+ :class:`~git.refs.reference.Reference`.
293
293
 
294
294
  :note:
295
295
  Mostly useful for UI purposes.
@@ -349,7 +349,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
349
349
  return cls._iter_from_process_or_stream(repo, proc)
350
350
 
351
351
  def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> Iterator["Commit"]:
352
- R"""Iterate _all_ parents of this commit.
352
+ R"""Iterate *all* parents of this commit.
353
353
 
354
354
  :param paths:
355
355
  Optional path or list of paths limiting the :class:`Commit`\s to those that
@@ -11,6 +11,7 @@ import os.path as osp
11
11
  import stat
12
12
  import sys
13
13
  import uuid
14
+ import urllib
14
15
 
15
16
  import git
16
17
  from git.cmd import Git
@@ -353,6 +354,11 @@ class Submodule(IndexObject, TraversableIterableObj):
353
354
  os.makedirs(module_abspath_dir)
354
355
  module_checkout_path = osp.join(str(repo.working_tree_dir), path)
355
356
 
357
+ if url.startswith("../"):
358
+ remote_name = repo.active_branch.tracking_branch().remote_name
359
+ repo_remote_url = repo.remote(remote_name).url
360
+ url = os.path.join(repo_remote_url, url)
361
+
356
362
  clone = git.Repo.clone_from(
357
363
  url,
358
364
  module_checkout_path,
@@ -794,9 +800,13 @@ class Submodule(IndexObject, TraversableIterableObj):
794
800
  + "Cloning url '%s' to '%s' in submodule %r" % (self.url, checkout_module_abspath, self.name),
795
801
  )
796
802
  if not dry_run:
803
+ if self.url.startswith("."):
804
+ url = urllib.parse.urljoin(self.repo.remotes.origin.url + "/", self.url)
805
+ else:
806
+ url = self.url
797
807
  mrepo = self._clone_repo(
798
808
  self.repo,
799
- self.url,
809
+ url,
800
810
  self.path,
801
811
  self.name,
802
812
  n=True,
git/objects/tree.py CHANGED
@@ -50,7 +50,9 @@ TraversedTreeTup = Union[Tuple[Union["Tree", None], IndexObjUnion, Tuple["Submod
50
50
 
51
51
  # --------------------------------------------------------
52
52
 
53
- cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b)
53
+
54
+ def cmp(a: str, b: str) -> int:
55
+ return (a > b) - (a < b)
54
56
 
55
57
 
56
58
  class TreeModifier:
git/refs/log.py CHANGED
@@ -126,7 +126,7 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
126
126
  elif len(fields) == 2:
127
127
  info, msg = fields
128
128
  else:
129
- raise ValueError("Line must have up to two TAB-separated fields." " Got %s" % repr(line_str))
129
+ raise ValueError("Line must have up to two TAB-separated fields. Got %s" % repr(line_str))
130
130
  # END handle first split
131
131
 
132
132
  oldhexsha = info[:40]
git/refs/symbolic.py CHANGED
@@ -39,7 +39,6 @@ from git.types import AnyGitObject, PathLike
39
39
  if TYPE_CHECKING:
40
40
  from git.config import GitConfigParser
41
41
  from git.objects.commit import Actor
42
- from git.refs import Head, TagReference, RemoteReference, Reference
43
42
  from git.refs.log import RefLogEntry
44
43
  from git.repo import Repo
45
44
 
@@ -387,17 +386,23 @@ class SymbolicReference:
387
386
  # set the commit on our reference
388
387
  return self._get_reference().set_object(object, logmsg)
389
388
 
390
- commit = property(
391
- _get_commit,
392
- set_commit, # type: ignore[arg-type]
393
- doc="Query or set commits directly",
394
- )
389
+ @property
390
+ def commit(self) -> "Commit":
391
+ """Query or set commits directly"""
392
+ return self._get_commit()
393
+
394
+ @commit.setter
395
+ def commit(self, commit: Union[Commit, "SymbolicReference", str]) -> "SymbolicReference":
396
+ return self.set_commit(commit)
397
+
398
+ @property
399
+ def object(self) -> AnyGitObject:
400
+ """Return the object our ref currently refers to"""
401
+ return self._get_object()
395
402
 
396
- object = property(
397
- _get_object,
398
- set_object, # type: ignore[arg-type]
399
- doc="Return the object our ref currently refers to",
400
- )
403
+ @object.setter
404
+ def object(self, object: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
405
+ return self.set_object(object)
401
406
 
402
407
  def _get_reference(self) -> "SymbolicReference":
403
408
  """
@@ -496,12 +501,14 @@ class SymbolicReference:
496
501
  return self
497
502
 
498
503
  # Aliased reference
499
- reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
500
- reference = property( # type: ignore[assignment]
501
- _get_reference,
502
- set_reference, # type: ignore[arg-type]
503
- doc="Returns the Reference we point to",
504
- )
504
+ @property
505
+ def reference(self) -> "SymbolicReference":
506
+ return self._get_reference()
507
+
508
+ @reference.setter
509
+ def reference(self, ref: Union[AnyGitObject, "SymbolicReference", str]) -> "SymbolicReference":
510
+ return self.set_reference(ref)
511
+
505
512
  ref = reference
506
513
 
507
514
  def is_valid(self) -> bool:
git/repo/base.py CHANGED
@@ -354,21 +354,19 @@ class Repo:
354
354
  def __hash__(self) -> int:
355
355
  return hash(self.git_dir)
356
356
 
357
- # Description property
358
- def _get_description(self) -> str:
357
+ @property
358
+ def description(self) -> str:
359
+ """The project's description"""
359
360
  filename = osp.join(self.git_dir, "description")
360
361
  with open(filename, "rb") as fp:
361
362
  return fp.read().rstrip().decode(defenc)
362
363
 
363
- def _set_description(self, descr: str) -> None:
364
+ @description.setter
365
+ def description(self, descr: str) -> None:
364
366
  filename = osp.join(self.git_dir, "description")
365
367
  with open(filename, "wb") as fp:
366
368
  fp.write((descr + "\n").encode(defenc))
367
369
 
368
- description = property(_get_description, _set_description, doc="the project's description")
369
- del _get_description
370
- del _set_description
371
-
372
370
  @property
373
371
  def working_tree_dir(self) -> Optional[PathLike]:
374
372
  """
@@ -514,7 +512,7 @@ class Repo:
514
512
  def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
515
513
  """An iterator yielding Submodule instances.
516
514
 
517
- See the `~git.objects.util.Traversable` interface for a description of `args`
515
+ See the :class:`~git.objects.util.Traversable` interface for a description of `args`
518
516
  and `kwargs`.
519
517
 
520
518
  :return:
@@ -885,13 +883,14 @@ class Repo:
885
883
  elif not value and fileexists:
886
884
  os.unlink(filename)
887
885
 
888
- daemon_export = property(
889
- _get_daemon_export,
890
- _set_daemon_export,
891
- doc="If True, git-daemon may export this repository",
892
- )
893
- del _get_daemon_export
894
- del _set_daemon_export
886
+ @property
887
+ def daemon_export(self) -> bool:
888
+ """If True, git-daemon may export this repository"""
889
+ return self._get_daemon_export()
890
+
891
+ @daemon_export.setter
892
+ def daemon_export(self, value: object) -> None:
893
+ self._set_daemon_export(value)
895
894
 
896
895
  def _get_alternates(self) -> List[str]:
897
896
  """The list of alternates for this repo from which objects can be retrieved.
@@ -929,11 +928,14 @@ class Repo:
929
928
  with open(alternates_path, "wb") as f:
930
929
  f.write("\n".join(alts).encode(defenc))
931
930
 
932
- alternates = property(
933
- _get_alternates,
934
- _set_alternates,
935
- doc="Retrieve a list of alternates paths or set a list paths to be used as alternates",
936
- )
931
+ @property
932
+ def alternates(self) -> List[str]:
933
+ """Retrieve a list of alternates paths or set a list paths to be used as alternates"""
934
+ return self._get_alternates()
935
+
936
+ @alternates.setter
937
+ def alternates(self, alts: List[str]) -> None:
938
+ self._set_alternates(alts)
937
939
 
938
940
  def is_dirty(
939
941
  self,
git/repo/fun.py CHANGED
@@ -301,7 +301,13 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
301
301
 
302
302
  # Handle type.
303
303
  if output_type == "commit":
304
- pass # Default.
304
+ obj = cast("TagObject", obj)
305
+ if obj and obj.type == "tag":
306
+ obj = deref_tag(obj)
307
+ else:
308
+ # Cannot do anything for non-tags.
309
+ pass
310
+ # END handle tag
305
311
  elif output_type == "tree":
306
312
  try:
307
313
  obj = cast(AnyGitObject, obj)
@@ -399,7 +405,7 @@ def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
399
405
  # END end handle tag
400
406
  except (IndexError, AttributeError) as e:
401
407
  raise BadName(
402
- f"Invalid revision spec '{rev}' - not enough " f"parent commits to reach '{token}{int(num)}'"
408
+ f"Invalid revision spec '{rev}' - not enough parent commits to reach '{token}{int(num)}'"
403
409
  ) from e
404
410
  # END exception handling
405
411
  # END parse loop
git/util.py CHANGED
@@ -464,6 +464,12 @@ def _is_cygwin_git(git_executable: str) -> bool:
464
464
 
465
465
  # Just a name given, not a real path.
466
466
  uname_cmd = osp.join(git_dir, "uname")
467
+
468
+ if not (pathlib.Path(uname_cmd).is_file() and os.access(uname_cmd, os.X_OK)):
469
+ _logger.debug(f"Failed checking if running in CYGWIN: {uname_cmd} is not an executable")
470
+ _is_cygwin_cache[git_executable] = is_cygwin
471
+ return is_cygwin
472
+
467
473
  process = subprocess.Popen([uname_cmd], stdout=subprocess.PIPE, universal_newlines=True)
468
474
  uname_out, _ = process.communicate()
469
475
  # retcode = process.poll()
@@ -484,7 +490,9 @@ def is_cygwin_git(git_executable: PathLike) -> bool: ...
484
490
 
485
491
 
486
492
  def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
487
- if sys.platform == "win32": # TODO: See if we can use `sys.platform != "cygwin"`.
493
+ # TODO: when py3.7 support is dropped, use the new interpolation f"{variable=}"
494
+ _logger.debug(f"sys.platform={sys.platform!r}, git_executable={git_executable!r}")
495
+ if sys.platform != "cygwin":
488
496
  return False
489
497
  elif git_executable is None:
490
498
  return False
@@ -1200,8 +1208,6 @@ class IterableList(List[T_IterableObj]):
1200
1208
  return list.__getattribute__(self, attr)
1201
1209
 
1202
1210
  def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj: # type: ignore[override]
1203
- assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str"
1204
-
1205
1211
  if isinstance(index, int):
1206
1212
  return list.__getitem__(self, index)
1207
1213
  elif isinstance(index, slice):
@@ -1214,8 +1220,6 @@ class IterableList(List[T_IterableObj]):
1214
1220
  # END handle getattr
1215
1221
 
1216
1222
  def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None:
1217
- assert isinstance(index, (int, str)), "Index of IterableList should be an int or str"
1218
-
1219
1223
  delindex = cast(int, index)
1220
1224
  if not isinstance(index, int):
1221
1225
  delindex = -1
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: GitPython
3
- Version: 3.1.44
3
+ Version: 3.1.45
4
4
  Summary: GitPython is a Python library used to interact with Git repositories
5
5
  Home-page: https://github.com/gitpython-developers/GitPython
6
6
  Author: Sebastian Thiel, Michael Trier
@@ -9,7 +9,6 @@ License: BSD-3-Clause
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Environment :: Console
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: BSD License
13
12
  Classifier: Operating System :: OS Independent
14
13
  Classifier: Operating System :: POSIX
15
14
  Classifier: Operating System :: Microsoft :: Windows
@@ -28,7 +27,7 @@ Description-Content-Type: text/markdown
28
27
  License-File: LICENSE
29
28
  License-File: AUTHORS
30
29
  Requires-Dist: gitdb<5,>=4.0.1
31
- Requires-Dist: typing-extensions>=3.7.4.3; python_version < "3.8"
30
+ Requires-Dist: typing-extensions>=3.10.0.2; python_version < "3.10"
32
31
  Provides-Extra: test
33
32
  Requires-Dist: coverage[toml]; extra == "test"
34
33
  Requires-Dist: ddt!=1.4.3,>=1.1.1; extra == "test"
@@ -45,6 +44,18 @@ Provides-Extra: doc
45
44
  Requires-Dist: sphinx<7.2,>=7.1.2; extra == "doc"
46
45
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
47
46
  Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
47
+ Dynamic: author
48
+ Dynamic: author-email
49
+ Dynamic: classifier
50
+ Dynamic: description
51
+ Dynamic: description-content-type
52
+ Dynamic: home-page
53
+ Dynamic: license
54
+ Dynamic: license-file
55
+ Dynamic: provides-extra
56
+ Dynamic: requires-dist
57
+ Dynamic: requires-python
58
+ Dynamic: summary
48
59
 
49
60
  ![Python package](https://github.com/gitpython-developers/GitPython/workflows/Python%20package/badge.svg)
50
61
  [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
@@ -1,44 +1,44 @@
1
- git/__init__.py,sha256=nkQImgv-bWdiZOFDjzN-gbt93FoRHD0nY6_t9LQxy4Y,8899
2
- git/cmd.py,sha256=QwiaBy0mFbi9xjRKhRgUVK-_-K6xVdFqh9l0cxPqPSc,67724
1
+ git/__init__.py,sha256=uSOfxveXReCHpCye__P5GcPeFwjdO-i1w3XScDWAdtA,8899
2
+ git/cmd.py,sha256=y01yN8P2JFuFP5dGzHIW9vd-uBQjPzK8yfZMZIZZb34,67472
3
3
  git/compat.py,sha256=y1E6y6O2q5r8clSlr8ZNmuIWG9nmHuehQEsVsmBffs8,4526
4
- git/config.py,sha256=vTUlK6d8ORqFqjOv4Vbq_Hm-5mp-jOAt1dkq0IdzJ3U,34933
4
+ git/config.py,sha256=ozS9-YPa6ihLRJP6iXHOk3EDK3QNdvdqYC7Zhx-itWM,35330
5
5
  git/db.py,sha256=vIW9uWSbqu99zbuU2ZDmOhVOv1UPTmxrnqiCtRHCfjE,2368
6
6
  git/diff.py,sha256=wmpMCIdMiVOqreGVPOGYyO4gFboGOAicyrvvI7PPjEg,27095
7
7
  git/exc.py,sha256=Gc7g1pHpn8OmTse30NHmJVsBJ2CYH8LxaR8y8UA3lIM,7119
8
8
  git/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  git/remote.py,sha256=pYn9dAlz-QwvNMWXD1M57pMPQitthOM86qTRK_cpTqU,46786
10
10
  git/types.py,sha256=MQzIDEOnoueXGsAJF_0MgUc_osH7Eu0Sw3DQofYzCVE,10272
11
- git/util.py,sha256=2uAv34zZ_827-zJ3-D5ACrVH-4Q4EO_KLUTH23zi2AI,43770
11
+ git/util.py,sha256=vOspIY3BSXR3UUhlVBA9lCryCzeUWQwvatFNYt2AkfQ,43981
12
12
  git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
13
- git/index/base.py,sha256=nDD7XVLNbgBKpJMrrTVyHBy6NVLWgDkk7oUw6ZOegPc,60808
14
- git/index/fun.py,sha256=37cA3DBC9vpAnSVu5TGA072SnoF5XZOkOukExwlejHs,16736
13
+ git/index/base.py,sha256=qG1hOdiZ0OyyerlN49z8kosOY-Fk8elOOH6_4O4aKsQ,61065
14
+ git/index/fun.py,sha256=tbE2qyVo35fRyH_eDu65PTX3jG7Ii0NrdH6L5zswFv4,16810
15
15
  git/index/typ.py,sha256=uuKNwitUw83FhVaLSwo4pY7PHDQudtZTLJrLGym4jcI,6570
16
16
  git/index/util.py,sha256=fULi7GPG-MvprKrRCD5c15GNdzku_1E38We0d97WB3A,3659
17
17
  git/objects/__init__.py,sha256=O6ZL_olX7e5-8iIbKviRPkVSJxN37WA-EC0q9d48U5Y,637
18
- git/objects/base.py,sha256=0dqNkSRVH0mk0-7ZKIkGBK7iNYrzLTVxwQFUd6CagsE,10277
18
+ git/objects/base.py,sha256=5-p9uSGWvPxX9hidvkGH9tQOJoH2eaRggUbMr9VJiL0,10285
19
19
  git/objects/blob.py,sha256=zwwq0KfOMYeP5J2tW5CQatoLyeqFRlfkxP1Vwx1h07s,1215
20
- git/objects/commit.py,sha256=GH1_83C9t7RGTukwozTHDgvxYQPRjTHhPDkXJyBbJyo,30553
20
+ git/objects/commit.py,sha256=oHdYcKxsW5HDHCEa225nHXKYdcQeOwdVrBbGjupJK9Y,30560
21
21
  git/objects/fun.py,sha256=B4jCqhAjm6Hl79GK58FPzW1H9K6Wc7Tx0rssyWmAcEE,8935
22
22
  git/objects/tag.py,sha256=jAGESnpmTEv-dLakPzheT5ILZFFArcItnXYqfxfDrgc,4441
23
- git/objects/tree.py,sha256=jJH888SHiP4dGzE-ra1yenQOyya_0C_MkHr06c1gHpM,13849
23
+ git/objects/tree.py,sha256=QzFHPk3bgQVeoA-lId6gi_0QyGGI6V2wp_icAhitdzI,13847
24
24
  git/objects/util.py,sha256=Nlza4zLgdPmr_Yasyvvs6c1rKtW_wMxI6wDmQpQ3ufw,23846
25
25
  git/objects/submodule/__init__.py,sha256=6xySp767LVz3UylWgUalntS_nGXRuVzXxDuFAv_Wc2c,303
26
- git/objects/submodule/base.py,sha256=MQ-2xV8JznGwy2hLQv1aeQNgAkhBhgc5tdtClFL3DmE,63901
26
+ git/objects/submodule/base.py,sha256=uSaBBs_y9eg3xbf3viP173HKQOVWY9TpWp2FX5VGFPI,64343
27
27
  git/objects/submodule/root.py,sha256=5eTtYNHasqdPq6q0oDCPr7IaO6uAHL3b4DxMoiO2LhE,20246
28
28
  git/objects/submodule/util.py,sha256=sQqAYaiSJdFkZa9NlAuK_wTsMNiS-kkQnQjvIoJtc_o,3509
29
29
  git/refs/__init__.py,sha256=DWlJNnsx-4jM_E-VycbP-FZUdn6iWhjnH_uZ_pZXBro,509
30
30
  git/refs/head.py,sha256=SGa3N301HfAi79X6UR5Mcg7mO9TnCH3Bk549kHlJVaQ,10513
31
- git/refs/log.py,sha256=kXiuAgTo1DIuM_BfbDUk9gQ0YO-mutIMVdHv1_ES90o,12493
31
+ git/refs/log.py,sha256=w31EeCsG1_8ZvW3RkgifmzQIcaBhFuN7fIZ3XTQxLHU,12490
32
32
  git/refs/reference.py,sha256=l6mhF4YLSEwtjz6b9PpOQH-fkng7EYWMaJhkjn-2jXA,5630
33
33
  git/refs/remote.py,sha256=WwqV9T7BbYf3F_WZNUQivu9xktIIKGklCjDpwQrhD-A,2806
34
- git/refs/symbolic.py,sha256=c8zOwaqzcg-J-rGrpuWdvh8zwMvSUqAHghd4vJoYG_s,34552
34
+ git/refs/symbolic.py,sha256=z5rUgeqRzMcXbbMlF8L13j3lu-ycI9azHa3-vToVuIM,34769
35
35
  git/refs/tag.py,sha256=kgzV2vhpL4FD2TqHb0BJuMRAHgAvJF-TcoyWlaB-djQ,5010
36
36
  git/repo/__init__.py,sha256=CILSVH36fX_WxVFSjD9o1WF5LgsNedPiJvSngKZqfVU,210
37
- git/repo/base.py,sha256=0GU6nKNdT8SYjDI5Y5DeZ1zCEX3tHeq1VW2MSpne05g,59891
38
- git/repo/fun.py,sha256=HSGC0-rqeKKx9fDg7JyQyMZgIwUWn-FnSZR_gRGpG-E,13573
39
- GitPython-3.1.44.dist-info/AUTHORS,sha256=tZ9LuyBks2V2HKTPK7kCmtd9Guu_LyU1oZHvU0NiAok,2334
40
- GitPython-3.1.44.dist-info/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
- GitPython-3.1.44.dist-info/METADATA,sha256=0O_Fr2Y7A-DlPYhlbSxGjblBC2mWkw3USNUhyL80Ip8,13245
42
- GitPython-3.1.44.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
43
- GitPython-3.1.44.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
- GitPython-3.1.44.dist-info/RECORD,,
37
+ git/repo/base.py,sha256=P81qtQiz5lcNaO97sFtmg1Tk9tasc0bgpgioF1rLApo,59972
38
+ git/repo/fun.py,sha256=LZewqHrAngGH5Q7YyUwIGP93GjlGD3WZbtzsh8LwGak,13803
39
+ gitpython-3.1.45.dist-info/licenses/AUTHORS,sha256=_upJOkW3eLK_ZywFzQDkKDp4JtF6xBf_Qk0ObAqTPvE,2347
40
+ gitpython-3.1.45.dist-info/licenses/LICENSE,sha256=hvyUwyGpr7wRUUcTURuv3tIl8lEA3MD3NQ6CvCMbi-s,1503
41
+ gitpython-3.1.45.dist-info/METADATA,sha256=Qu9hdYes2KAIsOoRh7JI-Zm7qeLrp_ccH36FvllVuKk,13456
42
+ gitpython-3.1.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
+ gitpython-3.1.45.dist-info/top_level.txt,sha256=0hzDuIp8obv624V3GmbqsagBWkk8ohtGU-Bc1PmTT0o,4
44
+ gitpython-3.1.45.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -55,5 +55,6 @@ Contributors are:
55
55
  -Eliah Kagan <eliah.kagan _at_ gmail.com>
56
56
  -Ethan Lin <et.repositories _at_ gmail.com>
57
57
  -Jonas Scharpf <jonas.scharpf _at_ checkmk.com>
58
+ -Gordon Marx
58
59
 
59
60
  Portions derived from other open source works and are clearly marked.