Example #1
0
    def test_fails_if_branch_is_protected(self, mocks_factory, fusion):
        def reject_push(*_args, **_kwargs):
            raise marge.git.GitError()

        mocklab, api, job = mocks_factory(on_push=reject_push)
        api.add_transition(
            GET(
                '/projects/{source_project_id}/repository/branches/useless_new_feature'.format(
                    source_project_id=mocklab.merge_request_info['source_project_id'],
                ),
            ),
            Ok(_branch('useless_new_feature', protected=True)),
            from_state='initial', to_state='protected'
        )

        if fusion is Fusion.gitlab_rebase:
            api.add_transition(
                PUT(
                    '/projects/{project_id}/merge_requests/{iid}/rebase'.format(
                        project_id=mocklab.merge_request_info['project_id'],
                        iid=mocklab.merge_request_info['iid'],
                    ),
                ),
                Error(marge.gitlab.MethodNotAllowed(405, {'message': '405 Method Not Allowed'})),
                from_state='initial',
            )

        with mocklab.expected_failure("Sorry, I can't modify protected branches!"):
            job.execute()

        assert api.state == 'protected'
Example #2
0
 def test_discovers_if_someone_closed_the_merge_request(
         self, unused_time_sleep):
     api, mocklab = self.api, self.mocklab
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/54/merge',
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='oops_someone_closed_it',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='closed'),
         from_state='oops_someone_closed_it',
     )
     message = 'Someone closed the merge request while I was attempting to merge it.'
     with patch('marge.job.update_from_target_branch_and_push',
                side_effect=mocklab.push_updated):
         with mocklab.expected_failure(message):
             job = self.make_job()
             job.execute()
     assert api.state == 'oops_someone_closed_it'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #3
0
 def test_tells_explicitly_that_gitlab_refused_to_merge(
         self, unused_time_sleep):
     api, mocklab = self.api, self.mocklab
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/54/merge',
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='rejected_for_misterious_reasons',
     )
     message = "Gitlab refused to merge this request and I don't know why!"
     with patch('marge.job.update_from_target_branch_and_push',
                side_effect=mocklab.push_updated):
         with mocklab.expected_failure(message):
             job = self.make_job()
             job.execute()
     assert api.state == 'rejected_for_misterious_reasons'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #4
0
 def test_handles_request_becoming_wip_after_push(self, unused_time_sleep):
     api, mocklab = self.api, self.mocklab
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/54/merge',
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='now_is_wip',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, work_in_progress=True),
         from_state='now_is_wip',
     )
     message = 'The request was marked as WIP as I was processing it (maybe a WIP commit?)'
     with patch('marge.job.update_from_target_branch_and_push',
                side_effect=mocklab.push_updated):
         with mocklab.expected_failure(message):
             job = self.make_job()
             job.execute()
     assert api.state == 'now_is_wip'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #5
0
 def test_guesses_git_hook_error_on_merge_refusal(self, unused_time_sleep):
     api, mocklab = self.api, self.mocklab
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/54/merge',
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='rejected_by_git_hook',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='reopened'),
         from_state='rejected_by_git_hook',
     )
     message = (
         'GitLab refused to merge this branch. I suspect that a Push Rule or a git-hook '
         'is rejecting my commits; maybe my email needs to be white-listed?'
     )
     with patch('marge.job.update_from_target_branch_and_push',
                side_effect=mocklab.push_updated):
         with mocklab.expected_failure(message):
             job = self.make_job()
             job.execute()
     assert api.state == 'rejected_by_git_hook'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #6
0
 def test_discovers_if_someone_closed_the_merge_request(self, mocks):
     mocklab, api, job = mocks
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(
                 iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha,
                  remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='oops_someone_closed_it',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='closed'),
         from_state='oops_someone_closed_it',
     )
     message = 'Someone closed the merge request while I was attempting to merge it.'
     with mocklab.expected_failure(message):
         job.execute()
     assert api.state == 'oops_someone_closed_it'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #7
0
 def test_handles_races_for_merging(self, unused_time_sleep):
     api, mocklab = self.api, self.mocklab
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/54/merge',
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.NotFound(404,
                                   {'message': '404 Branch Not Found'})),
         from_state='passed',
         to_state='someone_else_merged',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='merged'),
         from_state='someone_else_merged',
     )
     with patch('marge.job.update_from_target_branch_and_push',
                side_effect=mocklab.push_updated):
         job = self.make_job()
         job.execute()
     assert api.state == 'someone_else_merged'
     assert api.notes == []
Example #8
0
 def test_assumes_unresolved_discussions_on_merge_refusal(self, mocks):
     mocklab, api, job = mocks
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(
                 iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha,
                  remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='unresolved_discussions',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info),
         from_state='unresolved_discussions',
     )
     message = (
         "Gitlab refused to merge this request and I don't know why! "
         "Maybe you have unresolved discussions?")
     with mocklab.expected_failure(message):
         with patch.dict(
                 mocklab.project_info,
                 only_allow_merge_if_all_discussions_are_resolved=True):
             job.execute()
     assert api.state == 'unresolved_discussions'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #9
0
 def test_guesses_git_hook_error_on_merge_refusal(self, mocks):
     mocklab, api, job = mocks
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(
                 iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha,
                  remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='rejected_by_git_hook',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='reopened'),
         from_state='rejected_by_git_hook',
     )
     message = (
         'GitLab refused to merge this branch. I suspect that a Push Rule or a git-hook '
         'is rejecting my commits; maybe my email needs to be white-listed?'
     )
     with mocklab.expected_failure(message):
         job.execute()
     assert api.state == 'rejected_by_git_hook'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #10
0
 def test_handles_request_becoming_wip_after_push(self, mocks):
     mocklab, api, job = mocks
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(
                 iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha,
                  remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.MethodNotAllowed(
                 405, {'message': '405 Method Not Allowed'})),
         from_state='passed',
         to_state='now_is_wip',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, work_in_progress=True),
         from_state='now_is_wip',
     )
     message = 'The request was marked as WIP as I was processing it (maybe a WIP commit?)'
     with mocklab.expected_failure(message):
         job.execute()
     assert api.state == 'now_is_wip'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #11
0
 def test_handles_races_for_merging(self, api, mocklab):
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(
                 iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha,
                  should_remove_source_branch=True,
                  merge_when_pipeline_succeeds=True),
         ),
         Error(
             marge.gitlab.NotFound(404,
                                   {'message': '404 Branch Not Found'})),
         from_state='passed',
         to_state='someone_else_merged',
     )
     api.add_merge_request(
         dict(mocklab.merge_request_info, state='merged'),
         from_state='someone_else_merged',
     )
     with mocklab.branch_update():
         job = self.make_job(api, mocklab)
         job.execute()
     assert api.state == 'someone_else_merged'
     assert api.notes == []
Example #12
0
    def test_succeeds_second_time_if_master_moved(self, unused_time_sleep):
        api, mocklab = self.api, self.mocklab
        moved_master_sha = 'fafafa'
        first_rewritten_sha = '1o1'
        api.add_pipelines(
            mocklab.project_info['id'],
            _pipeline(sha1=first_rewritten_sha, status='success'),
            from_state=['pushed_but_master_moved', 'merged_rejected'],
        )
        api.add_transition(
            PUT(
                '/projects/1234/merge_requests/54/merge',
                dict(
                    sha=first_rewritten_sha,
                    should_remove_source_branch=True,
                    merge_when_pipeline_succeeds=True,
                ),
            ),
            Error(marge.gitlab.NotAcceptable()),
            from_state='pushed_but_master_moved',
            to_state='merge_rejected',
        )
        api.add_transition(
            GET('/projects/1234/repository/branches/useless_new_feature'),
            Ok({
                'commit':
                _commit(commit_id=first_rewritten_sha, status='success')
            }),
            from_state='pushed_but_master_moved')
        api.add_transition(GET('/projects/1234/repository/branches/master'),
                           Ok({
                               'commit':
                               _commit(commit_id=moved_master_sha,
                                       status='success')
                           }),
                           from_state='merge_rejected')

        def push_effects():
            assert api.state == 'initial'
            api.state = 'pushed_but_master_moved'
            yield mocklab.initial_master_sha, 'f00ba4', first_rewritten_sha

            assert api.state == 'merge_rejected'
            api.state = 'pushed'
            yield moved_master_sha, 'deadbeef', mocklab.rewritten_sha

        with patch('marge.job.update_from_target_branch_and_push',
                   side_effect=push_effects()):
            job = self.make_job(
                marge.job.MergeJobOptions.default(add_tested=True,
                                                  add_reviewers=False))
            job.execute()

        assert api.state == 'merged'
        assert api.notes == [
            "My job would be easier if people didn't jump the queue and push directly... *sigh*",
        ]
Example #13
0
 def test_tells_explicitly_that_gitlab_refused_to_merge(self, mocks):
     mocklab, api, job = mocks
     rewritten_sha = mocklab.rewritten_sha
     api.add_transition(
         PUT(
             '/projects/1234/merge_requests/{iid}/merge'.format(iid=mocklab.merge_request_info['iid']),
             dict(sha=rewritten_sha, should_remove_source_branch=True),
         ),
         Error(marge.gitlab.MethodNotAllowed(405, {'message': '405 Method Not Allowed'})),
         from_state='passed', to_state='rejected_for_mysterious_reasons',
     )
     message = "Gitlab refused to merge this request and I don't know why!"
     with mocklab.expected_failure(message):
         job.execute()
     assert api.state == 'rejected_for_mysterious_reasons'
     assert api.notes == ["I couldn't merge this branch: %s" % message]
Example #14
0
    def test_second_time_if_master_moved(self, mocks_factory, fusion, update_sha, rewrite_sha):
        initial_master_sha = 'eaeaea9e9e'
        moved_master_sha = 'fafafa'
        first_rewritten_sha = rewrite_sha(update_sha(INITIAL_MR_SHA, initial_master_sha))
        second_rewritten_sha = rewrite_sha(update_sha(first_rewritten_sha, moved_master_sha))

        # pylint: disable=unused-argument
        def push_effects(remote_url, remote_branch, old_sha, new_sha):
            nonlocal mocklab, target_branch, remote_target_repo

            if api.state == 'initial':
                assert old_sha == INITIAL_MR_SHA
                assert new_sha == first_rewritten_sha
                api.state = 'pushed_but_master_moved'
                remote_target_repo.set_ref(target_branch, moved_master_sha)
            elif api.state == 'merge_rejected':
                assert new_sha == second_rewritten_sha
                api.state = 'pushed'

        mocklab, api, job = mocks_factory(
            initial_master_sha=initial_master_sha,
            rewritten_sha=second_rewritten_sha,
            on_push=push_effects,
        )

        source_project_info = mocklab.forked_project_info or mocklab.project_info
        target_project_info = mocklab.project_info

        source_project_url = source_project_info['ssh_url_to_repo']
        target_project_url = target_project_info['ssh_url_to_repo']

        source_branch = mocklab.merge_request_info['source_branch']
        target_branch = mocklab.merge_request_info['target_branch']

        remote_source_repo = job.repo.mock_impl.remote_repos[source_project_url]
        remote_target_repo = job.repo.mock_impl.remote_repos[target_project_url]

        api.add_merge_request(
            dict(
                mocklab.merge_request_info,
                sha=first_rewritten_sha,
            ),
            from_state=['pushed_but_master_moved', 'merge_rejected'],
        )
        api.add_pipelines(
            mocklab.merge_request_info['source_project_id'],
            _pipeline(sha1=first_rewritten_sha, status='success'),
            from_state=['pushed_but_master_moved', 'merge_rejected'],
        )
        api.add_transition(
            PUT(
                '/projects/1234/merge_requests/{iid}/merge'.format(iid=mocklab.merge_request_info['iid']),
                dict(
                    sha=first_rewritten_sha,
                    should_remove_source_branch=True,
                ),
            ),
            Error(marge.gitlab.NotAcceptable()),
            from_state='pushed_but_master_moved', to_state='merge_rejected',
        )
        api.add_transition(
            GET(
                '/projects/{source_project_id}/repository/branches/useless_new_feature'.format(
                    source_project_id=mocklab.merge_request_info['source_project_id'],
                ),
            ),
            Ok({'commit': _commit(commit_id=first_rewritten_sha, status='success')}),
            from_state='pushed_but_master_moved'
        )
        api.add_transition(
            GET('/projects/1234/repository/branches/master'),
            Ok({'commit': _commit(commit_id=moved_master_sha, status='success')}),
            from_state='merge_rejected'
        )
        if fusion is Fusion.gitlab_rebase:
            rebase_url = '/projects/{project_id}/merge_requests/{iid}/rebase'.format(
                project_id=mocklab.merge_request_info['project_id'],
                iid=mocklab.merge_request_info['iid'],
            )

            api.add_transition(
                PUT(rebase_url), Ok(True),
                from_state='initial', to_state='pushed_but_master_moved',
                side_effect=lambda: (
                    remote_source_repo.set_ref(source_branch, first_rewritten_sha),
                    remote_target_repo.set_ref(target_branch, moved_master_sha)
                )
            )
            api.add_transition(
                PUT(rebase_url), Ok(True),
                from_state='merge_rejected', to_state='rebase-in-progress',
                side_effect=lambda: remote_source_repo.set_ref(source_branch, second_rewritten_sha)
            )

        job.execute()
        assert api.state == 'merged'
        assert api.notes == [
            "My job would be easier if people didn't jump the queue and push directly... *sigh*",
        ]