3838
3939import git .diff as git_diff
4040import os .path as osp
41+ from pathlib import Path
42+ from typing import Optional
4143
4244from .fun import (
4345 entry_key ,
8789Treeish = Union [Tree , Commit , str , bytes ]
8890
8991# ------------------------------------------------------------------------------------
92+ class _FileStore :
93+ """An utility class that stores original files somewhere and restores them
94+ to the original content at the exit"""
95+
96+ _dir : PathLike
97+
98+ def __init__ (self , tmp_dir : Optional [PathLike ] = None ):
99+
100+ self ._file_map : dict [PathLike , PathLike ] = {}
101+ self ._tmp_dir = tempfile .TemporaryDirectory (prefix = str (tmp_dir ))
102+
103+ def __enter__ (self ):
104+ return self
105+
106+ def __exit__ (self , exc , value , tb ):
107+ for file , store_file in self ._file_map .items ():
108+ with open (store_file , "rb" ) as rf , open (file , "wb" ) as wf :
109+ for line in rf :
110+ wf .write (line )
111+ Path (store_file ).unlink ()
112+ self ._dir .rmdir ()
113+
114+ @property
115+ def _dir (self ) -> Path :
116+ return Path (self ._tmp_dir .name )
117+
118+ def save (self , file : PathLike ) -> None :
119+ store_file = self ._dir / tempfile .mktemp ()
120+ self ._file_map [file ] = store_file
121+ with open (store_file , "wb" ) as wf , open (file , "rb" ) as rf :
122+ for line in rf :
123+ wf .write (line )
90124
91125
92126__all__ = ("IndexFile" , "CheckoutError" )
@@ -611,7 +645,7 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
611645 return os .path .relpath (path , self .repo .working_tree_dir )
612646
613647 def _preprocess_add_items (
614- self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , "Submodule" ]]
648+ self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , "Submodule" ]], file_store : _FileStore
615649 ) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
616650 """Split the items into two lists of path strings and BaseEntries."""
617651 paths = []
@@ -622,6 +656,7 @@ def _preprocess_add_items(
622656
623657 for item in items :
624658 if isinstance (item , (str , os .PathLike )):
659+ self ._autocrlf (item , file_store )
625660 paths .append (self ._to_relative_path (item ))
626661 elif isinstance (item , (Blob , Submodule )):
627662 entries .append (BaseIndexEntry .from_blob (item ))
@@ -632,6 +667,30 @@ def _preprocess_add_items(
632667 # END for each item
633668 return paths , entries
634669
670+ def _autocrlf (self , file : PathLike , file_store : _FileStore ) -> None :
671+ """If the config option `autocrlf` is True, replace CRLF with LF"""
672+
673+ reader = self .repo .config_reader ()
674+
675+ autocrlf = reader .get_value ("core" , "autocrlf" , False )
676+
677+ if not autocrlf :
678+ return
679+
680+ file_store .save (file )
681+
682+ with tempfile .TemporaryFile ("wb+" ) as tf :
683+ with open (file , "rb" ) as f :
684+ for line in f :
685+ line = line .replace (b"\r \n " , b"\n " )
686+ tf .write (line )
687+
688+ tf .seek (0 )
689+
690+ with open (file , "wb" ) as f :
691+ for line in tf :
692+ f .write (line )
693+
635694 def _store_path (self , filepath : PathLike , fprogress : Callable ) -> BaseIndexEntry :
636695 """Store file at filepath in the database and return the base index entry
637696 Needs the git_working_dir decorator active ! This must be assured in the calling code"""
@@ -802,82 +861,79 @@ def add(
802861 Objects that do not have a null sha will be added even if their paths
803862 do not exist.
804863 """
805- # sort the entries into strings and Entries, Blobs are converted to entries
806- # automatically
807- # paths can be git-added, for everything else we use git-update-index
808- paths , entries = self ._preprocess_add_items (items )
809- entries_added : List [BaseIndexEntry ] = []
810- # This code needs a working tree, therefore we try not to run it unless required.
811- # That way, we are OK on a bare repository as well.
812- # If there are no paths, the rewriter has nothing to do either
813- if paths :
814- entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
815-
816- # HANDLE ENTRIES
817- if entries :
818- null_mode_entries = [e for e in entries if e .mode == 0 ]
819- if null_mode_entries :
820- raise ValueError (
821- "At least one Entry has a null-mode - please use index.remove to remove files for clarity"
822- )
823- # END null mode should be remove
824-
825- # HANDLE ENTRY OBJECT CREATION
826- # create objects if required, otherwise go with the existing shas
827- null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
828- if null_entries_indices :
829-
830- @git_working_dir
831- def handle_null_entries (self : "IndexFile" ) -> None :
832- for ei in null_entries_indices :
833- null_entry = entries [ei ]
834- new_entry = self ._store_path (null_entry .path , fprogress )
835-
836- # update null entry
837- entries [ei ] = BaseIndexEntry (
838- (
839- null_entry .mode ,
840- new_entry .binsha ,
841- null_entry .stage ,
842- null_entry .path ,
864+
865+ with _FileStore () as file_store :
866+ # sort the entries into strings and Entries, Blobs are converted to entries
867+ # automatically
868+ # paths can be git-added, for everything else we use git-update-index
869+ paths , entries = self ._preprocess_add_items (items , file_store )
870+ entries_added : List [BaseIndexEntry ] = []
871+ # This code needs a working tree, therefore we try not to run it unless required.
872+ # That way, we are OK on a bare repository as well.
873+ # If there are no paths, the rewriter has nothing to do either
874+ if paths :
875+ entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
876+
877+ # HANDLE ENTRIES
878+ if entries :
879+ null_mode_entries = [e for e in entries if e .mode == 0 ]
880+ if null_mode_entries :
881+ raise ValueError (
882+ "At least one Entry has a null-mode - please use index.remove to remove files for clarity"
883+ )
884+ # END null mode should be remove
885+
886+ # HANDLE ENTRY OBJECT CREATION
887+ # create objects if required, otherwise go with the existing shas
888+ null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
889+ if null_entries_indices :
890+
891+ @git_working_dir
892+ def handle_null_entries (self : "IndexFile" ) -> None :
893+ for ei in null_entries_indices :
894+ null_entry = entries [ei ]
895+ new_entry = self ._store_path (null_entry .path , fprogress )
896+
897+ # update null entry
898+ entries [ei ] = BaseIndexEntry (
899+ (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path )
843900 )
844- )
845- # END for each entry index
846-
847- # end closure
848- handle_null_entries (self )
849- # END null_entry handling
850-
851- # REWRITE PATHS
852- # If we have to rewrite the entries, do so now, after we have generated
853- # all object sha's
854- if path_rewriter :
855- for i , e in enumerate (entries ):
856- entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
901+ # END for each entry index
902+
903+ # end closure
904+ handle_null_entries (self )
905+ # END null_entry handling
906+
907+ # REWRITE PATHS
908+ # If we have to rewrite the entries, do so now, after we have generated
909+ # all object sha's
910+ if path_rewriter :
911+ for i , e in enumerate (entries ):
912+ entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
913+ # END for each entry
914+ # END handle path rewriting
915+
916+ # just go through the remaining entries and provide progress info
917+ for i , entry in enumerate (entries ):
918+ progress_sent = i in null_entries_indices
919+ if not progress_sent :
920+ fprogress (entry .path , False , entry )
921+ fprogress (entry .path , True , entry )
922+ # END handle progress
857923 # END for each entry
858- # END handle path rewriting
859-
860- # just go through the remaining entries and provide progress info
861- for i , entry in enumerate (entries ):
862- progress_sent = i in null_entries_indices
863- if not progress_sent :
864- fprogress (entry .path , False , entry )
865- fprogress (entry .path , True , entry )
866- # END handle progress
867- # END for each entry
868- entries_added .extend (entries )
869- # END if there are base entries
870-
871- # FINALIZE
872- # add the new entries to this instance
873- for entry in entries_added :
874- self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
875-
876- if write :
877- self .write (ignore_extension_data = not write_extension_data )
878- # END handle write
924+ entries_added .extend (entries )
925+ # END if there are base entries
879926
880- return entries_added
927+ # FINALIZE
928+ # add the new entries to this instance
929+ for entry in entries_added :
930+ self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
931+
932+ if write :
933+ self .write (ignore_extension_data = not write_extension_data )
934+ # END handle write
935+
936+ return entries_added
881937
882938 def _items_to_rela_paths (
883939 self ,
0 commit comments