1515import os
1616import sys
1717import stat
18+ import subprocess
1819import git .diff as diff
1920
2021from git .objects import Blob , Tree , Object , Commit
@@ -51,6 +52,9 @@ class BaseIndexEntry(tuple):
5152 use numeric indices for performance reasons.
5253 """
5354
55+ def __str__ (self ):
56+ return "%o %s %i\t %s\n " % (self .mode , self .sha , self .stage , self .path )
57+
5458 @property
5559 def mode (self ):
5660 """
@@ -466,6 +470,12 @@ def _index_mode_to_tree_index_mode(cls, index_mode):
466470 ret |= (index_mode & 0111 )
467471 return ret
468472
473+ @classmethod
474+ def _tree_mode_to_index_mode (cls , tree_mode ):
475+ """
476+ Convert a tree mode to index mode as good as possible
477+ """
478+
469479 def iter_blobs (self , predicate = lambda t : True ):
470480 """
471481 Returns
@@ -597,9 +607,29 @@ def _to_relative_path(self, path):
597607 raise ValueError ("Absolute path %r is not in git repository at %r" % (path ,self .repo .git .git_dir ))
598608 return relative_path
599609
610+ def _preprocess_add_items (self , items ):
611+ """
612+ Split the items into two lists of path strings and BaseEntries.
613+ """
614+ paths = list ()
615+ entries = list ()
616+
617+ for item in items :
618+ if isinstance (item , basestring ):
619+ paths .append (self ._to_relative_path (item ))
620+ elif isinstance (item , Blob ):
621+ entries .append (BaseIndexEntry .from_blob (item ))
622+ elif isinstance (item , BaseIndexEntry ):
623+ entries .append (item )
624+ else :
625+ raise TypeError ("Invalid Type: %r" % item )
626+ # END for each item
627+ return (paths , entries )
628+
629+
600630 @clear_cache
601631 @default_index
602- def add (self , items , ** kwargs ):
632+ def add (self , items , force = True , ** kwargs ):
603633 """
604634 Add files from the working tree, specific blobs or BaseIndexEntries
605635 to the index. The underlying index file will be written immediately, hence
@@ -612,18 +642,23 @@ def add(self, items, **kwargs):
612642
613643 - path string
614644 strings denote a relative or absolute path into the repository pointing to
615- an existing file, i.e. CHANGES, lib/myfile.ext, /home/gitrepo/lib/myfile.ext.
645+ an existing file, i.e. CHANGES, lib/myfile.ext, ' /home/gitrepo/lib/myfile.ext' .
616646
617647 Paths provided like this must exist. When added, they will be written
618648 into the object database.
619649
650+ PathStrings may contain globs, such as 'lib/__init__*' or can be directories
651+ like 'lib', the latter ones will add all the files within the dirctory and
652+ subdirectories.
653+
620654 This equals a straight git-add.
621655
622656 They are added at stage 0
623657
624658 - Blob object
625659 Blobs are added as they are assuming a valid mode is set.
626- The file they refer to may or may not exist in the file system
660+ The file they refer to may or may not exist in the file system, but
661+ must be a path relative to our repository.
627662
628663 If their sha is null ( 40*0 ), their path must exist in the file system
629664 as an object will be created from the data at the path.The handling
@@ -634,20 +669,76 @@ def add(self, items, **kwargs):
634669 is not dereferenced automatically, except that it can be created on
635670 filesystems not supporting it as well.
636671
672+ Please note that globs or directories are not allowed in Blob objects.
673+
637674 They are added at stage 0
638675
639676 - BaseIndexEntry or type
640677 Handling equals the one of Blob objects, but the stage may be
641678 explicitly set.
642679
680+ ``force``
681+ If True, otherwise ignored or excluded files will be
682+ added anyway.
683+ As opposed to the git-add command, we enable this flag by default
684+ as the API user usually wants the item to be added even though
685+ they might be excluded.
686+
643687 ``**kwargs``
644688 Additional keyword arguments to be passed to git-update-index, such
645689 as index_only.
646690
647691 Returns
648692 List(BaseIndexEntries) representing the entries just actually added.
649693 """
650- raise NotImplementedError ("todo" )
694+ # sort the entries into strings and Entries, Blobs are converted to entries
695+ # automatically
696+ # paths can be git-added, for everything else we use git-update-index
697+ entries_added = list ()
698+ paths , entries = self ._preprocess_add_items (items )
699+
700+ if paths :
701+ git_add_output = self .repo .git .add (paths , v = True )
702+ # force rereading our entries
703+ del (self .entries )
704+ for line in git_add_output .splitlines ():
705+ # line contains:
706+ # add '<path>'
707+ added_file = line [5 :- 1 ]
708+ entries_added .append (self .entries [(added_file ,0 )])
709+ # END for each line
710+ # END path handling
711+
712+ if entries :
713+ null_mode_entries = [ e for e in entries if e .mode == 0 ]
714+ if null_mode_entries :
715+ raise ValueError ("At least one Entry has a null-mode - please use index.remove to remove files for clarity" )
716+ # END null mode should be remove
717+
718+ # create objects if required, otherwise go with the existing shas
719+ null_entries_indices = [ i for i ,e in enumerate (entries ) if e .sha == Object .NULL_HEX_SHA ]
720+ if null_entries_indices :
721+ hash_proc = self .repo .git .hash_object (w = True , stdin_paths = True , istream = subprocess .PIPE , as_process = True )
722+ hash_proc .stdin .write ('\n ' .join (entries [i ].path for i in null_entries_indices ))
723+ obj_ids = self ._flush_stdin_and_wait (hash_proc ).splitlines ()
724+ 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 ) )
725+
726+ # update IndexEntries with new object id
727+ for i ,new_sha in zip (null_entries_indices , obj_ids ):
728+ e = entries [i ]
729+ new_entry = BaseIndexEntry ((e .mode , new_sha , e .stage , e .path ))
730+ entries [i ] = new_entry
731+ # END for each index
732+ # END null_entry handling
733+
734+ # feed all the data to stdin
735+ update_index_proc = self .repo .git .update_index (index_info = True , istream = subprocess .PIPE , as_process = True , ** kwargs )
736+ update_index_proc .stdin .write ('\n ' .join (str (e ) for e in entries ))
737+ entries_added .extend (entries )
738+ self ._flush_stdin_and_wait (update_index_proc )
739+ # END if there are base entries
740+
741+ return entries_added
651742
652743 @clear_cache
653744 @default_index
@@ -768,6 +859,53 @@ def commit(self, message, parent_commits=None, head=True):
768859 fp .close ()
769860 os .remove (tmp_file_path )
770861
862+ @classmethod
863+ def _flush_stdin_and_wait (cls , proc ):
864+ proc .stdin .flush ()
865+ proc .stdin .close ()
866+ stdout = proc .stdout .read ()
867+ proc .wait ()
868+ return stdout
869+
870+ @default_index
871+ def checkout (self , paths = None , force = False , ** kwargs ):
872+ """
873+ Checkout the given paths or all files from the version in the index.
874+
875+ ``paths``
876+ If None, all paths in the index will be checked out. Otherwise an iterable
877+ or single path of relative or absolute paths pointing to files is expected.
878+ The command will ignore paths that do not exist.
879+
880+ ``force``
881+ If True, existing files will be overwritten. If False, these will
882+ be skipped.
883+
884+ ``**kwargs``
885+ Additional arguments to be pasesd to git-checkout-index
886+
887+ Returns
888+ self
889+ """
890+ args = ["--index" ]
891+ if force :
892+ args .append ("--force" )
893+
894+ if paths is None :
895+ args .append ("--all" )
896+ self .repo .git .checkout_index (* args , ** kwargs )
897+ else :
898+ if not isinstance (paths , (tuple ,list )):
899+ paths = [paths ]
900+
901+ args .append ("--stdin" )
902+ paths = [self ._to_relative_path (p ) for p in paths ]
903+ co_proc = self .repo .git .checkout_index (args , as_process = True , istream = subprocess .PIPE , ** kwargs )
904+ co_proc .stdin .write ('\n ' .join (paths ))
905+ self ._flush_stdin_and_wait (co_proc )
906+ # END paths handling
907+ return self
908+
771909 @clear_cache
772910 @default_index
773911 def reset (self , commit = 'HEAD' , working_tree = False , paths = None , head = False , ** kwargs ):
0 commit comments