GitPython 3.1.43__py3-none-any.whl → 3.1.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git/__init__.py +1 -1
- git/cmd.py +240 -222
- git/config.py +18 -11
- git/diff.py +6 -6
- git/index/base.py +34 -20
- git/index/fun.py +1 -1
- git/objects/base.py +2 -2
- git/objects/commit.py +19 -9
- git/objects/submodule/base.py +11 -1
- git/objects/tag.py +2 -3
- git/objects/tree.py +3 -1
- git/objects/util.py +3 -3
- git/refs/head.py +2 -2
- git/refs/log.py +1 -1
- git/refs/symbolic.py +24 -17
- git/remote.py +14 -7
- git/repo/base.py +43 -25
- git/repo/fun.py +9 -3
- git/types.py +1 -0
- git/util.py +13 -7
- {GitPython-3.1.43.dist-info → gitpython-3.1.45.dist-info}/METADATA +35 -26
- gitpython-3.1.45.dist-info/RECORD +44 -0
- {GitPython-3.1.43.dist-info → gitpython-3.1.45.dist-info}/WHEEL +1 -1
- {GitPython-3.1.43.dist-info → gitpython-3.1.45.dist-info/licenses}/AUTHORS +2 -0
- GitPython-3.1.43.dist-info/RECORD +0 -44
- {GitPython-3.1.43.dist-info → gitpython-3.1.45.dist-info/licenses}/LICENSE +0 -0
- {GitPython-3.1.43.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:
|
|
@@ -1269,6 +1286,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1269
1286
|
stdout=stdout_sink,
|
|
1270
1287
|
shell=shell,
|
|
1271
1288
|
universal_newlines=universal_newlines,
|
|
1289
|
+
encoding=defenc if universal_newlines else None,
|
|
1272
1290
|
**subprocess_kwargs,
|
|
1273
1291
|
)
|
|
1274
1292
|
except cmd_not_found_exception as err:
|
|
@@ -1318,7 +1336,7 @@ class Git(metaclass=_GitMeta):
|
|
|
1318
1336
|
out, err = proc.communicate()
|
|
1319
1337
|
watchdog.cancel()
|
|
1320
1338
|
if kill_check.is_set():
|
|
1321
|
-
err = 'Timeout: the command "%s" did not complete in %d
|
|
1339
|
+
err = 'Timeout: the command "%s" did not complete in %d secs.' % (
|
|
1322
1340
|
" ".join(redacted_command),
|
|
1323
1341
|
timeout,
|
|
1324
1342
|
)
|
git/config.py
CHANGED
|
@@ -87,15 +87,15 @@ class MetaParserBuilder(abc.ABCMeta): # noqa: B024
|
|
|
87
87
|
mutating_methods = clsdict[kmm]
|
|
88
88
|
for base in bases:
|
|
89
89
|
methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
|
|
90
|
-
for
|
|
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
|
|
@@ -452,7 +452,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
452
452
|
e = None # None, or an exception.
|
|
453
453
|
|
|
454
454
|
def string_decode(v: str) -> str:
|
|
455
|
-
if v
|
|
455
|
+
if v and v.endswith("\\"):
|
|
456
456
|
v = v[:-1]
|
|
457
457
|
# END cut trailing escapes to prevent decode error
|
|
458
458
|
|
|
@@ -496,19 +496,26 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
|
|
|
496
496
|
if mo:
|
|
497
497
|
# We might just have handled the last line, which could contain a quotation we want to remove.
|
|
498
498
|
optname, vi, optval = mo.group("option", "vi", "value")
|
|
499
|
+
optname = self.optionxform(optname.rstrip())
|
|
500
|
+
|
|
499
501
|
if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'):
|
|
500
502
|
pos = optval.find(";")
|
|
501
503
|
if pos != -1 and optval[pos - 1].isspace():
|
|
502
504
|
optval = optval[:pos]
|
|
503
505
|
optval = optval.strip()
|
|
504
|
-
|
|
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/diff.py
CHANGED
|
@@ -187,7 +187,7 @@ class Diffable:
|
|
|
187
187
|
paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
|
|
188
188
|
create_patch: bool = False,
|
|
189
189
|
**kwargs: Any,
|
|
190
|
-
) -> "DiffIndex":
|
|
190
|
+
) -> "DiffIndex[Diff]":
|
|
191
191
|
"""Create diffs between two items being trees, trees and index or an index and
|
|
192
192
|
the working tree. Detects renames automatically.
|
|
193
193
|
|
|
@@ -325,7 +325,7 @@ class DiffIndex(List[T_Diff]):
|
|
|
325
325
|
yield diffidx
|
|
326
326
|
elif change_type == "C" and diffidx.copied_file:
|
|
327
327
|
yield diffidx
|
|
328
|
-
elif change_type == "R" and diffidx.
|
|
328
|
+
elif change_type == "R" and diffidx.renamed_file:
|
|
329
329
|
yield diffidx
|
|
330
330
|
elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
|
|
331
331
|
yield diffidx
|
|
@@ -581,7 +581,7 @@ class Diff:
|
|
|
581
581
|
return None
|
|
582
582
|
|
|
583
583
|
@classmethod
|
|
584
|
-
def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex:
|
|
584
|
+
def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex["Diff"]:
|
|
585
585
|
"""Create a new :class:`DiffIndex` from the given process output which must be
|
|
586
586
|
in patch format.
|
|
587
587
|
|
|
@@ -674,7 +674,7 @@ class Diff:
|
|
|
674
674
|
return index
|
|
675
675
|
|
|
676
676
|
@staticmethod
|
|
677
|
-
def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> None:
|
|
677
|
+
def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]) -> None:
|
|
678
678
|
lines = lines_bytes.decode(defenc)
|
|
679
679
|
|
|
680
680
|
# Discard everything before the first colon, and the colon itself.
|
|
@@ -695,7 +695,7 @@ class Diff:
|
|
|
695
695
|
change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
|
|
696
696
|
score_str = "".join(_change_type[1:])
|
|
697
697
|
score = int(score_str) if score_str.isdigit() else None
|
|
698
|
-
path = path.strip()
|
|
698
|
+
path = path.strip("\n")
|
|
699
699
|
a_path = path.encode(defenc)
|
|
700
700
|
b_path = path.encode(defenc)
|
|
701
701
|
deleted_file = False
|
|
@@ -747,7 +747,7 @@ class Diff:
|
|
|
747
747
|
index.append(diff)
|
|
748
748
|
|
|
749
749
|
@classmethod
|
|
750
|
-
def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex":
|
|
750
|
+
def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]":
|
|
751
751
|
"""Create a new :class:`DiffIndex` from the given process output which must be
|
|
752
752
|
in raw format.
|
|
753
753
|
|