88import stat
99
1010import os
11+ import sys
12+ import weakref
1113
1214__all__ = ("Submodule" , "RootModule" )
1315
@@ -27,11 +29,43 @@ def sm_name(section):
2729#{ Classes
2830
2931class SubmoduleConfigParser (GitConfigParser ):
30- """Catches calls to _write, and updates the .gitmodules blob in the index
32+ """
33+ Catches calls to _write, and updates the .gitmodules blob in the index
3134 with the new data, if we have written into a stream. Otherwise it will
32- add the local file to the index to make it correspond with the working tree."""
33- _mutating_methods_ = tuple ()
35+ add the local file to the index to make it correspond with the working tree.
36+ Additionally, the cache must be cleared
37+ """
3438
39+ def __init__ (self , * args , ** kwargs ):
40+ self ._smref = None
41+ super (SubmoduleConfigParser , self ).__init__ (* args , ** kwargs )
42+
43+ #{ Interface
44+ def set_submodule (self , submodule ):
45+ """Set this instance's submodule. It must be called before
46+ the first write operation begins"""
47+ self ._smref = weakref .ref (submodule )
48+
49+ def flush_to_index (self ):
50+ """Flush changes in our configuration file to the index"""
51+ assert self ._smref is not None
52+ # should always have a file here
53+ assert not isinstance (self ._file_or_files , StringIO )
54+
55+ sm = self ._smref ()
56+ if sm is not None :
57+ sm .repo .index .add ([sm .k_modules_file ])
58+ sm ._clear_cache ()
59+ # END handle weakref
60+
61+ #} END interface
62+
63+ #{ Overridden Methods
64+ def write (self ):
65+ rval = super (SubmoduleConfigParser , self ).write ()
66+ self .flush_to_index ()
67+ return rval
68+ # END overridden methods
3569
3670class Submodule (base .IndexObject , Iterable , Traversable ):
3771 """Implements access to a git submodule. They are special in that their sha
@@ -44,16 +78,16 @@ class Submodule(base.IndexObject, Iterable, Traversable):
4478
4579 _id_attribute_ = "name"
4680 k_modules_file = '.gitmodules'
47- k_ref_option = 'ref '
48- k_ref_default = 'master'
81+ k_head_option = 'branch '
82+ k_head_default = 'master'
4983 k_def_mode = stat .S_IFDIR | stat .S_IFLNK # submodules are directories with link-status
5084
5185 # this is a bogus type for base class compatability
5286 type = 'submodule'
5387
54- __slots__ = ('_parent_commit' , '_url' , '_ref ' , '_name' )
88+ __slots__ = ('_parent_commit' , '_url' , '_branch ' , '_name' , '__weakref__ ' )
5589
56- def __init__ (self , repo , binsha , mode = None , path = None , name = None , parent_commit = None , url = None , ref = None ):
90+ def __init__ (self , repo , binsha , mode = None , path = None , name = None , parent_commit = None , url = None , branch = None ):
5791 """Initialize this instance with its attributes. We only document the ones
5892 that differ from ``IndexObject``
5993 :param repo: Our parent repository
@@ -66,8 +100,8 @@ def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commi
66100 self ._parent_commit = parent_commit
67101 if url is not None :
68102 self ._url = url
69- if ref is not None :
70- self ._ref = ref
103+ if branch is not None :
104+ self ._branch = branch
71105 if name is not None :
72106 self ._name = name
73107
@@ -77,13 +111,13 @@ def _set_cache_(self, attr):
77111 elif attr == '_parent_commit' :
78112 # set a default value, which is the root tree of the current head
79113 self ._parent_commit = self .repo .commit ()
80- elif attr in ('path' , '_url' , '_ref ' ):
114+ elif attr in ('path' , '_url' , '_branch ' ):
81115 reader = self .config_reader ()
82116 # default submodule values
83117 self .path = reader .get_value ('path' )
84118 self ._url = reader .get_value ('url' )
85119 # git-python extension values - optional
86- self ._ref = reader .get_value (self .k_ref_option , self .k_ref_default )
120+ self ._branch = reader .get_value (self .k_head_option , self .k_head_default )
87121 elif attr == '_name' :
88122 raise AttributeError ("Cannot retrieve the name of a submodule if it was not set initially" )
89123 else :
@@ -132,12 +166,21 @@ def _config_parser(cls, repo, parent_commit, read_only):
132166 # END handle exceptions
133167 # END handle non-bare working tree
134168
135- if not read_only and not parent_matches_head :
169+ if not read_only and ( repo . bare or not parent_matches_head ) :
136170 raise ValueError ("Cannot write blobs of 'historical' submodule configurations" )
137171 # END handle writes of historical submodules
138172
139- return GitConfigParser (fp_module , read_only = read_only )
173+ return SubmoduleConfigParser (fp_module , read_only = read_only )
140174
175+ def _clear_cache (self ):
176+ # clear the possibly changed values
177+ for name in ('path' , '_branch' , '_url' ):
178+ try :
179+ delattr (self , name )
180+ except AttributeError :
181+ pass
182+ # END try attr deletion
183+ # END for each name to delete
141184
142185 @classmethod
143186 def _sio_modules (cls , parent_commit ):
@@ -149,6 +192,7 @@ def _sio_modules(cls, parent_commit):
149192 def _config_parser_constrained (self , read_only ):
150193 """:return: Config Parser constrained to our submodule in read or write mode"""
151194 parser = self ._config_parser (self .repo , self ._parent_commit , read_only )
195+ parser .set_submodule (self )
152196 return SectionConstraint (parser , sm_section (self .name ))
153197
154198 #{ Edit Interface
@@ -178,6 +222,9 @@ def update(self, recursive=False, init=True):
178222
179223 try :
180224 mrepo = self .module ()
225+ for remote in mrepo .remotes :
226+ remote .fetch ()
227+ #END fetch new data
181228 except InvalidGitRepositoryError :
182229 if not init :
183230 return self
@@ -194,25 +241,42 @@ def update(self, recursive=False, init=True):
194241 # END handle OSError
195242 # END handle directory removal
196243
197- # don't check it out at first
198- mrepo = git .Repo .clone_from (self .url , self .path , n = True )
199- # ref can be a tag or a branch - we can checkout branches, but not tags
200- # tag_ref = git.TagReference(mrepo, TagReference.to_full_path(self.ref))
201- if tag_ref .is_valid ():
202- #if tag_ref.commit
203- mrepo .git .checkout (tag_ref )
204- else :
205- # assume it is a branch and try it
206- mrepo .git .checkout (self .hexsha , b = self .ref )
207- #if mrepo.head.ref.name != self.ref:
208- # mrepo.head.ref = git.Head(mrepo, git.Head.to_full_path(self.ref
244+ # don't check it out at first - nonetheless it will create a local
245+ # branch according to the remote-HEAD if possible
246+ mrepo = git .Repo .clone_from (self .url , module_path , n = True )
247+
248+ # see whether we have a valid branch to checkout
249+ try :
250+ remote_branch = mrepo .remotes .origin .refs [self .branch ]
251+ local_branch = git .Head (mrepo , git .Head .to_full_path (self .branch ))
252+ if not local_branch .is_valid ():
253+ mrepo .git .checkout (remote_branch , b = self .branch )
254+ # END initial checkout + branch creation
255+ # make sure we are not detached
256+ mrepo .head .ref = local_branch
257+ except IndexError :
258+ print >> sys .stderr , "Warning: Failed to checkout tracking branch %s" % self .branch
259+ #END handle tracking branch
209260 #END handle initalization
210261
211- # TODO: handle ref-path
212- if mrepo .head .commit .binsha != self .binsha :
213- mrepo .git .checkout (self .binsha )
262+ # if the commit to checkout is on the current branch, merge the branch
263+ if mrepo .head .is_detached :
264+ if mrepo .head .commit .binsha != self .binsha :
265+ mrepo .git .checkout (self .hexsha )
266+ # END checkout commit
267+ else :
268+ # TODO: allow to specify a rebase, merge, or reset
269+ # TODO: Warn if the hexsha forces the tracking branch off the remote
270+ # branch - this should be prevented when setting the branch option
271+ mrepo .head .reset (self .hexsha , index = True , working_tree = True )
214272 # END handle checkout
215273
274+ if recursive :
275+ for submodule in self .iter_items (self .module ()):
276+ submodule .update (recursive , init )
277+ # END handle recursive update
278+ # END for each submodule
279+
216280 return self
217281
218282 def set_parent_commit (self , commit , check = True ):
@@ -245,14 +309,8 @@ def set_parent_commit(self, commit, check=True):
245309 # update our sha, it could have changed
246310 self .binsha = pctree [self .path ].binsha
247311
248- # clear the possibly changed values
249- for name in ('path' , '_ref' , '_url' ):
250- try :
251- delattr (self , name )
252- except AttributeError :
253- pass
254- # END try attr deletion
255- # END for each name to delete
312+ self ._clear_cache ()
313+
256314 return self
257315
258316 def config_writer (self ):
@@ -262,6 +320,8 @@ def config_writer(self):
262320 :raise ValueError: if trying to get a writer on a parent_commit which does not
263321 match the current head commit
264322 :raise IOError: If the .gitmodules file/blob could not be read"""
323+ if self .repo .bare :
324+ raise InvalidGitRepositoryError ("Cannot change submodule configuration in a bare repository" )
265325 return self ._config_parser_constrained (read_only = False )
266326
267327 #} END edit interface
@@ -279,24 +339,28 @@ def module(self):
279339 raise InvalidGitRepositoryError ("Cannot retrieve module repository in bare parent repositories" )
280340 # END handle bare mode
281341
282- repo_path = join_path_native ( self .repo . working_tree_dir , self . path )
342+ module_path = self .module_path ()
283343 try :
284- repo = Repo (repo_path )
344+ repo = Repo (module_path )
285345 if repo != self .repo :
286346 return repo
287347 # END handle repo uninitialized
288348 except (InvalidGitRepositoryError , NoSuchPathError ):
289349 raise InvalidGitRepositoryError ("No valid repository at %s" % self .path )
290350 else :
291- raise InvalidGitRepositoryError ("Repository at %r was not yet checked out" % repo_path )
351+ raise InvalidGitRepositoryError ("Repository at %r was not yet checked out" % module_path )
292352 # END handle exceptions
353+
354+ def module_path (self ):
355+ """:return: full path to the root of our module. It is relative to the filesystem root"""
356+ return join_path_native (self .repo .working_tree_dir , self .path )
293357
294358 @property
295- def ref (self ):
296- """:return: The reference's name that we are to checkout"""
297- return self ._ref
359+ def branch (self ):
360+ """:return: The branch name that we are to checkout"""
361+ return self ._branch
298362
299- @property
363+ @property
300364 def url (self ):
301365 """:return: The url to the repository which our module-repository refers to"""
302366 return self ._url
@@ -347,9 +411,9 @@ def iter_items(cls, repo, parent_commit='HEAD'):
347411 n = sm_name (sms )
348412 p = parser .get_value (sms , 'path' )
349413 u = parser .get_value (sms , 'url' )
350- r = cls .k_ref_default
351- if parser .has_option (sms , cls .k_ref_option ):
352- r = parser .get_value (sms , cls .k_ref_option )
414+ b = cls .k_head_default
415+ if parser .has_option (sms , cls .k_head_option ):
416+ b = parser .get_value (sms , cls .k_head_option )
353417 # END handle optional information
354418
355419 # get the binsha
@@ -362,7 +426,7 @@ def iter_items(cls, repo, parent_commit='HEAD'):
362426 # fill in remaining info - saves time as it doesn't have to be parsed again
363427 sm ._name = n
364428 sm ._parent_commit = pc
365- sm ._ref = r
429+ sm ._branch = b
366430 sm ._url = u
367431
368432 yield sm
@@ -389,10 +453,14 @@ def __init__(self, repo):
389453 name = self .k_root_name ,
390454 parent_commit = repo .head .commit ,
391455 url = '' ,
392- ref = self .k_ref_default
456+ branch = self .k_head_default
393457 )
394458
395459
460+ def _clear_cache (self ):
461+ """May not do anything"""
462+ pass
463+
396464 #{ Interface
397465 def module (self ):
398466 """:return: the actual repository containing the submodules"""
0 commit comments