Skip to content

Commit a1e2f63

Browse files
committed
submodule: Fleshed out interface, and a partial test which is not yet usable. It showed that the ConfigParser needs some work. If the root is set, it also needs to refer to the root_commit instead of to the root-tree, as it will have to decide whether it works on the working tree's version of the .gitmodules file or the one in the repository
1 parent a1d1d2c commit a1e2f63

File tree

5 files changed

+171
-34
lines changed

5 files changed

+171
-34
lines changed

Diff for: lib/git/config.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from git.odict import OrderedDict
1616
from git.util import LockFile
1717

18-
__all__ = ('GitConfigParser', )
18+
__all__ = ('GitConfigParser', 'SectionConstraint')
1919

2020
class MetaParserBuilder(type):
2121
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
@@ -63,7 +63,29 @@ def flush_changes(self, *args, **kwargs):
6363
flush_changes.__name__ = non_const_func.__name__
6464
return flush_changes
6565

66+
67+
class SectionConstraint(object):
68+
"""Constrains a ConfigParser to only option commands which are constrained to
69+
always use the section we have been initialized with.
70+
71+
It supports all ConfigParser methods that operate on an option"""
72+
__slots__ = ("_config", "_section_name")
73+
_valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option")
6674

75+
def __init__(self, config, section):
76+
self._config = config
77+
self._section_name = section
78+
79+
def __getattr__(self, attr):
80+
if attr in self._valid_attrs_:
81+
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
82+
return super(SectionConstraint,self).__getattribute__(attr)
83+
84+
def _call_config(self, method, *args, **kwargs):
85+
"""Call the configuration at the given method which must take a section name
86+
as first argument"""
87+
return getattr(self._config, method)(self._section_name, *args, **kwargs)
88+
6789

6890
class GitConfigParser(cp.RawConfigParser, object):
6991
"""Implements specifics required to read git style configuration files.

Diff for: lib/git/ext/gitdb

Submodule gitdb updated from 78665b1 to 2ddc5ba

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

+112-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import base
2+
from cStringIO import StringIO
3+
from git.config import GitConfigParser
4+
from git.util import join_path_native
5+
from git.exc import InvalidGitRepositoryError, NoSuchPathError
26

37
__all__ = ("Submodule", )
48

@@ -7,10 +11,115 @@ class Submodule(base.IndexObject):
711
represents a commit in the submodule's repository which is to be checked out
812
at the path of this instance.
913
The submodule type does not have a string type associated with it, as it exists
10-
solely as a marker in the tree and index"""
14+
solely as a marker in the tree and index.
15+
16+
All methods work in bare and non-bare repositories."""
17+
18+
kModulesFile = '.gitmodules'
1119

1220
# this is a bogus type for base class compatability
1321
type = 'submodule'
1422

15-
# TODO: Add functions to retrieve a repo for the submodule, to allow
16-
# its initiailization and handling
23+
__slots__ = ('_root_tree', '_url', '_ref')
24+
25+
def _set_cache_(self, attr):
26+
if attr == 'size':
27+
raise ValueError("Submodules do not have a size as they do not refer to anything in this repository")
28+
elif attr == '_root_tree':
29+
# set a default value, which is the root tree of the current head
30+
self._root_tree = self.repo.tree()
31+
elif attr in ('path', '_url', '_ref'):
32+
reader = self.config_reader()
33+
# default submodule values
34+
self._path = reader.get_value('path')
35+
self._url = reader.get_value('url')
36+
# git-python extension values - optional
37+
self._ref = reader.get_value('ref', 'master')
38+
else:
39+
super(Submodule, self)._set_cache_(attr)
40+
# END handle attribute name
41+
42+
def _fp_config(self):
43+
""":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())
45+
46+
def _config_parser(self, read_only):
47+
""":return: Config Parser constrained to our submodule in read or write mode"""
48+
parser = GitConfigParser(self._fp_config(), read_only = read_only)
49+
return SectionConstraint(parser, 'submodule "%s"' % self.path)
50+
51+
#{ Edit Interface
52+
53+
@classmethod
54+
def add(cls, repo, path, url, skip_init=False):
55+
"""Add a new submodule to the given repository. This will alter the index
56+
as well as the .gitmodules file, but will not create a new commit.
57+
:param repo: Repository instance which should receive the submodule
58+
:param path: repository-relative path at which the submodule should be located
59+
It will be created as required during the repository initialization.
60+
:param url: git-clone compatible URL, see git-clone reference for more information
61+
:param skip_init: if True, the new repository will not be cloned to its location.
62+
:return: The newly created submodule instance"""
63+
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))
72+
# END handle exceptions
73+
self._root_tree = tree
74+
75+
# clear the possibly changing values
76+
del(self.path)
77+
del(self._ref)
78+
del(self._url)
79+
80+
def config_writer(self):
81+
""":return: a config writer instance allowing you to read and write the data
82+
belonging to this submodule into the .gitmodules file."""
83+
return self._config_parser(read_only=False)
84+
85+
#} END edit interface
86+
87+
#{ Query Interface
88+
89+
def module(self):
90+
""":return: Repo instance initialized from the repository at our submodule path
91+
:raise InvalidGitRepositoryError: if a repository was not available"""
92+
if self.repo.bare:
93+
raise InvalidGitRepositoryError("Cannot retrieve module repository in bare parent repositories")
94+
# END handle bare mode
95+
96+
repo_path = join_path_native(self.repo.working_tree_dir, self.path)
97+
try:
98+
return Repo(repo_path)
99+
except (InvalidGitRepositoryError, NoSuchPathError):
100+
raise InvalidGitRepositoryError("No valid repository at %s" % self.path)
101+
# END handle exceptions
102+
103+
def ref(self):
104+
""":return: The reference's name that we are to checkout"""
105+
return self._ref
106+
107+
def url(self):
108+
""":return: The url to the repository which our module-repository refers to"""
109+
return self._url
110+
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
116+
117+
def config_reader(self):
118+
""":return: ConfigReader instance which allows you to qurey the configuration values
119+
of this submodule, as provided by the .gitmodules file
120+
:note: The config reader will actually read the data directly from the repository
121+
and thus does not need nor care about your working tree.
122+
:note: Should be cached by the caller and only kept as long as needed"""
123+
return self._config_parser.read_only(read_only=True)
124+
125+
#} END query interface

Diff for: lib/git/remote.py

+4-26
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
from exc import GitCommandError
99
from objects import Commit
10-
from ConfigParser import NoOptionError
10+
from ConfigParser import NoOptionError
11+
from config import SectionConstraint
1112

1213
from git.util import (
1314
LazyMixin,
@@ -30,29 +31,6 @@
3031

3132
__all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
3233

33-
class _SectionConstraint(object):
34-
"""Constrains a ConfigParser to only option commands which are constrained to
35-
always use the section we have been initialized with.
36-
37-
It supports all ConfigParser methods that operate on an option"""
38-
__slots__ = ("_config", "_section_name")
39-
_valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option")
40-
41-
def __init__(self, config, section):
42-
self._config = config
43-
self._section_name = section
44-
45-
def __getattr__(self, attr):
46-
if attr in self._valid_attrs_:
47-
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
48-
return super(_SectionConstraint,self).__getattribute__(attr)
49-
50-
def _call_config(self, method, *args, **kwargs):
51-
"""Call the configuration at the given method which must take a section name
52-
as first argument"""
53-
return getattr(self._config, method)(self._section_name, *args, **kwargs)
54-
55-
5634
class RemoteProgress(object):
5735
"""
5836
Handler providing an interface to parse progress information emitted by git-push
@@ -449,7 +427,7 @@ def _config_section_name(self):
449427

450428
def _set_cache_(self, attr):
451429
if attr == "_config_reader":
452-
self._config_reader = _SectionConstraint(self.repo.config_reader(), self._config_section_name())
430+
self._config_reader = SectionConstraint(self.repo.config_reader(), self._config_section_name())
453431
else:
454432
super(Remote, self)._set_cache_(attr)
455433

@@ -735,4 +713,4 @@ def config_writer(self):
735713

736714
# clear our cache to assure we re-read the possibly changed configuration
737715
del(self._config_reader)
738-
return _SectionConstraint(writer, self._config_section_name())
716+
return SectionConstraint(writer, self._config_section_name())

Diff for: test/git/test_submodule.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,35 @@
66

77
class TestSubmodule(TestBase):
88

9-
def test_base(self):
10-
# TODO
11-
pass
9+
kCOTag = '0.1.6'
10+
11+
def _do_base_tests(self, rwrepo):
12+
"""Perform all tests in the given repository, it may be bare or nonbare"""
13+
14+
# uncached path/url - retrieves information from .gitmodules file
15+
16+
# changing the root_tree yields new values when querying them (i.e. cache is cleared)
17+
18+
19+
# size is invalid
20+
self.failUnlessRaises(ValueError, getattr, sm, 'size')
21+
22+
# fails if tree has no gitmodule file
23+
24+
if rwrepo.bare:
25+
# module fails
26+
pass
27+
else:
28+
# get the module repository
29+
pass
30+
# END bare handling
31+
32+
@with_rw_repo(kCOTag)
33+
def test_base_rw(self, rwrepo):
34+
self._do_base_tests(rwrepo)
35+
36+
@with_bare_rw_repo
37+
def test_base_bare(self, rwrepo):
38+
self._do_base_tests(rwrepo)
39+
1240

0 commit comments

Comments
 (0)