16
16
import sys
17
17
import stat
18
18
import subprocess
19
+ import glob
19
20
import git .diff as diff
20
21
22
+ from errors import GitCommandError
21
23
from git .objects import Blob , Tree , Object , Commit
22
24
from git .utils import SHA1Writer , LazyMixin , ConcurrentWriteOperation , join_path_native
23
25
@@ -662,7 +664,7 @@ def _preprocess_add_items(self, items):
662
664
663
665
@clear_cache
664
666
@default_index
665
- def add (self , items , force = True , ** kwargs ):
667
+ def add (self , items , force = True , fprogress = lambda * args : None ):
666
668
"""
667
669
Add files from the working tree, specific blobs or BaseIndexEntries
668
670
to the index. The underlying index file will be written immediately, hence
@@ -717,43 +719,130 @@ def add(self, items, force=True, **kwargs):
717
719
as the API user usually wants the item to be added even though
718
720
they might be excluded.
719
721
720
- ``**kwargs``
721
- Additional keyword arguments to be passed to git-update-index, such
722
- as index_only.
723
-
722
+ ``fprogress``
723
+ Function with signature f(path, done=False, item=item) called for each
724
+ path to be added, once once it is about to be added where done==False
725
+ and once after it was added where done=True.
726
+ item is set to the actual item we handle, either a Path or a BaseIndexEntry
727
+ Please note that the processed path is not guaranteed to be present
728
+ in the index already as the index is currently being processed.
729
+
724
730
Returns
725
731
List(BaseIndexEntries) representing the entries just actually added.
726
- """
732
+
733
+ Raises
734
+ GitCommandError if a supplied Path did not exist. Please note that BaseIndexEntry
735
+ Objects that do not have a null sha will be added even if their paths
736
+ do not exist.
737
+ """
738
+ # UTILITIES
739
+ def raise_exc (e ):
740
+ raise e
741
+
742
+ def expand_paths (paths ):
743
+ """Expand the directories in list of paths to the corresponding paths accordingly,
744
+ they will always be relative to the repository"""
745
+ out = list ()
746
+ r = self .repo .git .git_dir
747
+ rs = r + '/'
748
+ for path in paths :
749
+ abs_path = path
750
+ if not os .path .isabs (abs_path ):
751
+ abs_path = os .path .join (r , path )
752
+ # END make absolute path
753
+
754
+ # resolve globs if possible
755
+ if '?' in path or '*' in path or '[' in path :
756
+ out .extend (f .replace (rs , '' ) for f in expand_paths (glob .glob (abs_path )))
757
+ continue
758
+ # END glob handling
759
+ try :
760
+ for root , dirs , files in os .walk (abs_path , onerror = raise_exc ):
761
+ for rela_file in files :
762
+ # add relative paths only
763
+ out .append (os .path .join (root .replace (rs , '' ), rela_file ))
764
+ # END for each file in subdir
765
+ # END for each subdirectory
766
+ except OSError :
767
+ # was a file or something that could not be iterated
768
+ out .append (path )
769
+ # END path exception handling
770
+ # END for each path
771
+
772
+ # NOTE: git will add items multiple times even if a glob overlapped
773
+ # with manually specified paths or if paths where specified multiple
774
+ # times - we respect that and do not prune
775
+ return out
776
+ # END expand helper method
777
+
778
+ def write_path_to_stdin (proc , filepath , item , fmakeexc ):
779
+ """Write path to proc.stdin and make sure it processes the item, including progress.
780
+ @return: stdout string"""
781
+ fprogress (filepath , False , item )
782
+ try :
783
+ proc .stdin .write ("%s\n " % filepath )
784
+ except IOError :
785
+ # pipe broke, usually because some error happend
786
+ raise fmakeexc ()
787
+ # END write exception handling
788
+ proc .stdin .flush ()
789
+ # NOTE: if this hangs, you need at lest git 1.6.5.4 as a git-bugfix
790
+ # is needed for this
791
+ # TODO: Rewrite this using hash-object and update index to get
792
+ # rid of the bug-fix dependency, updaet intro.rst requirements
793
+ rval = proc .stdout .readline ().strip () # trigger operation
794
+ fprogress (filepath , True , item )
795
+ return rval
796
+ # END write_path_to_stdin
797
+
798
+
727
799
# sort the entries into strings and Entries, Blobs are converted to entries
728
800
# automatically
729
801
# paths can be git-added, for everything else we use git-update-index
730
802
entries_added = list ()
731
803
paths , entries = self ._preprocess_add_items (items )
732
804
805
+ # HANDLE PATHS
733
806
if paths :
734
- git_add_output = self .repo .git .add (paths , v = True )
735
- # force rereading our entries
807
+ # to get suitable progress information, pipe paths to stdin
808
+ args = ("--add" , "--replace" , "--verbose" , "--stdin" )
809
+ proc = self .repo .git .update_index (* args , ** {'as_process' :True , 'istream' :subprocess .PIPE })
810
+ make_exc = lambda : GitCommandError (("git-update-index" ,)+ args , 128 , proc .stderr .readline ())
811
+ filepaths = expand_paths (paths )
812
+ added_files = list ()
813
+
814
+ for filepath in filepaths :
815
+ write_path_to_stdin (proc , filepath , filepath , make_exc )
816
+ added_files .append (filepath )
817
+ # END for each filepath
818
+ self ._flush_stdin_and_wait (proc ) # ignore stdout
819
+
820
+ # force rereading our entries once it is all done
736
821
del (self .entries )
737
- for line in git_add_output .splitlines ():
738
- # line contains:
739
- # add '<path>'
740
- added_file = line [5 :- 1 ]
741
- entries_added .append (self .entries [(added_file ,0 )])
742
- # END for each line
822
+ entries_added .extend (self .entries [(f ,0 )] for f in added_files )
743
823
# END path handling
744
824
825
+ # HANDLE ENTRIES
745
826
if entries :
746
827
null_mode_entries = [ e for e in entries if e .mode == 0 ]
747
828
if null_mode_entries :
748
829
raise ValueError ("At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
749
830
# END null mode should be remove
750
831
832
+ # HANLDE ENTRY OBJECT CREATION
751
833
# create objects if required, otherwise go with the existing shas
752
834
null_entries_indices = [ i for i ,e in enumerate (entries ) if e .sha == Object .NULL_HEX_SHA ]
753
835
if null_entries_indices :
754
- hash_proc = self .repo .git .hash_object (w = True , stdin_paths = True , istream = subprocess .PIPE , as_process = True )
755
- hash_proc .stdin .write ('\n ' .join (entries [i ].path for i in null_entries_indices ))
756
- obj_ids = self ._flush_stdin_and_wait (hash_proc ).splitlines ()
836
+ # creating object ids is the time consuming part. Hence we will
837
+ # send progress for these now.
838
+ args = ("-w" , "--stdin-paths" )
839
+ proc = self .repo .git .hash_object (* args , ** {'istream' :subprocess .PIPE , 'as_process' :True })
840
+ make_exc = lambda : GitCommandError (("git-hash-object" ,)+ args , 128 , proc .stderr .readline ())
841
+ obj_ids = list ()
842
+ for ei in null_entries_indices :
843
+ entry = entries [ei ]
844
+ obj_ids .append (write_path_to_stdin (proc , entry .path , entry , make_exc ))
845
+ # END for each entry index
757
846
assert len (obj_ids ) == len (null_entries_indices ), "git-hash-object did not produce all requested objects: want %i, got %i" % ( len (null_entries_indices ), len (obj_ids ) )
758
847
759
848
# update IndexEntries with new object id
@@ -764,11 +853,22 @@ def add(self, items, force=True, **kwargs):
764
853
# END for each index
765
854
# END null_entry handling
766
855
767
- # feed all the data to stdin
768
- update_index_proc = self .repo .git .update_index (index_info = True , istream = subprocess .PIPE , as_process = True , ** kwargs )
769
- update_index_proc .stdin .write ('\n ' .join (str (e ) for e in entries ))
856
+ # feed pure entries to stdin
857
+ proc = self .repo .git .update_index (index_info = True , istream = subprocess .PIPE , as_process = True )
858
+ for i , entry in enumerate (entries ):
859
+ progress_sent = i in null_entries_indices
860
+ if not progress_sent :
861
+ fprogress (entry .path , False , entry )
862
+ # it cannot handle too-many newlines in this mode
863
+ if i != 0 :
864
+ proc .stdin .write ('\n ' )
865
+ proc .stdin .write (str (entry ))
866
+ proc .stdin .flush ()
867
+ if not progress_sent :
868
+ fprogress (entry .path , True , entry )
869
+ # END for each enty
870
+ self ._flush_stdin_and_wait (proc )
770
871
entries_added .extend (entries )
771
- self ._flush_stdin_and_wait (update_index_proc )
772
872
# END if there are base entries
773
873
774
874
return entries_added
0 commit comments