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" )
@@ -611,7 +645,7 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
611
645
return os .path .relpath (path , self .repo .working_tree_dir )
612
646
613
647
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
615
649
) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
616
650
"""Split the items into two lists of path strings and BaseEntries."""
617
651
paths = []
@@ -622,6 +656,7 @@ def _preprocess_add_items(
622
656
623
657
for item in items :
624
658
if isinstance (item , (str , os .PathLike )):
659
+ self ._autocrlf (item , file_store )
625
660
paths .append (self ._to_relative_path (item ))
626
661
elif isinstance (item , (Blob , Submodule )):
627
662
entries .append (BaseIndexEntry .from_blob (item ))
@@ -632,6 +667,30 @@ def _preprocess_add_items(
632
667
# END for each item
633
668
return paths , entries
634
669
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
+
635
694
def _store_path (self , filepath : PathLike , fprogress : Callable ) -> BaseIndexEntry :
636
695
"""Store file at filepath in the database and return the base index entry
637
696
Needs the git_working_dir decorator active ! This must be assured in the calling code"""
@@ -802,82 +861,79 @@ def add(
802
861
Objects that do not have a null sha will be added even if their paths
803
862
do not exist.
804
863
"""
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 )
843
900
)
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
857
923
# 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
879
926
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
881
937
882
938
def _items_to_rela_paths (
883
939
self ,
0 commit comments