@@ -28,6 +28,19 @@ def sm_name(section):
2828def mkhead (repo , path ):
2929 """:return: New branch/head instance"""
3030 return git .Head (repo , git .Head .to_full_path (path ))
31+
32+ def unbare_repo (func ):
33+ """Methods with this decorator raise InvalidGitRepositoryError if they
34+ encounter a bare repository"""
35+ def wrapper (self , * args , ** kwargs ):
36+ if self .repo .bare :
37+ raise InvalidGitRepositoryError ("Method '%s' cannot operate on bare repositories" % func .__name__ )
38+ #END bare method
39+ return func (self , * args , ** kwargs )
40+ # END wrapper
41+ wrapper .__name__ = func .__name__
42+ return wrapper
43+
3144#} END utilities
3245
3346
@@ -39,10 +52,14 @@ class SubmoduleConfigParser(GitConfigParser):
3952 with the new data, if we have written into a stream. Otherwise it will
4053 add the local file to the index to make it correspond with the working tree.
4154 Additionally, the cache must be cleared
55+
56+ Please note that no mutating method will work in bare mode
4257 """
4358
4459 def __init__ (self , * args , ** kwargs ):
4560 self ._smref = None
61+ self ._index = None
62+ self ._auto_write = True
4663 super (SubmoduleConfigParser , self ).__init__ (* args , ** kwargs )
4764
4865 #{ Interface
@@ -59,7 +76,11 @@ def flush_to_index(self):
5976
6077 sm = self ._smref ()
6178 if sm is not None :
62- sm .repo .index .add ([sm .k_modules_file ])
79+ index = self ._index
80+ if index is None :
81+ index = sm .repo .index
82+ # END handle index
83+ index .add ([sm .k_modules_file ], write = self ._auto_write )
6384 sm ._clear_cache ()
6485 # END handle weakref
6586
@@ -102,6 +123,7 @@ def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commi
102123 :param url: The url to the remote repository which is the submodule
103124 :param branch: Head instance to checkout when cloning the remote repository"""
104125 super (Submodule , self ).__init__ (repo , binsha , mode , path )
126+ self .size = 0
105127 if parent_commit is not None :
106128 self ._parent_commit = parent_commit
107129 if url is not None :
@@ -113,9 +135,7 @@ def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commi
113135 self ._name = name
114136
115137 def _set_cache_ (self , attr ):
116- if attr == 'size' :
117- raise ValueError ("Submodules do not have a size as they do not refer to anything in this repository" )
118- elif attr == '_parent_commit' :
138+ if attr == '_parent_commit' :
119139 # set a default value, which is the root tree of the current head
120140 self ._parent_commit = self .repo .commit ()
121141 elif attr in ('path' , '_url' , '_branch' ):
@@ -235,8 +255,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
235255 :note: works atomically, such that no change will be done if the repository
236256 update fails for instance"""
237257 if repo .bare :
238- raise InvalidGitRepositoryError ("Cannot add a submodule to bare repositories" )
239- #END handle bare mode
258+ raise InvalidGitRepositoryError ("Cannot add submodules to bare repositories" )
259+ # END handle bare repos
240260
241261 path = to_native_path_linux (path )
242262 if path .endswith ('/' ):
@@ -280,7 +300,8 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
280300 # END verify url
281301
282302 # update configuration and index
283- writer = sm .config_writer ()
303+ index = sm .repo .index
304+ writer = sm .config_writer (index = index , write = False )
284305 writer .set_value ('url' , url )
285306 writer .set_value ('path' , path )
286307
@@ -302,11 +323,10 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False):
302323 pcommit = repo .head .commit
303324 sm ._parent_commit = pcommit
304325 sm .binsha = mrepo .head .commit .binsha
305- repo . index .add ([sm ], write = True )
326+ index .add ([sm ], write = True )
306327
307328 return sm
308329
309-
310330 def update (self , recursive = False , init = True , to_latest_revision = False ):
311331 """Update the repository of this submodule to point to the checkout
312332 we point at with the binsha of this instance.
@@ -426,6 +446,85 @@ def update(self, recursive=False, init=True, to_latest_revision=False):
426446
427447 return self
428448
449+ @unbare_repo
450+ def move (self , module_path ):
451+ """Move the submodule to a another module path. This involves physically moving
452+ the repository at our current path, changing the configuration, as well as
453+ adjusting our index entry accordingly.
454+ :param module_path: the path to which to move our module, given as
455+ repository-relative path. Intermediate directories will be created
456+ accordingly. If the path already exists, it must be empty.
457+ Trailling (back)slashes are removed automatically
458+ :return: self
459+ :raise ValueError: if the module path existed and was not empty, or was a file
460+ :note: Currently the method is not atomic, and it could leave the repository
461+ in an inconsistent state if a sub-step fails for some reason
462+ """
463+ module_path = to_native_path_linux (module_path )
464+ if module_path .endswith ('/' ):
465+ module_path = module_path [:- 1 ]
466+ # END handle trailing slash
467+
468+ # VERIFY DESTINATION
469+ if module_path == self .path :
470+ return self
471+ #END handle no change
472+
473+ dest_path = join_path_native (self .repo .working_tree_dir , module_path )
474+ if os .path .isfile (dest_path ):
475+ raise ValueError ("Cannot move repository onto a file: %s" % dest_path )
476+ # END handle target files
477+
478+ # remove existing destination
479+ if os .path .exists (dest_path ):
480+ if len (os .listdir (dest_path )):
481+ raise ValueError ("Destination module directory was not empty" )
482+ #END handle non-emptyness
483+
484+ if os .path .islink (dest_path ):
485+ os .remove (dest_path )
486+ else :
487+ os .rmdir (dest_path )
488+ #END handle link
489+ else :
490+ # recreate parent directories
491+ # NOTE: renames() does that now
492+ pass
493+ #END handle existance
494+
495+ # move the module into place if possible
496+ cur_path = self .module_path ()
497+ if os .path .exists (cur_path ):
498+ os .renames (cur_path , dest_path )
499+ #END move physical module
500+
501+ # NOTE: from now on, we would have to undo the rename !
502+
503+ # rename the index entry - have to manipulate the index directly as
504+ # 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 )
522+
523+ return self
524+
525+
526+
527+ @unbare_repo
429528 def remove (self , module = True , force = False , configuration = True , dry_run = False ):
430529 """Remove this submodule from the repository. This will remove our entry
431530 from the .gitmodules file and the entry in the .git/config file.
@@ -449,10 +548,6 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
449548 :note: doesn't work in bare repositories
450549 :raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
451550 :raise OSError: if directories or files could not be removed"""
452- if self .repo .bare :
453- raise InvalidGitRepositoryError ("Cannot delete a submodule in bare repository" )
454- # END handle bare mode
455-
456551 if not (module + configuration ):
457552 raise ValueError ("Need to specify to delete at least the module, or the configuration" )
458553 # END handle params
@@ -565,31 +660,37 @@ def set_parent_commit(self, commit, check=True):
565660
566661 return self
567662
568- def config_writer (self ):
663+ @unbare_repo
664+ def config_writer (self , index = None , write = True ):
569665 """:return: a config writer instance allowing you to read and write the data
570666 belonging to this submodule into the .gitmodules file.
571667
668+ :param index: if not None, an IndexFile instance which should be written.
669+ defaults to the index of the Submodule's parent repository.
670+ :param write: if True, the index will be written each time a configuration
671+ value changes.
672+ :note: the parameters allow for a more efficient writing of the index,
673+ as you can pass in a modified index on your own, prevent automatic writing,
674+ and write yourself once the whole operation is complete
572675 :raise ValueError: if trying to get a writer on a parent_commit which does not
573676 match the current head commit
574677 :raise IOError: If the .gitmodules file/blob could not be read"""
575- if self .repo .bare :
576- raise InvalidGitRepositoryError ("Cannot change submodule configuration in a bare repository" )
577- return self ._config_parser_constrained (read_only = False )
678+ writer = self ._config_parser_constrained (read_only = False )
679+ if index is not None :
680+ writer .config ._index = index
681+ writer .config ._auto_write = write
682+ return writer
578683
579684 #} END edit interface
580685
581686 #{ Query Interface
582687
688+ @unbare_repo
583689 def module (self ):
584690 """:return: Repo instance initialized from the repository at our submodule path
585691 :raise InvalidGitRepositoryError: if a repository was not available. This could
586692 also mean that it was not yet initialized"""
587693 # late import to workaround circular dependencies
588-
589- if self .repo .bare :
590- raise InvalidGitRepositoryError ("Cannot retrieve module repository in bare parent repositories" )
591- # END handle bare mode
592-
593694 module_path = self .module_path ()
594695 try :
595696 repo = git .Repo (module_path )
0 commit comments