git-p4: refactoring of p4CmdList()

The function p4CmdList executes a Perforce command and decodes the marshalled python dictionary. Special care has to be taken for certain dictionary values which contain non-unicode characters. The old handling contained separate hacks for each of the corresponding dictionary keys. This commit tries to refactor the coding to handle the special cases uniformely. Signed-off-by: Kilian Kilger <kkilger@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kilian Kilger committed Jul 21, 2022 at 09:07 UTC d205483695366803e912da6ae24380c33c293467
1 file changed +42 -11
git-p4.py
+42 -11
@@ -822,6 +822,42 @@ def isModeExecChanged(src_mode, dst_mode):
822 return isModeExec(src_mode) != isModeExec(dst_mode)
823
824
825 +def p4KeysContainingNonUtf8Chars():
826 + """Returns all keys which may contain non UTF-8 encoded strings
827 + for which a fallback strategy has to be applied.
828 + """
829 + return ['desc', 'client', 'FullName']
830 +
831 +
832 +def p4KeysContainingBinaryData():
833 + """Returns all keys which may contain arbitrary binary data
834 + """
835 + return ['data']
836 +
837 +
838 +def p4KeyContainsFilePaths(key):
839 + """Returns True if the key contains file paths. These are handled by decode_path().
840 + Otherwise False.
841 + """
842 + return key.startswith('depotFile') or key in ['path', 'clientFile']
843 +
844 +
845 +def p4KeyWhichCanBeDirectlyDecoded(key):
846 + """Returns True if the key can be directly decoded as UTF-8 string
847 + Otherwise False.
848 +
849 + Keys which can not be encoded directly:
850 + - `data` which may contain arbitrary binary data
851 + - `desc` or `client` or `FullName` which may contain non-UTF8 encoded text
852 + - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text, handled by decode_path()
853 + """
854 + if key in p4KeysContainingNonUtf8Chars() or \
855 + key in p4KeysContainingBinaryData() or \
856 + p4KeyContainsFilePaths(key):
857 + return False
858 + return True
859 +
860 +
861 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
862 errors_as_exceptions=False, *k, **kw):
863
@@ -851,15 +887,13 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
887 try:
888 while True:
889 entry = marshal.load(p4.stdout)
890 +
891 if bytes is not str:
855 - # Decode unmarshalled dict to use str keys and values, except for:
856 - # - `data` which may contain arbitrary binary data
857 - # - `desc` or `client` or `FullName` which may contain non-UTF8 encoded text handled below, eagerly converted to bytes
858 - # - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text, handled by decode_path()
892 + # Decode unmarshalled dict to use str keys and values. Special cases are handled below.
893 decoded_entry = {}
894 for key, value in entry.items():
895 key = key.decode()
862 - if isinstance(value, bytes) and not (key in ('data', 'desc', 'FullName', 'path', 'clientFile', 'client') or key.startswith('depotFile')):
896 + if isinstance(value, bytes) and p4KeyWhichCanBeDirectlyDecoded(key):
897 value = value.decode()
898 decoded_entry[key] = value
899 # Parse out data if it's an error response
@@ -869,12 +903,9 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
903 if skip_info:
904 if 'code' in entry and entry['code'] == 'info':
905 continue
872 - if 'desc' in entry:
873 - entry['desc'] = metadata_stream_to_writable_bytes(entry['desc'])
874 - if 'client' in entry:
875 - entry['client'] = metadata_stream_to_writable_bytes(entry['client'])
876 - if 'FullName' in entry:
877 - entry['FullName'] = metadata_stream_to_writable_bytes(entry['FullName'])
906 + for key in p4KeysContainingNonUtf8Chars():
907 + if key in entry:
908 + entry[key] = metadata_stream_to_writable_bytes(entry[key])
909 if cb is not None:
910 cb(entry)
911 else: