Skip to content

Commit 4d36f8f

Browse files
committed
Improved GitConfigurationParser to better deal with streams and the corresponding locks. Submodule class now operates on parent_commits, the configuration is either streamed from the repository or written directly into a blob ( or file ) dependending on whether we have a working tree checkout or not which matches our parent_commit
1 parent a1e2f63 commit 4d36f8f

File tree

4 files changed

+60
-32
lines changed

4 files changed

+60
-32
lines changed

Diff for: lib/git/config.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ def read(self):
271271
if not hasattr(file_object, "seek"):
272272
try:
273273
fp = open(file_object)
274+
close_fp = True
274275
except IOError,e:
275276
continue
276-
close_fp = True
277277
# END fp handling
278278

279279
try:
@@ -308,17 +308,21 @@ def write(self):
308308
:raise IOError: if this is a read-only writer instance or if we could not obtain
309309
a file lock"""
310310
self._assure_writable("write")
311-
self._lock._obtain_lock()
312-
313311

314312
fp = self._file_or_files
315313
close_fp = False
316314

315+
# we have a physical file on disk, so get a lock
316+
if isinstance(fp, (basestring, file)):
317+
self._lock._obtain_lock()
318+
# END get lock for physical files
319+
317320
if not hasattr(fp, "seek"):
318321
fp = open(self._file_or_files, "w")
319322
close_fp = True
320323
else:
321324
fp.seek(0)
325+
# END handle stream or file
322326

323327
# WRITE DATA
324328
try:
@@ -390,7 +394,7 @@ def get_value(self, section, option, default = None):
390394
return valuestr
391395

392396
@needs_values
393-
@set_dirty_and_flush_changes
397+
@set_dirty_and_flush_changes
394398
def set_value(self, section, option, value):
395399
"""Sets the given option in section to the given value.
396400
It will create the section if required, and will not throw as opposed to the default

Diff for: lib/git/objects/submodule.py

+46-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
__all__ = ("Submodule", )
88

9+
class SubmoduleConfigParser(GitConfigParser):
10+
"""Catches calls to _write, and updates the .gitmodules blob in the index
11+
with the new data, if we have written into a stream. Otherwise it will
12+
add the local file to the index to make it correspond with the working tree."""
13+
_mutating_methods_ = tuple()
14+
15+
916
class Submodule(base.IndexObject):
1017
"""Implements access to a git submodule. They are special in that their sha
1118
represents a commit in the submodule's repository which is to be checked out
@@ -20,14 +27,14 @@ class Submodule(base.IndexObject):
2027
# this is a bogus type for base class compatability
2128
type = 'submodule'
2229

23-
__slots__ = ('_root_tree', '_url', '_ref')
30+
__slots__ = ('_parent_commit', '_url', '_ref')
2431

2532
def _set_cache_(self, attr):
2633
if attr == 'size':
2734
raise ValueError("Submodules do not have a size as they do not refer to anything in this repository")
28-
elif attr == '_root_tree':
35+
elif attr == '_parent_commit':
2936
# set a default value, which is the root tree of the current head
30-
self._root_tree = self.repo.tree()
37+
self._parent_commit = self.repo.commit()
3138
elif attr in ('path', '_url', '_ref'):
3239
reader = self.config_reader()
3340
# default submodule values
@@ -39,13 +46,26 @@ def _set_cache_(self, attr):
3946
super(Submodule, self)._set_cache_(attr)
4047
# END handle attribute name
4148

42-
def _fp_config(self):
49+
def _sio_modules(self):
4350
""":return: Configuration file as StringIO - we only access it through the respective blob's data"""
44-
return StringIO(self._root_tree[self.kModulesFile].datastream.read())
51+
sio = StringIO(self._parent_commit.tree[self.kModulesFile].datastream.read())
52+
sio.name = self.kModulesFile
53+
return sio
4554

4655
def _config_parser(self, read_only):
4756
""":return: Config Parser constrained to our submodule in read or write mode"""
48-
parser = GitConfigParser(self._fp_config(), read_only = read_only)
57+
parent_matches_head = self.repo.head.commit == self._parent_commit
58+
if not self.repo.bare and parent_matches_head:
59+
fp_module = self.kModulesFile
60+
else:
61+
fp_module = self._sio_modules()
62+
# END handle non-bare working tree
63+
64+
if not read_only and not parent_matches_head:
65+
raise ValueError("Cannot write blobs of 'historical' submodule configurations")
66+
# END handle writes of historical submodules
67+
68+
parser = GitConfigParser(fp_module, read_only = read_only)
4969
return SectionConstraint(parser, 'submodule "%s"' % self.path)
5070

5171
#{ Edit Interface
@@ -61,21 +81,24 @@ def add(cls, repo, path, url, skip_init=False):
6181
:param skip_init: if True, the new repository will not be cloned to its location.
6282
:return: The newly created submodule instance"""
6383

64-
def set_root_tree(self, root_tree):
65-
"""Set this instance to use the given tree which is supposed to contain the
66-
.gitmodules blob.
67-
:param root_tree: Tree'ish reference pointing at the root_tree
68-
:raise ValueError: if the root_tree didn't contain the .gitmodules blob."""
69-
tree = self.repo.tree(root_tree)
70-
if self.kModulesFile not in tree:
71-
raise ValueError("Tree %s did not contain the %s file" % (root_tree, self.kModulesFile))
84+
def set_parent_commit(self, commit):
85+
"""Set this instance to use the given commit whose tree is supposed to
86+
contain the .gitmodules blob.
87+
:param commit: Commit'ish reference pointing at the root_tree
88+
:raise ValueError: if the commit's tree didn't contain the .gitmodules blob."""
89+
pcommit = self.repo.commit(commit)
90+
if self.kModulesFile not in pcommit.tree:
91+
raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.kModulesFile))
7292
# END handle exceptions
73-
self._root_tree = tree
93+
self._parent_commit = pcommit
7494

75-
# clear the possibly changing values
76-
del(self.path)
77-
del(self._ref)
78-
del(self._url)
95+
# clear the possibly changed values
96+
for name in ('path', '_ref', '_url'):
97+
try:
98+
delattr(self, name)
99+
except AttributeError:
100+
pass
101+
# END for each name to delete
79102

80103
def config_writer(self):
81104
""":return: a config writer instance allowing you to read and write the data
@@ -108,11 +131,10 @@ def url(self):
108131
""":return: The url to the repository which our module-repository refers to"""
109132
return self._url
110133

111-
def root_tree(self):
112-
""":return: Tree instance referring to the tree which contains the .gitmodules file
113-
we are to use
114-
:note: will always point to the current head's root tree if it was not set explicitly"""
115-
return self._root_tree
134+
def parent_commit(self):
135+
""":return: Commit instance with the tree containing the .gitmodules file
136+
:note: will always point to the current head's commit if it was not set explicitly"""
137+
return self._parent_commit
116138

117139
def config_reader(self):
118140
""":return: ConfigReader instance which allows you to qurey the configuration values

Diff for: test/git/test_config.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ class TestBase(TestCase):
1414

1515
def _to_memcache(self, file_path):
1616
fp = open(file_path, "r")
17-
sio = StringIO.StringIO()
18-
sio.write(fp.read())
19-
sio.seek(0)
17+
sio = StringIO.StringIO(fp.read())
2018
sio.name = file_path
2119
return sio
2220

Diff for: test/git/test_submodule.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def _do_base_tests(self, rwrepo):
1919
# size is invalid
2020
self.failUnlessRaises(ValueError, getattr, sm, 'size')
2121

22-
# fails if tree has no gitmodule file
22+
# set_parent_commit fails if tree has no gitmodule file
23+
24+
2325

2426
if rwrepo.bare:
2527
# module fails
@@ -28,6 +30,8 @@ def _do_base_tests(self, rwrepo):
2830
# get the module repository
2931
pass
3032
# END bare handling
33+
34+
# Writing of historical submodule configurations must not work
3135

3236
@with_rw_repo(kCOTag)
3337
def test_base_rw(self, rwrepo):

0 commit comments

Comments
 (0)