8
8
import stat
9
9
10
10
import os
11
+ import sys
12
+ import weakref
11
13
12
14
__all__ = ("Submodule" , "RootModule" )
13
15
@@ -27,11 +29,43 @@ def sm_name(section):
27
29
#{ Classes
28
30
29
31
class 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
31
34
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
+ """
34
38
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
35
69
36
70
class Submodule (base .IndexObject , Iterable , Traversable ):
37
71
"""Implements access to a git submodule. They are special in that their sha
@@ -44,16 +78,16 @@ class Submodule(base.IndexObject, Iterable, Traversable):
44
78
45
79
_id_attribute_ = "name"
46
80
k_modules_file = '.gitmodules'
47
- k_ref_option = 'ref '
48
- k_ref_default = 'master'
81
+ k_head_option = 'branch '
82
+ k_head_default = 'master'
49
83
k_def_mode = stat .S_IFDIR | stat .S_IFLNK # submodules are directories with link-status
50
84
51
85
# this is a bogus type for base class compatability
52
86
type = 'submodule'
53
87
54
- __slots__ = ('_parent_commit' , '_url' , '_ref ' , '_name' )
88
+ __slots__ = ('_parent_commit' , '_url' , '_branch ' , '_name' , '__weakref__ ' )
55
89
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 ):
57
91
"""Initialize this instance with its attributes. We only document the ones
58
92
that differ from ``IndexObject``
59
93
:param repo: Our parent repository
@@ -66,8 +100,8 @@ def __init__(self, repo, binsha, mode=None, path=None, name = None, parent_commi
66
100
self ._parent_commit = parent_commit
67
101
if url is not None :
68
102
self ._url = url
69
- if ref is not None :
70
- self ._ref = ref
103
+ if branch is not None :
104
+ self ._branch = branch
71
105
if name is not None :
72
106
self ._name = name
73
107
@@ -77,13 +111,13 @@ def _set_cache_(self, attr):
77
111
elif attr == '_parent_commit' :
78
112
# set a default value, which is the root tree of the current head
79
113
self ._parent_commit = self .repo .commit ()
80
- elif attr in ('path' , '_url' , '_ref ' ):
114
+ elif attr in ('path' , '_url' , '_branch ' ):
81
115
reader = self .config_reader ()
82
116
# default submodule values
83
117
self .path = reader .get_value ('path' )
84
118
self ._url = reader .get_value ('url' )
85
119
# 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 )
87
121
elif attr == '_name' :
88
122
raise AttributeError ("Cannot retrieve the name of a submodule if it was not set initially" )
89
123
else :
@@ -132,12 +166,21 @@ def _config_parser(cls, repo, parent_commit, read_only):
132
166
# END handle exceptions
133
167
# END handle non-bare working tree
134
168
135
- if not read_only and not parent_matches_head :
169
+ if not read_only and ( repo . bare or not parent_matches_head ) :
136
170
raise ValueError ("Cannot write blobs of 'historical' submodule configurations" )
137
171
# END handle writes of historical submodules
138
172
139
- return GitConfigParser (fp_module , read_only = read_only )
173
+ return SubmoduleConfigParser (fp_module , read_only = read_only )
140
174
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
141
184
142
185
@classmethod
143
186
def _sio_modules (cls , parent_commit ):
@@ -149,6 +192,7 @@ def _sio_modules(cls, parent_commit):
149
192
def _config_parser_constrained (self , read_only ):
150
193
""":return: Config Parser constrained to our submodule in read or write mode"""
151
194
parser = self ._config_parser (self .repo , self ._parent_commit , read_only )
195
+ parser .set_submodule (self )
152
196
return SectionConstraint (parser , sm_section (self .name ))
153
197
154
198
#{ Edit Interface
@@ -178,6 +222,9 @@ def update(self, recursive=False, init=True):
178
222
179
223
try :
180
224
mrepo = self .module ()
225
+ for remote in mrepo .remotes :
226
+ remote .fetch ()
227
+ #END fetch new data
181
228
except InvalidGitRepositoryError :
182
229
if not init :
183
230
return self
@@ -194,25 +241,42 @@ def update(self, recursive=False, init=True):
194
241
# END handle OSError
195
242
# END handle directory removal
196
243
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
209
260
#END handle initalization
210
261
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 )
214
272
# END handle checkout
215
273
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
+
216
280
return self
217
281
218
282
def set_parent_commit (self , commit , check = True ):
@@ -245,14 +309,8 @@ def set_parent_commit(self, commit, check=True):
245
309
# update our sha, it could have changed
246
310
self .binsha = pctree [self .path ].binsha
247
311
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
+
256
314
return self
257
315
258
316
def config_writer (self ):
@@ -262,6 +320,8 @@ def config_writer(self):
262
320
:raise ValueError: if trying to get a writer on a parent_commit which does not
263
321
match the current head commit
264
322
: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" )
265
325
return self ._config_parser_constrained (read_only = False )
266
326
267
327
#} END edit interface
@@ -279,24 +339,28 @@ def module(self):
279
339
raise InvalidGitRepositoryError ("Cannot retrieve module repository in bare parent repositories" )
280
340
# END handle bare mode
281
341
282
- repo_path = join_path_native ( self .repo . working_tree_dir , self . path )
342
+ module_path = self .module_path ()
283
343
try :
284
- repo = Repo (repo_path )
344
+ repo = Repo (module_path )
285
345
if repo != self .repo :
286
346
return repo
287
347
# END handle repo uninitialized
288
348
except (InvalidGitRepositoryError , NoSuchPathError ):
289
349
raise InvalidGitRepositoryError ("No valid repository at %s" % self .path )
290
350
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 )
292
352
# 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 )
293
357
294
358
@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
298
362
299
- @property
363
+ @property
300
364
def url (self ):
301
365
""":return: The url to the repository which our module-repository refers to"""
302
366
return self ._url
@@ -347,9 +411,9 @@ def iter_items(cls, repo, parent_commit='HEAD'):
347
411
n = sm_name (sms )
348
412
p = parser .get_value (sms , 'path' )
349
413
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 )
353
417
# END handle optional information
354
418
355
419
# get the binsha
@@ -362,7 +426,7 @@ def iter_items(cls, repo, parent_commit='HEAD'):
362
426
# fill in remaining info - saves time as it doesn't have to be parsed again
363
427
sm ._name = n
364
428
sm ._parent_commit = pc
365
- sm ._ref = r
429
+ sm ._branch = b
366
430
sm ._url = u
367
431
368
432
yield sm
@@ -389,10 +453,14 @@ def __init__(self, repo):
389
453
name = self .k_root_name ,
390
454
parent_commit = repo .head .commit ,
391
455
url = '' ,
392
- ref = self .k_ref_default
456
+ branch = self .k_head_default
393
457
)
394
458
395
459
460
+ def _clear_cache (self ):
461
+ """May not do anything"""
462
+ pass
463
+
396
464
#{ Interface
397
465
def module (self ):
398
466
""":return: the actual repository containing the submodules"""
0 commit comments