@@ -733,12 +733,17 @@ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]
733733 # Discard everything before the first colon, and the colon itself.
734734 _ , _ , lines = lines .partition (":" )
735735
736- for line in lines .split ("\x00 :" ):
737- if not line :
736+ # Each record is a metadata field starting with a colon, followed by one path,
737+ # or two for copies and renames, each terminated by NUL. A path may itself
738+ # start with a colon or contain a line feed, so read the fields in order
739+ # instead of splitting at colons.
740+ fields = iter (lines .rstrip ("\n " ).split ("\x00 " ))
741+ for meta in fields :
742+ if not meta :
738743 # The line data is empty, skip.
739744 continue
740- meta , _ , path = line . partition ( " \x00 " )
741- path = path . rstrip ( " \x00 " )
745+ if meta . startswith ( ":" ):
746+ meta = meta [ 1 :]
742747 a_blob_id : Optional [str ]
743748 b_blob_id : Optional [str ]
744749 old_mode , new_mode , a_blob_id , b_blob_id , _change_type = meta .split (None , 4 )
@@ -748,7 +753,7 @@ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]
748753 change_type : Lit_change_type = cast (Lit_change_type , _change_type [0 ])
749754 score_str = "" .join (_change_type [1 :])
750755 score = int (score_str ) if score_str .isdigit () else None
751- path = path . strip ( " \n " )
756+ path = next ( fields , " " )
752757 a_path = path .encode (defenc )
753758 b_path = path .encode (defenc )
754759 deleted_file = False
@@ -767,13 +772,9 @@ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex["Diff"]
767772 new_file = True
768773 elif change_type == "C" :
769774 copied_file = True
770- a_path_str , b_path_str = path .split ("\x00 " , 1 )
771- a_path = a_path_str .encode (defenc )
772- b_path = b_path_str .encode (defenc )
775+ b_path = next (fields , "" ).encode (defenc )
773776 elif change_type == "R" :
774- a_path_str , b_path_str = path .split ("\x00 " , 1 )
775- a_path = a_path_str .encode (defenc )
776- b_path = b_path_str .encode (defenc )
777+ b_path = next (fields , "" ).encode (defenc )
777778 rename_from , rename_to = a_path , b_path
778779 elif change_type == "T" :
779780 # Nothing to do.
@@ -817,17 +818,17 @@ def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex[Diff]
817818 # :100644 100644 687099101... 37c5e30c8... M .gitignore
818819
819820 index : "DiffIndex" = DiffIndex ()
821+ text_list : List [bytes ] = []
820822 stderr_list : List [bytes ] = []
821823
822824 def finalize_process_with_stderr (proc : Union ["Popen" , "Git.AutoInterrupt" ]) -> None :
823825 finalize_process (proc , stderr = b"" .join (stderr_list ))
824826
827+ # Output is read line by line, but paths may contain line feeds, so parse
828+ # the records only once all of it has been read.
825829 handle_process_output (
826- proc ,
827- lambda byt : cls ._handle_diff_line (byt , repo , index ),
828- stderr_list .append ,
829- finalize_process_with_stderr ,
830- decode_streams = False ,
830+ proc , text_list .append , stderr_list .append , finalize_process_with_stderr , decode_streams = False
831831 )
832+ cls ._handle_diff_line (b"" .join (text_list ), repo , index )
832833
833834 return index
0 commit comments