From bbef1f916c8ab65ed7f9717859caf516ebedb335 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 9 Jul 2018 19:24:42 +0200 Subject: [cli] Output: handle bytes in API responses Closes #548 --- gitlab/v4/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 880b07d..b786e75 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -366,3 +366,6 @@ def run(gl, what, action, args, verbose, output, fields): printer.display(get_dict(data, fields), verbose=verbose, obj=data) elif isinstance(data, six.string_types): print(data) + else: + # We assume we got bytes + print(data.decode()) -- cgit v1.2.1 From a139179ea8246b2f000327bd1e106d5708077b31 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 12 Jul 2018 19:43:06 +0200 Subject: [cli] Fix the case where we have nothing to print --- gitlab/v4/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index b786e75..eca7d38 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -366,6 +366,5 @@ def run(gl, what, action, args, verbose, output, fields): printer.display(get_dict(data, fields), verbose=verbose, obj=data) elif isinstance(data, six.string_types): print(data) - else: - # We assume we got bytes + elif hasattr(data, 'decode'): print(data.decode()) -- cgit v1.2.1 From 34619042e4839cf1f3031b1c3e6f791104f02dfe Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 15 Jul 2018 17:21:09 +0200 Subject: Project import: fix the override_params parameter Closes #552 --- gitlab/v4/objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 9327e06..c266724 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -3605,7 +3605,8 @@ class ProjectManager(CRUDMixin, RESTManager): 'overwrite': overwrite } if override_params: - data['override_params'] = override_params + for k, v in override_params.items(): + data['override_params[%s]' % k] = v if namespace: data['namespace'] = namespace return self.gitlab.http_post('/projects/import', post_data=data, -- cgit v1.2.1 From 0379efaa641d22ccdb530214c56ec72891f73c4a Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 15 Jul 2018 18:03:33 +0200 Subject: Support group and global MR listing Closes #553 --- docs/gl_objects/mrs.rst | 42 ++++++++++++++++++++++++++++++++++++++++++ gitlab/__init__.py | 1 + gitlab/v4/objects.py | 29 +++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst index ca9b864..02b2e02 100644 --- a/docs/gl_objects/mrs.rst +++ b/docs/gl_objects/mrs.rst @@ -5,6 +5,48 @@ Merge requests You can use merge requests to notify a project that a branch is ready for merging. The owner of the target projet can accept the merge request. +Merge requests are linked to projects, but they can be listed globally or for +groups. + +Group and global listing +======================== + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.GroupMergeRequest` + + :class:`gitlab.v4.objects.GroupMergeRequestManager` + + :attr:`gitlab.v4.objects.Group.mergerequests` + + :class:`gitlab.v4.objects.MergeRequest` + + :class:`gitlab.v4.objects.MergeRequestManager` + + :attr:`gitlab.Gtilab.mergerequests` + +* GitLab API: https://docs.gitlab.com/ce/api/merge_requests.html + +Examples +-------- + +List the merge requests available on the GitLab server:: + + mrs = gl.mergerequests.list() + +List the merge requests for a group:: + + group = gl.groups.get('mygroup') + mrs = group.mergerequests.list() + +To edit or delete a merge request, create a ``ProjectMergeRequest`` object +first:: + + mr = group.mergerequests.list()[0] # pick the first MR + project = gl.projects.get(mr.project_id, lazy=True) + editable_mr = project.mergerequests.get(mr.iid) + +Project merge requests +====================== + Reference --------- diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 1c13093..4b9c4f4 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -114,6 +114,7 @@ class Gitlab(object): self.ldapgroups = objects.LDAPGroupManager(self) self.licenses = objects.LicenseManager(self) self.namespaces = objects.NamespaceManager(self) + self.mergerequests = objects.MergeRequestManager(self) self.notificationsettings = objects.NotificationSettingsManager(self) self.projects = objects.ProjectManager(self) self.runners = objects.RunnerManager(self) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index c266724..7f50440 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -710,8 +710,16 @@ class GroupMergeRequest(RESTObject): pass -class GroupMergeRequestManager(RESTManager): - pass +class GroupMergeRequestManager(ListMixin, RESTManager): + _path = '/groups/%(group_id)s/merge_requests' + _obj_cls = GroupMergeRequest + _from_parent_attrs = {'group_id': 'id'} + _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', + 'labels', 'created_after', 'created_before', + 'updated_after', 'updated_before', 'scope', 'author_id', + 'assignee_id', 'my_reaction_emoji', 'source_branch', + 'target_branch', 'search') + _types = {'labels': types.ListAttribute} class GroupMilestone(SaveMixin, ObjectDeleteMixin, RESTObject): @@ -842,6 +850,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): ('epics', 'GroupEpicManager'), ('issues', 'GroupIssueManager'), ('members', 'GroupMemberManager'), + ('mergerequests', 'GroupMergeRequestManager'), ('milestones', 'GroupMilestoneManager'), ('notificationsettings', 'GroupNotificationSettingsManager'), ('projects', 'GroupProjectManager'), @@ -1040,6 +1049,22 @@ class LicenseManager(RetrieveMixin, RESTManager): _optional_get_attrs = ('project', 'fullname') +class MergeRequest(RESTObject): + pass + + +class MergeRequestManager(ListMixin, RESTManager): + _path = '/merge_requests' + _obj_cls = MergeRequest + _from_parent_attrs = {'group_id': 'id'} + _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', + 'labels', 'created_after', 'created_before', + 'updated_after', 'updated_before', 'scope', 'author_id', + 'assignee_id', 'my_reaction_emoji', 'source_branch', + 'target_branch', 'search') + _types = {'labels': types.ListAttribute} + + class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): _short_print_attr = 'title' -- cgit v1.2.1 From 32ae92469f13fe2cbeb87361a4608dd5d95b3a70 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 15 Jul 2018 18:08:32 +0200 Subject: Implement MR.pipelines() Closes #555 --- docs/gl_objects/mrs.rst | 8 ++++++++ gitlab/v4/objects.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst index 02b2e02..7fdf4d8 100644 --- a/docs/gl_objects/mrs.rst +++ b/docs/gl_objects/mrs.rst @@ -116,6 +116,14 @@ List commits of a MR:: commits = mr.commits() +List the changes of a MR:: + + changes = mr.changes() + +List the pipelines for a MR:: + + pipelines = mr.pipelines() + List issues that will close on merge:: mr.closes_issues() diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 7f50440..71dd90c 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2157,6 +2157,24 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, path = '%s/%s/changes' % (self.manager.path, self.get_id()) return self.manager.gitlab.http_get(path, **kwargs) + @cli.register_custom_action('ProjectMergeRequest') + @exc.on_http_error(exc.GitlabListError) + def pipelines(self, **kwargs): + """List the merge request pipelines. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: List of changes + """ + path = '%s/%s/pipelines' % (self.manager.path, self.get_id()) + return self.manager.gitlab.http_get(path, **kwargs) + @cli.register_custom_action('ProjectMergeRequest', tuple(), ('merge_commit_message', 'should_remove_source_branch', -- cgit v1.2.1 From 35c8c8298392188c51e5956dd2eb90bb3d81a301 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 20 Jul 2018 07:24:25 +0200 Subject: MR: add the squash attribute for create/update Closes #557 --- gitlab/v4/objects.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 71dd90c..508ca7c 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2219,13 +2219,14 @@ class ProjectMergeRequestManager(CRUDMixin, RESTManager): _create_attrs = ( ('source_branch', 'target_branch', 'title'), ('assignee_id', 'description', 'target_project_id', 'labels', - 'milestone_id', 'remove_source_branch', 'allow_maintainer_to_push') + 'milestone_id', 'remove_source_branch', 'allow_maintainer_to_push', + 'squash') ) - _update_attrs = (tuple(), - ('target_branch', 'assignee_id', 'title', 'description', - 'state_event', 'labels', 'milestone_id', - 'remove_source_branch', 'discussion_locked', - 'allow_maintainer_to_push')) + _update_attrs = ( + tuple(), + ('target_branch', 'assignee_id', 'title', 'description', 'state_event', + 'labels', 'milestone_id', 'remove_source_branch', 'discussion_locked', + 'allow_maintainer_to_push', 'squash')) _list_filters = ('state', 'order_by', 'sort', 'milestone', 'view', 'labels', 'created_after', 'created_before', 'updated_after', 'updated_before', 'scope', 'author_id', -- cgit v1.2.1 From b325bd73400e3806e6ede59cc10011fbf138b877 Mon Sep 17 00:00:00 2001 From: David Guest Date: Thu, 26 Jul 2018 15:18:38 +1000 Subject: Added support for listing forks of a project (#562) --- docs/gl_objects/projects.rst | 4 ++++ gitlab/v4/objects.py | 24 +++++++++++++++++++++++- tools/python_test_v4.py | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 3950862..fdee215 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -95,6 +95,10 @@ Fork a project:: # fork to a specific namespace fork = project.forks.create({'namespace': 'myteam'}) +Get a list of forks for the project:: + + forks = project.forks.list() + Create/delete a fork relation between projects (requires admin permissions):: project.create_fork_relation(source_project.id) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 508ca7c..7438655 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1645,7 +1645,7 @@ class ProjectFork(RESTObject): pass -class ProjectForkManager(CreateMixin, RESTManager): +class ProjectForkManager(CreateMixin, ListMixin, RESTManager): _path = '/projects/%(project_id)s/fork' _obj_cls = ProjectFork _from_parent_attrs = {'project_id': 'id'} @@ -1655,6 +1655,28 @@ class ProjectForkManager(CreateMixin, RESTManager): 'with_merge_requests_enabled') _create_attrs = (tuple(), ('namespace', )) + def list(self, **kwargs): + """Retrieve a list of objects. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Returns: + list: The list of objects, or a generator if `as_list` is False + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the server cannot perform the request + """ + + path = self._compute_path('/projects/%(project_id)s/forks') + return ListMixin.list(self, path=path, **kwargs) + class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject): _short_print_attr = 'url' diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 3b54936..79a78bc 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -467,6 +467,9 @@ fork = admin_project.forks.create({'namespace': user1.username}) p = gl.projects.get(fork.id) assert(p.forked_from_project['id'] == admin_project.id) +forks = admin_project.forks.list() +assert(fork.id in map(lambda p: p.id, forks)) + # project hooks hook = admin_project.hooks.create({'url': 'http://hook.url'}) assert(len(admin_project.hooks.list()) == 1) -- cgit v1.2.1 From a1c79d2b7d719204c829235a9b0ebb08b45b4efb Mon Sep 17 00:00:00 2001 From: Will Rouesnel Date: Fri, 3 Aug 2018 04:46:30 +1000 Subject: Add support for project transfers from the projects interface. (#561) See https://docs.gitlab.com/ee/api/projects.html#transfer-a-project-to-a-new-namespace --- gitlab/v4/objects.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 7438655..bd7635f 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -3611,6 +3611,25 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): path = '/projects/%d/mirror/pull' % self.get_id() self.manager.gitlab.http_post(path, **kwargs) + @cli.register_custom_action('Project', ('to_namespace', )) + @exc.on_http_error(exc.GitlabTransferProjectError) + def transfer_project(self, to_namespace, **kwargs): + """Transfer a project to the given namespace ID + + Args: + to_namespace (str): ID or path of the namespace to transfer the + project to + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabTransferProjectError: If the project could not be transfered + """ + path = '/projects/%d/transfer' % (self.id,) + self.manager.gitlab.http_put(path, + post_data={"namespace": to_namespace}, + **kwargs) + class ProjectManager(CRUDMixin, RESTManager): _path = '/projects' -- cgit v1.2.1 From a68f459da690b4231dddcc6609de7e1e709ba7cf Mon Sep 17 00:00:00 2001 From: Matthias Schmitz Date: Tue, 14 Aug 2018 11:10:25 +0200 Subject: Minor typo "ou" vs. "or" This change fixes a minor type in the table of possible values for options in the global section. --- docs/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index 654c00a..95fa6f4 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -69,7 +69,7 @@ parameters. You can override the values in each GitLab server section. - Integer - Number of seconds to wait for an answer before failing. * - ``api_version`` - - ``3`` ou ``4`` + - ``3`` or ``4`` - The API version to use to make queries. Requires python-gitlab >= 1.3.0. * - ``per_page`` - Integer between 1 and 100 -- cgit v1.2.1 From 6ada4b004ab3a1b25b07809a0c87fec6f9c1fcb4 Mon Sep 17 00:00:00 2001 From: btmanm Date: Tue, 21 Aug 2018 07:58:29 +0800 Subject: Update projects.rst --- docs/gl_objects/projects.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index fdee215..7092fe6 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -292,7 +292,7 @@ Delete a custom attribute for a project:: Search projects by custom attribute:: - project.customattributes.set('type': 'internal') + project.customattributes.set('type', 'internal') gl.projects.list(custom_attributes={'type': 'internal'}) Project files -- cgit v1.2.1 From 80a68f9258422d5d74f05a20234070ce3d6f5559 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 24 Aug 2018 11:17:21 +0200 Subject: [docs] Add/updates notes about read-only objects MR and issues attached to the root API or groups are not editable. Provide notes describing how to manage this. --- docs/gl_objects/issues.rst | 22 ++++++++++++++++++++++ docs/gl_objects/mrs.rst | 15 ++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/gl_objects/issues.rst b/docs/gl_objects/issues.rst index 7abaa78..009bdf2 100644 --- a/docs/gl_objects/issues.rst +++ b/docs/gl_objects/issues.rst @@ -30,6 +30,17 @@ Use the ``state`` and ``label`` parameters to filter the results. Use the closed_issues = gl.issues.list(state='closed') tagged_issues = gl.issues.list(labels=['foo', 'bar']) +.. note:: + + It is not possible to edit or delete Issue objects. You need to create a + ProjectIssue object to perform changes:: + + issue = gl.issues.list()[0] + project = gl.projects.get(issue.project_id, lazy=True) + editable_issue = project.issues.get(issue.iid, lazy=True) + editable_issue.title = updated_title + editable_issue.save() + Group issues ============ @@ -55,6 +66,17 @@ List the group issues:: # Order using the order_by and sort parameters issues = group.issues.list(order_by='created_at', sort='desc') +.. note:: + + It is not possible to edit or delete GroupIssue objects. You need to create + a ProjectIssue object to perform changes:: + + issue = group.issues.list()[0] + project = gl.projects.get(issue.project_id, lazy=True) + editable_issue = project.issues.get(issue.iid, lazy=True) + editable_issue.title = updated_title + editable_issue.save() + Project issues ============== diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst index 7fdf4d8..a2aeff1 100644 --- a/docs/gl_objects/mrs.rst +++ b/docs/gl_objects/mrs.rst @@ -37,12 +37,17 @@ List the merge requests for a group:: group = gl.groups.get('mygroup') mrs = group.mergerequests.list() -To edit or delete a merge request, create a ``ProjectMergeRequest`` object -first:: +.. note:: - mr = group.mergerequests.list()[0] # pick the first MR - project = gl.projects.get(mr.project_id, lazy=True) - editable_mr = project.mergerequests.get(mr.iid) + It is not possible to edit or delete ``MergeRequest`` and + ``GroupMergeRequest`` objects. You need to create a ``ProjectMergeRequest`` + object to apply changes:: + + mr = group.mergerequests.list()[0] + project = gl.projects.get(mr.project_id, lazy=True) + editable_mr = project.mergerequests.get(mr.iid, lazy=True) + editable_mr.title = updated_title + editable_mr.save() Project merge requests ====================== -- cgit v1.2.1 From a221d7b35bc20da758e7467fe789e16613c54275 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 24 Aug 2018 17:09:55 +0200 Subject: Raise an exception on https redirects for PUT/POST POST and PUT requests are modified by clients when redirections happen. A common problem with python-gitlab is a misconfiguration of the server URL: the http to https redirection breaks some requests. With this change python-gitlab should detect problematic redirections, and raise a proper exception instead of failing with a cryptic error. Closes #565 --- gitlab/__init__.py | 48 +++++++++++++++++++++++++++++------------------- gitlab/exceptions.py | 4 ++++ gitlab/utils.py | 20 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 4b9c4f4..5b23ef8 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -28,6 +28,7 @@ import six import gitlab.config from gitlab.const import * # noqa from gitlab.exceptions import * # noqa +from gitlab import utils # noqa __title__ = 'python-gitlab' __version__ = '1.5.1' @@ -39,6 +40,9 @@ __copyright__ = 'Copyright 2013-2018 Gauvain Pocentek' warnings.filterwarnings('default', category=DeprecationWarning, module='^gitlab') +REDIRECT_MSG = ('python-gitlab detected an http to https redirection. You ' + 'must update your GitLab URL to use https:// to avoid issues.') + def _sanitize(value): if isinstance(value, dict): @@ -394,6 +398,26 @@ class Gitlab(object): else: return '%s%s' % (self._url, path) + def _check_redirects(self, result): + # Check the requests history to detect http to https redirections. + # If the initial verb is POST, the next request will use a GET request, + # leading to an unwanted behaviour. + # If the initial verb is PUT, the data will not be send with the next + # request. + # If we detect a redirection to https with a POST or a PUT request, we + # raise an exception with a useful error message. + if result.history and self._base_url.startswith('http:'): + for item in result.history: + if item.status_code not in (301, 302): + continue + # GET methods can be redirected without issue + if result.request.method == 'GET': + continue + # Did we end-up with an https:// URL? + location = item.headers.get('Location', None) + if location and location.startswith('https://'): + raise RedirectError(REDIRECT_MSG) + def http_request(self, verb, path, query_data={}, post_data=None, streamed=False, files=None, **kwargs): """Make an HTTP request to the Gitlab server. @@ -417,27 +441,11 @@ class Gitlab(object): GitlabHttpError: When the return code is not 2xx """ - def sanitized_url(url): - parsed = six.moves.urllib.parse.urlparse(url) - new_path = parsed.path.replace('.', '%2E') - return parsed._replace(path=new_path).geturl() - url = self._build_url(path) - def copy_dict(dest, src): - for k, v in src.items(): - if isinstance(v, dict): - # Transform dict values in new attributes. For example: - # custom_attributes: {'foo', 'bar'} => - # custom_attributes['foo']: 'bar' - for dict_k, dict_v in v.items(): - dest['%s[%s]' % (k, dict_k)] = dict_v - else: - dest[k] = v - params = {} - copy_dict(params, query_data) - copy_dict(params, kwargs) + utils.copy_dict(params, query_data) + utils.copy_dict(params, kwargs) opts = self._get_session_opts(content_type='application/json') @@ -462,7 +470,7 @@ class Gitlab(object): req = requests.Request(verb, url, json=json, data=data, params=params, files=files, **opts) prepped = self.session.prepare_request(req) - prepped.url = sanitized_url(prepped.url) + prepped.url = utils.sanitized_url(prepped.url) settings = self.session.merge_environment_settings( prepped.url, {}, streamed, verify, None) @@ -472,6 +480,8 @@ class Gitlab(object): while True: result = self.session.send(prepped, timeout=timeout, **settings) + self._check_redirects(result) + if 200 <= result.status_code < 300: return result diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 6736f67..650328a 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -41,6 +41,10 @@ class GitlabAuthenticationError(GitlabError): pass +class RedirectError(GitlabError): + pass + + class GitlabParsingError(GitlabError): pass diff --git a/gitlab/utils.py b/gitlab/utils.py index a449f81..49e2c88 100644 --- a/gitlab/utils.py +++ b/gitlab/utils.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +import six + class _StdoutStream(object): def __call__(self, chunk): @@ -31,3 +33,21 @@ def response_content(response, streamed, action, chunk_size): for chunk in response.iter_content(chunk_size=chunk_size): if chunk: action(chunk) + + +def copy_dict(dest, src): + for k, v in src.items(): + if isinstance(v, dict): + # Transform dict values to new attributes. For example: + # custom_attributes: {'foo', 'bar'} => + # "custom_attributes['foo']": "bar" + for dict_k, dict_v in v.items(): + dest['%s[%s]' % (k, dict_k)] = dict_v + else: + dest[k] = v + + +def sanitized_url(url): + parsed = six.moves.urllib.parse.urlparse(url) + new_path = parsed.path.replace('.', '%2E') + return parsed._replace(path=new_path).geturl() -- cgit v1.2.1 From 4d4c8ad1f75142fa1ca6ccd037e9d501ca873b60 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 25 Aug 2018 15:46:13 +0200 Subject: Add a FAQ --- docs/faq.rst | 33 +++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 34 insertions(+) create mode 100644 docs/faq.rst diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..fe71198 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,33 @@ +### +FAQ +### + +I cannot edit the merge request / issue I've just retrieved + It is likely that you used a ``MergeRequest``, ``GroupMergeRequest``, + ``Issue`` or ``GroupIssue`` object. These objects cannot be edited. But you + can create a new ``ProjectMergeRequest`` or ``ProjectIssue`` object to + apply changes. For example:: + + issue = gl.issues.list()[0] + project = gl.projects.get(issue.project_id, lazy=True) + editable_issue = project.issues.get(issue.iid, lazy=True) + # you can now edit the object + + See the :ref:`merge requests example ` and the + :ref:`issues examples `. + +How can I clone the repository of a project? + python-gitlab doesn't provide an API to clone a project. You have to use a + git library or call the ``git`` command. + + The git URI is exposed in the ``ssh_url_to_repo`` attribute of ``Project`` + objects. + + Example:: + + import subprocess + + project = gl.projects.create(data) # or gl.projects.get(project_id) + print(project.attributes) # displays all the attributes + git_url = project.ssh_url_to_repo + subprocess.call(['git', 'clone', git_url]) diff --git a/docs/index.rst b/docs/index.rst index 7805fcf..9c8cfd3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Contents: install cli api-usage + faq switching-to-v4 api-objects api/gitlab -- cgit v1.2.1 From e9506d15a971888a9af72b37d3e7dbce55e49126 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 25 Aug 2018 15:46:27 +0200 Subject: Minor doc updates --- RELEASE_NOTES.rst | 2 ++ docs/api-usage.rst | 8 ++++---- docs/gl_objects/commits.rst | 4 ++-- docs/gl_objects/issues.rst | 2 ++ docs/gl_objects/mrs.rst | 2 ++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 9e9fd8c..ac8daeb 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -14,6 +14,7 @@ Changes from 1.4 to 1.5 configuration, epics. * The ``GetFromListMixin`` class has been removed. The ``get()`` method is not available anymore for the following managers: + - UserKeyManager - DeployKeyManager - GroupAccessRequestManager @@ -27,6 +28,7 @@ Changes from 1.4 to 1.5 - ProjectPipelineJobManager - ProjectAccessRequestManager - TodoManager + * ``ProjectPipelineJob`` do not heritate from ``ProjectJob`` anymore and thus can only be listed. diff --git a/docs/api-usage.rst b/docs/api-usage.rst index ede2d47..fa6e0b0 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -7,7 +7,7 @@ python-gitlab supports both GitLab v3 and v4 APIs. To use the v3 make sure to .. note:: To use the v3 make sure to install python-gitlab 1.4. Only the v4 API is - documented here. See the documentation of earlier version for the v3 API. + documented here. See the documentation of earlier versions for the v3 API. ``gitlab.Gitlab`` class ======================= @@ -88,7 +88,7 @@ Examples: You can list the mandatory and optional attributes for object creation and update with the manager's ``get_create_attrs()`` and ``get_update_attrs()`` methods. They return 2 tuples, the first one is the list of mandatory -attributes, the second one the list of optional attribute: +attributes, the second one is the list of optional attribute: .. code-block:: python @@ -206,7 +206,7 @@ through a large number of items: for item in items: print(item.attributes) -The generator exposes extra listing information as received by the server: +The generator exposes extra listing information as received from the server: * ``current_page``: current page number (first page is 1) * ``prev_page``: if ``None`` the current page is the first one @@ -249,7 +249,7 @@ properly closed when you exit a ``with`` block: .. warning:: The context manager will also close the custom ``Session`` object you might - have used to build a ``Gitlab`` instance. + have used to build the ``Gitlab`` instance. Proxy configuration ------------------- diff --git a/docs/gl_objects/commits.rst b/docs/gl_objects/commits.rst index f662fcb..662d9c3 100644 --- a/docs/gl_objects/commits.rst +++ b/docs/gl_objects/commits.rst @@ -85,7 +85,7 @@ Reference + :class:`gitlab.v4.objects.ProjectCommitComment` + :class:`gitlab.v4.objects.ProjectCommitCommentManager` - + :attr:`gitlab.v4.objects.Commit.comments` + + :attr:`gitlab.v4.objects.ProjectCommit.comments` * GitLab API: https://docs.gitlab.com/ce/api/commits.html @@ -116,7 +116,7 @@ Reference + :class:`gitlab.v4.objects.ProjectCommitStatus` + :class:`gitlab.v4.objects.ProjectCommitStatusManager` - + :attr:`gitlab.v4.objects.Commit.statuses` + + :attr:`gitlab.v4.objects.ProjectCommit.statuses` * GitLab API: https://docs.gitlab.com/ce/api/commits.html diff --git a/docs/gl_objects/issues.rst b/docs/gl_objects/issues.rst index 009bdf2..12df90b 100644 --- a/docs/gl_objects/issues.rst +++ b/docs/gl_objects/issues.rst @@ -1,3 +1,5 @@ +.. _issues_examples: + ###### Issues ###### diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst index a2aeff1..b3b5e07 100644 --- a/docs/gl_objects/mrs.rst +++ b/docs/gl_objects/mrs.rst @@ -1,3 +1,5 @@ +.. _merge_requests_examples: + ############## Merge requests ############## -- cgit v1.2.1 From facbc8cb858ac400e912a905be3668ee2d33e2cd Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 25 Aug 2018 16:22:28 +0200 Subject: [cli] Fix the project-export download Closes #559 --- gitlab/v4/cli.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index eca7d38..a876f9e 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -19,6 +19,7 @@ from __future__ import print_function import inspect import operator +import sys import six @@ -54,11 +55,18 @@ class GitlabCLI(object): self.args[attr_name] = obj.get() def __call__(self): + # Check for a method that matches object + action + method = 'do_%s_%s' % (self.what, self.action) + if hasattr(self, method): + return getattr(self, method)() + + # Fallback to standard actions (get, list, create, ...) method = 'do_%s' % self.action if hasattr(self, method): return getattr(self, method)() - else: - return self.do_custom() + + # Finally try to find custom methods + return self.do_custom() def do_custom(self): in_obj = cli.custom_actions[self.cls_name][self.action][2] @@ -77,6 +85,20 @@ class GitlabCLI(object): else: return getattr(self.mgr, self.action)(**self.args) + def do_project_export_download(self): + try: + project = self.gl.projects.get(int(self.args['project_id']), + lazy=True) + data = project.exports.get().download() + if hasattr(sys.stdout, 'buffer'): + # python3 + sys.stdout.buffer.write(data) + else: + sys.stdout.write(data) + + except Exception as e: + cli.die("Impossible to download the export", e) + def do_create(self): try: return self.mgr.create(self.args) -- cgit v1.2.1 From d8c2488a7b32e8f4a36109c4a4d6d4aad7ab8942 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 25 Aug 2018 16:45:52 +0200 Subject: 1.6.0 release --- AUTHORS | 5 +++++ ChangeLog.rst | 20 +++++++++++++++++++- RELEASE_NOTES.rst | 9 +++++++++ gitlab/__init__.py | 2 +- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 14cb986..11ae684 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Aron Pammer Asher256 Bancarel Valentin Ben Brown +btmanm Carlo Mion Carlos Soriano Christian @@ -27,6 +28,7 @@ Cosimo Lupo Crestez Dan Leonard Cyril Jouve Daniel Kimsey +David Guest derek-austin Diego Giovane Pasqualin Dmytro Litvinov @@ -61,6 +63,7 @@ Mart Sõmermaa massimone88 Matej Zerovnik Matt Odden +Matthias Schmitz Matus Ferech Maura Hausman Maxime Guyot @@ -95,6 +98,8 @@ Stefan Klug Stefano Mandruzzato THEBAULT Julien Tim Neumann +Tom Downes Twan +Will Rouesnel Will Starms Yosi Zelensky diff --git a/ChangeLog.rst b/ChangeLog.rst index 5b2c497..beac7ff 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,23 @@ ChangeLog ========= +Version 1.6.0_ - 2018-08-25 +--------------------------- + +* [docs] Don't use hardcoded values for ids +* [docs] Improve the snippets examples +* [cli] Output: handle bytes in API responses +* [cli] Fix the case where we have nothing to print +* Project import: fix the override_params parameter +* Support group and global MR listing +* Implement MR.pipelines() +* MR: add the squash attribute for create/update +* Added support for listing forks of a project +* [docs] Add/update notes about read-only objects +* Raise an exception on https redirects for PUT/POST +* [docs] Add a FAQ +* [cli] Fix the project-export download + Version 1.5.1_ - 2018-06-23 --------------------------- @@ -643,7 +660,8 @@ Version 0.1 - 2013-07-08 * Initial release -.. _1.5.1: https://github.com/python-gitlab/python-gitlab/compare/1.4.0...1.5.1 +.. _1.6.0: https://github.com/python-gitlab/python-gitlab/compare/1.5.1...1.6.0 +.. _1.5.1: https://github.com/python-gitlab/python-gitlab/compare/1.5.0...1.5.1 .. _1.5.0: https://github.com/python-gitlab/python-gitlab/compare/1.4.0...1.5.0 .. _1.4.0: https://github.com/python-gitlab/python-gitlab/compare/1.3.0...1.4.0 .. _1.3.0: https://github.com/python-gitlab/python-gitlab/compare/1.2.0...1.3.0 diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index ac8daeb..1e53a88 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -4,6 +4,15 @@ Release notes This page describes important changes between python-gitlab releases. +Changes from 1.5 to 1.6 +======================= + +* When python-gitlab detects HTTP redirections from http to https it will raise + a RedirectionError instead of a cryptic error. + + Make sure to use an ``https://`` protocol in your GitLab URL parameter if the + server requires it. + Changes from 1.4 to 1.5 ======================= diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 5b23ef8..6afccf2 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -31,7 +31,7 @@ from gitlab.exceptions import * # noqa from gitlab import utils # noqa __title__ = 'python-gitlab' -__version__ = '1.5.1' +__version__ = '1.6.0' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' -- cgit v1.2.1 From ccf0c2ad35d4dd1af4f36e411027286a0be0f49f Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 4 Sep 2018 16:36:34 +0200 Subject: [docs] Fix the owned/starred usage documentation Closes #579 --- docs/api-usage.rst | 2 +- docs/gl_objects/projects.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index fa6e0b0..44df385 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -187,7 +187,7 @@ parameter to get all the items when using listing methods: .. code-block:: python all_groups = gl.groups.list(all=True) - all_owned_projects = gl.projects.owned(all=True) + all_owned_projects = gl.projects.list(owned=True, all=True) You can define the ``per_page`` value globally to avoid passing it to every ``list()`` method call: diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 7092fe6..8c2fc3f 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -45,10 +45,10 @@ Results can also be sorted using the following parameters: projects = gl.projects.list(visibility='public') # List owned projects - projects = gl.projects.owned() + projects = gl.projects.list(owned=True) # List starred projects - projects = gl.projects.starred() + projects = gl.projects.list(starred=True) # Search projects projects = gl.projects.list(search='keyword') -- cgit v1.2.1 From 256518cc1fab21c3dbfa7b67d5edcc81119090c5 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 4 Sep 2018 16:56:01 +0200 Subject: Use https:// for gitlab URL --- docs/switching-to-v4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/switching-to-v4.rst b/docs/switching-to-v4.rst index ef21060..e6490e3 100644 --- a/docs/switching-to-v4.rst +++ b/docs/switching-to-v4.rst @@ -10,7 +10,7 @@ solve some problems with the existing one. GitLab will stop supporting the v3 API soon, and you should consider switching to v4 if you use a recent version of GitLab (>= 9.0), or if you use -http://gitlab.com. +https://gitlab.com. Using the v4 API -- cgit v1.2.1 From b02c30f8b1829e87e2cc28ae7fdf8bb458a4b1c7 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 5 Sep 2018 18:01:07 +0200 Subject: [docs] fix cut and paste leftover --- docs/api-usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index 44df385..c2d50c4 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -2,7 +2,7 @@ Getting started with the API ############################ -python-gitlab supports both GitLab v3 and v4 APIs. To use the v3 make sure to +python-gitlab supports both GitLab v3 and v4 APIs. .. note:: -- cgit v1.2.1 From 042b706238810fa3b4fde92d298a709ebdb3a925 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 5 Sep 2018 18:04:15 +0200 Subject: [docs] add a warning about https:// http to https redirection cause problems. Make notes of this in the docs. --- docs/api-usage.rst | 5 +++++ docs/cli.rst | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index c2d50c4..73d1377 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -43,6 +43,11 @@ You can also use configuration files to create ``gitlab.Gitlab`` objects: See the :ref:`cli_configuration` section for more information about configuration files. +.. warning:: + + If the GitLab server you are using redirects requests from http to https, + make sure to use the ``https://`` protocol in the URL definition. + Note on password authentication ------------------------------- diff --git a/docs/cli.rst b/docs/cli.rst index 95fa6f4..220f079 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -78,6 +78,11 @@ parameters. You can override the values in each GitLab server section. You must define the ``url`` in each GitLab server section. +.. warning:: + + If the GitLab server you are using redirects requests from http to https, + make sure to use the ``https://`` protocol in the ``url`` definition. + Only one of ``private_token`` or ``oauth_token`` should be defined. If neither are defined an anonymous request will be sent to the Gitlab server, with very limited permissions. -- cgit v1.2.1 From 6f80380ed1de49dcc035d06408263d4961e7d18b Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 5 Sep 2018 18:13:45 +0200 Subject: Fix the https redirection test --- gitlab/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 6afccf2..99ff5c5 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -411,7 +411,7 @@ class Gitlab(object): if item.status_code not in (301, 302): continue # GET methods can be redirected without issue - if result.request.method == 'GET': + if item.request.method == 'GET': continue # Did we end-up with an https:// URL? location = item.headers.get('Location', None) -- cgit v1.2.1 From 9e60364306a894855c8e0744ed4b93cec8ea9ad0 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 5 Sep 2018 18:43:16 +0200 Subject: [docs] Add a note about GroupProject limited API --- docs/gl_objects/groups.rst | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 5ef5469..ff45c9b 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -31,6 +31,15 @@ List a group's projects:: projects = group.projects.list() +.. note:: + + ``GroupProject`` objects returned by this API call are very limited, and do + not provide all the features of ``Project`` objects. If you need to + manipulate projects, create a new ``Project`` object:: + + first_group_project = group.projects.list()[0] + manageable_project = gl.projects.get(first_group_project.id, lazy=True) + You can filter and sort the result using the following parameters: * ``archived``: limit by archived status @@ -76,11 +85,14 @@ List the subgroups for a group:: subgroups = group.subgroups.list() - # The GroupSubgroup objects don't expose the same API as the Group - # objects. If you need to manipulate a subgroup as a group, create a new - # Group object: - real_group = gl.groups.get(subgroup_id, lazy=True) - real_group.issues.list() +.. note:: + + The ``GroupSubgroup`` objects don't expose the same API as the ``Group`` + objects. If you need to manipulate a subgroup as a group, create a new + ``Group`` object:: + + real_group = gl.groups.get(subgroup_id, lazy=True) + real_group.issues.list() Group custom attributes ======================= -- cgit v1.2.1 From 83fb4f9ec5f60a122fe9db26c426be74c335e5d5 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 10 Sep 2018 13:49:18 -0500 Subject: add missing comma in ProjectIssueManager _create_attrs This fixes the argument handling for assignee/milestone ID when for `project-issue create` --- gitlab/v4/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index bd7635f..1d771ae 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1862,8 +1862,8 @@ class ProjectIssueManager(CRUDMixin, RESTManager): 'order_by', 'sort', 'search', 'created_after', 'created_before', 'updated_after', 'updated_before') _create_attrs = (('title', ), - ('description', 'confidential', 'assignee_id', - 'assignee_idss' 'milestone_id', 'labels', 'created_at', + ('description', 'confidential', 'assignee_ids', + 'assignee_id', 'milestone_id', 'labels', 'created_at', 'due_date', 'merge_request_to_resolve_discussions_of', 'discussion_to_resolve')) _update_attrs = (tuple(), ('title', 'description', 'confidential', -- cgit v1.2.1 From 77f4d3af9c1e5f08b8f4e3aa32c7944c9814dab0 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 26 Sep 2018 13:42:33 +0200 Subject: README: add a note about maintainers --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 56856b6..ff71fcb 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,12 @@ Python GitLab It supports the v4 API of GitLab, and provides a CLI tool (``gitlab``). +Maintainer(s) wanted +==================== + +We are looking for neww maintainer(s) for this project. See +https://github.com/python-gitlab/python-gitlab/issues/596. + Installation ============ -- cgit v1.2.1 From 21d257782bb1aea9d154e797986ed0f6cdd36fad Mon Sep 17 00:00:00 2001 From: Hans Donner Date: Sat, 29 Sep 2018 15:54:25 +0200 Subject: more flexible docker --- contrib/docker/Dockerfile | 12 ++++++------ contrib/docker/README.rst | 15 ++++++++------- contrib/docker/entrypoint-python-gitlab.sh | 21 +++++++++++++++++++++ contrib/docker/python-gitlab.cfg | 15 --------------- 4 files changed, 35 insertions(+), 28 deletions(-) create mode 100755 contrib/docker/entrypoint-python-gitlab.sh delete mode 100644 contrib/docker/python-gitlab.cfg diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 6663cac..6812c32 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,10 +1,10 @@ -FROM python:slim +FROM python:alpine -# Install python-gitlab RUN pip install --upgrade python-gitlab -# Copy sample configuration file -COPY python-gitlab.cfg / +COPY entrypoint-python-gitlab.sh /usr/local/bin/. -# Define the entrypoint that enable a configuration file -ENTRYPOINT ["gitlab", "--config-file", "/python-gitlab.cfg"] +WORKDIR /src + +ENTRYPOINT ["entrypoint-python-gitlab.sh"] +CMD ["--version"] diff --git a/contrib/docker/README.rst b/contrib/docker/README.rst index 90a576c..1627661 100644 --- a/contrib/docker/README.rst +++ b/contrib/docker/README.rst @@ -1,19 +1,20 @@ python-gitlab docker image ========================== -Dockerfile contributed by *oupala*: -https://github.com/python-gitlab/python-gitlab/issues/295 - How to build ------------ -``docker build -t me/python-gitlab:VERSION .`` +``docker build -t python-gitlab:VERSION .`` How to use ---------- -``docker run -it -v /path/to/python-gitlab.cfg:/python-gitlab.cfg python-gitlab ...`` +``docker run -it --rm -e GITLAB_PRIVATE_TOKEN= =/python-gitlab.cfg python-gitlab ...`` + +To change the endpoint, add `-e GITLAB_URL=` + + +Bring your own config file: +``docker run -it --rm -v /path/to/python-gitlab.cfg:/python-gitlab.cfg -e GITLAB_CFG=/python-gitlab.cfg python-gitlab ...`` -To make things easier you can create a shell alias: -``alias gitlab='docker run --rm -it -v /path/to/python-gitlab.cfg:/python-gitlab.cfg python-gitlab`` diff --git a/contrib/docker/entrypoint-python-gitlab.sh b/contrib/docker/entrypoint-python-gitlab.sh new file mode 100755 index 0000000..6422ad0 --- /dev/null +++ b/contrib/docker/entrypoint-python-gitlab.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +GITLAB_CFG=${GITLAB_CFG:-"/etc/python-gitlab-default.cfg"} + +cat << EOF > /etc/python-gitlab-default.cfg +[global] +default = gitlab +ssl_verify = ${GITLAB_SSL_VERIFY:-true} +timeout = ${GITLAB_TIMEOUT:-5} +api_version = ${GITLAB_API_VERSION:-4} +per_page = ${GITLAB_PER_PAGE:-10} + +[gitlab] +url = ${GITLAB_URL:-https://gitlab.com} +private_token = ${GITLAB_PRIVATE_TOKEN} +oauth_token = ${GITLAB_OAUTH_TOKEN} +http_username = ${GITLAB_HTTP_USERNAME} +http_password = ${GITLAB_HTTP_PASSWORD} +EOF + +exec gitlab --config-file "${GITLAB_CFG}" $@ diff --git a/contrib/docker/python-gitlab.cfg b/contrib/docker/python-gitlab.cfg deleted file mode 100644 index 0e51954..0000000 --- a/contrib/docker/python-gitlab.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[global] -default = somewhere -ssl_verify = true -timeout = 5 -api_version = 3 - -[somewhere] -url = https://some.whe.re -private_token = vTbFeqJYCY3sibBP7BZM -api_version = 4 - -[elsewhere] -url = http://else.whe.re:8080 -private_token = CkqsjqcQSFH5FQKDccu4 -timeout = 1 -- cgit v1.2.1 From ea71f1d121b723140671e2090182174234f0e2a1 Mon Sep 17 00:00:00 2001 From: Eric Sabouraud Date: Wed, 3 Oct 2018 21:36:56 +0200 Subject: Add project protected tags management (#581) --- docs/gl_objects/projects.rst | 33 +++++++++++++++++++++++++++++++++ gitlab/v4/objects.py | 13 +++++++++++++ 2 files changed, 46 insertions(+) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 8c2fc3f..dd444bf 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -657,3 +657,36 @@ Edit project push rules:: Delete project push rules:: pr.delete() + +Project protected tags +================== + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectProtectedTag` + + :class:`gitlab.v4.objects.ProjectProtectedTagManager` + + :attr:`gitlab.v4.objects.Project.protectedtags` + +* GitLab API: https://docs.gitlab.com/ce/api/protected_tags.html + +Examples +--------- + +Get a list of protected tags from a project:: + + protected_tags = project.protectedtags.list() + +Get a single protected tag or wildcard protected tag:: + + protected_tag = project.protectedtags.get('v*') + +Protect a single repository tag or several project repository tags using a wildcard protected tag:: + + project.protectedtags.create({'name': 'v*', 'create_access_level': '40'}) + +Unprotect the given protected tag or wildcard protected tag.:: + + protected_tag.delete() diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 1d771ae..281301e 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1965,6 +1965,18 @@ class ProjectTagManager(NoUpdateMixin, RESTManager): _create_attrs = (('tag_name', 'ref'), ('message',)) +class ProjectProtectedTag(ObjectDeleteMixin, RESTObject): + _id_attr = 'name' + _short_print_attr = 'name' + + +class ProjectProtectedTagManager(NoUpdateMixin, RESTManager): + _path = '/projects/%(project_id)s/protected_tags' + _obj_cls = ProjectProtectedTag + _from_parent_attrs = {'project_id': 'id'} + _create_attrs = (('name',), ('create_access_level',)) + + class ProjectMergeRequestApproval(SaveMixin, RESTObject): _id_attr = None @@ -3124,6 +3136,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ('pagesdomains', 'ProjectPagesDomainManager'), ('pipelines', 'ProjectPipelineManager'), ('protectedbranches', 'ProjectProtectedBranchManager'), + ('protectedtags', 'ProjectProtectedTagManager'), ('pipelineschedules', 'ProjectPipelineScheduleManager'), ('pushrules', 'ProjectPushRulesManager'), ('runners', 'ProjectRunnerManager'), -- cgit v1.2.1 From 6bb4d17a92832701b9f064a6577488cc42d20645 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Tue, 2 Oct 2018 19:56:53 +0200 Subject: fix(cli): print help and usage without config file Fixes #560 --- gitlab/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index 4870192..e79ac6d 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -98,7 +98,7 @@ def _get_base_parser(add_help=True): "will be used."), required=False) parser.add_argument("-o", "--output", - help=("Output format (v4 only): json|legacy|yaml"), + help="Output format (v4 only): json|legacy|yaml", required=False, choices=['json', 'legacy', 'yaml'], default="legacy") @@ -135,6 +135,10 @@ def main(): exit(0) parser = _get_base_parser(add_help=False) + if "--help" in sys.argv or "-h" in sys.argv: + parser.print_help() + exit(0) + # This first parsing step is used to find the gitlab config to use, and # load the propermodule (v3 or v4) accordingly. At that point we don't have # any subparser setup -- cgit v1.2.1 From c38775a5d52620a9c2d506d7b0952ea7ef0a11fc Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sat, 6 Oct 2018 16:50:27 +0200 Subject: refactor: rename MASTER_ACCESS to MAINTAINER_ACCESS to follow GitLab 11.0 docs See: https://docs.gitlab.com/ce/user/permissions.html#project-members-permissions --- docs/gl_objects/access_requests.rst | 4 ++-- docs/gl_objects/groups.rst | 2 +- docs/gl_objects/projects.rst | 2 +- docs/gl_objects/protected_branches.rst | 2 +- gitlab/const.py | 3 ++- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/gl_objects/access_requests.rst b/docs/gl_objects/access_requests.rst index 9a147c1..e890ce0 100644 --- a/docs/gl_objects/access_requests.rst +++ b/docs/gl_objects/access_requests.rst @@ -10,7 +10,7 @@ following constants are provided to represent the access levels: * ``gitlab.GUEST_ACCESS``: ``10`` * ``gitlab.REPORTER_ACCESS``: ``20`` * ``gitlab.DEVELOPER_ACCESS``: ``30`` -* ``gitlab.MASTER_ACCESS``: ``40`` +* ``gitlab.MAINTAINER_ACCESS``: ``40`` * ``gitlab.OWNER_ACCESS``: ``50`` References @@ -43,7 +43,7 @@ Create an access request:: Approve an access request:: ar.approve() # defaults to DEVELOPER level - ar.approve(access_level=gitlab.MASTER_ACCESS) # explicitly set access level + ar.approve(access_level=gitlab.MAINTAINER_ACCESS) # explicitly set access level Deny (delete) an access request:: diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index ff45c9b..0593672 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -142,7 +142,7 @@ The following constants define the supported access levels: * ``gitlab.GUEST_ACCESS = 10`` * ``gitlab.REPORTER_ACCESS = 20`` * ``gitlab.DEVELOPER_ACCESS = 30`` -* ``gitlab.MASTER_ACCESS = 40`` +* ``gitlab.MAINTAINER_ACCESS = 40`` * ``gitlab.OWNER_ACCESS = 50`` Reference diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index dd444bf..276686c 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -493,7 +493,7 @@ Add a project member:: Modify a project member (change the access level):: - member.access_level = gitlab.MASTER_ACCESS + member.access_level = gitlab.MAINTAINER_ACCESS member.save() Remove a member from the project team:: diff --git a/docs/gl_objects/protected_branches.rst b/docs/gl_objects/protected_branches.rst index bd2b22b..f0479e0 100644 --- a/docs/gl_objects/protected_branches.rst +++ b/docs/gl_objects/protected_branches.rst @@ -32,7 +32,7 @@ Create a protected branch:: p_branch = project.protectedbranches.create({ 'name': '*-stable', 'merge_access_level': gitlab.DEVELOPER_ACCESS, - 'push_access_level': gitlab.MASTER_ACCESS + 'push_access_level': gitlab.MAINTAINER_ACCESS }) Delete a protected branch:: diff --git a/gitlab/const.py b/gitlab/const.py index e4766d5..62f2403 100644 --- a/gitlab/const.py +++ b/gitlab/const.py @@ -18,7 +18,8 @@ GUEST_ACCESS = 10 REPORTER_ACCESS = 20 DEVELOPER_ACCESS = 30 -MASTER_ACCESS = 40 +MAINTAINER_ACCESS = 40 +MASTER_ACCESS = MAINTAINER_ACCESS OWNER_ACCESS = 50 VISIBILITY_PRIVATE = 0 -- cgit v1.2.1 From 6585c967732fe2a53c6ad6d4d2ab39aaa68258b0 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sun, 7 Oct 2018 17:34:01 +0200 Subject: docs(readme): add docs build information --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index ff71fcb..e8f2d76 100644 --- a/README.rst +++ b/README.rst @@ -54,6 +54,13 @@ Documentation The full documentation for CLI and API is available on `readthedocs `_. +Build the docs +-------------- +You can build the documentation using ``sphinx``:: + + pip install sphinx + python setup.py build_sphinx + Contributing ============ -- cgit v1.2.1 From 06e8ca8747256632c8a159f760860b1ae8f2b7b5 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Fri, 5 Oct 2018 19:00:41 +0200 Subject: fix(docker): use docker image with current sources --- .dockerignore | 5 +++++ Dockerfile | 16 ++++++++++++++++ README.rst | 23 ++++++++++++++++++++++- contrib/docker/Dockerfile | 10 ---------- contrib/docker/README.rst | 20 -------------------- contrib/docker/entrypoint-python-gitlab.sh | 21 --------------------- docker-entrypoint.sh | 21 +++++++++++++++++++++ 7 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 contrib/docker/Dockerfile delete mode 100644 contrib/docker/README.rst delete mode 100755 contrib/docker/entrypoint-python-gitlab.sh create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..204be74 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +venv/ +dist/ +build/ +*.egg-info +.github/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8c811b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.7-alpine AS build + +WORKDIR /opt/python-gitlab +COPY . . +RUN python setup.py bdist_wheel + +FROM python:3.7-alpine + +WORKDIR /opt/python-gitlab +COPY --from=build /opt/python-gitlab/dist dist/ +RUN pip install $(find dist -name *.whl) && \ + rm -rf dist/ +COPY docker-entrypoint.sh /usr/local/bin/ + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["--version"] diff --git a/README.rst b/README.rst index ff71fcb..f4a9357 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ It supports the v4 API of GitLab, and provides a CLI tool (``gitlab``). Maintainer(s) wanted ==================== -We are looking for neww maintainer(s) for this project. See +We are looking for new maintainer(s) for this project. See https://github.com/python-gitlab/python-gitlab/issues/596. Installation @@ -41,6 +41,27 @@ Install with pip pip install python-gitlab + +Using the python-gitlab docker image +==================================== + +How to build +------------ + +``docker build -t python-gitlab:TAG .`` + +How to use +---------- + +``docker run -it --rm -e GITLAB_PRIVATE_TOKEN= -v /path/to/python-gitlab.cfg:/python-gitlab.cfg python-gitlab ...`` + +To change the GitLab URL, use `-e GITLAB_URL=` + + +Bring your own config file: +``docker run -it --rm -v /path/to/python-gitlab.cfg:/python-gitlab.cfg -e GITLAB_CFG=/python-gitlab.cfg python-gitlab ...`` + + Bug reports =========== diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile deleted file mode 100644 index 6812c32..0000000 --- a/contrib/docker/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM python:alpine - -RUN pip install --upgrade python-gitlab - -COPY entrypoint-python-gitlab.sh /usr/local/bin/. - -WORKDIR /src - -ENTRYPOINT ["entrypoint-python-gitlab.sh"] -CMD ["--version"] diff --git a/contrib/docker/README.rst b/contrib/docker/README.rst deleted file mode 100644 index 1627661..0000000 --- a/contrib/docker/README.rst +++ /dev/null @@ -1,20 +0,0 @@ -python-gitlab docker image -========================== - -How to build ------------- - -``docker build -t python-gitlab:VERSION .`` - -How to use ----------- - -``docker run -it --rm -e GITLAB_PRIVATE_TOKEN= =/python-gitlab.cfg python-gitlab ...`` - -To change the endpoint, add `-e GITLAB_URL=` - - -Bring your own config file: -``docker run -it --rm -v /path/to/python-gitlab.cfg:/python-gitlab.cfg -e GITLAB_CFG=/python-gitlab.cfg python-gitlab ...`` - - diff --git a/contrib/docker/entrypoint-python-gitlab.sh b/contrib/docker/entrypoint-python-gitlab.sh deleted file mode 100755 index 6422ad0..0000000 --- a/contrib/docker/entrypoint-python-gitlab.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -GITLAB_CFG=${GITLAB_CFG:-"/etc/python-gitlab-default.cfg"} - -cat << EOF > /etc/python-gitlab-default.cfg -[global] -default = gitlab -ssl_verify = ${GITLAB_SSL_VERIFY:-true} -timeout = ${GITLAB_TIMEOUT:-5} -api_version = ${GITLAB_API_VERSION:-4} -per_page = ${GITLAB_PER_PAGE:-10} - -[gitlab] -url = ${GITLAB_URL:-https://gitlab.com} -private_token = ${GITLAB_PRIVATE_TOKEN} -oauth_token = ${GITLAB_OAUTH_TOKEN} -http_username = ${GITLAB_HTTP_USERNAME} -http_password = ${GITLAB_HTTP_PASSWORD} -EOF - -exec gitlab --config-file "${GITLAB_CFG}" $@ diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..6422ad0 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +GITLAB_CFG=${GITLAB_CFG:-"/etc/python-gitlab-default.cfg"} + +cat << EOF > /etc/python-gitlab-default.cfg +[global] +default = gitlab +ssl_verify = ${GITLAB_SSL_VERIFY:-true} +timeout = ${GITLAB_TIMEOUT:-5} +api_version = ${GITLAB_API_VERSION:-4} +per_page = ${GITLAB_PER_PAGE:-10} + +[gitlab] +url = ${GITLAB_URL:-https://gitlab.com} +private_token = ${GITLAB_PRIVATE_TOKEN} +oauth_token = ${GITLAB_OAUTH_TOKEN} +http_username = ${GITLAB_HTTP_USERNAME} +http_password = ${GITLAB_HTTP_PASSWORD} +EOF + +exec gitlab --config-file "${GITLAB_CFG}" $@ -- cgit v1.2.1 From d29a48981b521bf31d6f0304b88f39a63185328a Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sun, 7 Oct 2018 17:34:44 +0200 Subject: docs(cli): add PyYAML requirement notice Fixes #606 --- docs/cli.rst | 5 +++++ gitlab/v4/cli.py | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 220f079..2051d03 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -157,6 +157,11 @@ These options must be defined before the mandatory arguments. ``--output``, ``-o`` Output format. Defaults to a custom format. Can also be ``yaml`` or ``json``. + **Notice:** + + The `PyYAML package `_ is required to use the yaml output option. + You need to install it separately using ``pip install PyYAML`` + ``--fields``, ``-f`` Comma-separated list of fields to display (``yaml`` and ``json`` output formats only). If not used, all the object fields are displayed. diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index a876f9e..242874d 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -302,14 +302,24 @@ class JSONPrinter(object): class YAMLPrinter(object): def display(self, d, **kwargs): - import yaml # noqa - print(yaml.safe_dump(d, default_flow_style=False)) + try: + import yaml # noqa + print(yaml.safe_dump(d, default_flow_style=False)) + except ImportError: + exit("PyYaml is not installed.\n" + "Install it with `pip install PyYaml` " + "to use the yaml output feature") def display_list(self, data, fields, **kwargs): - import yaml # noqa - print(yaml.safe_dump( - [get_dict(obj, fields) for obj in data], - default_flow_style=False)) + try: + import yaml # noqa + print(yaml.safe_dump( + [get_dict(obj, fields) for obj in data], + default_flow_style=False)) + except ImportError: + exit("PyYaml is not installed.\n" + "Install it with `pip install PyYaml` " + "to use the yaml output feature") class LegacyPrinter(object): -- cgit v1.2.1 From 54b6a545399b51a34fb11819cc24f288bc191651 Mon Sep 17 00:00:00 2001 From: mkosiarc Date: Mon, 15 Oct 2018 22:17:06 +0200 Subject: [docs] fix discussions typo --- docs/gl_objects/discussions.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/gl_objects/discussions.rst b/docs/gl_objects/discussions.rst index 7673b7c..444d883 100644 --- a/docs/gl_objects/discussions.rst +++ b/docs/gl_objects/discussions.rst @@ -48,7 +48,7 @@ List the discussions for a resource (issue, merge request, snippet or commit):: Get a single discussion:: - discussion = resource.discussion.get(discussion_id) + discussion = resource.discussions.get(discussion_id) You can access the individual notes in the discussion through the ``notes`` attribute. It holds a list of notes in chronological order:: @@ -68,7 +68,7 @@ You can add notes to existing discussions:: You can get and update a single note using the ``*DiscussionNote`` resources:: - discussion = resource.discussion.get(discussion_id) + discussion = resource.discussions.get(discussion_id) # Get the latest note's id note_id = discussion.attributes['note'][-1]['id'] last_note = discussion.notes.get(note_id) @@ -77,7 +77,7 @@ You can get and update a single note using the ``*DiscussionNote`` resources:: Create a new discussion:: - discussion = resource.discussion.create({'body': 'First comment of discussion'}) + discussion = resource.discussions.create({'body': 'First comment of discussion'}) You can comment on merge requests and commit diffs. Provide the ``position`` dict to define where the comment should appear in the diff:: -- cgit v1.2.1 From 31d1c5dadb5f816d23e7882aa112042db019b681 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Wed, 31 Oct 2018 10:58:48 +0100 Subject: Add Gitter badge to README --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index a0b1825..77b123c 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,9 @@ .. image:: https://img.shields.io/pypi/pyversions/python-gitlab.svg :target: https://pypi.python.org/pypi/python-gitlab +.. image:: https://img.shields.io/gitter/room/python-gitlab/Lobby.svg + :target: https://gitter.im/python-gitlab/Lobby + Python GitLab ============= -- cgit v1.2.1 From 2c6c929f78dfda92d5ae93235bb9065d289a68cc Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 3 Nov 2018 09:52:21 +0100 Subject: Use the pythongitlab/test-python-gitlab docker image for tests This images is updated to the latest GitLab CE. Fix the diff() test to match the change in the API output. --- tools/build_test_env.sh | 2 +- tools/python_test_v4.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build_test_env.sh b/tools/build_test_env.sh index ebfb80a..3185f72 100755 --- a/tools/build_test_env.sh +++ b/tools/build_test_env.sh @@ -77,7 +77,7 @@ cleanup() { } try docker run --name gitlab-test --detach --publish 8080:80 \ - --publish 2222:22 gpocentek/test-python-gitlab:latest >/dev/null + --publish 2222:22 pythongitlab/test-python-gitlab:latest >/dev/null LOGIN='root' PASSWORD='5iveL!fe' diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 79a78bc..133aeb3 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -390,7 +390,7 @@ data = { ] } admin_project.commits.create(data) -assert('---' in admin_project.commits.list()[0].diff()[0]['diff']) +assert('@@' in admin_project.commits.list()[0].diff()[0]['diff']) # commit status commit = admin_project.commits.list()[0] -- cgit v1.2.1 From f7fbfca7e6a32a31dbf7ca8e1d4f83b34b7ac9db Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 28 Oct 2018 10:54:06 +0100 Subject: [docs] Add an example of pipeline schedule vars listing Closes #595 --- docs/gl_objects/builds.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index 51e7496..ee45090 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -141,6 +141,13 @@ Delete a schedule:: sched.delete() +List schedule variables:: + + # note: you need to use get() to retrieve the schedule variables. The + # attribute is not present in the response of a list() call + sched = projects.pipelineschedules.get(schedule_id) + vars = sched.attributes['variables'] + Create a schedule variable:: var = sched.variables.create({'key': 'foo', 'value': 'bar'}) -- cgit v1.2.1 From f51fa19dc4f78d036f18217436add00b7d94c39d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 3 Nov 2018 10:02:03 +0100 Subject: [README] Remove the "maintainer(s) wanted" notice Closes #596 --- README.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.rst b/README.rst index 77b123c..bed7f0e 100644 --- a/README.rst +++ b/README.rst @@ -20,12 +20,6 @@ Python GitLab It supports the v4 API of GitLab, and provides a CLI tool (``gitlab``). -Maintainer(s) wanted -==================== - -We are looking for new maintainer(s) for this project. See -https://github.com/python-gitlab/python-gitlab/issues/596. - Installation ============ -- cgit v1.2.1 From 6ad9da04496f040ae7d95701422434bc935a5a80 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sun, 4 Nov 2018 16:52:32 +0100 Subject: fix(cli): exit on config parse error, instead of crashing * Exit and hint user about possible errors * test: adjust test cases to config missing error --- gitlab/cli.py | 11 ++++++++--- gitlab/config.py | 17 +++++++++++++++++ gitlab/tests/test_config.py | 20 +++++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index e79ac6d..17917f5 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -17,6 +17,7 @@ # along with this program. If not, see . from __future__ import print_function + import argparse import functools import importlib @@ -143,9 +144,13 @@ def main(): # load the propermodule (v3 or v4) accordingly. At that point we don't have # any subparser setup (options, args) = parser.parse_known_args(sys.argv) - - config = gitlab.config.GitlabConfigParser(options.gitlab, - options.config_file) + try: + config = gitlab.config.GitlabConfigParser( + options.gitlab, + options.config_file + ) + except gitlab.config.ConfigError as e: + sys.exit(e) cli_module = importlib.import_module('gitlab.v%s.cli' % config.api_version) # Now we build the entire set of subcommands and do the complete parsing diff --git a/gitlab/config.py b/gitlab/config.py index 9f4c11d..1c76594 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -37,10 +37,27 @@ class GitlabDataError(ConfigError): pass +class GitlabConfigMissingError(ConfigError): + pass + + class GitlabConfigParser(object): def __init__(self, gitlab_id=None, config_files=None): self.gitlab_id = gitlab_id _files = config_files or _DEFAULT_FILES + file_exist = False + for file in _files: + if os.path.exists(file): + file_exist = True + if not file_exist: + raise GitlabConfigMissingError( + "Config file not found. \nPlease create one in " + "one of the following locations: {} \nor " + "specify a config file using the '-c' parameter.".format( + ", ".join(_DEFAULT_FILES) + ) + ) + self._config = configparser.ConfigParser() self._config.read(_files) diff --git a/gitlab/tests/test_config.py b/gitlab/tests/test_config.py index 0b585e8..d1e668e 100644 --- a/gitlab/tests/test_config.py +++ b/gitlab/tests/test_config.py @@ -76,11 +76,20 @@ per_page = 200 class TestConfigParser(unittest.TestCase): + @mock.patch('os.path.exists') + def test_missing_config(self, path_exists): + path_exists.return_value = False + with self.assertRaises(config.GitlabConfigMissingError): + config.GitlabConfigParser('test') + + @mock.patch('os.path.exists') @mock.patch('six.moves.builtins.open') - def test_invalid_id(self, m_open): + def test_invalid_id(self, m_open, path_exists): fd = six.StringIO(no_default_config) fd.close = mock.Mock(return_value=None) m_open.return_value = fd + path_exists.return_value = True + config.GitlabConfigParser('there') self.assertRaises(config.GitlabIDError, config.GitlabConfigParser) fd = six.StringIO(valid_config) @@ -90,12 +99,15 @@ class TestConfigParser(unittest.TestCase): config.GitlabConfigParser, gitlab_id='not_there') + @mock.patch('os.path.exists') @mock.patch('six.moves.builtins.open') - def test_invalid_data(self, m_open): + def test_invalid_data(self, m_open, path_exists): fd = six.StringIO(missing_attr_config) fd.close = mock.Mock(return_value=None, side_effect=lambda: fd.seek(0)) m_open.return_value = fd + path_exists.return_value = True + config.GitlabConfigParser('one') config.GitlabConfigParser('one') self.assertRaises(config.GitlabDataError, config.GitlabConfigParser, @@ -107,11 +119,13 @@ class TestConfigParser(unittest.TestCase): self.assertEqual('Unsupported per_page number: 200', emgr.exception.args[0]) + @mock.patch('os.path.exists') @mock.patch('six.moves.builtins.open') - def test_valid_data(self, m_open): + def test_valid_data(self, m_open, path_exists): fd = six.StringIO(valid_config) fd.close = mock.Mock(return_value=None) m_open.return_value = fd + path_exists.return_value = True cp = config.GitlabConfigParser() self.assertEqual("one", cp.gitlab_id) -- cgit v1.2.1 From a5ab2bb6272acd0285ce84ba6f01fe417c1c5124 Mon Sep 17 00:00:00 2001 From: Nic Grayson Date: Fri, 9 Nov 2018 11:45:41 -0600 Subject: Fix 3 typos --- docs/gl_objects/users.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index 3b9c040..9a0bbf5 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -190,7 +190,7 @@ are admin. * GitLab API: https://docs.gitlab.com/ce/api/users.html#list-all-gpg-keys -Exemples +Examples -------- List GPG keys for a user:: @@ -232,7 +232,7 @@ are admin. * GitLab API: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys -Exemples +Examples -------- List SSH keys for a user:: @@ -270,7 +270,7 @@ are admin. * GitLab API: https://docs.gitlab.com/ce/api/users.html#list-emails -Exemples +Examples -------- List emails for a user:: -- cgit v1.2.1 From b93f2a9ea9661521878ac45d70c7bd9a5a470548 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Mon, 19 Nov 2018 08:45:37 +0100 Subject: docs(projects): fix typo in code sample Fixes #630 --- docs/gl_objects/projects.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 276686c..5cc223f 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -247,7 +247,7 @@ generated by GitLab you need to: Import the project:: - gl.projects.import_project(open('/tmp/export.tgz', 'rb'), 'my_new_project') + ouput = gl.projects.import_project(open('/tmp/export.tgz', 'rb'), 'my_new_project') # Get a ProjectImport object to track the import status project_import = gl.projects.get(output['id'], lazy=True).imports.get() while project_import.import_status != 'finished': -- cgit v1.2.1 From ac2d65aacba5c19eca857290c5b47ead6bb4356d Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Tue, 20 Nov 2018 11:08:33 +0100 Subject: docs(groups): fix typo Fixes #635 --- docs/gl_objects/groups.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 0593672..c100f87 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -62,7 +62,7 @@ Update a group:: Remove a group:: - gl.group.delete(group_id) + gl.groups.delete(group_id) # or group.delete() -- cgit v1.2.1 From 95d0d745d4bafe702c89c972f644b049d6c810ab Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 3 Nov 2018 09:50:37 +0100 Subject: Add support to resource label events Closes #611 --- docs/gl_objects/labels.rst | 39 +++++++++++++++++++++++++++++++++++++++ docs/gl_objects/projects.rst | 2 +- gitlab/v4/objects.py | 40 +++++++++++++++++++++++++++++++++++++++- tools/python_test_v4.py | 17 +++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/docs/gl_objects/labels.rst b/docs/gl_objects/labels.rst index 1c98971..a4667aa 100644 --- a/docs/gl_objects/labels.rst +++ b/docs/gl_objects/labels.rst @@ -2,6 +2,9 @@ Labels ###### +Project labels +============== + Reference --------- @@ -48,3 +51,39 @@ Manage labels in issues and merge requests:: 'labels': ['foo']}) issue.labels.append('bar') issue.save() + +Label events +============ + +Resource label events keep track about who, when, and which label was added or +removed to an issuable. + +Group epic label events are only available in the EE edition. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectIssueResourceLabelEvent` + + :class:`gitlab.v4.objects.ProjectIssueResourceLabelEventManager` + + :attr:`gitlab.v4.objects.ProjectIssue.resourcelabelevents` + + :class:`gitlab.v4.objects.ProjectMergeRequestResourceLabelEvent` + + :class:`gitlab.v4.objects.ProjectMergeRequestResourceLabelEventManager` + + :attr:`gitlab.v4.objects.ProjectMergeRequest.resourcelabelevents` + + :class:`gitlab.v4.objects.GroupEpicResourceLabelEvent` + + :class:`gitlab.v4.objects.GroupEpicResourceLabelEventManager` + + :attr:`gitlab.v4.objects.GroupEpic.resourcelabelevents` + +* GitLab API: https://docs.gitlab.com/ee/api/resource_label_events.html + +Examples +-------- + +Get the events for a resource (issue, merge request or epic):: + + events = resource.resourcelabelevents.list() + +Get a specific event for a resource:: + + event = resource.resourcelabelevents.get(event_id) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 5cc223f..c8bd3eb 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -659,7 +659,7 @@ Delete project push rules:: pr.delete() Project protected tags -================== +====================== Reference --------- diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 281301e..84b3a86 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -662,9 +662,22 @@ class GroupEpicIssueManager(ListMixin, CreateMixin, UpdateMixin, DeleteMixin, return self._obj_cls(self, server_data) +class GroupEpicResourceLabelEvent(RESTObject): + pass + + +class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): + _path = ('/groups/%(group_id)s/epics/%(epic_id)s/resource_label_events') + _obj_cls = GroupEpicResourceLabelEvent + _from_parent_attrs = {'group_id': 'group_id', 'epic_id': 'id'} + + class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): _id_attr = 'iid' - _managers = (('issues', 'GroupEpicIssueManager'),) + _managers = ( + ('issues', 'GroupEpicIssueManager'), + ('resourcelabelevents', 'GroupEpicResourceLabelEventManager'), + ) class GroupEpicManager(CRUDMixin, RESTManager): @@ -1803,6 +1816,17 @@ class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, return source_issue, target_issue +class ProjectIssueResourceLabelEvent(RESTObject): + pass + + +class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager): + _path = ('/projects/%(project_id)s/issues/%(issue_iid)s' + '/resource_label_events') + _obj_cls = ProjectIssueResourceLabelEvent + _from_parent_attrs = {'project_id': 'project_id', 'issue_iid': 'iid'} + + class ProjectIssue(UserAgentDetailMixin, SubscribableMixin, TodoMixin, TimeTrackingMixin, ParticipantsMixin, SaveMixin, ObjectDeleteMixin, RESTObject): @@ -1813,6 +1837,7 @@ class ProjectIssue(UserAgentDetailMixin, SubscribableMixin, TodoMixin, ('discussions', 'ProjectIssueDiscussionManager'), ('links', 'ProjectIssueLinkManager'), ('notes', 'ProjectIssueNoteManager'), + ('resourcelabelevents', 'ProjectIssueResourceLabelEventManager'), ) @cli.register_custom_action('ProjectIssue', ('to_project_id',)) @@ -2086,6 +2111,17 @@ class ProjectMergeRequestDiscussionManager(RetrieveMixin, CreateMixin, _update_attrs = (('resolved',), tuple()) +class ProjectMergeRequestResourceLabelEvent(RESTObject): + pass + + +class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager): + _path = ('/projects/%(project_id)s/merge_requests/%(mr_iid)s' + '/resource_label_events') + _obj_cls = ProjectMergeRequestResourceLabelEvent + _from_parent_attrs = {'project_id': 'project_id', 'mr_iid': 'iid'} + + class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, ParticipantsMixin, SaveMixin, ObjectDeleteMixin, RESTObject): @@ -2097,6 +2133,8 @@ class ProjectMergeRequest(SubscribableMixin, TodoMixin, TimeTrackingMixin, ('diffs', 'ProjectMergeRequestDiffManager'), ('discussions', 'ProjectMergeRequestDiscussionManager'), ('notes', 'ProjectMergeRequestNoteManager'), + ('resourcelabelevents', + 'ProjectMergeRequestResourceLabelEventManager'), ) @cli.register_custom_action('ProjectMergeRequest') diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 133aeb3..1a111b3 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -539,6 +539,15 @@ assert(isinstance(issue1.user_agent_detail(), dict)) assert(issue1.user_agent_detail()['user_agent']) assert(issue1.participants()) +# issues labels and events +label2 = admin_project.labels.create({'name': 'label2', 'color': '#aabbcc'}) +issue1.labels = ['label2'] +issue1.save() +events = issue1.resourcelabelevents.list() +assert(events) +event = issue1.resourcelabelevents.get(events[0].id) +assert(event) + discussion = issue1.discussions.create({'body': 'Discussion body'}) assert(len(issue1.discussions.list()) == 1) d_note = discussion.notes.create({'body': 'first note'}) @@ -628,6 +637,14 @@ d_note_from_get.delete() discussion = mr.discussions.get(discussion.id) assert(len(discussion.attributes['notes']) == 1) +# mr labels and events +mr.labels = ['label2'] +mr.save() +events = mr.resourcelabelevents.list() +assert(events) +event = mr.resourcelabelevents.get(events[0].id) +assert(event) + # basic testing: only make sure that the methods exist mr.commits() mr.changes() -- cgit v1.2.1 From 0c9a00bb154007a0a9f665ca38e6fec50d378eaf Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 22 Nov 2018 18:17:37 +0100 Subject: [docs] Fix the milestone filetring doc (iid -> iids) Fixes #633 --- docs/gl_objects/milestones.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/milestones.rst b/docs/gl_objects/milestones.rst index 0d3f576..f24e13f 100644 --- a/docs/gl_objects/milestones.rst +++ b/docs/gl_objects/milestones.rst @@ -30,7 +30,7 @@ List the milestones for a project or a group:: You can filter the list using the following parameters: -* ``iid``: unique ID of the milestone for the project +* ``iids``: unique IDs of milestones for the project * ``state``: either ``active`` or ``closed`` * ``search``: to search using a string -- cgit v1.2.1 From bb251b8ef780216de03dde67912ad5fffbb30390 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 22 Nov 2018 18:36:15 +0100 Subject: [docs] Fix typo in custom attributes example Closes #628 --- docs/gl_objects/users.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index 9a0bbf5..d86d2ed 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -112,7 +112,7 @@ Delete a custom attribute for a user:: Search users by custom attribute:: - user.customattributes.set('role': 'QA') + user.customattributes.set('role', 'QA') gl.users.list(custom_attributes={'role': 'QA'}) User impersonation tokens -- cgit v1.2.1 From 1fb1296c9191e57e109c4e5eb9504bce191a6ff1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 24 Nov 2018 17:37:10 +0100 Subject: Improve error message handling in exceptions * Depending on the request Gitlab has a 'message' or 'error' attribute in the json data, handle both * Add some consistency by converting messages to unicode or str for exceptions (depending on the python version) Closes #616 --- gitlab/__init__.py | 8 ++++++-- gitlab/exceptions.py | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 99ff5c5..477d564 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -490,10 +490,14 @@ class Gitlab(object): time.sleep(wait_time) continue + error_message = result.content try: - error_message = result.json()['message'] + error_json = result.json() + for k in ('message', 'error'): + if k in error_json: + error_message = error_json[k] except (KeyError, ValueError, TypeError): - error_message = result.content + pass if result.status_code == 401: raise GitlabAuthenticationError( diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 650328a..0822d3e 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -28,7 +28,12 @@ class GitlabError(Exception): # Full http response self.response_body = response_body # Parsed error message from gitlab - self.error_message = error_message + try: + # if we receive str/bytes we try to convert to unicode/str to have + # consistent message types (see #616) + self.error_message = error_message.decode() + except Exception: + self.error_message = error_message def __str__(self): if self.response_code is not None: -- cgit v1.2.1 From ef1523a23737db45d0f439badcd8be564bcb67fb Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 24 Nov 2018 18:05:34 +0100 Subject: [feature] Add support for members all() method Closes #589 --- docs/gl_objects/groups.rst | 5 +++++ docs/gl_objects/projects.rst | 5 +++++ gitlab/v4/objects.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ tools/python_test_v4.py | 1 + 4 files changed, 59 insertions(+) diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index c100f87..7fcf980 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -164,6 +164,11 @@ List group members:: members = group.members.list() +List the group members recursively (including inherited members through +ancestor groups):: + + members = group.members.all(all=True) + Get a group member:: members = group.members.get(member_id) diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 5cc223f..dd43294 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -478,6 +478,11 @@ List the project members:: members = project.members.list() +List the project members recursively (including inherited members through +ancestor groups):: + + members = project.members.all(all=True) + Search project members matching a query string:: members = project.members.list(query='bar') diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 281301e..3f01955 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -705,6 +705,30 @@ class GroupMemberManager(CRUDMixin, RESTManager): _create_attrs = (('access_level', 'user_id'), ('expires_at', )) _update_attrs = (('access_level', ), ('expires_at', )) + @cli.register_custom_action('GroupMemberManager') + @exc.on_http_error(exc.GitlabListError) + def all(self, **kwargs): + """List all the members, included inherited ones. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of members + """ + + path = '%s/all' % self.path + return self.gitlab.http_list(path, **kwargs) + class GroupMergeRequest(RESTObject): pass @@ -1884,6 +1908,30 @@ class ProjectMemberManager(CRUDMixin, RESTManager): _create_attrs = (('access_level', 'user_id'), ('expires_at', )) _update_attrs = (('access_level', ), ('expires_at', )) + @cli.register_custom_action('ProjectMemberManager') + @exc.on_http_error(exc.GitlabListError) + def all(self, **kwargs): + """List all the members, included inherited ones. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of members + """ + + path = '%s/all' % self.path + return self.gitlab.http_list(path, **kwargs) + class ProjectNote(RESTObject): pass diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 133aeb3..8ff099b 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -244,6 +244,7 @@ assert(len(group2.members.list()) == 2) group1.members.delete(user1.id) assert(len(group1.members.list()) == 2) +assert(len(group1.members.all())) member = group1.members.get(user2.id) member.access_level = gitlab.const.OWNER_ACCESS member.save() -- cgit v1.2.1 From 67ab6371e69fbf137b95fd03105902206faabdac Mon Sep 17 00:00:00 2001 From: Roozbeh Farahbod Date: Tue, 4 Dec 2018 13:07:19 +0100 Subject: fix: docker entry point argument passing Fixes the problem of passing spaces in the arguments to the docker entrypoint. Before this fix, there was virtually no way to pass spaces in arguments such as task description. --- docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 6422ad0..bda8141 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -18,4 +18,4 @@ http_username = ${GITLAB_HTTP_USERNAME} http_password = ${GITLAB_HTTP_PASSWORD} EOF -exec gitlab --config-file "${GITLAB_CFG}" $@ +exec gitlab --config-file "${GITLAB_CFG}" "$@" -- cgit v1.2.1 From ad0b47667f98760d6a802a9d08b2da8f40d13e87 Mon Sep 17 00:00:00 2001 From: Roozbeh Farahbod Date: Tue, 4 Dec 2018 18:03:19 +0100 Subject: fix: enable use of YAML in the CLI In order to use the YAML output, PyYaml needs to be installed on the docker image. This commit adds the installation to the dockerfile as a separate layer. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8c811b0..489a420 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ FROM python:3.7-alpine WORKDIR /opt/python-gitlab COPY --from=build /opt/python-gitlab/dist dist/ +RUN pip install PyYaml RUN pip install $(find dist -name *.whl) && \ rm -rf dist/ COPY docker-entrypoint.sh /usr/local/bin/ -- cgit v1.2.1 From cebbbf67f2529bd9380276ac28abe726d3a57a81 Mon Sep 17 00:00:00 2001 From: Eric Sabouraud Date: Fri, 7 Dec 2018 18:10:41 +0100 Subject: Add access control options to protected branch creation --- docs/gl_objects/protected_branches.rst | 9 +++++++++ gitlab/v4/objects.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/protected_branches.rst b/docs/gl_objects/protected_branches.rst index f0479e0..3498aa5 100644 --- a/docs/gl_objects/protected_branches.rst +++ b/docs/gl_objects/protected_branches.rst @@ -35,6 +35,15 @@ Create a protected branch:: 'push_access_level': gitlab.MAINTAINER_ACCESS }) +Create a protected branch with more granular access control:: + + p_branch = project.protectedbranches.create({ + 'name': '*-stable', + 'allowed_to_push': [{"user_id": 99}, {"user_id": 98}], + 'allowed_to_merge': [{"group_id": 653}], + 'allowed_to_unprotect': [{"access_level": gitlab.MAINTAINER_ACCESS}] + }) + Delete a protected branch:: project.protectedbranches.delete('*-stable') diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 04444f7..fd673b5 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -3117,7 +3117,10 @@ class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): _path = '/projects/%(project_id)s/protected_branches' _obj_cls = ProjectProtectedBranch _from_parent_attrs = {'project_id': 'id'} - _create_attrs = (('name', ), ('push_access_level', 'merge_access_level')) + _create_attrs = (('name', ), + ('push_access_level', 'merge_access_level', + 'unprotect_access_level', 'allowed_to_push', + 'allowed_to_merge', 'allowed_to_unprotect')) class ProjectRunner(ObjectDeleteMixin, RESTObject): -- cgit v1.2.1 From 456f3c48e48dcff59e063c2572b6028f1abfba82 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 9 Dec 2018 09:37:38 +0100 Subject: Prepare the 1.7.0 release --- AUTHORS | 107 +++-------------------------------------------------- ChangeLog.rst | 26 +++++++++++++ gitlab/__init__.py | 2 +- 3 files changed, 33 insertions(+), 102 deletions(-) diff --git a/AUTHORS b/AUTHORS index 11ae684..f255ad7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,105 +1,10 @@ -Authors -------- +Authors / Maintainers +--------------------- -Gauvain Pocentek -Mika Mäenpää +Gauvain Pocentek +Max Wittig Contributors ------------ -Adam Reid -Alexander Skiba -Alex Widener -Amar Sood (tekacs) -Andjelko Horvat -Andreas Nüßlein -Andrew Austin -Armin Weihbold -Aron Pammer -Asher256 -Bancarel Valentin -Ben Brown -btmanm -Carlo Mion -Carlos Soriano -Christian -Christian Wenk -Colin D Bennett -Cosimo Lupo -Crestez Dan Leonard -Cyril Jouve -Daniel Kimsey -David Guest -derek-austin -Diego Giovane Pasqualin -Dmytro Litvinov -Eli Sarver -Eric L Frederich -Eric Sabouraud -Erik Weatherwax -fgouteroux -Greg Allen -Guillaume Delacour -Guyzmo -hakkeroid -Ian Sparks -itxaka -Ivica Arsov -Jakub Wilk -James (d0c_s4vage) Johnson -James E. Flemer -James Johnson -Jamie Bliss -Jason Antman -Jerome Robert -Johan Brandhorst -Jonathon Reinhart -Jon Banafato -Keith Wansbrough -Koen Smets -Kris Gambirazzi -leon -Lyudmil Nenov -Mart Sõmermaa -massimone88 -Matej Zerovnik -Matt Odden -Matthias Schmitz -Matus Ferech -Maura Hausman -Maxime Guyot -Max Wittig -Michael Overmeyer -Michal Galet -Mike Kobit -Mikhail Lopotkov -Miouge1 -Missionrulz -Mond WAN -Moritz Lipp -Nathan Giesbrecht -Nathan Schmidt -pa4373 -Patrick Miller -Pavel Savchenko -Peng Xiao -Pete Browne -Peter Mosmans -P. F. Chimento -Philipp Busch -Pierre Tardy -Rafael Eyng -Richard Hansen -Robert Lu -samcday -savenger -Stefan Crain -Stefan K. Dunkler -Stefan Klug -Stefano Mandruzzato -THEBAULT Julien -Tim Neumann -Tom Downes -Twan -Will Rouesnel -Will Starms -Yosi Zelensky + +See ``git log`` for a full list of contributors. diff --git a/ChangeLog.rst b/ChangeLog.rst index beac7ff..3e96318 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,31 @@ ChangeLog ========= +Version 1.7.0_ - 2018-12-09 +--------------------------- + +* [docs] Fix the owned/starred usage documentation +* [docs] Add a warning about http to https redirects +* Fix the https redirection test +* [docs] Add a note about GroupProject limited API +* Add missing comma in ProjectIssueManager _create_attrs +* More flexible docker image +* Add project protected tags management +* [cli] Print help and usage without config file +* Rename MASTER_ACCESS to MAINTAINER_ACCESS +* [docs] Add docs build information +* Use docker image with current sources +* [docs] Add PyYAML requirement notice +* Add Gitter badge to README +* [docs] Add an example of pipeline schedule vars listing +* [cli] Exit on config parse error, instead of crashing +* Add support for resource label events +* [docs] Fix the milestone filetring doc (iid -> iids) +* [docs] Fix typo in custom attributes example +* Improve error message handling in exceptions +* Add support for members all() method +* Add access control options to protected branch creation + Version 1.6.0_ - 2018-08-25 --------------------------- @@ -660,6 +685,7 @@ Version 0.1 - 2013-07-08 * Initial release +.. _1.7.0: https://github.com/python-gitlab/python-gitlab/compare/1.6.0...1.7.0 .. _1.6.0: https://github.com/python-gitlab/python-gitlab/compare/1.5.1...1.6.0 .. _1.5.1: https://github.com/python-gitlab/python-gitlab/compare/1.5.0...1.5.1 .. _1.5.0: https://github.com/python-gitlab/python-gitlab/compare/1.4.0...1.5.0 diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 477d564..01f9426 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -31,7 +31,7 @@ from gitlab.exceptions import * # noqa from gitlab import utils # noqa __title__ = 'python-gitlab' -__version__ = '1.6.0' +__version__ = '1.7.0' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' -- cgit v1.2.1 From 6898097c45d53a3176882a3d9cb86c0015f8d491 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sun, 9 Dec 2018 10:42:16 +0100 Subject: docs(setup): use proper readme on PyPI --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 02773eb..b592e7c 100644 --- a/setup.py +++ b/setup.py @@ -11,11 +11,13 @@ def get_version(): if line.startswith('__version__'): return eval(line.split('=')[-1]) +with open("README.rst", "r") as readme_file: + readme = readme_file.read() setup(name='python-gitlab', version=get_version(), description='Interact with GitLab API', - long_description='Interact with GitLab API', + long_description=readme, author='Gauvain Pocentek', author_email='gauvain@pocentek.net', license='LGPLv3', -- cgit v1.2.1 From bed8e1ba80c73b1d976ec865756b62e66342ce32 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sat, 15 Dec 2018 11:38:41 +0100 Subject: docs(readme): provide commit message guidelines Fixes #660 --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index bed7f0e..393398e 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,9 @@ You can contribute to the project in multiple ways: * Add unit and functional tests * Everything else you can think of +We prefer commit messages to be formatted using the `conventional-changelog `_. +This leads to more readable messages that are easy to follow when looking through the project history. + Provide your patches as github pull requests. Thanks! Running unit tests -- cgit v1.2.1 From cb388d6e6d5ec6ef1746edfffb3449c17e31df34 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 8 Jan 2019 07:06:45 +0100 Subject: fix(api): make reset_time_estimate() work again Closes #672 --- gitlab/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/mixins.py b/gitlab/mixins.py index 2c80f36..ca68658 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -532,7 +532,7 @@ class TimeTrackingMixin(object): GitlabAuthenticationError: If authentication is not correct GitlabTimeTrackingError: If the time tracking update cannot be done """ - path = '%s/%s/rest_time_estimate' % (self.manager.path, self.get_id()) + path = '%s/%s/reset_time_estimate' % (self.manager.path, self.get_id()) return self.manager.gitlab.http_post(path, **kwargs) @cli.register_custom_action(('ProjectIssue', 'ProjectMergeRequest'), -- cgit v1.2.1 From 7a3724f3fca93b4f55aed5132cf46d3718c4f594 Mon Sep 17 00:00:00 2001 From: Srikanth Chelluri Date: Tue, 8 Jan 2019 20:58:26 -0500 Subject: fix: handle empty 'Retry-After' header from GitLab When requests are throttled (HTTP response code 429), python-gitlab assumed that 'Retry-After' existed in the response headers. This is not always the case and so the request fails due to a KeyError. The change in this commit adds a rudimentary exponential backoff to the 'http_request' method, which defaults to 10 retries but can be set to -1 to retry without bound. --- docs/api-usage.rst | 16 +++++++++++++++- gitlab/__init__.py | 14 +++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index 73d1377..a5afbda 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -299,7 +299,9 @@ Rate limits python-gitlab obeys the rate limit of the GitLab server by default. On receiving a 429 response (Too Many Requests), python-gitlab sleeps for the -amount of time in the Retry-After header that GitLab sends back. +amount of time in the Retry-After header that GitLab sends back. If GitLab +does not return a response with the Retry-After header, python-gitlab will +perform an exponential backoff. If you don't want to wait, you can disable the rate-limiting feature, by supplying the ``obey_rate_limit`` argument. @@ -312,6 +314,18 @@ supplying the ``obey_rate_limit`` argument. gl = gitlab.gitlab(url, token, api_version=4) gl.projects.list(all=True, obey_rate_limit=False) +If you do not disable the rate-limiting feature, you can supply a custom value +for ``max_retries``; by default, this is set to 10. To retry without bound when +throttled, you can set this parameter to -1. This parameter is ignored if +``obey_rate_limit`` is set to ``False``. + +.. code-block:: python + + import gitlab + import requests + + gl = gitlab.gitlab(url, token, api_version=4) + gl.projects.list(all=True, max_retries=12) .. warning:: diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 01f9426..c280974 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -477,6 +477,10 @@ class Gitlab(object): # obey the rate limit by default obey_rate_limit = kwargs.get("obey_rate_limit", True) + # set max_retries to 10 by default, disable by setting it to -1 + max_retries = kwargs.get("max_retries", 10) + cur_retries = 0 + while True: result = self.session.send(prepped, timeout=timeout, **settings) @@ -486,9 +490,13 @@ class Gitlab(object): return result if 429 == result.status_code and obey_rate_limit: - wait_time = int(result.headers["Retry-After"]) - time.sleep(wait_time) - continue + if max_retries == -1 or cur_retries < max_retries: + wait_time = 2 ** cur_retries * 0.1 + if "Retry-After" in result.headers: + wait_time = int(result.headers["Retry-After"]) + cur_retries += 1 + time.sleep(wait_time) + continue error_message = result.content try: -- cgit v1.2.1 From 16bda20514e036e51bef210b565671174cdeb637 Mon Sep 17 00:00:00 2001 From: Srikanth Chelluri Date: Tue, 8 Jan 2019 22:12:25 -0500 Subject: fix: remove decode() on error_message string The integration tests failed because a test called 'decode()' on a string-type variable - the GitLabException class handles byte-to-string conversion already in its __init__. This commit removes the call to 'decode()' in the test. ``` Traceback (most recent call last): File "./tools/python_test_v4.py", line 801, in assert 'Retry later' in error_message.decode() AttributeError: 'str' object has no attribute 'decode' ``` --- tools/python_test_v4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 30e4456..aacb3e7 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -798,7 +798,7 @@ for i in range(20, 40): except gitlab.GitlabCreateError as e: error_message = e.error_message break -assert 'Retry later' in error_message.decode() +assert 'Retry later' in error_message [current_project.delete() for current_project in projects] settings.throttle_authenticated_api_enabled = False settings.save() -- cgit v1.2.1 From 3133b48a24ce3c9e2547bf2a679d73431dfbefab Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sun, 13 Jan 2019 12:24:07 +0100 Subject: chore: release tags to PyPI automatically Fixes #609 --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 10277f7..6b18f8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,3 +21,12 @@ install: - pip install tox script: - tox -e $TOX_ENV + +deploy: + provider: pypi + user: max-wittig + password: + secure: LmNkZdbNe1oBSJ/PeTCKXaeu9Ml/biY4ZN4aedbD4lLXbxV/sgsHEE4N1Xrg2D/CJsnNjBY7CHzO0vL5iak8IRpV61xkdquZHvAUQKuhjMY30HopReAEw8sP+Wpf3lYcD1BjC5KT9vqWG99feoQ6epRt//Xm4DdkBYNmmUsCsMBTZLlGnj3B/mE8w+XQxQpdA2QzpRJ549N12vidwZRKqP0Zuug3rELVSo64O2bpqarKx/EeUUhTXZ0Y4XeVYgvuHBjvPqtuSJzR17CNkjaBhacD7EFTP34sAaCKGRDpfYiiiGx9LeKOEAv5Hj0+LOqEC/o6EyiIFviE+HvLQ/kBLJ6Oo2p47fibyIU/YOAFdZYKmBRq2ZUaV0DhhuuCRPZ+yLrsuaFRrKTVEMsHVtdsXJkW5gKG08vwOndW+kamppRhkAcdFVyokIgu/6nPBRWMuS6ue2aKoKRdP2gmqk0daKM1ao2uv06A2/J1/xkPy1EX5MjyK8Mh78ooKjITp5DHYn8l1pxaB0YcEkRzfwMyLErGQaRDgo7rCOm0tTRNhArkn0VE1/KLKFbATo2NSxZDwUJQ5TBNCEqfdBN1VzNEduJ7ajbZpq3DsBRM/9hzQ5LLxn7azMl9m+WmT12Qcgz25wg2Sgbs9Z2rT6fto5h8GSLpy8ReHo+S6fALJBzA4pg= + distributions: sdist bdist_wheel + on: + tags: true -- cgit v1.2.1 From 4bd027aac41c41f7e22af93c7be0058d2faf7fb4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 13 Jan 2019 13:12:17 +0100 Subject: fix(api): avoid parameter conflicts with python and gitlab Provide another way to send data to gitlab with a new `query_parameters` argument. This parameter can be used to explicitly define the dict of items to send to the server, so that **kwargs are only used to specify python-gitlab specific parameters. Closes #566 Closes #629 --- RELEASE_NOTES.rst | 19 +++++++++++++++++++ docs/api-usage.rst | 19 +++++++++++++++++++ docs/gl_objects/commits.rst | 8 ++++++++ docs/gl_objects/users.rst | 4 +++- gitlab/__init__.py | 15 ++++++++++++++- tools/python_test_v4.py | 2 +- 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 1e53a88..6abb980 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -4,6 +4,25 @@ Release notes This page describes important changes between python-gitlab releases. +Changes from 1.7 to 1.8 +======================= + +* You can now use the ``query_parameters`` argument in method calls to define + arguments to send to the GitLab server. This allows to avoid conflicts + between python-gitlab and GitLab server variables, and allows to use the + python reserved keywords as GitLab arguments. + + The following examples make the same GitLab request with the 2 syntaxes:: + + projects = gl.projects.list(owned=True, starred=True) + projects = gl.projects.list(query_parameters={'owned': True, 'starred': True}) + + The following example only works with the new parameter:: + + activities = gl.user_activities.list( + query_parameters={'from': '2019-01-01'}, + all=True) + Changes from 1.5 to 1.6 ======================= diff --git a/docs/api-usage.rst b/docs/api-usage.rst index a5afbda..8ab252c 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -118,6 +118,25 @@ Some objects also provide managers to access related GitLab resources: project = gl.projects.get(1) issues = project.issues.list() +python-gitlab allows to send any data to the GitLab server when making queries. +In case of invalid or missing arguments python-gitlab will raise an exception +with the GitLab server error message: + +.. code-block:: python + + >>> gl.projects.list(sort='invalid value') + ... + GitlabListError: 400: sort does not have a valid value + +You can use the ``query_parameters`` argument to send arguments that would +conflict with python or python-gitlab when using them as kwargs: + +.. code-block:: python + + gl.user_activities.list(from='2019-01-01') ## invalid + + gl.user_activities.list(query_parameters={'from': '2019-01-01'}) # OK + Gitlab Objects ============== diff --git a/docs/gl_objects/commits.rst b/docs/gl_objects/commits.rst index 662d9c3..9f48c98 100644 --- a/docs/gl_objects/commits.rst +++ b/docs/gl_objects/commits.rst @@ -27,6 +27,14 @@ results:: commits = project.commits.list(ref_name='my_branch') commits = project.commits.list(since='2016-01-01T00:00:00Z') +.. note:: + + The available ``all`` listing argument conflicts with the python-gitlab + argument. Use ``query_parameters`` to avoid the conflict:: + + commits = project.commits.list(all=True, + query_parameters={'ref_name': 'my_branch'}) + Create a commit:: # See https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index d86d2ed..e66ef3a 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -312,4 +312,6 @@ Examples Get the users activities:: - activities = gl.user_activities.list(all=True, as_list=False) + activities = gl.user_activities.list( + query_parameters={'from': '2018-07-01'}, + all=True, as_list=False) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index c280974..4f00603 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -445,7 +445,20 @@ class Gitlab(object): params = {} utils.copy_dict(params, query_data) - utils.copy_dict(params, kwargs) + + # Deal with kwargs: by default a user uses kwargs to send data to the + # gitlab server, but this generates problems (python keyword conflicts + # and python-gitlab/gitlab conflicts). + # So we provide a `query_parameters` key: if it's there we use its dict + # value as arguments for the gitlab server, and ignore the other + # arguments, except pagination ones (per_page and page) + if 'query_parameters' in kwargs: + utils.copy_dict(params, kwargs['query_parameters']) + for arg in ('per_page', 'page'): + if arg in kwargs: + params[arg] = kwargs[arg] + else: + utils.copy_dict(params, kwargs) opts = self._get_session_opts(content_type='application/json') diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index aacb3e7..958e350 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -773,7 +773,7 @@ snippets = gl.snippets.list(all=True) assert(len(snippets) == 0) # user activities -gl.user_activities.list() +gl.user_activities.list(query_parameters={'from': '2019-01-01'}) # events gl.events.list() -- cgit v1.2.1 From 35a6d85acea4776e9c4ad23ff75259481a6bcf8d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 19 Jan 2019 09:13:58 +0100 Subject: fix(api): Don't try to parse raw downloads http_get always tries to interpret the retrieved data if the content-type is json. In some cases (artifact download for instance) this is not the expected behavior. This patch changes http_get and download methods to always get the raw data without parsing. Closes #683 --- gitlab/__init__.py | 10 +++++++--- gitlab/v4/objects.py | 21 +++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index c280974..0387b0f 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -517,7 +517,8 @@ class Gitlab(object): error_message=error_message, response_body=result.content) - def http_get(self, path, query_data={}, streamed=False, **kwargs): + def http_get(self, path, query_data={}, streamed=False, raw=False, + **kwargs): """Make a GET request to the Gitlab server. Args: @@ -525,6 +526,7 @@ class Gitlab(object): 'http://whatever/v4/api/projecs') query_data (dict): Data to send as query parameters streamed (bool): Whether the data should be streamed + raw (bool): If True do not try to parse the output as json **kwargs: Extra options to send to the server (e.g. sudo) Returns: @@ -538,8 +540,10 @@ class Gitlab(object): """ result = self.http_request('get', path, query_data=query_data, streamed=streamed, **kwargs) - if (result.headers['Content-Type'] == 'application/json' and - not streamed): + + if (result.headers['Content-Type'] == 'application/json' + and not streamed + and not raw): try: return result.json() except Exception: diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index fd673b5..c3714d8 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1128,7 +1128,7 @@ class Snippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject): """ path = '/snippets/%s/raw' % self.get_id() result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @@ -1365,7 +1365,7 @@ class ProjectJob(RESTObject, RefreshMixin): """ path = '%s/%s/artifacts' % (self.manager.path, self.get_id()) result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @cli.register_custom_action('ProjectJob') @@ -1393,7 +1393,7 @@ class ProjectJob(RESTObject, RefreshMixin): """ path = '%s/%s/artifacts/%s' % (self.manager.path, self.get_id(), path) result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @cli.register_custom_action('ProjectJob') @@ -1419,7 +1419,7 @@ class ProjectJob(RESTObject, RefreshMixin): """ path = '%s/%s/trace' % (self.manager.path, self.get_id()) result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @@ -2654,7 +2654,7 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, path = '%s/%s/raw' % (self.path, file_path) query_data = {'ref': ref} result = self.gitlab.http_get(path, query_data=query_data, - streamed=streamed, **kwargs) + streamed=streamed, raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @@ -2897,7 +2897,7 @@ class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, """ path = "%s/%s/raw" % (self.manager.path, self.get_id()) result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @@ -3174,7 +3174,7 @@ class ProjectExport(RefreshMixin, RESTObject): """ path = '/projects/%d/export/download' % self.project_id result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @@ -3315,7 +3315,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): """ path = '/projects/%s/repository/blobs/%s/raw' % (self.get_id(), sha) result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @cli.register_custom_action('Project', ('from_', 'to')) @@ -3391,7 +3391,8 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): if sha: query_data['sha'] = sha result = self.manager.gitlab.http_get(path, query_data=query_data, - streamed=streamed, **kwargs) + raw=True, streamed=streamed, + **kwargs) return utils.response_content(result, streamed, action, chunk_size) @cli.register_custom_action('Project', ('forked_from_id', )) @@ -3674,7 +3675,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): """ path = '/projects/%d/snapshot' % self.get_id() result = self.manager.gitlab.http_get(path, streamed=streamed, - **kwargs) + raw=True, **kwargs) return utils.response_content(result, streamed, action, chunk_size) @cli.register_custom_action('Project', ('scope', 'search')) -- cgit v1.2.1