From 43e8a2a82deff4c95e156fc951f88ff6e95cf7b8 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 17:59:53 +0100 Subject: [PATCH 01/29] Add support for MergeRequest validation Both API and CLI support this feature. fixes #105 --- gitlab/cli.py | 20 ++++++++++++++++++++ gitlab/exceptions.py | 22 +++++++++++++++++++--- gitlab/objects.py | 37 +++++++++++++++++++++++++++++++++++++ tools/functional_tests.sh | 17 +++++++++++++++++ tools/python_test.py | 18 ++++++++++++++++++ 5 files changed, 111 insertions(+), 3 deletions(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index 090978b4d..91c45a086 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -41,6 +41,12 @@ 'blob': {'required': ['id', 'project-id', 'filepath']}, 'builds': {'required': ['id', 'project-id']}}, + gitlab.ProjectMergeRequest: { + 'merge': {'required': ['id', 'project-id'], + 'optional': ['merge-commit-message', + 'should-remove-source-branch', + 'merged-when-build-succeeds']} + }, gitlab.ProjectMilestone: {'issues': {'required': ['id', 'project-id']}}, gitlab.Project: {'search': {'required': ['query']}, 'owned': {}, @@ -217,6 +223,18 @@ def do_project_build_retry(self, cls, gl, what, args): except Exception as e: _die("Impossible to retry project build (%s)" % str(e)) + def do_project_merge_request_merge(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + should_remove = args['should_remove_source_branch'] + build_succeeds = args['merged_when_build_succeeds'] + return o.merge( + merge_commit_message=args['merge_commit_message'], + should_remove_source_branch=should_remove, + merged_when_build_succeeds=build_succeeds) + except Exception as e: + _die("Impossible to validate merge request (%s)" % str(e)) + def do_project_milestone_issues(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) @@ -298,6 +316,8 @@ def _populate_sub_parser_by_class(cls, sub_parser): d = EXTRA_ACTIONS[cls][action_name] [sub_parser_action.add_argument("--%s" % arg, required=True) for arg in d.get('required', [])] + [sub_parser_action.add_argument("--%s" % arg, required=False) + for arg in d.get('optional', [])] def _build_parser(args=sys.argv[1:]): diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 1b5ec6a56..ce1f68011 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -91,6 +91,18 @@ class GitlabUnblockError(GitlabOperationError): pass +class GitlabMRForbiddenError(GitlabOperationError): + pass + + +class GitlabMRClosedError(GitlabOperationError): + pass + + +class GitlabMROnBuildSuccessError(GitlabOperationError): + pass + + def raise_error_from_response(response, error, expected_code=200): """Tries to parse gitlab error message from response and raises error. @@ -99,7 +111,8 @@ def raise_error_from_response(response, error, expected_code=200): If response status code is 401, raises instead GitlabAuthenticationError. response: requests response object - error: Error-class to raise. Should be inherited from GitLabError + error: Error-class or dict {return-code => class} of possible error class + to raise. Should be inherited from GitLabError """ if expected_code == response.status_code: @@ -110,8 +123,11 @@ def raise_error_from_response(response, error, expected_code=200): except (KeyError, ValueError): message = response.content - if response.status_code == 401: - error = GitlabAuthenticationError + if isinstance(error, dict): + error = error.get(response.status_code, GitlabOperationError) + else: + if response.status_code == 401: + error = GitlabAuthenticationError raise error(error_message=message, response_code=response.status_code, diff --git a/gitlab/objects.py b/gitlab/objects.py index c5a47a039..3b8a46784 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1085,6 +1085,43 @@ def Note(self, id=None, **kwargs): self.gitlab, id, project_id=self.project_id, merge_request_id=self.id, **kwargs) + def merge(self, merge_commit_message=None, + should_remove_source_branch=False, + merged_when_build_succeeds=False, + **kwargs): + """Accept the merge request. + + Args: + merge_commit_message (bool): Commit message + should_remove_source_branch (bool): If True, removes the source + branch + merged_when_build_succeeds (bool): Wait for the build to succeed, + then merge + + Returns: + ProjectMergeRequet: The updated MR + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabMRForbiddenError: If the user doesn't have permission to + close thr MR + GitlabMRClosedError: If the MR is already closed + """ + url = '/projects/%s/merge_requests/%s/merge' % (self.project_id, + self.id) + data = {} + if merge_commit_message: + data['merge_commit_message'] = merge_commit_message + if should_remove_source_branch: + data['should_remove_source_branch'] = 'should_remove_source_branch' + if merged_when_build_succeeds: + data['merged_when_build_succeeds'] = 'merged_when_build_succeeds' + + r = self.gitlab._raw_put(url, data=data, **kwargs) + errors = {401: GitlabMRForbiddenError, + 405: GitlabMRClosedError} + raise_error_from_response(r, errors) + return ProjectMergeRequest(self, r.json()) + class ProjectMergeRequestManager(BaseManager): obj_cls = ProjectMergeRequest diff --git a/tools/functional_tests.sh b/tools/functional_tests.sh index 84339e30f..a4a8d06c7 100755 --- a/tools/functional_tests.sh +++ b/tools/functional_tests.sh @@ -80,6 +80,23 @@ testcase "branch creation" ' --branch-name branch1 --ref master >/dev/null 2>&1 ' +GITLAB project-file create --project-id "$PROJECT_ID" \ + --file-path README2 --branch-name branch1 --content "CONTENT" \ + --commit-message "second commit" >/dev/null 2>&1 + +testcase "merge request creation" ' + OUTPUT=$(GITLAB project-merge-request create \ + --project-id "$PROJECT_ID" \ + --source-branch branch1 --target-branch master \ + --title "Update README") +' +MR_ID=$(pecho "${OUTPUT}" | grep ^id: | cut -d' ' -f2) + +testcase "merge request validation" ' + GITLAB project-merge-request merge --project-id "$PROJECT_ID" \ + --id "$MR_ID" >/dev/null 2>&1 +' + testcase "branch deletion" ' GITLAB project-branch delete --project-id "$PROJECT_ID" \ --name branch1 >/dev/null 2>&1 diff --git a/tools/python_test.py b/tools/python_test.py index d32dccd36..c5e955e9a 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -208,3 +208,21 @@ v1 = admin_project.variables.get(v1.key) assert(v1.value == 'new_value1') v1.delete() + +# branches and merges +to_merge = admin_project.branches.create({'branch_name': 'branch1', + 'ref': 'master'}) +admin_project.files.create({'file_path': 'README2.rst', + 'branch_name': 'branch1', + 'content': 'Initial content', + 'commit_message': 'New commit in new branch'}) +mr = admin_project.mergerequests.create({'source_branch': 'branch1', + 'target_branch': 'master', + 'title': 'MR readme2'}) +ret = mr.merge() +admin_project.branches.delete('branch1') + +try: + mr.merge() +except gitlab.GitlabMRClosedError: + pass From ccbea3f59a1be418ea5adf90d25dbfb49f72dfde Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 18:10:13 +0100 Subject: [PATCH 02/29] minor docs fixes --- gitlab/exceptions.py | 7 ++++--- gitlab/objects.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index ce1f68011..8190696ab 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -110,9 +110,10 @@ def raise_error_from_response(response, error, expected_code=200): If response status code is 401, raises instead GitlabAuthenticationError. - response: requests response object - error: Error-class or dict {return-code => class} of possible error class - to raise. Should be inherited from GitLabError + Args: + response: requests response object + error: Error-class or dict {return-code => class} of possible error + class to raise. Should be inherited from GitLabError """ if expected_code == response.status_code: diff --git a/gitlab/objects.py b/gitlab/objects.py index 3b8a46784..56cec387a 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1569,7 +1569,7 @@ def search(self, query, **kwargs): **kwargs: Additional arguments to send to GitLab. Returns: - list(Project): A list of matching projects. + list(gitlab.Gitlab.Project): A list of matching projects. """ return self.gitlab._raw_list("/projects/search/" + query, Project, **kwargs) @@ -1582,7 +1582,7 @@ def all(self, **kwargs): **kwargs: Additional arguments to send to GitLab. Returns: - list(Project): The list of projects. + list(gitlab.Gitlab.Project): The list of projects. """ return self.gitlab._raw_list("/projects/all", Project, **kwargs) @@ -1594,7 +1594,7 @@ def owned(self, **kwargs): **kwargs: Additional arguments to send to GitLab. Returns: - list(Project): The list of owned projects. + list(gitlab.Gitlab.Project): The list of owned projects. """ return self.gitlab._raw_list("/projects/owned", Project, **kwargs) From f6a51d67c38c883775240d8c612d492bf023c2e4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 18:32:31 +0100 Subject: [PATCH 03/29] MR: add support for cancel_merge_when_build_succeeds --- gitlab/cli.py | 8 ++++++++ gitlab/objects.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/gitlab/cli.py b/gitlab/cli.py index 91c45a086..efa2d76ad 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -42,6 +42,7 @@ 'filepath']}, 'builds': {'required': ['id', 'project-id']}}, gitlab.ProjectMergeRequest: { + 'cancel': {'required': ['id', 'project-id']}, 'merge': {'required': ['id', 'project-id'], 'optional': ['merge-commit-message', 'should-remove-source-branch', @@ -223,6 +224,13 @@ def do_project_build_retry(self, cls, gl, what, args): except Exception as e: _die("Impossible to retry project build (%s)" % str(e)) + def do_project_merge_request_cancel(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + return o.cancel_merge_when_build_succeeds() + except Exception as e: + _die("Impossible to cancel merge request (%s)" % str(e)) + def do_project_merge_request_merge(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/objects.py b/gitlab/objects.py index 56cec387a..9ae9c50d4 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1085,6 +1085,18 @@ def Note(self, id=None, **kwargs): self.gitlab, id, project_id=self.project_id, merge_request_id=self.id, **kwargs) + def cancel_merge_when_build_succeeds(self, **kwargs): + """Cancel merge when build succeeds.""" + + u = ('/projects/%s/merge_requests/%s/cancel_merge_when_build_succeeds' + % (self.project_id, self.id)) + r = self.gitlab._raw_put(u, **kwargs) + errors = {401: GitlabMRForbiddenError, + 405: GitlabMRClosedError, + 406: GitlabMROnBuildSuccessError} + raise_error_from_response(r, errors) + return ProjectMergeRequest(self, r.json()) + def merge(self, merge_commit_message=None, should_remove_source_branch=False, merged_when_build_succeeds=False, From 571a382f0595ea7cfd5424b1e4f5009fcb531642 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 20:08:04 +0100 Subject: [PATCH 04/29] MR: add support for closes_issues --- gitlab/cli.py | 9 +++++++++ gitlab/objects.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/gitlab/cli.py b/gitlab/cli.py index efa2d76ad..cd64ada28 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -42,6 +42,7 @@ 'filepath']}, 'builds': {'required': ['id', 'project-id']}}, gitlab.ProjectMergeRequest: { + 'closes-issues': {'required': ['id', 'project-id']}, 'cancel': {'required': ['id', 'project-id']}, 'merge': {'required': ['id', 'project-id'], 'optional': ['merge-commit-message', @@ -224,6 +225,14 @@ def do_project_build_retry(self, cls, gl, what, args): except Exception as e: _die("Impossible to retry project build (%s)" % str(e)) + def do_project_merge_request_closesissues(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + return o.closes_issues() + except Exception as e: + _die("Impossible to list issues closed by merge request (%s)" % + str(e)) + def do_project_merge_request_cancel(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/objects.py b/gitlab/objects.py index 9ae9c50d4..9ce075ae9 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1097,6 +1097,20 @@ def cancel_merge_when_build_succeeds(self, **kwargs): raise_error_from_response(r, errors) return ProjectMergeRequest(self, r.json()) + def closes_issues(self, **kwargs): + """List issues closed by the MR. + + Returns: + list (ProjectIssue): List of closed issues + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the server fails to perform the request. + """ + url = ('/projects/%s/merge_requests/%s/closes_issues' % + (self.project_id, self.id)) + return self.gitlab._raw_list(url, ProjectIssue, **kwargs) + def merge(self, merge_commit_message=None, should_remove_source_branch=False, merged_when_build_succeeds=False, From 349f66e2959ae57c3399f44edf09da8a775067ce Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 21:07:14 +0100 Subject: [PATCH 05/29] add "external" parameter for users --- gitlab/objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 9ce075ae9..896cefde3 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -505,12 +505,13 @@ class User(GitlabObject): requiredCreateAttrs = ['email', 'username', 'name', 'password'] optionalCreateAttrs = ['skype', 'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider', 'bio', 'admin', - 'can_create_group', 'website_url', 'confirm'] + 'can_create_group', 'website_url', 'confirm', + 'external'] requiredUpdateAttrs = ['email', 'username', 'name'] optionalUpdateAttrs = ['password', 'skype', 'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider', 'bio', 'admin', 'can_create_group', 'website_url', - 'confirm'] + 'confirm', 'external'] managers = [('keys', UserKeyManager, [('user_id', 'id')])] def _data_for_gitlab(self, extra_parameters={}, update=False): From 9a9a4c41c02072bd7fad18f75702ec7bb7407ac6 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 21:10:36 +0100 Subject: [PATCH 06/29] Add deletion support for issues and MR This is supported in gitlabhq master branch for admin users (soft deletion). --- gitlab/objects.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 896cefde3..8b9e2752b 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -944,7 +944,6 @@ class ProjectIssue(GitlabObject): _url = '/projects/%(project_id)s/issues/' _constructorTypes = {'author': 'User', 'assignee': 'User', 'milestone': 'ProjectMilestone'} - canDelete = False requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] # FIXME: state_event is only valid with update @@ -1072,7 +1071,6 @@ class ProjectMergeRequest(GitlabObject): _url = '/projects/%(project_id)s/merge_request' _urlPlural = '/projects/%(project_id)s/merge_requests' _constructorTypes = {'author': 'User', 'assignee': 'User'} - canDelete = False requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] optionalCreateAttrs = ['assignee_id'] From 61bc24fd341e53718f8b9c06c3ac1bbcd55d2530 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 22 Mar 2016 21:14:58 +0100 Subject: [PATCH 07/29] Add missing group creation parameters description and visibility_level are optional parameters for group creation. --- gitlab/objects.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 8b9e2752b..2ceb37f71 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -664,6 +664,7 @@ class Group(GitlabObject): canUpdate = False _constructorTypes = {'projects': 'Project'} requiredCreateAttrs = ['name', 'path'] + optionalCreateAttrs = ['description', 'visibility_level'] shortPrintAttr = 'name' managers = [('members', GroupMemberManager, [('group_id', 'id')])] @@ -673,6 +674,10 @@ class Group(GitlabObject): MASTER_ACCESS = 40 OWNER_ACCESS = 50 + VISIBILITY_PRIVATE = 0 + VISIBILITY_INTERNAL = 10 + VISIBILITY_PUBLIC = 20 + def Member(self, id=None, **kwargs): warnings.warn("`Member` is deprecated, use `members` instead", DeprecationWarning) From 858352b9a337471401dd2c8d9552c13efab625e6 Mon Sep 17 00:00:00 2001 From: Guyzmo Date: Sat, 2 Apr 2016 16:39:39 +0200 Subject: [PATCH 08/29] Adding a Session instance for all HTTP requests The session instance will make it easier for setting up once headers, including the authentication payload, and it's also making it easier to override HTTP queries handling, when using [betamax](https://github.com/sigmavirus24/betamax) for recording and replaying the test suites. --- gitlab/__init__.py | 75 ++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 6c7519537..f5e42940b 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -122,6 +122,9 @@ def __init__(self, url, private_token=None, #: Whether SSL certificates should be validated self.ssl_verify = ssl_verify + #: Create a session object for requests + self.session = requests.Session() + self.settings = ApplicationSettingsManager(self) self.user_keys = UserKeyManager(self) self.users = UserManager(self) @@ -260,11 +263,11 @@ def _raw_get(self, path, content_type=None, **kwargs): headers = self._create_headers(content_type) try: - return requests.get(url, - params=kwargs, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + return self.session.get(url, + params=kwargs, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -298,10 +301,10 @@ def _raw_post(self, path, data=None, content_type=None, **kwargs): url = '%s%s' % (self._url, path) headers = self._create_headers(content_type) try: - return requests.post(url, params=kwargs, data=data, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + return self.session.post(url, params=kwargs, data=data, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -311,10 +314,10 @@ def _raw_put(self, path, data=None, content_type=None, **kwargs): headers = self._create_headers(content_type) try: - return requests.put(url, data=data, params=kwargs, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + return self.session.put(url, data=data, params=kwargs, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -324,11 +327,11 @@ def _raw_delete(self, path, content_type=None, **kwargs): headers = self._create_headers(content_type) try: - return requests.delete(url, - params=kwargs, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + return self.session.delete(url, + params=kwargs, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -366,9 +369,9 @@ def list(self, obj_class, **kwargs): del params[attribute] try: - r = requests.get(url, params=params, headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + r = self.session.get(url, params=params, headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -434,8 +437,8 @@ def get(self, obj_class, id=None, **kwargs): del params[attribute] try: - r = requests.get(url, params=params, headers=headers, - verify=self.ssl_verify, timeout=self.timeout) + r = self.session.get(url, params=params, headers=headers, + verify=self.ssl_verify, timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -487,11 +490,11 @@ def delete(self, obj, id=None, **kwargs): del params[attribute] try: - r = requests.delete(url, - params=params, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + r = self.session.delete(url, + params=params, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -535,10 +538,10 @@ def create(self, obj, **kwargs): data = obj._data_for_gitlab(extra_parameters=kwargs) try: - r = requests.post(url, data=data, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + r = self.session.post(url, data=data, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -585,10 +588,10 @@ def update(self, obj, **kwargs): data = obj._data_for_gitlab(extra_parameters=kwargs, update=True) try: - r = requests.put(url, data=data, - headers=headers, - verify=self.ssl_verify, - timeout=self.timeout) + r = self.session.put(url, data=data, + headers=headers, + verify=self.ssl_verify, + timeout=self.timeout) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) From 5fe7e27bb16a06271f87bf19473b8604df92b4f7 Mon Sep 17 00:00:00 2001 From: Adam Reid Date: Fri, 6 May 2016 09:44:15 -0400 Subject: [PATCH 09/29] Enable updates on ProjectIssueNotes --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 2ceb37f71..931161181 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -935,7 +935,7 @@ class ProjectHookManager(BaseManager): class ProjectIssueNote(GitlabObject): _url = '/projects/%(project_id)s/issues/%(issue_id)s/notes' _constructorTypes = {'author': 'User'} - canUpdate = False + canUpdate = True canDelete = False requiredUrlAttrs = ['project_id', 'issue_id'] requiredCreateAttrs = ['body'] From 7a8f81b32e47dab6da495f5cefffe48566934744 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 8 May 2016 11:55:04 +0200 Subject: [PATCH 10/29] Add support for Project raw_blob --- gitlab/objects.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 2ceb37f71..a7f35bdbb 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1474,6 +1474,24 @@ def blob(self, sha, filepath, **kwargs): raise_error_from_response(r, GitlabGetError) return r.content + def raw_blob(self, sha, **kwargs): + """Return the raw file contents for a blob by blob SHA. + + Args: + sha(str): ID of the blob + + Returns: + str: The blob content + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the server fails to perform the request. + """ + url = "/projects/%s/repository/raw_blobs/%s" % (self.id, sha) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.content + def archive(self, sha=None, **kwargs): """Return a tarball of the repository. From 250f34806b1414b5346b4eaf268eb2537d8017de Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 8 May 2016 12:02:19 +0200 Subject: [PATCH 11/29] Implement project compare Fixes #112 --- gitlab/objects.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index a7f35bdbb..3a44f6ebd 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1475,7 +1475,7 @@ def blob(self, sha, filepath, **kwargs): return r.content def raw_blob(self, sha, **kwargs): - """Return the raw file contents for a blob by blob SHA. + """Returns the raw file contents for a blob by blob SHA. Args: sha(str): ID of the blob @@ -1492,6 +1492,26 @@ def raw_blob(self, sha, **kwargs): raise_error_from_response(r, GitlabGetError) return r.content + def compare(self, from_, to, **kwargs): + """Returns a diff between two branches/commits. + + Args: + from_(str): orig branch/SHA + to(str): dest branch/SHA + + Returns: + str: The diff + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the server fails to perform the request. + """ + url = "/projects/%s/repository/compare" % self.id + url = "%s?from=%s&to=%s" % (url, from_, to) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return r.json() + def archive(self, sha=None, **kwargs): """Return a tarball of the repository. From 1600770e1d01aaaa90dbfd602e073e4e4a578fc1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 8 May 2016 12:43:11 +0200 Subject: [PATCH 12/29] Implement project contributors --- gitlab/objects.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 3a44f6ebd..9594c2bf7 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1512,6 +1512,21 @@ def compare(self, from_, to, **kwargs): raise_error_from_response(r, GitlabGetError) return r.json() + def contributors(self): + """Returns a list of contributors for the project. + + Returns: + list: The contibutors + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the server fails to perform the request. + """ + url = "/projects/%s/repository/contributors" % self.id + r = self.gitlab._raw_get(url) + raise_error_from_response(r, GitlabListError) + return r.json() + def archive(self, sha=None, **kwargs): """Return a tarball of the repository. From 64af39818d02af1b40644d71fd047d6bc3f6e69e Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 8 May 2016 13:07:41 +0200 Subject: [PATCH 13/29] Drop the next_url attribute when listing Fixes #106 --- gitlab/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index f5e42940b..1994d5578 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -368,6 +368,10 @@ def list(self, obj_class, **kwargs): for attribute in obj_class.requiredUrlAttrs: del params[attribute] + # Also remove the next-url attribute that make queries fail + if 'next_url' in params: + del params['next_url'] + try: r = self.session.get(url, params=params, headers=headers, verify=self.ssl_verify, From 111b7d9a4ee60176714b950d7ed9da86c6051feb Mon Sep 17 00:00:00 2001 From: Adam Reid Date: Mon, 9 May 2016 14:25:28 -0400 Subject: [PATCH 14/29] Remove unnecessary canUpdate property from ProjectIssuesNote --- gitlab/objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 931161181..692434183 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -935,7 +935,6 @@ class ProjectHookManager(BaseManager): class ProjectIssueNote(GitlabObject): _url = '/projects/%(project_id)s/issues/%(issue_id)s/notes' _constructorTypes = {'author': 'User'} - canUpdate = True canDelete = False requiredUrlAttrs = ['project_id', 'issue_id'] requiredCreateAttrs = ['body'] From f12c732f5e0dff7db1048adf50f54bfdd63ca6fc Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 10 May 2016 21:36:51 +0200 Subject: [PATCH 15/29] Add new optional attributes for projects Fixes #116 --- gitlab/objects.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 9594c2bf7..8b2733f2d 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1300,12 +1300,14 @@ class Project(GitlabObject): optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', 'public', 'visibility_level', - 'namespace_id', 'description', 'path', 'import_url'] + 'namespace_id', 'description', 'path', 'import_url', + 'builds_enabled', 'public_builds'] optionalUpdateAttrs = ['name', 'default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', 'public', 'visibility_level', 'namespace_id', 'description', - 'path', 'import_url'] + 'path', 'import_url', 'builds_enabled', + 'public_builds'] shortPrintAttr = 'path' managers = [ ('branches', ProjectBranchManager, [('project_id', 'id')]), @@ -1627,7 +1629,8 @@ class UserProject(GitlabObject): optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', 'snippets_enabled', 'public', 'visibility_level', - 'description'] + 'description', 'builds_enabled', 'public_builds', + 'import_url'] class ProjectManager(BaseManager): From d204e6614d29d60a5d0790dde8f26d3efe78b9e8 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 10 May 2016 21:55:08 +0200 Subject: [PATCH 16/29] Enable deprecation warnings for gitlab only Fixes #96 --- gitlab/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 1994d5578..5009e132c 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -38,7 +38,8 @@ __license__ = 'LGPL3' __copyright__ = 'Copyright 2013-2016 Gauvain Pocentek' -warnings.simplefilter('always', DeprecationWarning) +warnings.filterwarnings('default', category=DeprecationWarning, + module='^gitlab') def _sanitize(value): From 1915519f449f9b751aa9cd6bc584472602b58f36 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 10 May 2016 21:56:43 +0200 Subject: [PATCH 17/29] enable python 3.5 tests for travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 27cd7f1a9..9788fc6ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: 2.7 env: + - TOX_ENV=py35 - TOX_ENV=py34 - TOX_ENV=py27 - TOX_ENV=pep8 From aff99b13fdfcfb68e8c9ae45d817d5ec20752626 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 10 May 2016 22:57:22 +0200 Subject: [PATCH 18/29] Rework merge requests update Fixes #76 --- gitlab/objects.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 8b2733f2d..53f23c197 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -215,7 +215,10 @@ def _data_for_gitlab(self, extra_parameters={}, update=False): attributes = list(attributes) + ['sudo', 'page', 'per_page'] for attribute in attributes: if hasattr(self, attribute): - data[attribute] = getattr(self, attribute) + value = getattr(self, attribute) + if isinstance(value, list): + value = ",".join(value) + data[attribute] = value data.update(extra_parameters) @@ -1078,7 +1081,8 @@ class ProjectMergeRequest(GitlabObject): _constructorTypes = {'author': 'User', 'assignee': 'User'} requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] - optionalCreateAttrs = ['assignee_id'] + optionalCreateAttrs = ['assignee_id', 'description', 'target_project_id', + 'labels', 'milestone_id'] managers = [('notes', ProjectMergeRequestNoteManager, [('project_id', 'project_id'), ('merge_request_id', 'id')])] @@ -1089,6 +1093,19 @@ def Note(self, id=None, **kwargs): self.gitlab, id, project_id=self.project_id, merge_request_id=self.id, **kwargs) + def _data_for_gitlab(self, extra_parameters={}, update=False): + data = (super(ProjectMergeRequest, self) + ._data_for_gitlab(extra_parameters)) + if update: + # Drop source_branch attribute as it is not accepted by the gitlab + # server (Issue #76) + # We need to unserialize and reserialize the + # data, this is far from optimal + d = json.loads(data) + d.pop('source_branch', None) + data = json.dumps(d) + return data + def cancel_merge_when_build_succeeds(self, **kwargs): """Cancel merge when build succeeds.""" From c3f5b3ac14c1767a5b65e7771496990f5ce6b9f0 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 10 May 2016 23:02:25 +0200 Subject: [PATCH 19/29] Revert "enable python 3.5 tests for travis" This reverts commit 1915519f449f9b751aa9cd6bc584472602b58f36. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9788fc6ba..27cd7f1a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: 2.7 env: - - TOX_ENV=py35 - TOX_ENV=py34 - TOX_ENV=py27 - TOX_ENV=pep8 From 0a1bb94b58bfcf837f846be7bd4d4075ab16eea6 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 12 May 2016 17:33:45 +0200 Subject: [PATCH 20/29] Rework the Gitlab.delete method Fixes #107 --- gitlab/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 5009e132c..bdee4aa4b 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -471,16 +471,18 @@ def delete(self, obj, id=None, **kwargs): if inspect.isclass(obj): if not issubclass(obj, GitlabObject): raise GitlabError("Invalid class: %s" % obj) - params = {} - params[obj.idAttr] = id - else: - params = obj.__dict__.copy() + + params = {obj.idAttr: id if id else getattr(obj, obj.idAttr)} params.update(kwargs) + missing = [] for k in itertools.chain(obj.requiredUrlAttrs, obj.requiredDeleteAttrs): if k not in params: - missing.append(k) + try: + params[k] = getattr(obj, k) + except KeyError: + missing.append(k) if missing: raise GitlabDeleteError('Missing attribute(s): %s' % ", ".join(missing)) @@ -493,6 +495,10 @@ def delete(self, obj, id=None, **kwargs): # url-parameters left for attribute in obj.requiredUrlAttrs: del params[attribute] + if obj._id_in_delete_url: + # The ID is already built, no need to add it as extra key in query + # string + params.pop(obj.idAttr) try: r = self.session.delete(url, From 8ce8218e2fb155c933946d9959a9b53f6a905d21 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 12 May 2016 17:43:18 +0200 Subject: [PATCH 21/29] ProjectFile: file_path is required for deletion --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 53f23c197..df9715753 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1226,7 +1226,7 @@ class ProjectFile(GitlabObject): requiredCreateAttrs = ['file_path', 'branch_name', 'content', 'commit_message'] optionalCreateAttrs = ['encoding'] - requiredDeleteAttrs = ['branch_name', 'commit_message'] + requiredDeleteAttrs = ['branch_name', 'commit_message', 'file_path'] getListWhenNoId = False shortPrintAttr = 'file_path' getRequiresId = False From 45adb6e4dbe7667376639d68078754d6d72cb55c Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 12 May 2016 21:03:51 +0200 Subject: [PATCH 22/29] Rename some methods to better match the API URLs Also deprecate the file_* methods in favor of the files manager. --- gitlab/objects.py | 25 ++++++++++++++++++++++--- tools/python_test.py | 6 ++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index df9715753..6aa44d173 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1448,6 +1448,11 @@ def Tag(self, id=None, **kwargs): **kwargs) def tree(self, path='', ref_name='', **kwargs): + warnings.warn("`tree` is deprecated, use `repository_tree` instead", + DeprecationWarning) + return self.repository_tree(path, ref_name, **kwargs) + + def repository_tree(self, path='', ref_name='', **kwargs): """Return a list of files in the repository. Args: @@ -1474,6 +1479,11 @@ def tree(self, path='', ref_name='', **kwargs): return r.json() def blob(self, sha, filepath, **kwargs): + warnings.warn("`blob` is deprecated, use `repository_blob` instead", + DeprecationWarning) + return self.repository_blob(sha, filepath, **kwargs) + + def repository_blob(self, sha, filepath, **kwargs): """Return the content of a file for a commit. Args: @@ -1493,7 +1503,7 @@ def blob(self, sha, filepath, **kwargs): raise_error_from_response(r, GitlabGetError) return r.content - def raw_blob(self, sha, **kwargs): + def repository_raw_blob(self, sha, **kwargs): """Returns the raw file contents for a blob by blob SHA. Args: @@ -1511,7 +1521,7 @@ def raw_blob(self, sha, **kwargs): raise_error_from_response(r, GitlabGetError) return r.content - def compare(self, from_, to, **kwargs): + def repository_compare(self, from_, to, **kwargs): """Returns a diff between two branches/commits. Args: @@ -1531,7 +1541,7 @@ def compare(self, from_, to, **kwargs): raise_error_from_response(r, GitlabGetError) return r.json() - def contributors(self): + def repository_contributors(self): """Returns a list of contributors for the project. Returns: @@ -1580,6 +1590,9 @@ def create_file(self, path, branch, content, message, **kwargs): GitlabConnectionError: If the server cannot be reached. GitlabCreateError: If the server fails to perform the request. """ + warnings.warn("`create_file` is deprecated, " + "use `files.create()` instead", + DeprecationWarning) url = "/projects/%s/repository/files" % self.id url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % (path, branch, content, message)) @@ -1587,6 +1600,9 @@ def create_file(self, path, branch, content, message, **kwargs): raise_error_from_response(r, GitlabCreateError, 201) def update_file(self, path, branch, content, message, **kwargs): + warnings.warn("`update_file` is deprecated, " + "use `files.update()` instead", + DeprecationWarning) url = "/projects/%s/repository/files" % self.id url += ("?file_path=%s&branch_name=%s&content=%s&commit_message=%s" % (path, branch, content, message)) @@ -1594,6 +1610,9 @@ def update_file(self, path, branch, content, message, **kwargs): raise_error_from_response(r, GitlabUpdateError) def delete_file(self, path, branch, message, **kwargs): + warnings.warn("`delete_file` is deprecated, " + "use `files.delete()` instead", + DeprecationWarning) url = "/projects/%s/repository/files" % self.id url += ("?file_path=%s&branch_name=%s&commit_message=%s" % (path, branch, message)) diff --git a/tools/python_test.py b/tools/python_test.py index c5e955e9a..df8de2362 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -1,4 +1,5 @@ import base64 +import time import gitlab @@ -135,6 +136,7 @@ 'commit_message': 'Initial commit'}) readme = admin_project.files.get(file_path='README', ref='master') readme.content = base64.b64encode("Improved README") +time.sleep(2) readme.save(branch_name="master", commit_message="new commit") readme.delete(commit_message="Removing README") @@ -145,10 +147,10 @@ readme = admin_project.files.get(file_path='README.rst', ref='master') assert(readme.decode() == 'Initial content') -tree = admin_project.tree() +tree = admin_project.repository_tree() assert(len(tree) == 1) assert(tree[0]['name'] == 'README.rst') -blob = admin_project.blob('master', 'README.rst') +blob = admin_project.repository_blob('master', 'README.rst') assert(blob == 'Initial content') archive1 = admin_project.archive() archive2 = admin_project.archive('master') From 24c283f5861f21e51489afc815bd9f31bff58bee Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 13 May 2016 18:36:30 +0200 Subject: [PATCH 23/29] Deprecate Project.archive() --- gitlab/objects.py | 7 +++++++ tools/python_test.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 6aa44d173..94bf1d980 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1556,7 +1556,14 @@ def repository_contributors(self): raise_error_from_response(r, GitlabListError) return r.json() + def archive(self, sha=None, **kwargs): + warnings.warn("`archive` is deprecated, " + "use `repository_archive` instead", + DeprecationWarning) + return self.repository_archive(path, ref_name, **kwargs) + + def repository_archive(self, sha=None, **kwargs): """Return a tarball of the repository. Args: diff --git a/tools/python_test.py b/tools/python_test.py index df8de2362..d07143570 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -152,8 +152,8 @@ assert(tree[0]['name'] == 'README.rst') blob = admin_project.repository_blob('master', 'README.rst') assert(blob == 'Initial content') -archive1 = admin_project.archive() -archive2 = admin_project.archive('master') +archive1 = admin_project.repository_archive() +archive2 = admin_project.repository_archive('master') assert(archive1 == archive2) # labels From 1de6b7e7641f2c0cb101a82385cee569aa786e3f Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 13 May 2016 19:07:48 +0200 Subject: [PATCH 24/29] implement star/unstar for projects --- gitlab/cli.py | 25 ++++++++++++++++++++++++- gitlab/exceptions.py | 7 ++++++- gitlab/objects.py | 40 ++++++++++++++++++++++++++++++++++++++++ tools/python_test.py | 6 ++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index cd64ada28..02dac8ee2 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -52,7 +52,10 @@ gitlab.ProjectMilestone: {'issues': {'required': ['id', 'project-id']}}, gitlab.Project: {'search': {'required': ['query']}, 'owned': {}, - 'all': {}}, + 'all': {}, + 'starred': {}, + 'star': {'required': ['id']}, + 'unstar': {'required': ['id']}}, gitlab.User: {'block': {'required': ['id']}, 'unblock': {'required': ['id']}, 'search': {'required': ['query']}, @@ -170,12 +173,32 @@ def do_project_all(self, cls, gl, what, args): except Exception as e: _die("Impossible to list all projects (%s)" % str(e)) + def do_project_starred(self, cls, gl, what, args): + try: + return gl.projects.starred() + except Exception as e: + _die("Impossible to list starred projects (%s)" % str(e)) + def do_project_owned(self, cls, gl, what, args): try: return gl.projects.owned() except Exception as e: _die("Impossible to list owned projects (%s)" % str(e)) + def do_project_star(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.star() + except Exception as e: + _die("Impossible to star project (%s)" % str(e)) + + def do_project_unstar(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.unstar() + except Exception as e: + _die("Impossible to unstar project (%s)" % str(e)) + def do_user_block(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 8190696ab..49a3728e7 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -116,7 +116,12 @@ def raise_error_from_response(response, error, expected_code=200): class to raise. Should be inherited from GitLabError """ - if expected_code == response.status_code: + if isinstance(expected_code, int): + expected_codes = [expected_code] + else: + expected_codes = expected_code + + if response.status_code in expected_codes: return try: diff --git a/gitlab/objects.py b/gitlab/objects.py index 94bf1d980..139a92e4c 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1651,6 +1651,34 @@ def delete_fork_relation(self): r = self.gitlab._raw_delete(url) raise_error_from_response(r, GitlabDeleteError) + def star(self): + """Star a project. + + Returns: + Project: the updated Project + + Raises: + GitlabConnectionError: If the server cannot be reached. + """ + url = "/projects/%s/star" % self.id + r = self.gitlab._raw_post(url) + raise_error_from_response(r, GitlabGetError, [201, 304]) + return Project(self.gitlab, r.json()) if r.status_code == 201 else self + + def unstar(self): + """Unstar a project. + + Returns: + Project: the updated Project + + Raises: + GitlabConnectionError: If the server cannot be reached. + """ + url = "/projects/%s/star" % self.id + r = self.gitlab._raw_delete(url) + raise_error_from_response(r, GitlabDeleteError, [200, 304]) + return Project(self.gitlab, r.json()) if r.status_code == 200 else self + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' @@ -1727,6 +1755,18 @@ def owned(self, **kwargs): """ return self.gitlab._raw_list("/projects/owned", Project, **kwargs) + def starred(self, **kwargs): + """List starred projects. + + Args: + all (bool): If True, return all the items, without pagination + **kwargs: Additional arguments to send to GitLab. + + Returns: + list(gitlab.Gitlab.Project): The list of starred projects. + """ + return self.gitlab._raw_list("/projects/starred", Project, **kwargs) + class UserProjectManager(BaseManager): obj_cls = UserProject diff --git a/tools/python_test.py b/tools/python_test.py index d07143570..d09d24b20 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -228,3 +228,9 @@ mr.merge() except gitlab.GitlabMRClosedError: pass + +# stars +admin_project = admin_project.star() +assert(admin_project.star_count == 1) +admin_project = admin_project.unstar() +assert(admin_project.star_count == 0) From 62e4fb9b09efbf9080a6787bcbde09067a9b83ef Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 13 May 2016 19:16:50 +0200 Subject: [PATCH 25/29] implement list/get licenses --- gitlab/__init__.py | 2 ++ gitlab/objects.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index bdee4aa4b..ddf6bb219 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -70,6 +70,7 @@ class Gitlab(object): groups (GroupManager): Manager for GitLab members hooks (HookManager): Manager for GitLab hooks issues (IssueManager): Manager for GitLab issues + licenses (LicenseManager): Manager for licenses project_branches (ProjectBranchManager): Manager for GitLab projects branches project_commits (ProjectCommitManager): Manager for GitLab projects @@ -133,6 +134,7 @@ def __init__(self, url, private_token=None, self.groups = GroupManager(self) self.hooks = HookManager(self) self.issues = IssueManager(self) + self.licenses = LicenseManager(self) self.project_branches = ProjectBranchManager(self) self.project_commits = ProjectCommitManager(self) self.project_keys = ProjectKeyManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 60ca43a57..7c76d534e 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -747,6 +747,18 @@ class IssueManager(BaseManager): obj_cls = Issue +class License(GitlabObject): + _url = '/licenses' + canDelete = False + canUpdate = False + canCreate = False + idAttr = 'key' + + +class LicenseManager(BaseManager): + obj_cls = License + + class ProjectBranch(GitlabObject): _url = '/projects/%(project_id)s/repository/branches' _constructorTypes = {'author': 'User', "committer": "User"} From 417d27cf7c9569d5057dcced5481a6b9c8dfde2a Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 13 May 2016 21:33:37 +0200 Subject: [PATCH 26/29] fix pep8 tests --- gitlab/objects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 7c76d534e..ebfba8b0b 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1567,7 +1567,6 @@ def repository_contributors(self): raise_error_from_response(r, GitlabListError) return r.json() - def archive(self, sha=None, **kwargs): warnings.warn("`archive` is deprecated, " "use `repository_archive` instead", From fd4539715da589df5a81b333e71875289687922d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 15 May 2016 07:35:06 +0200 Subject: [PATCH 27/29] Manage optional parameters for list() and get() * List these elements in the API doc * Implement for License objects --- docs/ext/docstrings.py | 28 +++++++++++++++++++--------- docs/ext/template.j2 | 8 ++++++++ gitlab/cli.py | 16 +++++++++++++--- gitlab/objects.py | 7 +++++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/docs/ext/docstrings.py b/docs/ext/docstrings.py index 4724fc575..4520e437e 100644 --- a/docs/ext/docstrings.py +++ b/docs/ext/docstrings.py @@ -30,17 +30,26 @@ def _process_docstring(app, what, name, obj, options, lines): class GitlabDocstring(GoogleDocstring): def _build_doc(self): cls = self._obj.obj_cls + opt_get_list = cls.optionalGetAttrs + opt_list_list = cls.optionalListAttrs md_create_list = list(itertools.chain(cls.requiredUrlAttrs, cls.requiredCreateAttrs)) opt_create_list = cls.optionalCreateAttrs + opt_get_keys = "None" + if opt_get_list: + opt_get_keys = ", ".join(['``%s``' % i for i in opt_get_list]) + + opt_list_keys = "None" + if opt_list_list: + opt_list_keys = ", ".join(['``%s``' % i for i in opt_list_list]) + md_create_keys = opt_create_keys = "None" if md_create_list: - md_create_keys = "%s" % ", ".join(['``%s``' % i for i in - md_create_list]) + md_create_keys = ", ".join(['``%s``' % i for i in md_create_list]) if opt_create_list: - opt_create_keys = "%s" % ", ".join(['``%s``' % i for i in - opt_create_list]) + opt_create_keys = ", ".join(['``%s``' % i for i in + opt_create_list]) md_update_list = list(itertools.chain(cls.requiredUrlAttrs, cls.requiredUpdateAttrs)) @@ -48,11 +57,10 @@ def _build_doc(self): md_update_keys = opt_update_keys = "None" if md_update_list: - md_update_keys = "%s" % ", ".join(['``%s``' % i for i in - md_update_list]) + md_update_keys = ", ".join(['``%s``' % i for i in md_update_list]) if opt_update_list: - opt_update_keys = "%s" % ", ".join(['``%s``' % i for i in - opt_update_list]) + opt_update_keys = ", ".join(['``%s``' % i for i in + opt_update_list]) tmpl_file = os.path.join(os.path.dirname(__file__), 'template.j2') with open(tmpl_file) as fd: @@ -62,7 +70,9 @@ def _build_doc(self): md_create_keys=md_create_keys, opt_create_keys=opt_create_keys, md_update_keys=md_update_keys, - opt_update_keys=opt_update_keys) + opt_update_keys=opt_update_keys, + opt_get_keys=opt_get_keys, + opt_list_keys=opt_list_keys) return output.split('\n') diff --git a/docs/ext/template.j2 b/docs/ext/template.j2 index 980a7ed70..29f4a0091 100644 --- a/docs/ext/template.j2 +++ b/docs/ext/template.j2 @@ -19,3 +19,11 @@ Mandatory arguments for object update: {{ md_create_keys }} Optional arguments for object update: {{ opt_create_keys }} {% endif %} + +{% if cls.canList %} +Optional arguments for object listing: {{ opt_list_keys }} +{% endif %} + +{% if cls.canGet %} +Optional arguments for object listing: {{ opt_get_keys }} +{% endif %} diff --git a/gitlab/cli.py b/gitlab/cli.py index 02dac8ee2..c7dacebd0 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -313,7 +313,7 @@ def _populate_sub_parser_by_class(cls, sub_parser): sub_parser_action.add_argument("--page", required=False) sub_parser_action.add_argument("--per-page", required=False) - elif action_name in ["get", "delete"]: + if action_name in ["get", "delete"]: if cls not in [gitlab.CurrentUser]: if cls.getRequiresId: id_attr = cls.idAttr.replace('_', '-') @@ -323,7 +323,17 @@ def _populate_sub_parser_by_class(cls, sub_parser): required=True) for x in cls.requiredGetAttrs if x != cls.idAttr] - elif action_name == "create": + if action_name == "get": + [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), + required=False) + for x in cls.optionalGetAttrs] + + if action_name == "list": + [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), + required=False) + for x in cls.optionalListAttrs] + + if action_name == "create": [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), required=True) for x in cls.requiredCreateAttrs] @@ -331,7 +341,7 @@ def _populate_sub_parser_by_class(cls, sub_parser): required=False) for x in cls.optionalCreateAttrs] - elif action_name == "update": + if action_name == "update": id_attr = cls.idAttr.replace('_', '-') sub_parser_action.add_argument("--%s" % id_attr, required=True) diff --git a/gitlab/objects.py b/gitlab/objects.py index ebfba8b0b..9c6197c0c 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -183,6 +183,10 @@ class GitlabObject(object): requiredUrlAttrs = [] #: Attributes that are required when retrieving list of objects. requiredListAttrs = [] + #: Attributes that are optional when retrieving list of objects. + optionalListAttrs = [] + #: Attributes that are optional when retrieving single object. + optionalGetAttrs = [] #: Attributes that are required when retrieving single object. requiredGetAttrs = [] #: Attributes that are required when deleting object. @@ -754,6 +758,9 @@ class License(GitlabObject): canCreate = False idAttr = 'key' + optionalListAttrs = ['popular'] + optionalGetAttrs = ['project', 'fullname'] + class LicenseManager(BaseManager): obj_cls = License From 57936af70758f35ea28ad060c4ead2d916a3b47e Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 16 May 2016 13:14:48 +0200 Subject: [PATCH 28/29] update changelog and authors --- AUTHORS | 2 ++ ChangeLog | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/AUTHORS b/AUTHORS index 9a00c26cd..dc45ad59b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -27,3 +27,5 @@ Richard Hansen James (d0c_s4vage) Johnson Mikhail Lopotkov Asher256 +Adam Reid +Guyzmo diff --git a/ChangeLog b/ChangeLog index ac4b4778f..392a081d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +Version 0.13 + + * Add support for MergeRequest validation + * MR: add support for cancel_merge_when_build_succeeds + * MR: add support for closes_issues + * Add "external" parameter for users + * Add deletion support for issues and MR + * Add missing group creation parameters + * Add a Session instance for all HTTP requests + * Enable updates on ProjectIssueNotes + * Add support for Project raw_blob + * Implement project compare + * Implement project contributors + * Drop the next_url attribute when listing + * Remove unnecessary canUpdate property from ProjectIssuesNote + * Add new optional attributes for projects + * Enable deprecation warnings for gitlab only + * Rework merge requests update + * Rework the Gitlab.delete method + * ProjectFile: file_path is required for deletion + * Rename some methods to better match the API URLs + * Deprecate the file_* methods in favor of the files manager + * Implement star/unstar for projects + * Implement list/get licenses + * Manage optional parameters for list() and get() + Version 0.12.2 * Add new `ProjectHook` attributes From 0535808d5a82ffbcd5a7ea23ecc4d0c22dad34a1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 16 May 2016 13:16:58 +0200 Subject: [PATCH 29/29] version bump --- gitlab/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index ddf6bb219..05e6075fa 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -32,7 +32,7 @@ from gitlab.objects import * # noqa __title__ = 'python-gitlab' -__version__ = '0.12.2' +__version__ = '0.13' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3'