1919import threading
2020from collections import OrderedDict
2121from textwrap import dedent
22- from typing import Any , Dict , List , Optional
22+ import warnings
2323
2424from git .compat import (
2525defenc ,
2929is_win ,
3030)
3131from git .exc import CommandError
32- from git .util import is_cygwin_git ,cygpath ,expand_path
32+ from git .util import is_cygwin_git ,cygpath ,expand_path , remove_password_if_present
3333
3434from .exc import (
3535GitCommandError ,
4040stream_copy ,
4141)
4242
43- from .types import PathLike
44-
4543execute_kwargs = {'istream' ,'with_extended_output' ,
4644'with_exceptions' ,'as_process' ,'stdout_as_string' ,
4745'output_stream' ,'with_stdout' ,'kill_after_timeout' ,
@@ -85,8 +83,8 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
8583line = line .decode (defenc )
8684handler (line )
8785except Exception as ex :
88- log .error ("Pumping %r of cmd(%s) failed due to: %r" ,name ,cmdline ,ex )
89- raise CommandError (['<%s-pump>' % name ]+ cmdline ,ex )from ex
86+ log .error ("Pumping %r of cmd(%s) failed due to: %r" ,name ,remove_password_if_present ( cmdline ) ,ex )
87+ raise CommandError (['<%s-pump>' % name ]+ remove_password_if_present ( cmdline ) ,ex )from ex
9088finally :
9189stream .close ()
9290
@@ -105,7 +103,7 @@ def pump_stream(cmdline, name, stream, is_decode, handler):
105103for name ,stream ,handler in pumps :
106104t = threading .Thread (target = pump_stream ,
107105args = (cmdline ,name ,stream ,decode_streams ,handler ))
108- t .setDaemon ( True )
106+ t .daemon = True
109107t .start ()
110108threads .append (t )
111109
@@ -140,7 +138,7 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
140138
141139## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
142140# see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
143- PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP
141+ PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess .CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
144142if is_win else 0 )
145143
146144
@@ -212,7 +210,7 @@ def refresh(cls, path=None):
212210# - a GitCommandNotFound error is spawned by ourselves
213211# - a PermissionError is spawned if the git executable provided
214212# cannot be executed for whatever reason
215-
213+
216214has_git = False
217215try :
218216cls ().version ()
@@ -408,7 +406,7 @@ def read_all_from_possibly_closed_stream(stream):
408406if status != 0 :
409407errstr = read_all_from_possibly_closed_stream (self .proc .stderr )
410408log .debug ('AutoInterrupt wait stderr: %r' % (errstr ,))
411- raise GitCommandError (self .args ,status ,errstr )
409+ raise GitCommandError (remove_password_if_present ( self .args ) ,status ,errstr )
412410# END status handling
413411return status
414412# END auto interrupt
@@ -500,7 +498,7 @@ def readlines(self, size=-1):
500498# skipcq: PYL-E0301
501499def __iter__ (self ):
502500return self
503-
501+
504502def __next__ (self ):
505503return self .next ()
506504
@@ -519,7 +517,7 @@ def __del__(self):
519517self ._stream .read (bytes_left + 1 )
520518# END handle incomplete read
521519
522- def __init__ (self ,working_dir : Optional [ PathLike ] = None )-> None :
520+ def __init__ (self ,working_dir = None ):
523521"""Initialize this instance with:
524522
525523 :param working_dir:
@@ -528,12 +526,12 @@ def __init__(self, working_dir: Optional[PathLike]=None) -> None:
528526 It is meant to be the working tree directory if available, or the
529527 .git directory in case of bare repositories."""
530528super (Git ,self ).__init__ ()
531- self ._working_dir = expand_path (working_dir )if working_dir is not None else None
529+ self ._working_dir = expand_path (working_dir )
532530self ._git_options = ()
533- self ._persistent_git_options = []# type: List[str]
531+ self ._persistent_git_options = []
534532
535533# Extra environment variables to pass to git commands
536- self ._environment = {}# type: Dict[str, Any]
534+ self ._environment = {}
537535
538536# cached command slots
539537self .cat_file_header = None
@@ -547,7 +545,7 @@ def __getattr__(self, name):
547545return LazyMixin .__getattr__ (self ,name )
548546return lambda * args ,** kwargs :self ._call_process (name ,* args ,** kwargs )
549547
550- def set_persistent_git_options (self ,** kwargs )-> None :
548+ def set_persistent_git_options (self ,** kwargs ):
551549"""Specify command line options to the git executable
552550 for subsequent subcommand calls
553551
@@ -641,7 +639,7 @@ def execute(self, command,
641639
642640 :param env:
643641 A dictionary of environment variables to be passed to `subprocess.Popen`.
644-
642+
645643 :param max_chunk_size:
646644 Maximum number of bytes in one chunk of data passed to the output_stream in
647645 one invocation of write() method. If the given number is not positive then
@@ -685,8 +683,10 @@ def execute(self, command,
685683 :note:
686684 If you add additional keyword arguments to the signature of this method,
687685 you must update the execute_kwargs tuple housed in this module."""
686+ # Remove password for the command if present
687+ redacted_command = remove_password_if_present (command )
688688if self .GIT_PYTHON_TRACE and (self .GIT_PYTHON_TRACE != 'full' or as_process ):
689- log .info (' ' .join (command ))
689+ log .info (' ' .join (redacted_command ))
690690
691691# Allow the user to have the command executed in their working dir.
692692cwd = self ._working_dir or os .getcwd ()
@@ -707,7 +707,7 @@ def execute(self, command,
707707if is_win :
708708cmd_not_found_exception = OSError
709709if kill_after_timeout :
710- raise GitCommandError (command ,'"kill_after_timeout" feature is not supported on Windows.' )
710+ raise GitCommandError (redacted_command ,'"kill_after_timeout" feature is not supported on Windows.' )
711711else :
712712if sys .version_info [0 ]> 2 :
713713cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
@@ -722,7 +722,7 @@ def execute(self, command,
722722if istream :
723723istream_ok = "<valid stream>"
724724log .debug ("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)" ,
725- command ,cwd ,universal_newlines ,shell ,istream_ok )
725+ redacted_command ,cwd ,universal_newlines ,shell ,istream_ok )
726726try :
727727proc = Popen (command ,
728728env = env ,
@@ -738,7 +738,7 @@ def execute(self, command,
738738** subprocess_kwargs
739739 )
740740except cmd_not_found_exception as err :
741- raise GitCommandNotFound (command ,err )from err
741+ raise GitCommandNotFound (redacted_command ,err )from err
742742
743743if as_process :
744744return self .AutoInterrupt (proc ,command )
@@ -788,7 +788,7 @@ def _kill_process(pid):
788788watchdog .cancel ()
789789if kill_check .isSet ():
790790stderr_value = ('Timeout: the command "%s" did not complete in %d '
791- 'secs.' % (" " .join (command ),kill_after_timeout ))
791+ 'secs.' % (" " .join (redacted_command ),kill_after_timeout ))
792792if not universal_newlines :
793793stderr_value = stderr_value .encode (defenc )
794794# strip trailing "\n"
@@ -812,7 +812,7 @@ def _kill_process(pid):
812812proc .stderr .close ()
813813
814814if self .GIT_PYTHON_TRACE == 'full' :
815- cmdstr = " " .join (command )
815+ cmdstr = " " .join (redacted_command )
816816
817817def as_text (stdout_value ):
818818return not output_stream and safe_decode (stdout_value )or '<OUTPUT_STREAM>'
@@ -828,7 +828,7 @@ def as_text(stdout_value):
828828# END handle debug printing
829829
830830if with_exceptions and status != 0 :
831- raise GitCommandError (command ,status ,stderr_value ,stdout_value )
831+ raise GitCommandError (redacted_command ,status ,stderr_value ,stdout_value )
832832
833833if isinstance (stdout_value ,bytes )and stdout_as_string :# could also be output_stream
834834stdout_value = safe_decode (stdout_value )
@@ -905,8 +905,14 @@ def transform_kwarg(self, name, value, split_single_char_options):
905905
906906def transform_kwargs (self ,split_single_char_options = True ,** kwargs ):
907907"""Transforms Python style kwargs into git command line options."""
908+ # Python 3.6 preserves the order of kwargs and thus has a stable
909+ # order. For older versions sort the kwargs by the key to get a stable
910+ # order.
911+ if sys .version_info [:2 ]< (3 ,6 ):
912+ kwargs = OrderedDict (sorted (kwargs .items (),key = lambda x :x [0 ]))
913+ warnings .warn ("Python 3.5 support is deprecated and will be removed 2021-09-05.\n " +
914+ "It does not preserve the order for key-word arguments and enforce lexical sorting instead." )
908915args = []
909- kwargs = OrderedDict (sorted (kwargs .items (),key = lambda x :x [0 ]))
910916for k ,v in kwargs .items ():
911917if isinstance (v , (list ,tuple )):
912918for value in v :