def test_merge_status(self, pull_request): self.merge_mock.return_value = MergeResponse(True, False, None, MergeFailureReason.NONE) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None assert pull_request._last_merge_status is None status, msg = PullRequestModel().merge_status(pull_request) assert status is True assert msg.eval() == 'This pull request can be automatically merged.' self.merge_mock.assert_called_once_with( pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, self.workspace_id, dry_run=True) assert pull_request._last_merge_source_rev == self.source_commit assert pull_request._last_merge_target_rev == self.target_commit assert pull_request._last_merge_status is MergeFailureReason.NONE self.merge_mock.reset_mock() status, msg = PullRequestModel().merge_status(pull_request) assert status is True assert msg.eval() == 'This pull request can be automatically merged.' assert self.merge_mock.called is False
def test_update_skips_new_version_if_unchanged(pr_util): pull_request = pr_util.create_pull_request() model = PullRequestModel() model.update_commits(pull_request) # Expect that it still has no versions assert len(model.get_versions(pull_request)) == 0
def test_merge_status_known_failure(self, pull_request): self.merge_mock.return_value = MergeResponse( False, False, None, MergeFailureReason.MERGE_FAILED) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None assert pull_request._last_merge_status is None status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert (msg.eval() == 'This pull request cannot be merged because of conflicts.') self.merge_mock.assert_called_once_with( pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, self.workspace_id, dry_run=True) assert pull_request._last_merge_source_rev == self.source_commit assert pull_request._last_merge_target_rev == self.target_commit assert (pull_request._last_merge_status is MergeFailureReason.MERGE_FAILED) self.merge_mock.reset_mock() status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert (msg.eval() == 'This pull request cannot be merged because of conflicts.') assert self.merge_mock.called is False
def test_merge_status_unknown_failure(self, pull_request): self.merge_mock.return_value = MergeResponse( False, False, None, MergeFailureReason.UNKNOWN) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None assert pull_request._last_merge_status is None status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert msg.eval() == ( 'This pull request cannot be merged because of an unhandled' ' exception.') self.merge_mock.assert_called_once_with( pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, self.workspace_id, dry_run=True) assert pull_request._last_merge_source_rev is None assert pull_request._last_merge_target_rev is None assert pull_request._last_merge_status is None self.merge_mock.reset_mock() status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert msg.eval() == ( 'This pull request cannot be merged because of an unhandled' ' exception.') assert self.merge_mock.called is True
def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util): model = PullRequestModel() pull_request = pr_util.create_pull_request() pr_util.update_source_repository() pr_util.update_source_repository() model.update_commits(pull_request) # Expect to find a new comment about the change expected_message = textwrap.dedent("""\ Auto status change to |under_review| .. role:: added .. role:: removed .. parsed-literal:: Changed commits: * :added:`1 added` * :removed:`0 removed` Changed files: * `A file_2 <#a_c--92ed3b5f07b4>`_ .. |under_review| replace:: *"Under Review"*""") pull_request_comments = sorted(pull_request.comments, key=lambda c: c.modified_at) update_comment = pull_request_comments[-1] assert update_comment.text == expected_message
def _update_commits(self, pull_request): try: if PullRequestModel().has_valid_update_type(pull_request): updated_version, changes = PullRequestModel().update_commits( pull_request) if updated_version: msg = _( u'Pull request updated to "{source_commit_id}" with ' u'{count_added} added, {count_removed} removed ' u'commits.').format(source_commit_id=pull_request. source_ref_parts.commit_id, count_added=len(changes.added), count_removed=len(changes.removed)) h.flash(msg, category='success') else: h.flash(_("Nothing changed in pull request."), category='warning') else: msg = _(u"Skipping update of pull request due to reference " u"type: {reference_type}").format( reference_type=pull_request.source_ref_parts.type) h.flash(msg, category='warning') except CommitDoesNotExistError: h.flash(_(u'Update failed due to missing commits.'), category='error')
def test_get_awaiting_my_review(self, pull_request): PullRequestModel().update_reviewers(pull_request, [pull_request.author]) prs = PullRequestModel().get_awaiting_my_review( pull_request.target_repo, user_id=pull_request.author.user_id) assert isinstance(prs, list) assert len(prs) == 1
def close_pull_request(request, apiuser, repoid, pullrequestid, userid=Optional(OAttr('apiuser'))): """ Close the pull request specified by `pullrequestid`. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param repoid: Repository name or repository ID to which the pull request belongs. :type repoid: str or int :param pullrequestid: ID of the pull request to be closed. :type pullrequestid: int :param userid: Close the pull request as this user. :type userid: Optional(str or int) Example output: .. code-block:: bash "id": <id_given_in_input>, "result": { "pull_request_id": "<int>", "closed": "<bool>" }, "error": null """ repo = get_repo_or_error(repoid) if not isinstance(userid, Optional): if (has_superadmin_permission(apiuser) or HasRepoPermissionAnyApi('repository.admin')( user=apiuser, repo_name=repo.repo_name)): apiuser = get_user_or_error(userid) else: raise JSONRPCError('userid is not the same as your user') pull_request = get_pull_request_or_error(pullrequestid) if not PullRequestModel().check_user_update( pull_request, apiuser, api=True): raise JSONRPCError( 'pull request `%s` close failed, no permission to close.' % (pullrequestid, )) if pull_request.is_closed(): raise JSONRPCError('pull request `%s` is already closed' % (pullrequestid, )) PullRequestModel().close_pull_request(pull_request.pull_request_id, apiuser) Session.commit() data = { 'pull_request_id': pull_request.pull_request_id, 'closed': True, } return data
def test_update_writes_snapshot_into_pull_request_version(pr_util): model = PullRequestModel() pull_request = pr_util.create_pull_request() pr_util.update_source_repository() model.update_commits(pull_request) # Expect that it has a version entry now assert len(model.get_versions(pull_request)) == 1
def test_update_assigns_comments_to_the_new_version(pr_util): model = PullRequestModel() pull_request = pr_util.create_pull_request() comment = pr_util.create_comment() pr_util.update_source_repository() model.update_commits(pull_request) # Expect that the comment is linked to the pr version now assert comment.pull_request_version == model.get_versions(pull_request)[0]
def index(self): source_repo = c.rhodecode_db_repo try: source_repo.scm_instance().get_commit() except EmptyRepositoryError: h.flash(h.literal(_('There are no commits yet')), category='warning') redirect(url('summary_home', repo_name=source_repo.repo_name)) commit_id = request.GET.get('commit') branch_ref = request.GET.get('branch') bookmark_ref = request.GET.get('bookmark') try: source_repo_data = PullRequestModel().generate_repo_data( source_repo, commit_id=commit_id, branch=branch_ref, bookmark=bookmark_ref) except CommitDoesNotExistError as e: log.exception(e) h.flash(_('Commit does not exist'), 'error') redirect(url('pullrequest_home', repo_name=source_repo.repo_name)) default_target_repo = source_repo if (source_repo.parent and not source_repo.parent.scm_instance().is_empty()): # change default if we have a parent repo default_target_repo = source_repo.parent target_repo_data = PullRequestModel().generate_repo_data( default_target_repo) selected_source_ref = source_repo_data['refs']['selected_ref'] title_source_ref = selected_source_ref.split(':', 2)[1] c.default_title = PullRequestModel().generate_pullrequest_title( source=source_repo.repo_name, source_ref=title_source_ref, target=default_target_repo.repo_name) c.default_repo_data = { 'source_repo_name': source_repo.repo_name, 'source_refs_json': json.dumps(source_repo_data), 'target_repo_name': default_target_repo.repo_name, 'target_refs_json': json.dumps(target_repo_data), } c.default_source_ref = selected_source_ref return render('/pullrequests/pullrequest.html')
def test_get_commit_ids(self, pull_request): # The PR has been not merget yet, so expect an exception with pytest.raises(ValueError): PullRequestModel()._get_commit_ids(pull_request) # Merge revision is in the revisions list pull_request.merge_rev = pull_request.revisions[0] commit_ids = PullRequestModel()._get_commit_ids(pull_request) assert commit_ids == pull_request.revisions # Merge revision is not in the revisions list pull_request.merge_rev = 'f000' * 10 commit_ids = PullRequestModel()._get_commit_ids(pull_request) assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
def _reject_close(self, pull_request): if pull_request.is_closed(): raise HTTPForbidden() PullRequestModel().close_pull_request_with_comment( pull_request, c.rhodecode_user, c.rhodecode_db_repo) Session().commit()
def test_comment_force_close_pull_request(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request() pull_request_id = pull_request.pull_request_id reviewers_ids = [1, 2] PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) author = pull_request.user_id repo = pull_request.target_repo.repo_id self.app.post( url(controller='pullrequests', action='comment', repo_name=pull_request.target_repo.scm_instance().name, pull_request_id=str(pull_request_id)), params={ 'changeset_status': 'forced_closed', 'csrf_token': csrf_token}, status=302) pull_request = PullRequest.get(pull_request_id) action = 'user_closed_pull_request:%d' % pull_request_id journal = UserLog.query().filter( UserLog.user_id == author, UserLog.repository_id == repo, UserLog.action == action).all() assert len(journal) == 1 # check only the latest status, not the review status status = ChangesetStatusModel().get_status( pull_request.source_repo, pull_request=pull_request) assert status == ChangesetStatus.STATUS_REJECTED
def test_works_for_missing_git_references_during_update(self, pr_util): pull_request = self._prepare_pull_request(pr_util) removed_commit_id = pr_util.remove_one_commit() self.assert_commit_cannot_be_accessed(removed_commit_id, pull_request) pr_version = PullRequestModel().get_versions(pull_request)[0] self.assert_diff_can_be_fetched(pr_version)
def test_close_calls_cleanup_and_hook(self, pull_request): PullRequestModel().close_pull_request(pull_request, pull_request.author) self.workspace_remove_mock.assert_called_once_with(self.workspace_id) self.hook_mock.assert_called_with(self.pull_request, self.pull_request.author, 'close')
def _update_reviewers(self, pull_request_id): reviewers_ids = map( int, filter(lambda v: v not in [None, ''], request.POST.get('reviewers_ids', '').split(','))) PullRequestModel().update_reviewers(pull_request_id, reviewers_ids) Session().commit()
def test_create_version_from_snapshot_updates_attributes(pr_util): pull_request = pr_util.create_pull_request() # Avoiding default values pull_request.status = PullRequest.STATUS_CLOSED pull_request._last_merge_source_rev = "0" * 40 pull_request._last_merge_target_rev = "1" * 40 pull_request._last_merge_status = 1 pull_request.merge_rev = "2" * 40 # Remember automatic values created_on = pull_request.created_on updated_on = pull_request.updated_on # Create a new version of the pull request version = PullRequestModel()._create_version_from_snapshot(pull_request) # Check attributes assert version.title == pr_util.create_parameters['title'] assert version.description == pr_util.create_parameters['description'] assert version.status == PullRequest.STATUS_CLOSED assert version.created_on == created_on assert version.updated_on == updated_on assert version.user_id == pull_request.user_id assert version.revisions == pr_util.create_parameters['revisions'] assert version.source_repo == pr_util.source_repository assert version.source_ref == pr_util.create_parameters['source_ref'] assert version.target_repo == pr_util.target_repository assert version.target_ref == pr_util.create_parameters['target_ref'] assert version._last_merge_source_rev == pull_request._last_merge_source_rev assert version._last_merge_target_rev == pull_request._last_merge_target_rev assert version._last_merge_status == pull_request._last_merge_status assert version.merge_rev == pull_request.merge_rev assert version.pull_request == pull_request
def test_api_comment_pull_request(self, pr_util, no_notifications): pull_request = pr_util.create_pull_request() pull_request_id = pull_request.pull_request_id author = pull_request.user_id repo = pull_request.target_repo.repo_id id_, params = build_data(self.apikey, 'comment_pull_request', repoid=pull_request.target_repo.repo_name, pullrequestid=pull_request.pull_request_id, message='test message') response = api_call(self.app, params) pull_request = PullRequestModel().get(pull_request.pull_request_id) comments = ChangesetCommentsModel().get_comments( pull_request.target_repo.repo_id, pull_request=pull_request) expected = { 'pull_request_id': pull_request.pull_request_id, 'comment_id': comments[-1].comment_id, 'status': None } assert_ok(id_, expected, response.body) action = 'user_commented_pull_request:%d' % pull_request_id journal = UserLog.query()\ .filter(UserLog.user_id == author)\ .filter(UserLog.repository_id == repo)\ .filter(UserLog.action == action)\ .all() assert len(journal) == 2
def test_merge_failed(self, pull_request, merge_extras): user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) self.merge_mock.return_value = MergeResponse( False, False, '6126b7bfcc82ad2d3deaee22af926b082ce54cc6', MergeFailureReason.MERGE_FAILED) PullRequestModel().merge(pull_request, pull_request.author, extras=merge_extras) message = ( u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}' u'\n\n {pr_title}'.format( pr_id=pull_request.pull_request_id, source_repo=safe_unicode( pull_request.source_repo.scm_instance().name), source_ref_name=pull_request.source_ref_parts.name, pr_title=safe_unicode(pull_request.title))) self.merge_mock.assert_called_once_with( pull_request.target_ref_parts, pull_request.source_repo.scm_instance(), pull_request.source_ref_parts, self.workspace_id, user_name=user.username, user_email=user.email, message=message) pull_request = PullRequest.get(pull_request.pull_request_id) assert self.invalidation_mock.called is False assert pull_request.merge_rev is None
def test_merge_status_when_target_is_locked(self, pull_request): pull_request.target_repo.locked = [1, u'12345.50', 'lock_web'] status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert msg.eval() == ( 'This pull request cannot be merged because the target repository' ' is locked.')
def _meets_merge_pre_conditions(self, pull_request, user): if not PullRequestModel().check_user_merge(pull_request, user): raise HTTPForbidden() merge_status, msg = PullRequestModel().merge_status(pull_request) if not merge_status: log.debug("Cannot merge, not mergeable.") h.flash(msg, category='error') return False if (pull_request.calculated_review_status() is not ChangesetStatus.STATUS_APPROVED): log.debug("Cannot merge, approval is pending.") msg = _('Pull request reviewer approval is pending.') h.flash(msg, category='error') return False return True
def assert_pr_file_changes(pull_request, added=None, modified=None, removed=None): pr_versions = PullRequestModel().get_versions(pull_request) # always use first version, ie original PR to calculate changes pull_request_version = pr_versions[0] old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs( pull_request, pull_request_version) file_changes = PullRequestModel()._calculate_file_changes( old_diff_data, new_diff_data) assert added == file_changes.added, \ 'expected added:%s vs value:%s' % (added, file_changes.added) assert modified == file_changes.modified, \ 'expected modified:%s vs value:%s' % (modified, file_changes.modified) assert removed == file_changes.removed, \ 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
def _merge_pull_request(self, pull_request, user, extras): merge_resp = PullRequestModel().merge(pull_request, user, extras=extras) if merge_resp.executed: log.debug("The merge was successful, closing the pull request.") PullRequestModel().close_pull_request(pull_request.pull_request_id, user) Session().commit() msg = _('Pull request was successfully merged and closed.') h.flash(msg, category='success') else: log.debug("The merge was not successful. Merge response: %s", merge_resp) msg = PullRequestModel().merge_status_message( merge_resp.failure_reason) h.flash(msg, category='error')
def test_pull_request_stays_if_update_without_change(pr_util, voted_status): pull_request = pr_util.create_pull_request() pr_util.create_status_votes(voted_status, *pull_request.reviewers) # Update, without change PullRequestModel().update_commits(pull_request) # Expect that review status is the voted_status expected_review_status = voted_status assert pull_request.calculated_review_status() == expected_review_status
def delete(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) #only owner can delete it ! if pull_request.author.user_id == c.rhodecode_user.user_id: PullRequestModel().delete(pull_request) Session().commit() h.flash(_('Successfully deleted pull request'), category='success') return redirect( url('admin_settings_my_account', anchor='pullrequests')) raise HTTPForbidden()
def test_pull_request_under_review_if_update(pr_util, voted_status): pull_request = pr_util.create_pull_request() pr_util.create_status_votes(voted_status, *pull_request.reviewers) # Update, with change pr_util.update_source_repository() PullRequestModel().update_commits(pull_request) # Expect that review status is the voted_status expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW assert pull_request.calculated_review_status() == expected_review_status
def test_merge_status_requirements_check_source(self, pull_request): def has_largefiles(self, repo): return repo == pull_request.target_repo patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles) with patcher: status, msg = PullRequestModel().merge_status(pull_request) assert status is False assert msg == 'Source repository large files support is disabled.'
def test_commit_keeps_status_if_unchanged_after_update_of_pull_request( pr_util, voted_status): pull_request = pr_util.create_pull_request() commit_id = pull_request.revisions[-1] pr_util.create_status_votes(voted_status, pull_request.reviewers[0]) pr_util.update_source_repository() PullRequestModel().update_commits(pull_request) assert pull_request.revisions[-1] == commit_id status = ChangesetStatusModel().get_status(repo=pr_util.source_repository, revision=commit_id) assert status == voted_status
def test_reviewer_notifications(self, backend, csrf_token): # We have to use the app.post for this test so it will create the # notifications properly with the new PR commits = [ {'message': 'ancestor', 'added': [FileNode('file_A', content='content_of_ancestor')]}, {'message': 'change', 'added': [FileNode('file_a', content='content_of_change')]}, {'message': 'change-child'}, {'message': 'ancestor-child', 'parents': ['ancestor'], 'added': [ FileNode('file_B', content='content_of_ancestor_child')]}, {'message': 'ancestor-child-2'}, ] commit_ids = backend.create_master_repo(commits) target = backend.create_repo(heads=['ancestor-child']) source = backend.create_repo(heads=['change']) response = self.app.post( url( controller='pullrequests', action='create', repo_name=source.repo_name), params={ 'source_repo': source.repo_name, 'source_ref': 'branch:default:' + commit_ids['change'], 'target_repo': target.repo_name, 'target_ref': 'branch:default:' + commit_ids['ancestor-child'], 'pullrequest_desc': 'Description', 'pullrequest_title': 'Title', 'review_members': '2', 'revisions': commit_ids['change'], 'user': '', 'csrf_token': csrf_token, }, status=302) location = response.headers['Location'] pull_request_id = int(location.rsplit('/', 1)[1]) pull_request = PullRequest.get(pull_request_id) # Check that a notification was made notifications = Notification.query()\ .filter(Notification.created_by == pull_request.author.user_id, Notification.type_ == Notification.TYPE_PULL_REQUEST, Notification.subject.contains("wants you to review " "pull request #%d" % pull_request_id)) assert len(notifications.all()) == 1 # Change reviewers and check that a notification was made PullRequestModel().update_reviewers(pull_request.pull_request_id, [1]) assert len(notifications.all()) == 2