1212from git .util import (
1313 Iterable ,
1414 join_path_native ,
15- to_native_path_linux
15+ to_native_path_linux ,
16+ RemoteProgress
1617 )
1718
1819from git .config import SectionConstraint
1920from git .exc import (
2021 InvalidGitRepositoryError ,
2122 NoSuchPathError
2223 )
24+
2325import stat
2426import git
2527
2931
3032import shutil
3133
32- __all__ = ["Submodule" ]
34+ __all__ = ["Submodule" , "UpdateProgress" ]
35+
36+
37+ class UpdateProgress (RemoteProgress ):
38+ """Class providing detailed progress information to the caller who should
39+ derive from it and implement the ``update(...)`` message"""
40+ CLONE , FETCH , UPDWKTREE = [1 << x for x in range (RemoteProgress ._num_op_codes , RemoteProgress ._num_op_codes + 3 )]
41+ _num_op_codes = RemoteProgress ._num_op_codes + 3
42+
43+ __slots__ = tuple ()
44+
45+
46+ BEGIN = UpdateProgress .BEGIN
47+ END = UpdateProgress .END
48+ CLONE = UpdateProgress .CLONE
49+ FETCH = UpdateProgress .FETCH
50+ UPDWKTREE = UpdateProgress .UPDWKTREE
3351
3452
3553# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import
@@ -285,7 +303,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
285303
286304 return sm
287305
288- def update (self , recursive = False , init = True , to_latest_revision = False ):
306+ def update (self , recursive = False , init = True , to_latest_revision = False , progress = None ,
307+ dry_run = False ):
289308 """Update the repository of this submodule to point to the checkout
290309 we point at with the binsha of this instance.
291310
@@ -297,20 +316,51 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
297316 This only works if we have a local tracking branch, which is the case
298317 if the remote repository had a master branch, or of the 'branch' option
299318 was specified for this submodule and the branch existed remotely
319+ :param progress: UpdateProgress instance or None of no progress should be shown
320+ :param dry_run: if True, the operation will only be simulated, but not performed.
321+ All performed operations are read-only
300322 :note: does nothing in bare repositories
301323 :note: method is definitely not atomic if recurisve is True
302324 :return: self"""
303325 if self .repo .bare :
304326 return self
305327 #END pass in bare mode
306328
329+ if progress is None :
330+ progress = UpdateProgress ()
331+ #END handle progress
332+ prefix = ''
333+ if dry_run :
334+ prefix = "DRY-RUN: "
335+ #END handle prefix
336+
337+ # to keep things plausible in dry-run mode
338+ if dry_run :
339+ mrepo = None
340+ #END init mrepo
307341
308342 # ASSURE REPO IS PRESENT AND UPTODATE
309343 #####################################
310344 try :
311345 mrepo = self .module ()
312- for remote in mrepo .remotes :
313- remote .fetch ()
346+ rmts = mrepo .remotes
347+ len_rmts = len (rmts )
348+ for i , remote in enumerate (rmts ):
349+ op = FETCH
350+ if i == 0 :
351+ op |= BEGIN
352+ #END handle start
353+
354+ progress .update (op , i , len_rmts , prefix + "Fetching remote %s of submodule %r" % (remote , self .name ))
355+ #===============================
356+ if not dry_run :
357+ remote .fetch (progress = progress )
358+ #END handle dry-run
359+ #===============================
360+ if i == len_rmts - 1 :
361+ op |= END
362+ #END handle end
363+ progress .update (op , i , len_rmts , prefix + "Done fetching remote of submodule %r" % self .name )
314364 #END fetch new data
315365 except InvalidGitRepositoryError :
316366 if not init :
@@ -320,7 +370,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
320370
321371 # there is no git-repository yet - but delete empty paths
322372 module_path = join_path_native (self .repo .working_tree_dir , self .path )
323- if os .path .isdir (module_path ):
373+ if not dry_run and os .path .isdir (module_path ):
324374 try :
325375 os .rmdir (module_path )
326376 except OSError :
@@ -330,40 +380,51 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
330380
331381 # don't check it out at first - nonetheless it will create a local
332382 # branch according to the remote-HEAD if possible
333- mrepo = git .Repo .clone_from (self .url , module_path , n = True )
383+ progress .update (BEGIN | CLONE , 0 , 1 , prefix + "Cloning %s to %s in submodule %r" % (self .url , module_path , self .name ))
384+ if not dry_run :
385+ mrepo = git .Repo .clone_from (self .url , module_path , n = True )
386+ #END handle dry-run
387+ progress .update (END | CLONE , 0 , 1 , prefix + "Done cloning to %s" % module_path )
334388
335- # see whether we have a valid branch to checkout
336- try :
337- # find a remote which has our branch - we try to be flexible
338- remote_branch = find_first_remote_branch (mrepo .remotes , self .branch_name )
339- local_branch = mkhead (mrepo , self .branch_path )
340-
341- # have a valid branch, but no checkout - make sure we can figure
342- # that out by marking the commit with a null_sha
343- local_branch .set_object (util .Object (mrepo , self .NULL_BIN_SHA ))
344- # END initial checkout + branch creation
345-
346- # make sure HEAD is not detached
347- mrepo .head .set_reference (local_branch , logmsg = "submodule: attaching head to %s" % local_branch )
348- mrepo .head .ref .set_tracking_branch (remote_branch )
349- except IndexError :
350- print >> sys .stderr , "Warning: Failed to checkout tracking branch %s" % self .branch_path
351- #END handle tracking branch
352389
353- # NOTE: Have to write the repo config file as well, otherwise
354- # the default implementation will be offended and not update the repository
355- # Maybe this is a good way to assure it doesn't get into our way, but
356- # we want to stay backwards compatible too ... . Its so redundant !
357- self .repo .config_writer ().set_value (sm_section (self .name ), 'url' , self .url )
390+ if not dry_run :
391+ # see whether we have a valid branch to checkout
392+ try :
393+ # find a remote which has our branch - we try to be flexible
394+ remote_branch = find_first_remote_branch (mrepo .remotes , self .branch_name )
395+ local_branch = mkhead (mrepo , self .branch_path )
396+
397+ # have a valid branch, but no checkout - make sure we can figure
398+ # that out by marking the commit with a null_sha
399+ local_branch .set_object (util .Object (mrepo , self .NULL_BIN_SHA ))
400+ # END initial checkout + branch creation
401+
402+ # make sure HEAD is not detached
403+ mrepo .head .set_reference (local_branch , logmsg = "submodule: attaching head to %s" % local_branch )
404+ mrepo .head .ref .set_tracking_branch (remote_branch )
405+ except IndexError :
406+ print >> sys .stderr , "Warning: Failed to checkout tracking branch %s" % self .branch_path
407+ #END handle tracking branch
408+
409+ # NOTE: Have to write the repo config file as well, otherwise
410+ # the default implementation will be offended and not update the repository
411+ # Maybe this is a good way to assure it doesn't get into our way, but
412+ # we want to stay backwards compatible too ... . Its so redundant !
413+ self .repo .config_writer ().set_value (sm_section (self .name ), 'url' , self .url )
414+ #END handle dry_run
358415 #END handle initalization
359416
360417
361418 # DETERMINE SHAS TO CHECKOUT
362419 ############################
363420 binsha = self .binsha
364421 hexsha = self .hexsha
365- is_detached = mrepo .head .is_detached
366- if to_latest_revision :
422+ if mrepo is not None :
423+ # mrepo is only set if we are not in dry-run mode or if the module existed
424+ is_detached = mrepo .head .is_detached
425+ #END handle dry_run
426+
427+ if not dry_run and to_latest_revision :
367428 msg_base = "Cannot update to latest revision in repository at %r as " % mrepo .working_dir
368429 if not is_detached :
369430 rref = mrepo .head .ref .tracking_branch ()
@@ -380,27 +441,35 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
380441 # END handle to_latest_revision option
381442
382443 # update the working tree
383- if mrepo .head .commit .binsha != binsha :
384- if is_detached :
385- # NOTE: for now we force, the user is no supposed to change detached
386- # submodules anyway. Maybe at some point this becomes an option, to
387- # properly handle user modifications - see below for future options
388- # regarding rebase and merge.
389- mrepo .git .checkout (hexsha , force = True )
390- else :
391- # TODO: allow to specify a rebase, merge, or reset
392- # TODO: Warn if the hexsha forces the tracking branch off the remote
393- # branch - this should be prevented when setting the branch option
394- mrepo .head .reset (hexsha , index = True , working_tree = True )
395- # END handle checkout
444+ # handles dry_run
445+ if mrepo is not None and mrepo .head .commit .binsha != binsha :
446+ progress .update (BEGIN | UPDWKTREE , 0 , 1 , prefix + "Updating working tree at %s for submodule %r" % (self .path , self .name ))
447+ if not dry_run :
448+ if is_detached :
449+ # NOTE: for now we force, the user is no supposed to change detached
450+ # submodules anyway. Maybe at some point this becomes an option, to
451+ # properly handle user modifications - see below for future options
452+ # regarding rebase and merge.
453+ mrepo .git .checkout (hexsha , force = True )
454+ else :
455+ # TODO: allow to specify a rebase, merge, or reset
456+ # TODO: Warn if the hexsha forces the tracking branch off the remote
457+ # branch - this should be prevented when setting the branch option
458+ mrepo .head .reset (hexsha , index = True , working_tree = True )
459+ # END handle checkout
460+ #END handle dry_run
461+ progress .update (END | UPDWKTREE , 0 , 1 , prefix + "Done updating working tree for submodule %r" % self .name )
396462 # END update to new commit only if needed
397463
398464 # HANDLE RECURSION
399465 ##################
400466 if recursive :
401- for submodule in self .iter_items (self .module ()):
402- submodule .update (recursive , init , to_latest_revision )
403- # END handle recursive update
467+ # in dry_run mode, the module might not exist
468+ if mrepo is not None :
469+ for submodule in self .iter_items (self .module ()):
470+ submodule .update (recursive , init , to_latest_revision , progress = progress , dry_run = dry_run )
471+ # END handle recursive update
472+ #END handle dry run
404473 # END for each submodule
405474
406475 return self
@@ -800,8 +869,7 @@ def config_reader(self):
800869 def children (self ):
801870 """
802871 :return: IterableList(Submodule, ...) an iterable list of submodules instances
803- which are children of this submodule
804- :raise InvalidGitRepositoryError: if the submodule is not checked-out"""
872+ which are children of this submodule or 0 if the submodule is not checked out"""
805873 return self ._get_intermediate_items (self )
806874
807875 #} END query interface
0 commit comments