def billable_users(self) -> int: """ Number of paying/registered users on the organization. """ try: # If the org is a user, this'll throw RuntimeError users = { user['username'] for user in get(self._token, self._url + '/members') } for group in get(self._token, '/groups'): gname = group['full_path'] if (gname in self.name or self.name in gname) \ and self.name != gname: users |= { user['username'] for user in get( self._token, '/groups/{name}/members'.format( name=quote_plus(gname))) } return len(users) except RuntimeError: return 1
def diffstat(self): """ Gets additions and deletions of a merge request. >>> from os import environ >>> pr = GitLabMergeRequest( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', 2 ... ) >>> pr.diffstat (2, 0) :return: An (additions, deletions) tuple. """ changes = get(self._token, self._url + '/changes')['changes'] results = [] expr = re.compile(r'@@ [0-9+,-]+ [0-9+,-]+ @@') for change in changes: diff = change['diff'] match = expr.search(diff) if not match: # for binary files match is None continue start_index = match.end() results += diff[start_index:].split('\n') additions = len([line for line in results if line.startswith('+')]) deletions = len([line for line in results if line.startswith('-')]) return additions, deletions
def unified_diff(self): """ Retrieves the unified diff for the commit excluding the diff index. """ return '\n'.join(patch['diff'] for patch in get(self._token, self._url + '/diff') )
def get_patch_for_file(self, filename: str): r""" Retrieves the unified diff for the commit. >>> from os import environ >>> commit = GitLabCommit( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', '3fc4b86' ... ) >>> assert (commit.get_patch_for_file('README.md') == ... '--- a/README.md\n+++ b/README.md\n@@ -1,2 +1,4 @@\n ' ... '# test\n a test repo\n+\n+a tst pr\n') But only if it exists! >>> commit.get_patch_for_file('IDONTEXISTFILE') Traceback (most recent call last): ... IGitt.ElementDoesntExistError: The file does not exist. :param filename: The file to retrieve patch for. :return: A string containing the patch. :raises ElementDoesntExistError: If the given filename does not exist. """ diff = get(self._token, self._url + '/diff') for patch in diff: if filename in (patch['new_path'], patch['old_path']): return patch['diff'] raise ElementDoesntExistError('The file does not exist.')
def _search(self, search_type, state: Union[MergeRequestStates, IssueStates, None]): """ Retrives a list of all issues or merge requests. :param search_type: A string for type of object i.e. issues for issue and merge_requests for merge requests. :param state: A string for MR/issue state (opened or closed) :return: List of issues/merge requests. """ url = self._url + '/{}'.format(search_type) if state is None: return get(self._token, url) elif isinstance(state, IssueStates): state = GL_ISSUE_STATE_TRANSLATION[state] elif isinstance(state, MergeRequestStates): state = GL_MR_STATE_TRANSLATION[state] return get(self._token, url, {'state': state})
def suborgs(self) -> Set[Organization]: """ Returns the sub-organizations within this organization, recursively. """ result = set() for suborg_data in get(self._token, self._url + '/subgroups'): suborg = GitLabOrganization.from_data(suborg_data, self._token, suborg_data['full_path']) result |= {suborg} | suborg.suborgs return result
def hooks(self) -> Set[str]: """ Retrieves all URLs this repository is hooked to. :return: Set of URLs (str). """ hook_url = self._url + '/hooks' hooks = get(self._token, hook_url) return {hook['url'] for hook in hooks}
def reactions(self) -> Set[GitLabReaction]: """ Retrieves the reactions / award emojis applied on the issue. """ url = self._url + '/award_emoji' reactions = get(self._token, url) return { GitLabReaction.from_data(r, self._token, self, r['id']) for r in reactions }
def master_repositories(self): """ Retrieves repositories the user has admin access to. """ repo_list = get(self._token, '/projects', {'membership': True}) return { GitLabRepository.from_data(repo, self._token, repo['path_with_namespace']) for repo in self._get_repos_with_permissions( repo_list, AccessLevel.ADMIN) }
def filter_issues(self, state: str = 'opened') -> set: """ Filters the issues from the repository based on properties. :param state: 'opened' or 'closed' or 'all'. """ return { GitLabIssue.from_data(res, self._token, self.full_name, res['iid']) for res in get(self._token, self._url + '/issues', {'state': state}) }
def get_permission_level(self, user) -> AccessLevel: """ Retrieves the permission level for the specified user on this repository. """ members = get(self._token, self._url + '/members') if user.username not in map(lambda m: m['username'], members): return (AccessLevel.CAN_VIEW if self.data['visibility'] != 'private' else AccessLevel.NONE) curr_member_idx = next(i for (i, d) in enumerate(members) if d['username'] == user.username) return AccessLevel(members[curr_member_idx]['access_level'])
def repositories(self) -> Set[Repository]: """ Returns the list of repositories contained in this organization including subgroup repositories, recursively. """ from IGitt.GitLab.GitLabRepository import GitLabRepository return { GitLabRepository.from_data(repo, self._token, repo['id']) for repo in get(self._token, self._url + '/projects') }.union({repo for org in self.suborgs for repo in org.repositories})
def raw_members(self) -> list: """ Gets all members including the ones of groups around subgroups as a list of dicts from the JSON responses. """ members = list(get(self._token, self._url + '/members')) if '/' in self.name: members.extend( GitLabOrganization(self._token, self.name.rsplit( '/', maxsplit=1)[0]).raw_members()) return members
def delete_hook(self, url: str): """ Deletes all webhooks to the given URL. :param url: The URL to not fire the webhook to anymore. :raises RuntimeError: If something goes wrong (network, auth...). """ hook_url = self._url + '/hooks' hooks = get(self._token, hook_url) # Do not use self.hooks since id of the hook is needed for hook in hooks: if hook['url'] == url: delete(self._token, hook_url + '/' + str(hook['id']))
def commits(self): """ Retrieves the set of commits in this repository. :return: A set of GitLabCommit objects. """ # Don't move to module, leads to circular imports from IGitt.GitLab.GitLabCommit import GitLabCommit return { GitLabCommit.from_data(commit, self._token, self.full_name, commit['id']) for commit in get(self._token, self._url + '/repository/commits') }
def mrs_closed_by(self): """ Returns the merge requests that close this issue. """ from IGitt.GitLab.GitLabMergeRequest import GitLabMergeRequest url = '{url}/closed_by'.format(url=self._url) mrs = get(self._token, url) return { GitLabMergeRequest.from_data(mr, self._token, self._repository, mr['iid']) for mr in mrs if mr['state'] == MergeRequestStates.MERGED.value }
def affected_files(self): """ Retrieves affected files from a GitLab merge request. >>> from os import environ >>> pr = GitLabMergeRequest( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', 2 ... ) >>> pr.affected_files {'README.md'} :return: A set of filenames. """ changes = get(self._token, self._url + '/changes')['changes'] return {change['old_path'] for change in changes}
def owned_repositories(self): """ Retrieves repositories owned by the authenticated user. >>> from os import environ >>> GitLab = GitLab(GitLabOAuthToken(viron['GITLAB_TEST_TOKEN'])) >>> sorted(map(lambda x: x.full_name, GitLab.owned_repositories) {'gitmate-test-user/test'} :return: A set of GitLabRepository objects. """ repo_list = get(self._token, '/projects', {'owned': True}) return { GitLabRepository.from_data(repo, self._token, repo['path_with_namespace']) for repo in repo_list }
def available_labels(self) -> Set[str]: """ Retrieves a set of captions that are available for labelling bugs. >>> from os import environ >>> issue = GitLabIssue(GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', 1) >>> sorted(issue.available_labels) ['a', 'b', 'c'] :return: A set of label captions (str). """ return { label['name'] for label in get( self._token, '/projects/' + quote_plus(self._repository) + '/labels') }
def merge_requests(self) -> set: """ Retrieves a set of merge request objects. >>> from os import environ >>> repo = GitLabRepository( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test' ... ) >>> len(repo.merge_requests) 4 """ from IGitt.GitLab.GitLabMergeRequest import GitLabMergeRequest return { GitLabMergeRequest.from_data(res, self._token, self.full_name, res['iid']) for res in get(self._token, self._url + '/merge_requests') }
def get_labels(self) -> Set[str]: """ Retrieves the labels of the repository. >>> from os import environ >>> repo = GitLabRepository( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test' ... ) >>> sorted(repo.get_labels()) ['a', 'b', 'c'] :return: A set of strings containing the label captions. """ return { label['name'] for label in get(self._token, self._url + '/labels') }
def write_repositories(self): """ Retrieves the full names of repositories this user can write to. >>> from os import environ >>> GitLab = GitLab(GitLabOAuthToken(viron['GITLAB_TEST_TOKEN'])) >>> sorted(map(lambda x: x.full_name, GitLab.write_repositories)) ['gitmate-test-user/test', 'nkprince007/gitmate-test'] :return: A set of GitLabRepository objects. """ repo_list = get(self._token, '/projects', {'membership': True}) return { GitLabRepository.from_data(repo, self._token, repo['path_with_namespace']) for repo in self._get_repos_with_permissions( repo_list, AccessLevel.CAN_WRITE) }
def commits(self): """ Retrieves a tuple of commit objects that are included in the PR. >>> from os import environ >>> pr = GitLabMergeRequest( ... GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', 2 ... ) >>> assert ([commit.sha for commit in pr.commits] == [ ... '99f484ae167dcfcc35008ba3b5b564443d425ee0', ... 'bbd11b50412d34072f1889e4cea04a32de183605']) :return: A tuple of commit objects. """ commits = get(self._token, self._url + '/commits') return tuple( GitLabCommit(self._token, self._repository, commit['id']) for commit in commits)
def comments(self) -> List[GitLabComment]: r""" Retrieves comments from the issue. As of now, the list of comments is not sorted. Related issue on GitLab CE here - https://gitlab.com/gitlab-org/gitlab-ce/issues/32057 >>> from os import environ >>> issue = GitLabIssue(GitLabOAuthToken(environ['GITLAB_TEST_TOKEN']), ... 'gitmate-test-user/test', 3) >>> comments = issue.comments >>> for comment in comments: ... print(comment.body) Stop staring at me. Go, get your work done. :return: A list of Comment objects. """ return [ GitLabComment.from_data(result, self._token, self._repository, self.number, CommentType.ISSUE, result['id']) for result in get(self._token, self._url + '/notes') ]
def get_statuses(self) -> Set[CommitStatus]: """ Retrieves the all commit statuses. :return: A (frozen)set of CommitStatus objects. :raises RuntimeError: If something goes wrong (network, auth...). """ # rebuild the url with full sha because gitlab doesn't work that way url = '/projects/{repo}/repository/commits/{sha}/statuses'.format( repo=quote_plus(self._repository), sha=self.sha) statuses = get(self._token, url) # Only the first of each context is the one we want result = set() contexts = set() for status in statuses: if status['name'] not in contexts: result.add(CommitStatus( INV_GL_STATE_TRANSLATION[status['status']], status['description'], status['name'], status['target_url'])) contexts.add(status['name']) return result
def get_content(self, ref='master'): data = {'path': self._url, 'ref': ref} self.data = get(token=self._token, url=self._url, params=data)