Skip to content

[MINOR] Add support for the remainder of .gitmodules attributes #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions gitlab_submodule/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,27 @@ def __init__(self,
parent_ref: str,
name: str,
path: str,
url: str):
url: str,
branch: Optional[str]=None,
ignore: Optional[str]=None,
update: Optional[str]=None,
recurse: bool=False,
shallow: bool=False):

self.parent_project = parent_project
self.parent_ref = parent_ref
self.name = name
self.path = path
self.url = url
self.branch = branch
self.ignore = ignore
self.update = update
self.recurse = recurse
self.shallow = shallow

def keys(self):
return {'parent_project', 'parent_ref', 'name', 'path', 'url'}
return {'parent_project', 'parent_ref', 'name', 'path', 'url',
'update', 'branch', 'ignore', 'shallow', 'recurse'}

def __getitem__(self, key):
if key in self.keys():
Expand All @@ -31,24 +43,29 @@ def __getitem__(self, key):
def __str__(self):
keys = sorted(self.keys())
class_part = f"<class '{self.__class__.__name__}'>"

def to_str(key):
if isinstance(self[key], str):
return f"'{self[key]}'"
else:
return str(self[key])

attributes = [f"'{key}': {to_str(key)}" for key in keys]
return class_part + ' => {' + ', '.join(attributes) + '}'

def __repr__(self):
return '{} ({}, {}, {}, {}, {})'.format(
return '{} ({})'.format(
self.__class__.__name__,
repr(self.parent_project),
f"'{self.parent_ref}'",
f"'{self.name}'",
f"'{self.path}'",
f"'{self.url}'",
", ".join((
repr(self.parent_project),
repr(self.parent_ref),
repr(self.name),
repr(self.path),
repr(self.url),
repr(self.branch),
repr(self.ignore),
repr(self.update),
repr(self.recurse),
repr(self.shallow),
))
)


Expand Down
42 changes: 24 additions & 18 deletions gitlab_submodule/read_gitmodules.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import configparser
import re
from typing import Iterable, List, Optional, Tuple
from typing import Iterable, List, Optional, Union

from gitlab.v4.objects import Project

Expand All @@ -18,14 +19,12 @@ def iterate_project_submodules(
gitmodules_file_content = _get_gitmodules_file_content(project, ref)
if not gitmodules_file_content:
return []
for (name, url, path) in _read_gitmodules_file_content(
for kwargs in _read_gitmodules_file_content(
gitmodules_file_content):
yield Submodule(
parent_project=project,
parent_ref=ref if ref else project.default_branch,
name=name,
url=url,
path=path)
**kwargs)


def _get_gitmodules_file_content(project: Project,
Expand All @@ -38,17 +37,24 @@ def _get_gitmodules_file_content(project: Project,
except Exception:
return None


def _read_gitmodules_file_content(
gitmodules_file_content: str) -> Iterable[Tuple[str, str, str]]:
"""Some basic regex extractions to parse content of .gitmodules file
"""
name_regex = r'\[submodule "([a-zA-Z0-9\.\-/_]+)"\]'
path_regex = r'path ?= ?([a-zA-Z0-9\.\-/_]+)'
url_regex = r'url ?= ?([a-zA-Z0-9\.\-/_:@]+)'
names = re.findall(name_regex, gitmodules_file_content)
paths = re.findall(path_regex, gitmodules_file_content)
urls = re.findall(url_regex, gitmodules_file_content)
if not (len(names) == len(paths) == len(urls)):
raise RuntimeError('Failed parsing the .gitmodules content')
return zip(names, urls, paths)
gitmodules_file_content: str) -> Iterable[dict[str, Union[None, bool, str]]]:
"""Parses contents of .gitmodule file using configparser"""
config = configparser.ConfigParser()
config.optionxform = str
config.read_string(gitmodules_file_content)
stropts = ('branch', 'ignore', 'update')
boolopts = ('recurse', 'shallow')
name_regex = r'submodule "([a-zA-Z0-9\.\-/_]+)"'
for section in config.sections():
try:
kwargs = {
'name': re.match(name_regex, section).group(1),
'path': config.get(section, 'path'),
'url': config.get(section, 'url')
}
except (AttributeError, KeyError):
raise RuntimeError('Failed parsing the .gitmodules contnet')
kwargs.update((opt, config.get(section, opt, fallback=None)) for opt in stropts)
kwargs.update((opt, config.getboolean(section, opt, fallback=False)) for opt in boolopts)
yield kwargs
33 changes: 22 additions & 11 deletions tests/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ def test_Submodule_as_dict(self):
parent_ref='main',
name='test_submodule',
url='[email protected]:test/submodule',
path='include/test_submodule'
path='include/test_submodule',
update='rebase'
)
submodule_dict = dict(submodule)
self.assertEqual(len(submodule_dict.keys()), 5)
self.assertEqual(len(submodule_dict.keys()), 10)
self.assertEqual(submodule_dict['parent_ref'], 'main')
self.assertEqual(submodule_dict['name'], 'test_submodule')
self.assertEqual(submodule_dict['update'], 'rebase')
self.assertEqual(submodule_dict['branch'], None)

def test_Submodule_str(self):
mock_project = DictMock()
Expand All @@ -57,11 +60,13 @@ def test_Submodule_str(self):
)
self.assertEqual(
"<class 'Submodule'> => {"
"'branch': None, 'ignore': None, "
"'name': 'test_submodule', "
"'parent_project': <class 'DictMock'> => {'id': 123456789}, "
"'parent_ref': 'main', "
"'path': 'include/test_submodule', "
"'url': '[email protected]:test/submodule'}",
"'recurse': False, 'shallow': False, "
"'update': None, 'url': '[email protected]:test/submodule'}",
str(submodule)
)

Expand All @@ -73,11 +78,13 @@ def test_Submodule_repr(self):
parent_ref='main',
name='test_submodule',
url='[email protected]:test/submodule',
path='include/test_submodule'
path='include/test_submodule',
branch='development'
)
self.assertEqual(
"Submodule ({'id': 123456789}, 'main', 'test_submodule',"
" 'include/test_submodule', '[email protected]:test/submodule')",
" 'include/test_submodule', '[email protected]:test/submodule',"
" 'development', None, None, False, False)",
repr(submodule)
)

Expand Down Expand Up @@ -157,7 +164,8 @@ def test_Subproject_str(self):
parent_ref='main',
name='test_submodule',
url='[email protected]:test/submodule',
path='include/test_submodule'
path='include/test_submodule',
branch = "development"
)
mock_project = DictMock()
mock_project.name = 'project'
Expand All @@ -175,11 +183,13 @@ def test_Subproject_str(self):
)
self.assertEqual(
" 'submodule': <class 'Submodule'> => {"
"'branch': 'development', 'ignore': None, "
"'name': 'test_submodule', "
"'parent_project': <class 'DictMock'> => {'id': '123456789'}, "
"'parent_ref': 'main', "
"'path': 'include/test_submodule', "
"'url': '[email protected]:test/submodule'},",
"'recurse': False, 'shallow': False, "
"'update': None, 'url': '[email protected]:test/submodule'},",
str_lines[1]
)
self.assertEqual(
Expand All @@ -201,7 +211,8 @@ def test_Subproject_repr(self):
parent_ref='main',
name='test_submodule',
url='[email protected]:test/submodule',
path='include/test_submodule'
path='include/test_submodule',
branch = "development"
)
mock_project = DictMock()
mock_project.name = 'project'
Expand All @@ -218,9 +229,9 @@ def test_Subproject_repr(self):
str_lines[0]
)
self.assertEqual(
" Submodule ({'id': '123456789'}, 'main', "
"'test_submodule', 'include/test_submodule', "
"'[email protected]:test/submodule'),",
" Submodule ({'id': '123456789'}, 'main', 'test_submodule',"
" 'include/test_submodule', '[email protected]:test/submodule',"
" 'development', None, None, False, False),",
str_lines[1]
)
self.assertEqual(
Expand Down