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 ,
89
91
# ------------------------------------------------------------------------------------
90
92
91
93
94
+ class _FileStore :
95
+ """An utility class that stores original files somewhere and restores them
96
+ to the original content at the exit"""
97
+
98
+ _dir : PathLike
99
+
100
+ def __init__ (self , tmp_dir : Optional [PathLike ] = None ):
101
+
102
+ self ._file_map : dict [PathLike , PathLike ] = {}
103
+ self ._tmp_dir = tempfile .TemporaryDirectory (prefix = str (tmp_dir ))
104
+
105
+ def __enter__ (self ):
106
+ return self
107
+
108
+ def __exit__ (self , exc , value , tb ):
109
+ for file , store_file in self ._file_map .items ():
110
+ with open (store_file , "rb" ) as rf , open (file , "wb" ) as wf :
111
+ for line in rf :
112
+ wf .write (line )
113
+ Path (store_file ).unlink ()
114
+ self ._dir .rmdir ()
115
+
116
+ @property
117
+ def _dir (self ) -> Path :
118
+ return Path (self ._tmp_dir .name )
119
+
120
+ def save (self , file : PathLike ) -> None :
121
+ store_file = self ._dir / tempfile .mktemp ()
122
+ self ._file_map [file ] = store_file
123
+ with open (store_file , "wb" ) as wf , open (file , "rb" ) as rf :
124
+ for line in rf :
125
+ wf .write (line )
126
+
127
+
92
128
__all__ = ("IndexFile" , "CheckoutError" )
93
129
94
130
@@ -611,7 +647,7 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
611
647
return os .path .relpath (path , self .repo .working_tree_dir )
612
648
613
649
def _preprocess_add_items (
614
- self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , "Submodule" ]]
650
+ self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , "Submodule" ]], file_store : _FileStore
615
651
) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
616
652
"""Split the items into two lists of path strings and BaseEntries."""
617
653
paths = []
@@ -622,6 +658,7 @@ def _preprocess_add_items(
622
658
623
659
for item in items :
624
660
if isinstance (item , (str , os .PathLike )):
661
+ self ._autocrlf (item , file_store )
625
662
paths .append (self ._to_relative_path (item ))
626
663
elif isinstance (item , (Blob , Submodule )):
627
664
entries .append (BaseIndexEntry .from_blob (item ))
@@ -632,6 +669,30 @@ def _preprocess_add_items(
632
669
# END for each item
633
670
return paths , entries
634
671
672
+ def _autocrlf (self , file : PathLike , file_store : _FileStore ) -> None :
673
+ """If the config option `autocrlf` is True, replace CRLF with LF"""
674
+
675
+ reader = self .repo .config_reader ()
676
+
677
+ autocrlf = reader .get_value ("core" , "autocrlf" , False )
678
+
679
+ if not autocrlf :
680
+ return
681
+
682
+ file_store .save (file )
683
+
684
+ with tempfile .TemporaryFile ("wb+" ) as tf :
685
+ with open (file , "rb" ) as f :
686
+ for line in f :
687
+ line = line .replace (b"\r \n " , b"\n " )
688
+ tf .write (line )
689
+
690
+ tf .seek (0 )
691
+
692
+ with open (file , "wb" ) as f :
693
+ for line in tf :
694
+ f .write (line )
695
+
635
696
def _store_path (self , filepath : PathLike , fprogress : Callable ) -> BaseIndexEntry :
636
697
"""Store file at filepath in the database and return the base index entry
637
698
Needs the git_working_dir decorator active ! This must be assured in the calling code"""
@@ -802,82 +863,79 @@ def add(
802
863
Objects that do not have a null sha will be added even if their paths
803
864
do not exist.
804
865
"""
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 ,
866
+
867
+ with _FileStore () as file_store :
868
+ # sort the entries into strings and Entries, Blobs are converted to entries
869
+ # automatically
870
+ # paths can be git-added, for everything else we use git-update-index
871
+ paths , entries = self ._preprocess_add_items (items , file_store )
872
+ entries_added : List [BaseIndexEntry ] = []
873
+ # This code needs a working tree, therefore we try not to run it unless required.
874
+ # That way, we are OK on a bare repository as well.
875
+ # If there are no paths, the rewriter has nothing to do either
876
+ if paths :
877
+ entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
878
+
879
+ # HANDLE ENTRIES
880
+ if entries :
881
+ null_mode_entries = [e for e in entries if e .mode == 0 ]
882
+ if null_mode_entries :
883
+ raise ValueError (
884
+ "At least one Entry has a null-mode - please use index.remove to remove files for clarity"
885
+ )
886
+ # END null mode should be remove
887
+
888
+ # HANDLE ENTRY OBJECT CREATION
889
+ # create objects if required, otherwise go with the existing shas
890
+ null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
891
+ if null_entries_indices :
892
+
893
+ @git_working_dir
894
+ def handle_null_entries (self : "IndexFile" ) -> None :
895
+ for ei in null_entries_indices :
896
+ null_entry = entries [ei ]
897
+ new_entry = self ._store_path (null_entry .path , fprogress )
898
+
899
+ # update null entry
900
+ entries [ei ] = BaseIndexEntry (
901
+ (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path )
843
902
)
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 )))
903
+ # END for each entry index
904
+
905
+ # end closure
906
+ handle_null_entries (self )
907
+ # END null_entry handling
908
+
909
+ # REWRITE PATHS
910
+ # If we have to rewrite the entries, do so now, after we have generated
911
+ # all object sha's
912
+ if path_rewriter :
913
+ for i , e in enumerate (entries ):
914
+ entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
915
+ # END for each entry
916
+ # END handle path rewriting
917
+
918
+ # just go through the remaining entries and provide progress info
919
+ for i , entry in enumerate (entries ):
920
+ progress_sent = i in null_entries_indices
921
+ if not progress_sent :
922
+ fprogress (entry .path , False , entry )
923
+ fprogress (entry .path , True , entry )
924
+ # END handle progress
857
925
# 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
926
+ entries_added .extend (entries )
927
+ # END if there are base entries
879
928
880
- return entries_added
929
+ # FINALIZE
930
+ # add the new entries to this instance
931
+ for entry in entries_added :
932
+ self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
933
+
934
+ if write :
935
+ self .write (ignore_extension_data = not write_extension_data )
936
+ # END handle write
937
+
938
+ return entries_added
881
939
882
940
def _items_to_rela_paths (
883
941
self ,
0 commit comments