From 85476070627f6478d236580fc3286f700e30c4ae Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Mon, 4 May 2020 17:36:58 +0100 Subject: lorrycontroller.gitlab: Use and create subgroups as needed The current code only uses top-level groups since GitLab did not originally support subgroups. For repositories with more than two path components, it joins multiple components together as the project name. However, subgroups were implemented in version 9.0, so we can use them and remove this mangling. --- lorrycontroller/gitlab.py | 85 ++++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 56 deletions(-) (limited to 'lorrycontroller') diff --git a/lorrycontroller/gitlab.py b/lorrycontroller/gitlab.py index 48b161b..13becfe 100644 --- a/lorrycontroller/gitlab.py +++ b/lorrycontroller/gitlab.py @@ -16,7 +16,7 @@ import re import urllib.parse -import itertools + try: import gitlab except ImportError: @@ -45,79 +45,52 @@ class Gitlab(object): raise MissingGitlabModuleError('gitlab module missing\n' '\tpython-gitlab is required with GitLab as the git server') - def first(self, predicate, iterable): - return next(filter(predicate, iterable)) - - def split_and_unslashify_path(self, path): - group, project = path.split('/', 1) - return group, project.replace('/', '_') - def find_project(self, repo_path): - group, project = self.split_and_unslashify_path(repo_path) - predicate = lambda x: x.namespace['name'] == group and x.name == project - - return self.first(predicate, self.gl.projects.list(search=project)) + return self.gl.projects.get(repo_path) def has_project(self, repo_path): try: - return bool(self.find_project(repo_path)) - except StopIteration: + self.find_project(repo_path) + return True + except gitlab.GitlabGetError: return False def create_project(self, repo_path): - # GitLab only supports one level of namespacing. - group_name, project_name = self.split_and_unslashify_path(repo_path) - group = None - try: - group = self.gl.groups.get(group_name) - except gitlab.GitlabGetError as e: - if e.response_code == 404: - group = self.gl.groups.create( - {'name': group_name, 'path': group_name}) + path_comps = repo_path.split('/') + + if len(path_comps) < 2: + raise ValueError('cannot create GitLab project outside a group') + + # Create hierarchy of groups as necessary + parent_group = None + for group_name in path_comps[:-1]: + if parent_group is None: + group_path = group_name else: - raise + group_path = parent_group.full_path + '/' + group_name + try: + group = self.gl.groups.get(group_path) + except gitlab.GitlabGetError as e: + if e.response_code != 404: + raise + data = {'name': group_name, 'path': group_name} + if parent_group is not None: + data['parent_id'] = parent_group.id + group = self.gl.groups.create(data) + parent_group = group project = { - 'name': project_name, + 'name': path_comps[-1], 'public': True, 'merge_requests_enabled': False, 'namespace_id': group.id, - # Set the original path in the description. We will use this to - # work around lack of multi-level namespacing. - 'description': 'original_path: %s' % repo_path } self.gl.projects.create(project) - def try_get_original_path(self, project_description): - match = re.search('original_path:\s(.*)', str(project_description)) - if match: - return match.groups()[0] - - def suitable_path(self, project): - '''Return a path for a downstream Lorry Controller instance to consume. - - Should the path that was lorried have contained more than one level of - namespacing (more than one '/' within the repository path), then for - GitLab to handle this, we replace any '/'s (remaining in the project - name after extracting the group name) with underscores (_). To preserve - the original path, we set the 'original_path' within the project - description. - This method will attempt to return 'original_path' if it was set, - otherwise it will return the 'path_with_namespace', being of the format - 'group_name/project_name', rather than 'group_name/project/name'. - ''' - return (self.try_get_original_path(project.description) or - project.path_with_namespace) - def list_projects(self): - '''List projects on a GitLab instance. - - In attempt to handle GitLab's current lack of multi-level namespacing - (see: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772), return - the 'original_path' stored in a project's description, if it exists. - ''' + '''List projects on a GitLab instance.''' - return [self.suitable_path(x) for x in self.gl.projects.list()] + return [x.path_with_namespace for x in self.gl.projects.list()] def get_project_url(self, protocol, project_path): '''Return the clone url for a GitLab project. -- cgit v1.2.1