9
9
import os .path as osp
10
10
import subprocess
11
11
import tempfile
12
+ from contextlib import contextmanager
13
+ from enum import auto
12
14
from io import BytesIO
15
+ from pathlib import Path
13
16
from stat import S_ISLNK
14
17
from typing import (
15
18
IO ,
22
25
Iterator ,
23
26
List ,
24
27
NoReturn ,
28
+ Optional ,
25
29
Sequence ,
26
30
Tuple ,
27
31
Type ,
72
76
StageType = int
73
77
Treeish = Union [Tree , Commit , str , bytes ]
74
78
75
- # ------------------------------------------------------------------------------------
79
+ # Store temporary file modifications -------------------------------------------
80
+
81
+ class _FileStore :
82
+ """An utility class that stores original files somewhere and restores them
83
+ to the original content at the exit"""
84
+
85
+ _dir : PathLike
86
+ def __init__ (self , tmp_dir : Optional [PathLike ] = None ):
87
+ self ._file_map : dict [PathLike , PathLike ] = {}
88
+ self ._tmp_dir = tempfile .TemporaryDirectory (prefix = str (tmp_dir ))
89
+
90
+ def __enter__ (self ):
91
+ return self
92
+
93
+ def __exit__ (self , exc , value , tb ):
94
+ for file , store_file in self ._file_map .items ():
95
+ with open (store_file , "rb" ) as rf , open (file , "wb" ) as wf :
96
+ for line in rf :
97
+ wf .write (line )
98
+ Path (store_file ).unlink ()
99
+ self ._dir .rmdir ()
100
+
101
+ @property
102
+ def _dir (self ) -> Path :
103
+ return Path (self ._tmp_dir .name )
104
+
105
+ def save (self , file : PathLike ) -> None :
106
+ store_file = self ._dir / tempfile .mktemp ()
107
+ self ._file_map [file ] = store_file
108
+ with open (store_file , "wb" ) as wf , open (file , "rb" ) as rf :
109
+ for line in rf :
110
+ wf .write (line )
111
+
112
+
113
+
114
+
115
+ # ------------------------------------------------------------------------------
76
116
77
117
78
118
__all__ = ('IndexFile' , 'CheckoutError' )
@@ -578,8 +618,10 @@ def _to_relative_path(self, path: PathLike) -> PathLike:
578
618
raise ValueError ("Absolute path %r is not in git repository at %r" % (path , self .repo .working_tree_dir ))
579
619
return os .path .relpath (path , self .repo .working_tree_dir )
580
620
581
- def _preprocess_add_items (self , items : Sequence [Union [PathLike , Blob , BaseIndexEntry , 'Submodule' ]]
582
- ) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
621
+ def _preprocess_add_items (self ,
622
+ items : Sequence [Union [PathLike , Blob , BaseIndexEntry , 'Submodule' ]],
623
+ file_store : _FileStore
624
+ ) -> Tuple [List [PathLike ], List [BaseIndexEntry ]]:
583
625
""" Split the items into two lists of path strings and BaseEntries. """
584
626
paths = []
585
627
entries = []
@@ -589,6 +631,7 @@ def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexE
589
631
590
632
for item in items :
591
633
if isinstance (item , (str , os .PathLike )):
634
+ self ._autocrlf (item , file_store )
592
635
paths .append (self ._to_relative_path (item ))
593
636
elif isinstance (item , (Blob , Submodule )):
594
637
entries .append (BaseIndexEntry .from_blob (item ))
@@ -599,6 +642,29 @@ def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexE
599
642
# END for each item
600
643
return paths , entries
601
644
645
+ def _autocrlf (self , file : PathLike , file_store : _FileStore ) -> PathLike :
646
+ """If the config option `autocrlf` is True, replace CRLF with LF"""
647
+
648
+ reader = self .repo .config_reader ()
649
+
650
+ autocrlf = reader .get_value ("core" , "autocrlf" , False )
651
+
652
+ if not autocrlf :
653
+ return file
654
+
655
+ file_store .save (file )
656
+
657
+ with open (file , "rb" ) as f :
658
+ content = f .read ()
659
+
660
+ content = content .replace (b"\r \n " , b"\n " )
661
+
662
+ with open (file , "wb" ) as f :
663
+ f .write (content )
664
+
665
+ return file
666
+
667
+
602
668
def _store_path (self , filepath : PathLike , fprogress : Callable ) -> BaseIndexEntry :
603
669
"""Store file at filepath in the database and return the base index entry
604
670
Needs the git_working_dir decorator active ! This must be assured in the calling code"""
@@ -753,70 +819,72 @@ def add(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']
753
819
# sort the entries into strings and Entries, Blobs are converted to entries
754
820
# automatically
755
821
# paths can be git-added, for everything else we use git-update-index
756
- paths , entries = self ._preprocess_add_items (items )
757
- entries_added : List [BaseIndexEntry ] = []
758
- # This code needs a working tree, therefore we try not to run it unless required.
759
- # That way, we are OK on a bare repository as well.
760
- # If there are no paths, the rewriter has nothing to do either
761
- if paths :
762
- entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
763
-
764
- # HANDLE ENTRIES
765
- if entries :
766
- null_mode_entries = [e for e in entries if e .mode == 0 ]
767
- if null_mode_entries :
768
- raise ValueError (
769
- "At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
770
- # END null mode should be remove
771
-
772
- # HANDLE ENTRY OBJECT CREATION
773
- # create objects if required, otherwise go with the existing shas
774
- null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
775
- if null_entries_indices :
776
- @ git_working_dir
777
- def handle_null_entries (self : 'IndexFile' ) -> None :
778
- for ei in null_entries_indices :
779
- null_entry = entries [ei ]
780
- new_entry = self ._store_path (null_entry .path , fprogress )
781
-
782
- # update null entry
783
- entries [ei ] = BaseIndexEntry (
784
- (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path ))
785
- # END for each entry index
786
- # end closure
787
- handle_null_entries (self )
788
- # END null_entry handling
789
-
790
- # REWRITE PATHS
791
- # If we have to rewrite the entries, do so now, after we have generated
792
- # all object sha's
793
- if path_rewriter :
794
- for i , e in enumerate (entries ):
795
- entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
822
+ with _FileStore () as file_store :
823
+ paths , entries = self ._preprocess_add_items (items , file_store )
824
+ import ipdb ; ipdb .set_trace ()
825
+ entries_added : List [BaseIndexEntry ] = []
826
+ # This code needs a working tree, therefore we try not to run it unless required.
827
+ # That way, we are OK on a bare repository as well.
828
+ # If there are no paths, the rewriter has nothing to do either
829
+ if paths :
830
+ entries_added .extend (self ._entries_for_paths (paths , path_rewriter , fprogress , entries ))
831
+
832
+ # HANDLE ENTRIES
833
+ if entries :
834
+ null_mode_entries = [e for e in entries if e .mode == 0 ]
835
+ if null_mode_entries :
836
+ raise ValueError (
837
+ "At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
838
+ # END null mode should be remove
839
+
840
+ # HANDLE ENTRY OBJECT CREATION
841
+ # create objects if required, otherwise go with the existing shas
842
+ null_entries_indices = [i for i , e in enumerate (entries ) if e .binsha == Object .NULL_BIN_SHA ]
843
+ if null_entries_indices :
844
+ @ git_working_dir
845
+ def handle_null_entries (self : 'IndexFile' ) -> None :
846
+ for ei in null_entries_indices :
847
+ null_entry = entries [ei ]
848
+ new_entry = self ._store_path (null_entry .path , fprogress )
849
+
850
+ # update null entry
851
+ entries [ei ] = BaseIndexEntry (
852
+ (null_entry .mode , new_entry .binsha , null_entry .stage , null_entry .path ))
853
+ # END for each entry index
854
+ # end closure
855
+ handle_null_entries (self )
856
+ # END null_entry handling
857
+
858
+ # REWRITE PATHS
859
+ # If we have to rewrite the entries, do so now, after we have generated
860
+ # all object sha's
861
+ if path_rewriter :
862
+ for i , e in enumerate (entries ):
863
+ entries [i ] = BaseIndexEntry ((e .mode , e .binsha , e .stage , path_rewriter (e )))
864
+ # END for each entry
865
+ # END handle path rewriting
866
+
867
+ # just go through the remaining entries and provide progress info
868
+ for i , entry in enumerate (entries ):
869
+ progress_sent = i in null_entries_indices
870
+ if not progress_sent :
871
+ fprogress (entry .path , False , entry )
872
+ fprogress (entry .path , True , entry )
873
+ # END handle progress
796
874
# END for each entry
797
- # END handle path rewriting
798
-
799
- # just go through the remaining entries and provide progress info
800
- for i , entry in enumerate (entries ):
801
- progress_sent = i in null_entries_indices
802
- if not progress_sent :
803
- fprogress (entry .path , False , entry )
804
- fprogress (entry .path , True , entry )
805
- # END handle progress
806
- # END for each entry
807
- entries_added .extend (entries )
808
- # END if there are base entries
809
-
810
- # FINALIZE
811
- # add the new entries to this instance
812
- for entry in entries_added :
813
- self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
814
-
815
- if write :
816
- self .write (ignore_extension_data = not write_extension_data )
817
- # END handle write
875
+ entries_added .extend (entries )
876
+ # END if there are base entries
818
877
819
- return entries_added
878
+ # FINALIZE
879
+ # add the new entries to this instance
880
+ for entry in entries_added :
881
+ self .entries [(entry .path , 0 )] = IndexEntry .from_base (entry )
882
+
883
+ if write :
884
+ self .write (ignore_extension_data = not write_extension_data )
885
+ # END handle write
886
+
887
+ return entries_added
820
888
821
889
def _items_to_rela_paths (self , items : Union [PathLike , Sequence [Union [PathLike , BaseIndexEntry , Blob , Submodule ]]]
822
890
) -> List [PathLike ]:
0 commit comments