1616import sys
1717import stat
1818import subprocess
19+ import glob
1920import git .diff as diff
2021
22+ from errors import GitCommandError
2123from git .objects import Blob , Tree , Object , Commit
2224from git .utils import SHA1Writer , LazyMixin , ConcurrentWriteOperation , join_path_native
2325
@@ -662,7 +664,7 @@ def _preprocess_add_items(self, items):
662664
663665 @clear_cache
664666 @default_index
665- def add (self , items , force = True , ** kwargs ):
667+ def add (self , items , force = True , fprogress = lambda * args : None ):
666668 """
667669 Add files from the working tree, specific blobs or BaseIndexEntries
668670 to the index. The underlying index file will be written immediately, hence
@@ -717,43 +719,130 @@ def add(self, items, force=True, **kwargs):
717719 as the API user usually wants the item to be added even though
718720 they might be excluded.
719721
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+
724730 Returns
725731 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+
727799 # sort the entries into strings and Entries, Blobs are converted to entries
728800 # automatically
729801 # paths can be git-added, for everything else we use git-update-index
730802 entries_added = list ()
731803 paths , entries = self ._preprocess_add_items (items )
732804
805+ # HANDLE PATHS
733806 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
736821 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 )
743823 # END path handling
744824
825+ # HANDLE ENTRIES
745826 if entries :
746827 null_mode_entries = [ e for e in entries if e .mode == 0 ]
747828 if null_mode_entries :
748829 raise ValueError ("At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
749830 # END null mode should be remove
750831
832+ # HANLDE ENTRY OBJECT CREATION
751833 # create objects if required, otherwise go with the existing shas
752834 null_entries_indices = [ i for i ,e in enumerate (entries ) if e .sha == Object .NULL_HEX_SHA ]
753835 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
757846 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 ) )
758847
759848 # update IndexEntries with new object id
@@ -764,11 +853,22 @@ def add(self, items, force=True, **kwargs):
764853 # END for each index
765854 # END null_entry handling
766855
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 )
770871 entries_added .extend (entries )
771- self ._flush_stdin_and_wait (update_index_proc )
772872 # END if there are base entries
773873
774874 return entries_added
0 commit comments