@@ -41,6 +41,17 @@ def wrapper(self, *args, **kwargs):
4141 wrapper .__name__ = func .__name__
4242 return wrapper
4343
44+ def find_remote_branch (remotes , branch ):
45+ """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
46+ for remote in remotes :
47+ try :
48+ return remote .refs [branch .name ]
49+ except IndexError :
50+ continue
51+ # END exception handling
52+ #END for remote
53+ raise InvalidGitRepositoryError ("Didn't find remote branch %r in any of the given remotes" , branch
54+
4455#} END utilities
4556
4657
@@ -375,7 +386,8 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
375386
376387 # see whether we have a valid branch to checkout
377388 try :
378- remote_branch = mrepo .remotes .origin .refs [self .branch .name ]
389+ # find a remote which has our branch - we try to be flexible
390+ remote_branch = find_remote_branch (mrepo .remotes , self .branch )
379391 local_branch = self .branch
380392 if not local_branch .is_valid ():
381393 # Setup a tracking configuration - branch doesn't need to
@@ -447,14 +459,18 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
447459 return self
448460
449461 @unbare_repo
450- def move (self , module_path ):
462+ def move (self , module_path , module_only = False ):
451463 """Move the submodule to a another module path. This involves physically moving
452464 the repository at our current path, changing the configuration, as well as
453465 adjusting our index entry accordingly.
454466 :param module_path: the path to which to move our module, given as
455467 repository-relative path. Intermediate directories will be created
456468 accordingly. If the path already exists, it must be empty.
457469 Trailling (back)slashes are removed automatically
470+ :param module_only: if True, only the repository managed by this submodule
471+ will be moved, not the configuration. This will effectively
472+ leave your repository in an inconsistent state unless the configuration
473+ and index already point to the target location.
458474 :return: self
459475 :raise ValueError: if the module path existed and was not empty, or was a file
460476 :note: Currently the method is not atomic, and it could leave the repository
@@ -475,6 +491,13 @@ def move(self, module_path):
475491 raise ValueError ("Cannot move repository onto a file: %s" % dest_path )
476492 # END handle target files
477493
494+ index = self .repo .index
495+ tekey = index .entry_key (module_path , 0 )
496+ # if the target item already exists, fail
497+ if not module_only and tekey in index .entries :
498+ raise ValueError ("Index entry for target path did alredy exist" )
499+ #END handle index key already there
500+
478501 # remove existing destination
479502 if os .path .exists (dest_path ):
480503 if len (os .listdir (dest_path )):
@@ -502,23 +525,23 @@ def move(self, module_path):
502525
503526 # rename the index entry - have to manipulate the index directly as
504527 # git-mv cannot be used on submodules ... yeah
505- index = self . repo . index
506- try :
507- ekey = index .entry_key (self .path , 0 )
508- entry = index .entries [ekey ]
509- del (index .entries [ekey ])
510- nentry = git .IndexEntry (entry [:3 ]+ (module_path ,)+ entry [4 :])
511- ekey = index .entry_key ( module_path , 0 )
512- index . entries [ ekey ] = nentry
513- except KeyError :
514- raise ValueError ( "Submodule's entry at %r did not exist" % ( self . path ))
515- #END handle submodule doesn't exist
516-
517- # update configuration
518- writer = self . config_writer ( index = index ) # auto-write
519- writer . set_value ( ' path' , module_path )
520- self . path = module_path
521- del ( writer )
528+ if not module_only :
529+ try :
530+ ekey = index .entry_key (self .path , 0 )
531+ entry = index .entries [ekey ]
532+ del (index .entries [ekey ])
533+ nentry = git .IndexEntry (entry [:3 ]+ (module_path ,)+ entry [4 :])
534+ index .entries [ tekey ] = nentry
535+ except KeyError :
536+ raise ValueError ( "Submodule's entry at %r did not exist" % ( self . path ))
537+ #END handle submodule doesn't exist
538+
539+ # update configuration
540+ writer = self . config_writer ( index = index ) # auto-write
541+ writer . set_value ( 'path' , module_path )
542+ self . path = module_path
543+ del ( writer )
544+ # END handle module_only
522545
523546 return self
524547
@@ -543,6 +566,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
543566 this flag enables you to safely delete the repository of your submodule.
544567 :param dry_run: if True, we will not actually do anything, but throw the errors
545568 we would usually throw
569+ :return: self
546570 :note: doesn't work in bare repositories
547571 :raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
548572 :raise OSError: if directories or files could not be removed"""
@@ -624,6 +648,8 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
624648 self .config_writer ().remove_section ()
625649 # END delete configuration
626650
651+ return self
652+
627653 def set_parent_commit (self , commit , check = True ):
628654 """Set this instance to use the given commit whose tree is supposed to
629655 contain the .gitmodules blob.
@@ -859,6 +885,152 @@ def _clear_cache(self):
859885 pass
860886
861887 #{ Interface
888+
889+ def update (self , previous_commit = None , recursive = True , force_remove = False , init = True , to_latest_revision = False ):
890+ """Update the submodules of this repository to the current HEAD commit.
891+ This method behaves smartly by determining changes of the path of a submodules
892+ repository, next to changes to the to-be-checked-out commit or the branch to be
893+ checked out. This works if the submodules ID does not change.
894+ Additionally it will detect addition and removal of submodules, which will be handled
895+ gracefully.
896+
897+ :param previous_commit: If set to a commit'ish, the commit we should use
898+ as the previous commit the HEAD pointed to before it was set to the commit it points to now.
899+ If None, it defaults to ORIG_HEAD otherwise, or the parent of the current
900+ commit if it is not given
901+ :param recursive: if True, the children of submodules will be updated as well
902+ using the same technique
903+ :param force_remove: If submodules have been deleted, they will be forcibly removed.
904+ Otherwise the update may fail if a submodule's repository cannot be deleted as
905+ changes have been made to it (see Submodule.update() for more information)
906+ :param init: If we encounter a new module which would need to be initialized, then do it.
907+ :param to_latest_revision: If True, instead of checking out the revision pointed to
908+ by this submodule's sha, the checked out tracking branch will be merged with the
909+ newest remote branch fetched from the repository's origin"""
910+ if self .repo .bare :
911+ raise InvalidGitRepositoryError ("Cannot update submodules in bare repositories" )
912+ # END handle bare
913+
914+ repo = self .repo
915+
916+ # HANDLE COMMITS
917+ ##################
918+ cur_commit = repo .head .commit
919+ if previous_commit is None :
920+ symref = SymbolicReference (repo , SymbolicReference .to_full_path ('ORIG_HEAD' ))
921+ try :
922+ previous_commit = symref .commit
923+ except Exception :
924+ pcommits = cur_commit .parents
925+ if pcommits :
926+ previous_commit = pcommits [0 ]
927+ else :
928+ # in this special case, we just diff against ourselve, which
929+ # means exactly no change
930+ previous_commit = cur_commit
931+ # END handle initial commit
932+ # END no ORIG_HEAD
933+ else :
934+ previous_commit = repo .commit (previous_commit ) # obtain commit object
935+ # END handle previous commit
936+
937+
938+ # HANDLE REMOVALS
939+ psms = type (self ).list_items (repo , parent_commit = previous_commit )
940+ sms = self .children ()
941+ spsms = set (psms )
942+ ssms = set (sms )
943+
944+ # HANDLE REMOVALS
945+ ###################
946+ for rsm in (spsms - ssms ):
947+ # fake it into thinking its at the current commit to allow deletion
948+ # of previous module. Trigger the cache to be updated before that
949+ #rsm.url
950+ rsm ._parent_commit = repo .head .commit
951+ rsm .remove (configuration = False , module = True , force = force_remove )
952+ # END for each removed submodule
953+
954+ # HANDLE PATH RENAMES + url changes + branch changes
955+ for csm in (spsms & ssms ):
956+ psm = psms [csm .name ]
957+ sm = sms [csm .name ]
958+
959+ if sm .path != psm .path and psm .module_exists ():
960+ # move the module to the new path
961+ psm .move (sm .path , module_only = True )
962+ # END handle path changes
963+
964+ if sm .module_exists ():
965+ # handle url change
966+ if sm .url != psm .url :
967+ # Add the new remote, remove the old one
968+ # This way, if the url just changes, the commits will not
969+ # have to be re-retrieved
970+ nn = '__new_origin__'
971+ smm = sm .module ()
972+ rmts = smm .remotes
973+ assert nn not in rmts
974+ smr = smm .create_remote (nn , sm .url )
975+ srm .fetch ()
976+
977+ # now delete the changed one
978+ orig_name = None
979+ for remote in rmts :
980+ if remote .url == psm .url :
981+ orig_name = remote .name
982+ smm .delete_remote (remote )
983+ break
984+ # END if urls match
985+ # END for each remote
986+
987+ # rename the new remote back to what it was
988+ # if we have not found any remote with the original url
989+ # we may not have a name. This is a special case,
990+ # and its okay to fail her
991+ assert orig_name is not None , "Couldn't find original remote-repo at url %r" % psm .url
992+ smr .rename (orig_name )
993+ # END handle url
994+
995+ if sm .branch != psm .branch :
996+ # finally, create a new tracking branch which tracks the
997+ # new remote branch
998+ smm = sm .module ()
999+ smmr = smm .remotes
1000+ tbr = git .Head .create (smm , sm .branch .name )
1001+ tbr .set_tracking_branch (find_remote_branch (smmr , sm .branch ))
1002+
1003+ # figure out whether the previous tracking branch contains
1004+ # new commits compared to the other one, if not we can
1005+ # delete it.
1006+ try :
1007+ tbr = find_remote_branch (smmr , psm .branch )
1008+ if len (smm .git .cherry (tbr , psm .branch )) == 0 :
1009+ psm .branch .delete (smm , psm .branch )
1010+ #END delete original tracking branch if there are no changes
1011+ except InvalidGitRepositoryError :
1012+ # ignore it if the previous branch couldn't be found in the
1013+ # current remotes, this just means we can't handle it
1014+ pass
1015+ # END exception handling
1016+ #END handle branch
1017+ #END handle
1018+ # END for each common submodule
1019+
1020+ # FINALLY UPDATE ALL ACTUAL SUBMODULES
1021+ ##########################################
1022+ for sm in sms :
1023+ sm .update (recursive = True , init = init , to_latest_revision = to_latest_revision )
1024+
1025+ # update recursively depth first - question is which inconsitent
1026+ # state will be better in case it fails somewhere. Defective branch
1027+ # or defective depth
1028+ if recursive :
1029+ type (cls )(sm .module ()).update (recursive = True , force_remove = force_remove ,
1030+ init = init , to_latest_revision = to_latest_revision )
1031+ #END handle recursive
1032+ # END for each submodule to update
1033+
8621034 def module (self ):
8631035 """:return: the actual repository containing the submodules"""
8641036 return self .repo
0 commit comments