Skip to content

Commit 7946cb8

Browse files
committed
improve retrieving submodule entries from trees/commits
1 parent 24f75e7 commit 7946cb8

5 files changed

Lines changed: 80 additions & 27 deletions

File tree

git/objects/commit.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, aut
132132
if gpgsig is not None:
133133
self.gpgsig = gpgsig
134134

135+
def __getitem__(self, item):
136+
return self.tree.join(item, parent_commit=self)
137+
135138
@classmethod
136139
def _get_intermediate_items(cls, commit):
137140
return commit.parents

git/objects/submodule/base.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from io import BytesIO
33
import logging
44
import os
5+
import re
56
import stat
67
from unittest import SkipTest
78
import uuid
@@ -24,6 +25,7 @@
2425
BadName
2526
)
2627
from git.objects.base import IndexObject, Object
28+
from git.objects.submodule.util import SM_SECTION_NAME_REGEX
2729
from git.objects.util import Traversable
2830
from git.util import (
2931
Iterable,
@@ -116,20 +118,28 @@ def __init__(self, repo, binsha, mode=None, path=None, name=None, parent_commit=
116118
self._name = name
117119

118120
def _set_cache_(self, attr):
119-
if attr in ('path', '_url', '_branch_path'):
121+
if attr in ('path', '_url', '_branch_path', '_name'):
120122
reader = self.config_reader()
121123
# default submodule values
122124
try:
123125
self.path = reader.get('path')
124126
except cp.NoSectionError as e:
125-
raise ValueError("This submodule instance does not exist anymore in '%s' file"
126-
% osp.join(self.repo.working_tree_dir, '.gitmodules')) from e
127+
if self.repo.working_tree_dir:
128+
raise ValueError("This submodule instance does not exist anymore in '%s' file"
129+
% osp.join(self.repo.working_tree_dir, '.gitmodules')) from e
130+
else:
131+
raise ValueError("This submodule instance does not exist anymore in bare repo at '%s'"
132+
% self.repo.git_dir) from e
127133
# end
128134
self._url = reader.get('url')
129135
# git-python extension values - optional
130136
self._branch_path = reader.get_value(self.k_head_option, git.Head.to_full_path(self.k_head_default))
131-
elif attr == '_name':
132-
raise AttributeError("Cannot retrieve the name of a submodule if it was not set initially")
137+
section_name = reader._section_name
138+
m = re.match(SM_SECTION_NAME_REGEX, section_name)
139+
if not m:
140+
raise RuntimeError('Unexpected submodule section name in %s: %s' % (reader.file_or_files, section_name))
141+
name = m['name']
142+
self._name = name
133143
else:
134144
super(Submodule, self)._set_cache_(attr)
135145
# END handle attribute name
@@ -362,9 +372,7 @@ def add(cls, repo, name, path, url=None, branch=None, no_checkout=False, depth=N
362372
if sm.exists():
363373
# reretrieve submodule from tree
364374
try:
365-
sm = repo.head.commit.tree[path]
366-
sm._name = name
367-
return sm
375+
return repo.head.commit[path]
368376
except KeyError:
369377
# could only be in index
370378
index = repo.index
@@ -927,7 +935,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False):
927935

928936
return self
929937

930-
def set_parent_commit(self, commit, check=True):
938+
def set_parent_commit(self, commit, check=True, pcommit_name=None):
931939
"""Set this instance to use the given commit whose tree is supposed to
932940
contain the .gitmodules blob.
933941
@@ -953,20 +961,17 @@ def set_parent_commit(self, commit, check=True):
953961

954962
prev_pc = self._parent_commit
955963
self._parent_commit = pcommit
964+
if pcommit_name:
965+
self._name = pcommit_name
956966

957967
if check:
958-
parser = self._config_parser(self.repo, self._parent_commit, read_only=True)
959-
if not parser.has_section(sm_section(self.name)):
960-
self._parent_commit = prev_pc
961-
raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit))
962-
# END handle submodule did not exist
963-
# END handle checking mode
968+
self._set_cache_('path')
964969

965970
# update our sha, it could have changed
966971
# If check is False, we might see a parent-commit that doesn't even contain the submodule anymore.
967972
# in that case, mark our sha as being NULL
968973
try:
969-
self.binsha = pctree[self.path].binsha
974+
self.binsha = pctree.join(self.path, parent_commit=pcommit).binsha
970975
except KeyError:
971976
self.binsha = self.NULL_BIN_SHA
972977
# end
@@ -1140,6 +1145,14 @@ def parent_commit(self):
11401145
return self.repo.commit()
11411146
return self._parent_commit
11421147

1148+
@parent_commit.setter
1149+
def parent_commit(self, parent_commit):
1150+
assert parent_commit
1151+
if self._parent_commit != parent_commit:
1152+
self._clear_cache()
1153+
self._parent_commit = parent_commit
1154+
1155+
11431156
@property
11441157
def name(self):
11451158
""":return: The name of this submodule. It is used to identify it within the
@@ -1195,7 +1208,7 @@ def iter_items(cls, repo, parent_commit='HEAD'):
11951208
# get the binsha
11961209
index = repo.index
11971210
try:
1198-
sm = rt[p]
1211+
sm = pc[p]
11991212
except KeyError:
12001213
# try the index, maybe it was just added
12011214
try:

git/objects/submodule/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#{ Utilities
1111

1212

13+
SM_SECTION_NAME_REGEX = '^submodule "(?P<name>.*)"$'
14+
1315
def sm_section(name):
1416
""":return: section title used in .gitmodules configuration file"""
1517
return 'submodule "%s"' % name

git/objects/tree.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _iter_convert_to_object(self, iterable):
207207
raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e
208208
# END for each item
209209

210-
def join(self, file):
210+
def join(self, file, parent_commit=None):
211211
"""Find the named object in this tree's contents
212212
:return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule``
213213
@@ -218,7 +218,7 @@ def join(self, file):
218218
item = self
219219
tokens = file.split('/')
220220
for i, token in enumerate(tokens):
221-
item = tree[token]
221+
item = tree.join(token, parent_commit=parent_commit)
222222
if item.type == 'tree':
223223
tree = item
224224
else:
@@ -234,8 +234,15 @@ def join(self, file):
234234
else:
235235
for info in self._cache:
236236
if info[2] == file: # [2] == name
237-
return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1],
238-
join_path(self.path, info[2]))
237+
type_id = info[1] >> 12
238+
typ = self._map_id_to_type[type_id]
239+
if typ == Submodule:
240+
binsha = info[0]
241+
mode = info[1]
242+
path = join_path(self.path, info[2])
243+
return typ(self.repo, binsha=binsha, mode=mode, path=path, parent_commit=parent_commit)
244+
else:
245+
return typ(self.repo, info[0], info[1], join_path(self.path, info[2]))
239246
# END for each obj
240247
raise KeyError(msg % file)
241248
# END handle long paths

test/test_submodule.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ def tearDown(self):
5252
def _do_base_tests(self, rwrepo):
5353
"""Perform all tests in the given repository, it may be bare or nonbare"""
5454
# manual instantiation
55-
smm = Submodule(rwrepo, "\0" * 20)
56-
# name needs to be set in advance
57-
self.assertRaises(AttributeError, getattr, smm, 'name')
55+
Submodule(rwrepo, b"\0" * 20, name='foo')
5856

5957
# iterate - 1 submodule
6058
sms = Submodule.list_items(rwrepo, self.k_subm_current)
@@ -118,8 +116,13 @@ def _do_base_tests(self, rwrepo):
118116

119117
# make the old into a new - this doesn't work as the name changed
120118
self.assertRaises(ValueError, smold.set_parent_commit, self.k_subm_current)
119+
120+
# passing the module name explicitly works
121+
smold.set_parent_commit(self.k_subm_current, pcommit_name='gitdb')
122+
assert smold.hexsha == 'f2233fbf40f3f69309ce5cc714e99fcbdcd33ec3'
121123
# the sha is properly updated
122-
smold.set_parent_commit(self.k_subm_changed + "~1")
124+
smold.set_parent_commit(self.k_subm_changed + "~1", pcommit_name='lib/git/ext/gitdb')
125+
assert smold.hexsha == '18152febd428e67b86bb4fb68ec1691d4de75a9c'
123126
assert smold.binsha != sm.binsha
124127

125128
# raises if the sm didn't exist in new parent - it keeps its
@@ -148,7 +151,7 @@ def _do_base_tests(self, rwrepo):
148151
###########
149152
# preliminary tests
150153
# adding existing returns exactly the existing
151-
sma = Submodule.add(rwrepo, sm.name, sm.path)
154+
sma = Submodule.add(rwrepo, sm.name, sm.path, url=sm.path)
152155
assert sma.path == sm.path
153156

154157
# no url and no module at path fails
@@ -713,7 +716,10 @@ def test_git_submodules_and_add_sm_with_new_commit(self, rwdir):
713716
sm2.move(sm2.path + '_moved')
714717

715718
parent.index.commit("moved submodules")
716-
719+
# Below we'll test writing to the submodule's config, but first we update its `parent_commit` attr, otherwise it
720+
# will still point to the previous commit, which is now in the parent's history (i.e. no longer HEAD), so
721+
# attempting to write to it will fail
722+
sm.parent_commit = parent.commit()
717723
with sm.config_writer() as writer:
718724
writer.set_value('user.email', 'example@example.com')
719725
writer.set_value('user.name', 'me')
@@ -945,3 +951,25 @@ def test_depth(self, rwdir):
945951
sm_depth = 1
946952
sm = parent.create_submodule(sm_name, sm_name, url=self._small_repo_url(), depth=sm_depth)
947953
self.assertEqual(len(list(sm.module().iter_commits())), sm_depth)
954+
955+
@with_rw_directory
956+
def test_submodule_name(self, rwdir):
957+
parent_dir = osp.join(rwdir, 'test_tree')
958+
parent = git.Repo.init(parent_dir)
959+
sm_name = 'test_module'
960+
sm_path = 'mymodules/myname'
961+
sm_dir = osp.join(parent_dir, sm_path)
962+
parent.create_submodule(sm_name, sm_path, url=self._small_repo_url())
963+
sm_repo = git.Repo(sm_dir)
964+
965+
sm_repo.git.checkout('v3.0.2')
966+
parent.git.commit('-a','-m','add test submodule: smmap v3.0.2')
967+
tree = parent.tree()
968+
sm_tree = tree['mymodules/myname']
969+
assert sm_tree.hexsha == 'f4d7a58b4d96200cd057a38a0758d3c84901f57e'
970+
971+
sm_repo.git.checkout('v3.0.4')
972+
parent.git.commit('-a','-m','update test submodule to v3.0.4')
973+
tree = parent.tree()
974+
sm_tree = tree['mymodules/myname']
975+
assert sm_tree.hexsha == '5e5f940dff80beaa3eedf9342ef502f5e630d5ed'

0 commit comments

Comments
 (0)