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*", ]
def __init__(self, gitlab_url=None, fork=False, merge_request_options=None): super().__init__(gitlab_url, fork=fork, merge_request_options=merge_request_options) api = self.api self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.merge_request_info['source_project_id'], _pipeline(sha1=rewritten_sha, status='running', ref=self.merge_request_info['source_branch']), from_state='pushed', to_state='passed', ) api.add_pipelines( self.merge_request_info['source_project_id'], _pipeline(sha1=rewritten_sha, status='success', ref=self.merge_request_info['source_branch']), from_state=['passed', 'merged'], ) source_project_id = self.merge_request_info['source_project_id'] api.add_transition( GET( '/projects/{}/repository/branches/{}'.format( source_project_id, self.merge_request_info['source_branch'], ), ), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET( '/projects/{}/repository/branches/{}'.format( source_project_id, self.merge_request_info['source_branch'], ), ), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed' ) api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format(iid=self.merge_request_info['iid']), dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state=['passed', 'skipped'], to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') api.add_transition( GET('/projects/1234/repository/branches/{}'.format(self.merge_request_info['target_branch'])), Ok({'commit': {'id': self.rewritten_sha}}), from_state='merged' ) api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:" )
def __init__(self, gitlab_url=None): super().__init__(gitlab_url) api = self.api self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='running'), from_state='pushed', to_state='passed', ) api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='success'), from_state=['passed', 'merged'], ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed') api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state='passed', to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') api.add_transition(GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.rewritten_sha }}), from_state='merged') api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:")
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'
def test_fails_on_not_acceptable_if_master_did_not_move( self, unused_time_sleep): api, mocklab = self.api, self.mocklab new_branch_head_sha = '99ba110035' api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({ 'commit': _commit(commit_id=new_branch_head_sha, status='success') }), from_state='pushed', to_state='pushed_but_head_changed') with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): with mocklab.expected_failure( "Someone pushed to branch while we were trying to merge"): job = self.make_job( marge.job.MergeJobOptions.default(add_tested=True, add_reviewers=False)) job.execute() assert api.state == 'pushed_but_head_changed' assert api.notes == [ "I couldn't merge this branch: Someone pushed to branch while we were trying to merge", ]
def test_fails_on_not_acceptable_if_master_did_not_move( self, api, mocklab, test_params): new_branch_head_sha = '99ba110035' api.add_transition(GET( '/projects/{source_project_id}/repository/branches/useless_new_feature' .format(source_project_id=test_params.source_project_id, ), ), Ok({ 'commit': _commit(commit_id=new_branch_head_sha, status='success') }), from_state='pushed', to_state='pushed_but_head_changed') with mocklab.branch_update(): with mocklab.expected_failure( "Someone pushed to branch while we were trying to merge"): job = self.make_job( api, mocklab, options=marge.job.MergeJobOptions.default( add_tested=True, add_reviewers=False), ) job.execute() assert api.state == 'pushed_but_head_changed' assert api.notes == [ "I couldn't merge this branch: Someone pushed to branch while we were trying to merge", ]
def test_succeeds_if_source_is_master(self, mocklab_factory): mocklab = mocklab_factory(merge_request_options={ 'source_branch': 'master', 'target_branch': 'production' }, ) api = mocklab.api api.add_transition( GET( '/projects/1234/repository/branches/{source}'.format( source=mocklab.merge_request_info['source_branch'], ), ), Ok({'commit': { 'id': mocklab.rewritten_sha }}), from_state='initial', to_state='pushed', ) with patch.object( marge.single_merge_job.SingleMergeJob, 'add_trailers', side_effect=lambda *_: mocklab.push_updated()[2], autospec=True, ): job = self.make_job( api, mocklab, options=marge.job.MergeJobOptions.default(add_tested=True, add_reviewers=False), ) job.execute() assert api.state == 'merged' assert api.notes == []
def test_succeeds_with_updated_branch(self, mocks): mocklab, api, job = mocks api.add_transition( GET( '/projects/1234/repository/branches/{source}'.format( source=mocklab.merge_request_info['source_branch'], ), ), Ok({'commit': {'id': mocklab.rewritten_sha}}), from_state='initial', to_state='pushed', ) job.execute() assert api.state == 'merged' assert api.notes == []
def test_fuse_mr_when_source_branch_was_moved(self): api, mocklab = self.api, self.mocklab batch_merge_job = self.get_batch_merge_job() merge_request = Mock( source_project_id=batch_merge_job._project.id, target_branch='master', source_branch=self.mocklab.merge_request_info['source_branch'], ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': commit(commit_id='abc', status='running')}), ) with pytest.raises(CannotMerge) as exc_info: batch_merge_job.accept_mr(merge_request, mocklab.initial_master_sha) assert str(exc_info.value) == 'Someone pushed to branch while we were trying to merge'
def test_fails_on_not_acceptable_if_master_did_not_move(self, mocks): mocklab, api, job = mocks new_branch_head_sha = '99ba110035' 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=new_branch_head_sha, status='success')}), from_state='pushed', to_state='pushed_but_head_changed' ) with mocklab.expected_failure("Someone pushed to branch while we were trying to merge"): job.execute() assert api.state == 'pushed_but_head_changed' assert api.notes == [ "I couldn't merge this branch: Someone pushed to branch while we were trying to merge", ]
def test_succeeds_if_source_is_master(self, mocks_factory): mocklab, api, job = mocks_factory( extra_mocklab_opts=dict(merge_request_options={ 'source_branch': 'master', 'target_branch': 'production', }), ) api.add_transition( GET( '/projects/1234/repository/branches/{source}'.format( source=mocklab.merge_request_info['source_branch'], ), ), Ok({'commit': {'id': mocklab.rewritten_sha}}), from_state='initial', to_state='pushed', ) job.execute() assert api.state == 'merged' assert api.notes == []
def test_fails_if_branch_is_protected(self, api, mocklab, test_params): api.add_transition(GET( '/projects/{source_project_id}/repository/branches/useless_new_feature' .format(source_project_id=test_params.source_project_id, ), ), Ok(_branch('useless_new_feature', protected=True)), from_state='initial', to_state='protected') with mocklab.expected_failure( "Sorry, I can't push rewritten changes to protected branches!" ): job = self.make_job( api, mocklab, options=marge.job.MergeJobOptions.default(add_tested=True, add_reviewers=False), ) job.repo.push.side_effect = marge.git.GitError() job.execute() assert api.state == 'protected'
def test_calculates_merge_when_pipeline_succeeds_correctly( self, mocks, only_allow_merge_if_pipeline_succeeds): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha project_info = dict(TEST_PROJECT_INFO) project_info[ "only_allow_merge_if_pipeline_succeeds"] = only_allow_merge_if_pipeline_succeeds api.add_project(project_info) api.add_transition( PUT( '/projects/{pid}/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid'], pid=project_info["id"]), dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds= only_allow_merge_if_pipeline_succeeds), ), Ok(True), to_state='merged', ) job.execute() assert api.state == 'merged'
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*", ]
def __init__(self, gitlab_url=None): self.gitlab_url = gitlab_url = gitlab_url or 'http://git.example.com' self.api = api = ApiMock(gitlab_url=gitlab_url, auth_token='no-token', initial_state='initial') api.add_transition(GET('/version'), Ok({'version': '9.2.3-ee'})) self.user_info = dict(test_user.INFO) self.user_id = self.user_info['id'] api.add_user(self.user_info, is_current=True) self.project_info = dict(test_project.INFO) api.add_project(self.project_info) self.commit_info = dict(test_commit.INFO) api.add_commit(self.project_info['id'], self.commit_info) self.author_id = 234234 self.merge_request_info = { 'id': 53, 'iid': 54, 'title': 'a title', 'project_id': 1234, 'author': { 'id': self.author_id }, 'assignee': { 'id': self.user_id }, 'approved_by': [], 'state': 'opened', 'sha': self.commit_info['id'], 'source_project_id': 1234, 'target_project_id': 1234, 'source_branch': 'useless_new_feature', 'target_branch': 'master', 'work_in_progress': False, 'web_url': 'http://git.example.com/group/project/merge_request/666', } api.add_merge_request(self.merge_request_info) self.initial_master_sha = '505e' self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='running'), from_state='pushed', to_state='passed', ) api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='success'), from_state=['passed', 'merged'], ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed') api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state='passed', to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') self.approvals_info = dict( test_approvals.INFO, id=self.merge_request_info['id'], iid=self.merge_request_info['iid'], project_id=self.merge_request_info['project_id'], approvals_left=0, ) api.add_approvals(self.approvals_info) api.add_transition( GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.initial_master_sha }}), ) api.add_transition(GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.rewritten_sha }}), from_state='merged') api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:")