38
38
39
39
import git .diff as git_diff
40
40
import os .path as osp
41
+ from pathlib import Path
42
+ from typing import Optional
41
43
42
44
from .fun import (
43
45
entry_key ,
87
89
Treeish = Union [Tree , Commit , str , bytes ]
88
90
89
91
# ------------------------------------------------------------------------------------
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 )
90
124
91
125
92
126
__all__ = ("IndexFile" , "CheckoutError" )
@@ -610,8 +644,8 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
610
644
raise ValueError ("Absolute path %r is not in git repository at %r" % (path , self .repo .working_tree_dir ))
611
645
return os .path .relpath (path , self .repo .working_tree_dir )
612
646
613
- def _preprocess_add_items (self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , 'Submodule' ]]
614
- ) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
647
+ def _preprocess_add_items (self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , 'Submodule' ]],
648
+ file_store : _FileStore ) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
615
649
""" Split the items into two lists of path strings and BaseEntries. """
616
650
paths = []
617
651
entries = []
@@ -621,6 +655,7 @@ def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexE
621
655
622
656
for item in items :
623
657
if isinstance (item , (str , os .PathLike )):
658
+ self ._autocrlf (item , file_store )
624
659
paths .append (self ._to_relative_path (item ))
625
660
elif isinstance (item , (Blob , Submodule )):
626
661
entries .append (BaseIndexEntry .from_blob (item ))
@@ -630,6 +665,31 @@ def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexE
630
665
raise TypeError ("Invalid Type: %r" % item )
631
666
# END for each item
632
667
return paths , entries
668
+
669
+ def _autocrlf (self , file : PathLike , file_store : _FileStore ) -> None :
670
+ """If the config option `autocrlf` is True, replace CRLF with LF"""
671
+
672
+ reader = self .repo .config_reader ()
673
+
674
+ autocrlf = reader .get_value ("core" , "autocrlf" , False )
675
+
676
+ if not autocrlf :
677
+ return
678
+
679
+ file_store .save (file )
680
+
681
+ with tempfile .TemporaryFile ("wb+" ) as tf :
682
+ with open (file , "rb" ) as f :
683
+ for line in f :
684
+ line = line .replace (b"\r \n " , b"\n " )
685
+ tf .write (line )
686
+
687
+ tf .seek (0 )
688
+
689
+ with open (file , "wb" ) as f :
690
+ for line in tf :
691
+ f .write (line )
692
+
633
693
634
694
def _store_path (self , filepath : PathLike , fprogress : Callable ) -> BaseIndexEntry :
635
695
"""Store file at filepath in the database and return the base index entry
@@ -801,73 +861,75 @@ def add(
801
861
Objects that do not have a null sha will be added even if their paths
802
862
do not exist.
803
863
"""
804
- # sort the entries into strings and Entries, Blobs are converted to entries
805
- # automatically
806
- # paths can be git-added, for everything else we use git-update-index
807
- paths , entries = self ._preprocess_add_items (items )
808
- entries_added : List [BaseIndexEntry ] = []
809
- # This code needs a working tree, therefore we try not to run it unless required.
810
- # That way, we are OK on a bare repository as well.
811
- # If there are no paths, the rewriter has nothing to do either
812
- if paths :
813
- entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
814
-
815
- # HANDLE ENTRIES
816
- if entries :
817
- null_mode_entries = [e for e in entries if e .mode == 0 ]
818
- if null_mode_entries :
819
- raise ValueError (
820
- "At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
821
- # END null mode should be remove
822
-
823
- # HANDLE ENTRY OBJECT CREATION
824
- # create objects if required, otherwise go with the existing shas
825
- null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
826
- if null_entries_indices :
827
- @ git_working_dir
828
- def handle_null_entries (self : 'IndexFile' ) -> None :
829
- for ei in null_entries_indices :
830
- null_entry = entries [ei ]
831
- new_entry = self ._store_path (null_entry .path , fprogress )
832
-
833
- # update null entry
834
- entries [ei ] = BaseIndexEntry (
835
- (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path ))
836
- # END for each entry index
837
- # end closure
838
- handle_null_entries (self )
839
- # END null_entry handling
840
-
841
- # REWRITE PATHS
842
- # If we have to rewrite the entries, do so now, after we have generated
843
- # all object sha's
844
- if path_rewriter :
845
- for i , e in enumerate (entries ):
846
- entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
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
+ # END null mode should be remove
884
+
885
+ # HANDLE ENTRY OBJECT CREATION
886
+ # create objects if required, otherwise go with the existing shas
887
+ null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
888
+ if null_entries_indices :
889
+ @ git_working_dir
890
+ def handle_null_entries (self : 'IndexFile' ) -> None :
891
+ for ei in null_entries_indices :
892
+ null_entry = entries [ei ]
893
+ new_entry = self ._store_path (null_entry .path , fprogress )
894
+
895
+ # update null entry
896
+ entries [ei ] = BaseIndexEntry (
897
+ (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path ))
898
+ # END for each entry index
899
+ # end closure
900
+ handle_null_entries (self )
901
+ # END null_entry handling
902
+
903
+ # REWRITE PATHS
904
+ # If we have to rewrite the entries, do so now, after we have generated
905
+ # all object sha's
906
+ if path_rewriter :
907
+ for i , e in enumerate (entries ):
908
+ entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
909
+ # END for each entry
910
+ # END handle path rewriting
911
+
912
+ # just go through the remaining entries and provide progress info
913
+ for i , entry in enumerate (entries ):
914
+ progress_sent = i in null_entries_indices
915
+ if not progress_sent :
916
+ fprogress (entry .path , False , entry )
917
+ fprogress (entry .path , True , entry )
918
+ # END handle progress
847
919
# END for each entry
848
- # END handle path rewriting
849
-
850
- # just go through the remaining entries and provide progress info
851
- for i , entry in enumerate (entries ):
852
- progress_sent = i in null_entries_indices
853
- if not progress_sent :
854
- fprogress (entry .path , False , entry )
855
- fprogress (entry .path , True , entry )
856
- # END handle progress
857
- # END for each entry
858
- entries_added .extend (entries )
859
- # END if there are base entries
860
-
861
- # FINALIZE
862
- # add the new entries to this instance
863
- for entry in entries_added :
864
- self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
865
-
866
- if write :
867
- self .write (ignore_extension_data = not write_extension_data )
868
- # END handle write
920
+ entries_added .extend (entries )
921
+ # END if there are base entries
869
922
870
- return entries_added
923
+ # FINALIZE
924
+ # add the new entries to this instance
925
+ for entry in entries_added :
926
+ self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
927
+
928
+ if write :
929
+ self .write (ignore_extension_data = not write_extension_data )
930
+ # END handle write
931
+
932
+ return entries_added
871
933
872
934
def _items_to_rela_paths (
873
935
self ,
0 commit comments