@@ -41,6 +41,17 @@ def wrapper(self, *args, **kwargs):
41
41
wrapper .__name__ = func .__name__
42
42
return wrapper
43
43
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
+
44
55
#} END utilities
45
56
46
57
@@ -375,7 +386,8 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
375
386
376
387
# see whether we have a valid branch to checkout
377
388
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 )
379
391
local_branch = self .branch
380
392
if not local_branch .is_valid ():
381
393
# Setup a tracking configuration - branch doesn't need to
@@ -447,14 +459,18 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
447
459
return self
448
460
449
461
@unbare_repo
450
- def move (self , module_path ):
462
+ def move (self , module_path , module_only = False ):
451
463
"""Move the submodule to a another module path. This involves physically moving
452
464
the repository at our current path, changing the configuration, as well as
453
465
adjusting our index entry accordingly.
454
466
:param module_path: the path to which to move our module, given as
455
467
repository-relative path. Intermediate directories will be created
456
468
accordingly. If the path already exists, it must be empty.
457
469
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.
458
474
:return: self
459
475
:raise ValueError: if the module path existed and was not empty, or was a file
460
476
:note: Currently the method is not atomic, and it could leave the repository
@@ -475,6 +491,13 @@ def move(self, module_path):
475
491
raise ValueError ("Cannot move repository onto a file: %s" % dest_path )
476
492
# END handle target files
477
493
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
+
478
501
# remove existing destination
479
502
if os .path .exists (dest_path ):
480
503
if len (os .listdir (dest_path )):
@@ -502,23 +525,23 @@ def move(self, module_path):
502
525
503
526
# rename the index entry - have to manipulate the index directly as
504
527
# 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
522
545
523
546
return self
524
547
@@ -543,6 +566,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
543
566
this flag enables you to safely delete the repository of your submodule.
544
567
:param dry_run: if True, we will not actually do anything, but throw the errors
545
568
we would usually throw
569
+ :return: self
546
570
:note: doesn't work in bare repositories
547
571
:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
548
572
: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):
624
648
self .config_writer ().remove_section ()
625
649
# END delete configuration
626
650
651
+ return self
652
+
627
653
def set_parent_commit (self , commit , check = True ):
628
654
"""Set this instance to use the given commit whose tree is supposed to
629
655
contain the .gitmodules blob.
@@ -859,6 +885,152 @@ def _clear_cache(self):
859
885
pass
860
886
861
887
#{ 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
+
862
1034
def module (self ):
863
1035
""":return: the actual repository containing the submodules"""
864
1036
return self .repo
0 commit comments