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 +1 -1
- git/cmd.py +239 -222
- git/config.py +17 -10
- git/index/base.py +24 -10
- git/index/fun.py +1 -1
- git/objects/base.py +2 -2
- git/objects/commit.py +2 -2
- git/objects/submodule/base.py +11 -1
- git/objects/tree.py +3 -1
- git/refs/log.py +1 -1
- git/refs/symbolic.py +24 -17
- git/repo/base.py +22 -20
- git/repo/fun.py +8 -2
- git/util.py +9 -5
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info}/METADATA +15 -4
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info}/RECORD +20 -20
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info}/WHEEL +1 -1
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info/licenses}/AUTHORS +1 -0
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info/licenses}/LICENSE +0 -0
- {GitPython-3.1.44.dist-info → gitpython-3.1.45.dist-info}/top_level.txt +0 -0
git/__init__.py
CHANGED
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
91
|
-
if
|
|
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
|
|
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[
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
700
|
+
def open_stream() -> BinaryIO:
|
|
701
|
+
return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc))
|
|
694
702
|
else:
|
|
695
|
-
|
|
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``,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
git/objects/submodule/base.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
|
397
|
-
|
|
398
|
-
set_object
|
|
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
|
-
|
|
500
|
-
reference
|
|
501
|
-
_get_reference
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
358
|
-
def
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: GitPython
|
|
3
|
-
Version: 3.1.
|
|
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.
|
|
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
|

|
|
50
61
|
[](https://readthedocs.org/projects/gitpython/?badge=stable)
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
git/__init__.py,sha256=
|
|
2
|
-
git/cmd.py,sha256=
|
|
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=
|
|
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=
|
|
11
|
+
git/util.py,sha256=vOspIY3BSXR3UUhlVBA9lCryCzeUWQwvatFNYt2AkfQ,43981
|
|
12
12
|
git/index/__init__.py,sha256=i-Nqb8Lufp9aFbmxpQBORmmQnjEVVM1Pn58fsQkyGgQ,406
|
|
13
|
-
git/index/base.py,sha256=
|
|
14
|
-
git/index/fun.py,sha256=
|
|
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=
|
|
18
|
+
git/objects/base.py,sha256=5-p9uSGWvPxX9hidvkGH9tQOJoH2eaRggUbMr9VJiL0,10285
|
|
19
19
|
git/objects/blob.py,sha256=zwwq0KfOMYeP5J2tW5CQatoLyeqFRlfkxP1Vwx1h07s,1215
|
|
20
|
-
git/objects/commit.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
38
|
-
git/repo/fun.py,sha256=
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,,
|
|
File without changes
|
|
File without changes
|