Exemple #1
0
def add_welcome_comment(
    pr: MergeRequest,
    autorespond_text: str='Hi! This is GitMate v2.0!'
):
    """
    Adds a welcome comment to pull requests.
    """
    sign = TimestampSigner().sign(autorespond_text)
    msg = ('{}\n\n(Powered by [GitMate.io](https://gitmate.io))\n\n'
           '<!-- Timestamp signature `{}` -->'.format(autorespond_text, sign))
    pr.add_comment(msg)
Exemple #2
0
def gitmate_ack(pr: MergeRequest,
                comment: Comment,
                ack_strs: str = 'ack, reack',
                unack_strs: str = 'unack'):
    """
    A responder to ack and unack commits
    """
    body = comment.body.lower()
    commits = pr.commits
    perm_level = pr.repository.get_permission_level(comment.author)
    comment_slices = map_comment_parts_to_keywords(ack_strs, unack_strs, body)

    has_commit_sha = any(
        sha_compiled.search(string) for _list in comment_slices.values()
        for string in _list)

    # return right away if the comment isn't related to ack / unack command
    if not any(comment_slices) or not has_commit_sha:
        return
    elif perm_level.value < AccessLevel.CAN_WRITE.value:
        msg = ('Sorry @{}, you do not have the necessary permission '
               'levels to perform the action.'.format(comment.author.username))
        pr.add_comment(msg)
        return

    db_pr, created = MergeRequestModel.objects.get_or_create(
        repo=Repository.from_igitt_repo(pr.repository),
        number=pr.number,
        defaults={'acks': dict()})

    if created:
        # GitMate was integrated to the repo after syncing the pull request
        add_review_status(pr)
        db_pr.refresh_from_db()

    for commit in commits:
        for substring in comment_slices['unack']:
            if commit.sha[:6] in substring:
                db_pr.acks[_get_commit_hash(commit)] = _status_to_dict(
                    unack(commit))

        for substring in comment_slices['ack']:
            if commit.sha[:6] in substring:
                db_pr.acks[_get_commit_hash(commit)] = _status_to_dict(
                    ack(commit))

    db_pr.save()
    pr.head.set_status(db_pr.ack_state)
Exemple #3
0
def apply_command_on_merge_request(
    pr: MergeRequest,
    comment: Comment,
    enable_rebase: bool = False,
    enable_merge: bool = False,
    enable_fastforward: bool = False,
    merge_admin_only: bool = True,
    fastforward_admin_only: bool = True,
):
    """
    Performs a merge, fastforward or rebase of a merge request when an
    authorized user posts a command mentioning the keywords ``merge``,
    ``fastforward``/``ff`` or ``rebase`` respectively.

    e.g. ``@gitmate-bot rebase`` rebases the pull request with master.
    """
    username = Repository.from_igitt_repo(pr.repository).user.username
    cmd, cmd_past = get_matched_command(comment.body, username)
    enabled_cmd = {
        'rebase': enable_rebase,
        'merge': enable_merge,
        'fastforward': enable_fastforward
    }.get(cmd)

    if enabled_cmd:
        if not verify_command_access(comment, merge_admin_only,
                                     fastforward_admin_only, cmd):
            pr.add_comment(
                f'Hey @{comment.author.username}, you do not have the access '
                f'to perform the {cmd} action with [GitMate.io]'
                '(https://gitmate.io). Please ask a maintainer to give you '
                'access. :warning:')
            return

        pr.add_comment(
            f'Hey! I\'m [GitMate.io](https://gitmate.io)! This pull request is'
            f' being {cmd_past} automatically. Please **DO NOT** push while '
            f'{cmd} is in progress or your changes would be lost permanently '
            ':warning:')
        head_clone_url = pr.source_repository.clone_url
        base_clone_url = pr.target_repository.clone_url
        output = run_in_container(settings.REBASER_IMAGE, 'python', 'run.py',
                                  cmd, head_clone_url, base_clone_url,
                                  pr.head_branch_name, pr.base_branch_name)
        output = json.loads(output)
        if output['status'] == 'success':
            pr.add_comment(
                f'Automated {cmd} with [GitMate.io](https://gitmate.io) was '
                'successful! :tada:')
        elif 'error' in output:
            # hiding oauth token for safeguarding user privacy
            error = output['error'].replace(head_clone_url,
                                            '<hidden_oauth_token>')
            error = error.replace(base_clone_url, '<hidden_oauth_token>')
            pr.add_comment(f'Automated {cmd} failed! Please {cmd} your pull '
                           'request manually via the command line.\n\n'
                           'Reason:\n```\n{}\n```'.format(error))
Exemple #4
0
def add_labels_based_on_size(pr: MergeRequest,
                             size_scheme: str = 'size/{size}'):
    """
    Labels the pull request with size labels according to the amount of
    code touched, commits and files involved. Helps plan the review in
    advance.
    """
    sizes = {'XXL', 'XL', 'L', 'M', 'S', 'XS'}
    with lock_igitt_object('label mr', pr):
        labels = pr.labels.difference(
            {size_scheme.format(size=size)
             for size in sizes})

        lines_added, lines_deleted = pr.diffstat
        commit_score = 4 * len(pr.commits)
        file_score = 4 * len(pr.affected_files)

        if commit_score + file_score + lines_added + lines_deleted <= 100:
            pr.labels = {size_scheme.format(size='XS')}.union(labels)

        elif commit_score + file_score + lines_added + lines_deleted <= 250:
            pr.labels = {size_scheme.format(size='S')}.union(labels)

        elif commit_score + file_score + lines_added + lines_deleted <= 500:
            pr.labels = {size_scheme.format(size='M')}.union(labels)

        elif commit_score + file_score + lines_added + lines_deleted <= 1000:
            pr.labels = {size_scheme.format(size='L')}.union(labels)

        elif commit_score + file_score + lines_added + lines_deleted <= 1500:
            pr.labels = {size_scheme.format(size='XL')}.union(labels)

        else:
            pr.labels = {size_scheme.format(size='XXL')}.union(labels)
class TestMergeRequest(IGittTestCase):
    def setUp(self):
        self.mr = MergeRequest()
        self.repo = Repository()

    @patch.object(Repository, 'hoster', new_callable=PropertyMock)
    @patch.object(Repository, 'full_name', new_callable=PropertyMock)
    @patch.object(MergeRequest, 'repository', new_callable=PropertyMock)
    def test_get_keywords_issues(self, mock_repository, mock_full_name,
                                 mock_hoster):
        mock_hoster.return_value = 'github'
        mock_full_name.return_value = 'gitmate-test-user/test'
        mock_repository.return_value = self.repo

        test_cases = [
            ({('123', 'gitmate-test-user/test')},
             ['https://github.com/gitmate-test-user/test/issues/123']),
            ({('234', 'gitmate-test-user/test/repo')},
             ['gitmate-test-user/test/repo#234']),
            ({('345', 'gitmate-test-user/test')},
             ['gitmate-test-user/test#345']),
            ({('456', 'gitmate-test-user/test')}, ['#456']),
            ({('345', 'gitmate-test-user/test')}, [
                'hey there [#123](https://github.com/gitmate-test-user/test/issues/345)'
            ])
        ]

        for expected, body in test_cases:
            self.assertEqual(self.mr._get_keywords_issues(r'', body), expected)

        bad = [
            '[#123]',
            '#123ds',
            'https://saucelabs.com/beta/tests/18c6aed24ed143d3bd1d1096498f34ac/commands#178',
        ]

        for body in bad:
            self.assertEqual(self.mr._get_keywords_issues(r'', body), set())
Exemple #6
0
def sync_updated_pr_with_issue(pr: MergeRequest,
                               sync_assignees: bool='Synchronize Assignees'):
    issues = pr.closes_issues
    repo = Repository.from_igitt_repo(pr.repository)
    pr_obj = MergeRequestModel.objects.get_or_create(
        repo=repo, number=pr.number)[0]
    data = defaultdict(dict)

    with lock_igitt_object('label mr', pr):
        labels = pr.labels
        for issue in issues:
            labels = issue.labels | labels
        pr.labels = labels

    if sync_assignees:
        with lock_igitt_object('assign mr', pr):
            assignees = pr.assignees
            for issue in issues:
                assignees |= issue.assignees
                data[str(issue.number)]['assignees'] = True
            pr.assignees = assignees

    pr_obj.closes_issues = data
    pr_obj.save()
Exemple #7
0
def remove_stale_label_from_merge_requests(
        pr: MergeRequest,
        *args,
        stale_label: str = 'Label to be used for marking stale'
):
    """
    Unassigns the chosen label from pull requests when they are updated again.
    """
    if len(args) > 0 and args[0] == stale_label:
        # LABELED and UNLABELED events return the label used, skip action if
        # the label was ``stale_label``
        return

    with lock_igitt_object('label mr', pr):
        pr.labels = pr.labels - {stale_label}
Exemple #8
0
def mark_pending_review_or_wip_accordingly(
        pr: MergeRequest,
        wip_label: str = 'Work in progress',
        pending_review_label: str = 'Review pending'):
    """
    Labels the pull request as pending review and removes work in
    progress on every changed PR accordingly. But retains work in progress
    label, if title of the pull request begins with "wip".
    """
    with lock_igitt_object('label mr', pr):
        labels = pr.labels
        # Allows [wip] and WIP:
        if not 'wip' in pr.title.lower()[:4]:
            labels.add(pending_review_label)
            labels.discard(wip_label)
        else:
            labels.add(wip_label)
            labels.discard(pending_review_label)

        pr.labels = labels
 def setUp(self):
     self.mr = MergeRequest()
     self.repo = Repository()