diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 57d6b76b3..a027cdd60 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -618,3 +618,37 @@ markdown to reference the uploaded file:: issue.notes.create({ "body": "See the [attached file]({})".format(uploaded_file["url"]) }) + +Project push rules +================== + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectPushRules` + + :class:`gitlab.v4.objects.ProjectPushRulesManager` + + :attr:`gitlab.v4.objects.Project.pushrules` + +* GitLab API: https://docs.gitlab.com/ee/api/projects.html#push-rules + +Examples +--------- + +Create project push rules (at least one rule is necessary):: + + project.pushrules.create({'deny_delete_tag': True}) + +Get project push rules (returns None is there are no push rules):: + + pr = project.pushrules.get() + +Edit project push rules:: + + pr.branch_name_regex = '^(master|develop|support-\d+|release-\d+\..+|hotfix-.+|feature-.+)$' + pr.save() + +Delete project push rules:: + + pr.delete() diff --git a/gitlab/mixins.py b/gitlab/mixins.py index 966a647fe..761fed875 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -66,6 +66,8 @@ def get(self, id=None, **kwargs): GitlabGetError: If the server cannot perform the request """ server_data = self.gitlab.http_get(self.path, **kwargs) + if server_data is None: + return None return self._obj_cls(self, server_data) @@ -305,9 +307,12 @@ def delete(self, id, **kwargs): GitlabAuthenticationError: If authentication is not correct GitlabDeleteError: If the server cannot perform the request """ - if not isinstance(id, int): - id = id.replace('/', '%2F') - path = '%s/%s' % (self.path, id) + if id is None: + path = self.path + else: + if not isinstance(id, int): + id = id.replace('/', '%2F') + path = '%s/%s' % (self.path, id) self.gitlab.http_delete(path, **kwargs) diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 0e50de174..451bec8be 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -143,8 +143,9 @@ def _populate_sub_parser_by_class(cls, sub_parser): action='store_true') if action_name == 'delete': - id_attr = cls._id_attr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, required=True) + if cls._id_attr is not None: + id_attr = cls._id_attr.replace('_', '-') + sub_parser_action.add_argument("--%s" % id_attr, required=True) if action_name == "get": if gitlab.mixins.GetWithoutIdMixin not in inspect.getmro(cls): diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 13c9995fe..f36475b6d 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2364,6 +2364,27 @@ class ProjectPipelineScheduleManager(CRUDMixin, RESTManager): ('description', 'ref', 'cron', 'cron_timezone', 'active')) +class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = None + + +class ProjectPushRulesManager(GetWithoutIdMixin, CreateMixin, UpdateMixin, + DeleteMixin, RESTManager): + _path = '/projects/%(project_id)s/push_rule' + _obj_cls = ProjectPushRules + _from_parent_attrs = {'project_id': 'id'} + _create_attrs = (tuple(), + ('deny_delete_tag', 'member_check', + 'prevent_secrets', 'commit_message_regex', + 'branch_name_regex', 'author_email_regex', + 'file_name_regex', 'max_file_size')) + _update_attrs = (tuple(), + ('deny_delete_tag', 'member_check', + 'prevent_secrets', 'commit_message_regex', + 'branch_name_regex', 'author_email_regex', + 'file_name_regex', 'max_file_size')) + + class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -2755,6 +2776,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ('pipelines', 'ProjectPipelineManager'), ('protectedbranches', 'ProjectProtectedBranchManager'), ('pipelineschedules', 'ProjectPipelineScheduleManager'), + ('pushrules', 'ProjectPushRulesManager'), ('runners', 'ProjectRunnerManager'), ('services', 'ProjectServiceManager'), ('snippets', 'ProjectSnippetManager'),