1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
import base
from cStringIO import StringIO
from git.config import GitConfigParser
from git.util import join_path_native
from git.exc import InvalidGitRepositoryError, NoSuchPathError
__all__ = ("Submodule", )
class SubmoduleConfigParser(GitConfigParser):
"""Catches calls to _write, and updates the .gitmodules blob in the index
with the new data, if we have written into a stream. Otherwise it will
add the local file to the index to make it correspond with the working tree."""
_mutating_methods_ = tuple()
class Submodule(base.IndexObject):
"""Implements access to a git submodule. They are special in that their sha
represents a commit in the submodule's repository which is to be checked out
at the path of this instance.
The submodule type does not have a string type associated with it, as it exists
solely as a marker in the tree and index.
All methods work in bare and non-bare repositories."""
kModulesFile = '.gitmodules'
# this is a bogus type for base class compatability
type = 'submodule'
__slots__ = ('_parent_commit', '_url', '_ref')
def _set_cache_(self, attr):
if attr == 'size':
raise ValueError("Submodules do not have a size as they do not refer to anything in this repository")
elif attr == '_parent_commit':
# set a default value, which is the root tree of the current head
self._parent_commit = self.repo.commit()
elif attr in ('path', '_url', '_ref'):
reader = self.config_reader()
# default submodule values
self._path = reader.get_value('path')
self._url = reader.get_value('url')
# git-python extension values - optional
self._ref = reader.get_value('ref', 'master')
else:
super(Submodule, self)._set_cache_(attr)
# END handle attribute name
def _sio_modules(self):
""":return: Configuration file as StringIO - we only access it through the respective blob's data"""
sio = StringIO(self._parent_commit.tree[self.kModulesFile].datastream.read())
sio.name = self.kModulesFile
return sio
def _config_parser(self, read_only):
""":return: Config Parser constrained to our submodule in read or write mode"""
parent_matches_head = self.repo.head.commit == self._parent_commit
if not self.repo.bare and parent_matches_head:
fp_module = self.kModulesFile
else:
fp_module = self._sio_modules()
# END handle non-bare working tree
if not read_only and not parent_matches_head:
raise ValueError("Cannot write blobs of 'historical' submodule configurations")
# END handle writes of historical submodules
parser = GitConfigParser(fp_module, read_only = read_only)
return SectionConstraint(parser, 'submodule "%s"' % self.path)
#{ Edit Interface
@classmethod
def add(cls, repo, path, url, skip_init=False):
"""Add a new submodule to the given repository. This will alter the index
as well as the .gitmodules file, but will not create a new commit.
:param repo: Repository instance which should receive the submodule
:param path: repository-relative path at which the submodule should be located
It will be created as required during the repository initialization.
:param url: git-clone compatible URL, see git-clone reference for more information
:param skip_init: if True, the new repository will not be cloned to its location.
:return: The newly created submodule instance"""
def set_parent_commit(self, commit):
"""Set this instance to use the given commit whose tree is supposed to
contain the .gitmodules blob.
:param commit: Commit'ish reference pointing at the root_tree
:raise ValueError: if the commit's tree didn't contain the .gitmodules blob."""
pcommit = self.repo.commit(commit)
if self.kModulesFile not in pcommit.tree:
raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.kModulesFile))
# END handle exceptions
self._parent_commit = pcommit
# clear the possibly changed values
for name in ('path', '_ref', '_url'):
try:
delattr(self, name)
except AttributeError:
pass
# END for each name to delete
def config_writer(self):
""":return: a config writer instance allowing you to read and write the data
belonging to this submodule into the .gitmodules file."""
return self._config_parser(read_only=False)
#} END edit interface
#{ Query Interface
def module(self):
""":return: Repo instance initialized from the repository at our submodule path
:raise InvalidGitRepositoryError: if a repository was not available"""
if self.repo.bare:
raise InvalidGitRepositoryError("Cannot retrieve module repository in bare parent repositories")
# END handle bare mode
repo_path = join_path_native(self.repo.working_tree_dir, self.path)
try:
return Repo(repo_path)
except (InvalidGitRepositoryError, NoSuchPathError):
raise InvalidGitRepositoryError("No valid repository at %s" % self.path)
# END handle exceptions
def ref(self):
""":return: The reference's name that we are to checkout"""
return self._ref
def url(self):
""":return: The url to the repository which our module-repository refers to"""
return self._url
def parent_commit(self):
""":return: Commit instance with the tree containing the .gitmodules file
:note: will always point to the current head's commit if it was not set explicitly"""
return self._parent_commit
def config_reader(self):
""":return: ConfigReader instance which allows you to qurey the configuration values
of this submodule, as provided by the .gitmodules file
:note: The config reader will actually read the data directly from the repository
and thus does not need nor care about your working tree.
:note: Should be cached by the caller and only kept as long as needed"""
return self._config_parser.read_only(read_only=True)
#} END query interface
|