Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d4fd7fc

Browse files
committed
Submodule now only supports branches to be given as hint that will svn-external like behaviour. Implemented first version of update, which works for now, but probably needs to see more features
1 parent ceee7d7 commit d4fd7fc

File tree

4 files changed

+174
-69
lines changed

4 files changed

+174
-69
lines changed

lib/git/config.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,23 @@ def __new__(metacls, name, bases, clsdict):
2323
"""
2424
Equip all base-class methods with a needs_values decorator, and all non-const methods
2525
with a set_dirty_and_flush_changes decorator in addition to that."""
26-
mutating_methods = clsdict['_mutating_methods_']
27-
for base in bases:
28-
methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") )
29-
for name, method in methods:
30-
if name in clsdict:
31-
continue
32-
method_with_values = needs_values(method)
33-
if name in mutating_methods:
34-
method_with_values = set_dirty_and_flush_changes(method_with_values)
35-
# END mutating methods handling
36-
37-
clsdict[name] = method_with_values
38-
# END for each base
26+
kmm = '_mutating_methods_'
27+
if kmm in clsdict:
28+
mutating_methods = clsdict[kmm]
29+
for base in bases:
30+
methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") )
31+
for name, method in methods:
32+
if name in clsdict:
33+
continue
34+
method_with_values = needs_values(method)
35+
if name in mutating_methods:
36+
method_with_values = set_dirty_and_flush_changes(method_with_values)
37+
# END mutating methods handling
38+
39+
clsdict[name] = method_with_values
40+
# END for each name/method pair
41+
# END for each base
42+
# END if mutating methods configuration is set
3943

4044
new_type = super(MetaParserBuilder, metacls).__new__(metacls, name, bases, clsdict)
4145
return new_type

lib/git/objects/submodule.py

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import stat
99

1010
import os
11+
import sys
12+
import weakref
1113

1214
__all__ = ("Submodule", "RootModule")
1315

@@ -27,11 +29,43 @@ def sm_name(section):
2729
#{ Classes
2830

2931
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
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

3670
class 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"""

lib/git/objects/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def addToStack( stack, item, branch_first, depth ):
343343
if prune( rval, d ):
344344
continue
345345

346-
skipStartItem = ignore_self and ( item == self )
346+
skipStartItem = ignore_self and ( item is self )
347347
if not skipStartItem and predicate( rval, d ):
348348
yield rval
349349

0 commit comments

Comments
 (0)