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 merge(self, repo_name, pull_request_id): """ POST /{repo_name}/pull-request/{pull_request_id} Merge will perform a server-side merge of the specified pull request, if the pull request is approved and mergeable. After succesfull merging, the pull request is automatically closed, with a relevant comment. """ pull_request_id = safe_int(pull_request_id) pull_request = PullRequest.get_or_404(pull_request_id) user = c.rhodecode_user if self._meets_merge_pre_conditions(pull_request, user): log.debug("Pre-conditions checked, trying to merge.") extras = vcs_operation_context( request.environ, repo_name=pull_request.target_repo.repo_name, username=user.username, action='push', scm=pull_request.target_repo.repo_type) self._merge_pull_request(pull_request, user, extras) return redirect( url('pullrequest_show', repo_name=pull_request.target_repo.repo_name, pull_request_id=pull_request.pull_request_id))
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 _prepare_get_all_query(self, repo_name, source=False, statuses=None, opened_by=None, order_by=None, order_dir='desc'): repo = self._get_repo(repo_name) q = PullRequest.query() # source or target if source: q = q.filter(PullRequest.source_repo == repo) else: q = q.filter(PullRequest.target_repo == repo) # closed,opened if statuses: q = q.filter(PullRequest.status.in_(statuses)) # opened by filter if opened_by: q = q.filter(PullRequest.user_id.in_(opened_by)) if order_by: order_map = { 'name_raw': PullRequest.pull_request_id, 'title': PullRequest.title, 'updated_on_raw': PullRequest.updated_on } if order_dir == 'asc': q = q.order_by(order_map[order_by].asc()) else: q = q.order_by(order_map[order_by].desc()) return q
def create(self, created_by, org_repo, org_ref, other_repo, other_ref, revisions, reviewers, title, description=None): created_by_user = self._get_user(created_by) org_repo = self._get_repo(org_repo) other_repo = self._get_repo(other_repo) new = PullRequest() new.org_repo = org_repo new.org_ref = org_ref new.other_repo = other_repo new.other_ref = other_ref new.revisions = revisions new.title = title new.description = description new.author = created_by_user self.sa.add(new) Session().flush() #members for member in reviewers: _usr = self._get_user(member) reviewer = PullRequestReviewers(_usr, new) self.sa.add(reviewer) #notification to reviewers notif = NotificationModel() pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name, pull_request_id=new.pull_request_id, qualified=True, ) subject = safe_unicode( h.link_to( _('%(user)s wants you to review pull request #%(pr_id)s') % \ {'user': created_by_user.username, 'pr_id': new.pull_request_id}, pr_url ) ) body = description kwargs = { 'pr_title': title, 'pr_user_created': h.person(created_by_user.email), 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name, qualified=True,), 'pr_url': pr_url, 'pr_revisions': revisions } notif.create(created_by=created_by_user, subject=subject, body=body, recipients=reviewers, type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs) return new
def show(self, repo_name, pull_request_id): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() c.pull_request = PullRequest.get_or_404(pull_request_id) c.allowed_to_change_status = self._get_is_allowed_change_status( c.pull_request) cc_model = ChangesetCommentsModel() cs_model = ChangesetStatusModel() _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, pull_request=c.pull_request, with_revisions=True) cs_statuses = defaultdict(list) for st in _cs_statuses: cs_statuses[st.author.username] += [st] c.pull_request_reviewers = [] c.pull_request_pending_reviewers = [] for o in c.pull_request.reviewers: st = cs_statuses.get(o.user.username, None) if st: sorter = lambda k: k.version st = [(x, list(y)[0]) for x, y in (groupby(sorted(st, key=sorter), sorter))] else: c.pull_request_pending_reviewers.append(o.user) c.pull_request_reviewers.append([o.user, st]) # pull_requests repo_name we opened it against # ie. other_repo must match if repo_name != c.pull_request.other_repo.repo_name: raise HTTPNotFound # load compare data into template context enable_comments = not c.pull_request.is_closed() self._load_compare_data(c.pull_request, enable_comments=enable_comments) # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) # comments c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) # (badly named) pull-request status calculation based on reviewer votes c.current_changeset_status = cs_model.calculate_status( c.pull_request_reviewers, ) c.changeset_statuses = ChangesetStatus.STATUSES c.as_form = False c.ancestor = None # there is one - but right here we don't know which return render('/pullrequests/pullrequest_show.html')
def show(self, repo_name, pull_request_id): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() c.pull_request = PullRequest.get_or_404(pull_request_id) c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) cc_model = ChangesetCommentsModel() cs_model = ChangesetStatusModel() _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, pull_request=c.pull_request, with_revisions=True) cs_statuses = defaultdict(list) for st in _cs_statuses: cs_statuses[st.author.username] += [st] c.pull_request_reviewers = [] c.pull_request_pending_reviewers = [] for o in c.pull_request.reviewers: st = cs_statuses.get(o.user.username, None) if st: sorter = lambda k: k.version st = [(x, list(y)[0]) for x, y in (groupby(sorted(st, key=sorter), sorter))] else: c.pull_request_pending_reviewers.append(o.user) c.pull_request_reviewers.append([o.user, st]) # pull_requests repo_name we opened it against # ie. other_repo must match if repo_name != c.pull_request.other_repo.repo_name: raise HTTPNotFound # load compare data into template context enable_comments = not c.pull_request.is_closed() self._load_compare_data(c.pull_request, enable_comments=enable_comments) # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) # comments c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) # (badly named) pull-request status calculation based on reviewer votes c.current_changeset_status = cs_model.calculate_status( c.pull_request_reviewers, ) c.changeset_statuses = ChangesetStatus.STATUSES c.as_form = False c.ancestor = None # there is one - but right here we don't know which return render('/pullrequests/pullrequest_show.html')
def create(self, created_by, source_repo, source_ref, target_repo, target_ref, revisions, reviewers, title, description=None): created_by_user = self._get_user(created_by) source_repo = self._get_repo(source_repo) target_repo = self._get_repo(target_repo) pull_request = PullRequest() pull_request.source_repo = source_repo pull_request.source_ref = source_ref pull_request.target_repo = target_repo pull_request.target_ref = target_ref pull_request.revisions = revisions pull_request.title = title pull_request.description = description pull_request.author = created_by_user Session().add(pull_request) Session().flush() # members / reviewers for user_id in set(reviewers): user = self._get_user(user_id) reviewer = PullRequestReviewers(user, pull_request) Session().add(reviewer) # Set approval status to "Under Review" for all commits which are # part of this pull request. ChangesetStatusModel().set_status( repo=target_repo, status=ChangesetStatus.STATUS_UNDER_REVIEW, user=created_by_user, pull_request=pull_request) self.notify_reviewers(pull_request, reviewers) self._trigger_pull_request_hook(pull_request, created_by_user, 'create') return pull_request
def comment(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) if pull_request.is_closed(): raise HTTPForbidden() status = request.POST.get('changeset_status') change_status = request.POST.get('change_changeset_status') text = request.POST.get('text') if status and change_status: text = text or (_('Status change -> %s') % ChangesetStatus.get_status_lbl(status)) comm = ChangesetCommentsModel().create( text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, pull_request=pull_request_id, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), status_change=(ChangesetStatus.get_status_lbl(status) if status and change_status else None) ) # get status if set ! if status and change_status: ChangesetStatusModel().set_status( c.rhodecode_db_repo.repo_id, status, c.rhodecode_user.user_id, comm, pull_request=pull_request_id ) action_logger(self.rhodecode_user, 'user_commented_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) if request.POST.get('save_close'): PullRequestModel().close_pull_request(pull_request_id) action_logger(self.rhodecode_user, 'user_closed_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect(h.url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id)) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comm: c.co = comm data.update(comm.get_dict()) data.update({'rendered_text': render('changeset/changeset_comment_block.html')}) return data
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 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_remove_pull_request_branch(self, backend_git, csrf_token): branch_name = 'development' commits = [ {'message': 'initial-commit'}, {'message': 'old-feature'}, {'message': 'new-feature', 'branch': branch_name}, ] repo = backend_git.create_repo(commits) commit_ids = backend_git.commit_ids pull_request = PullRequest() pull_request.source_repo = repo pull_request.target_repo = repo pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( branch=branch_name, commit_id=commit_ids['new-feature']) pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature']) pull_request.revisions = [commit_ids['new-feature']] pull_request.title = u"Test" pull_request.description = u"Description" pull_request.author = UserModel().get_by_username( TEST_USER_ADMIN_LOGIN) Session().add(pull_request) Session().commit() vcs = repo.scm_instance() vcs.remove_ref('refs/heads/{}'.format(branch_name)) response = self.app.get(url( controller='pullrequests', action='show', repo_name=repo.repo_name, pull_request_id=str(pull_request.pull_request_id))) assert response.status_int == 200 assert_response = AssertResponse(response) assert_response.element_contains( '#changeset_compare_view_content .alert strong', 'Missing commits') assert_response.element_contains( '#changeset_compare_view_content .alert', 'This pull request cannot be displayed, because one or more' ' commits no longer exist in the source repository.')
def my_account_my_pullrequests(self): c.my_pull_requests = PullRequest.query()\ .filter(PullRequest.user_id== self.rhodecode_user.user_id)\ .all() c.participate_in_pull_requests = \ [x.pull_request for x in PullRequestReviewers.query()\ .filter(PullRequestReviewers.user_id== self.rhodecode_user.user_id)\ .all()] return render('admin/users/user_edit_my_account_pullrequests.html')
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
def pull_requests(self, pull_request_id): """ Global redirect for Pull Requests :param pull_request_id: id of pull requests in the system """ pull_request = PullRequest.get_or_404(pull_request_id) repo_name = pull_request.target_repo.repo_name return redirect( url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id))
def get_all(self, repo_name, from_=False, closed=False): """Get all PRs for repo. Default is all PRs to the repo, PRs from the repo if from_. Closed PRs are only included if closed is true.""" repo = self._get_repo(repo_name) q = PullRequest.query() if from_: q = q.filter(PullRequest.org_repo == repo) else: q = q.filter(PullRequest.other_repo == repo) if not closed: q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED) return q.order_by(PullRequest.created_on.desc()).all()
def update(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) if pull_request.is_closed(): raise HTTPForbidden() #only owner or admin can update it owner = pull_request.author.user_id == c.rhodecode_user.user_id if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: 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() return True raise HTTPForbidden()
def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token): 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': '1', '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) # target_ref has to point to the ancestor's commit_id in order to # show the correct diff expected_target_ref = 'branch:default:' + commit_ids['ancestor'] assert pull_request.target_ref == expected_target_ref # Check generated diff contents response = response.follow() assert 'content_of_ancestor' not in response.body assert 'content_of_ancestor-child' not in response.body assert 'content_of_change' in response.body
def test_merge_pull_request_disabled(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request(mergeable=False) pull_request_id = pull_request.pull_request_id pull_request = PullRequest.get(pull_request_id) response = self.app.post( url(controller='pullrequests', action='merge', repo_name=pull_request.target_repo.scm_instance().name, pull_request_id=str(pull_request.pull_request_id)), params={'csrf_token': csrf_token}).follow() assert response.status_int == 200 assert 'Server-side pull request merging is disabled.' in response.body
def test_merge_pull_request_not_approved(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request(mergeable=True) pull_request_id = pull_request.pull_request_id repo_name = pull_request.target_repo.scm_instance().name, response = self.app.post( url(controller='pullrequests', action='merge', repo_name=str(repo_name[0]), pull_request_id=str(pull_request_id)), params={'csrf_token': csrf_token}).follow() pull_request = PullRequest.get(pull_request_id) assert response.status_int == 200 assert ' Reviewer approval is pending.' in response.body
def update(self, repo_name, pull_request_id): pull_request_id = safe_int(pull_request_id) pull_request = PullRequest.get_or_404(pull_request_id) # only owner or admin can update it allowed_to_update = PullRequestModel().check_user_update( pull_request, c.rhodecode_user) if allowed_to_update: if 'reviewers_ids' in request.POST: self._update_reviewers(pull_request_id) elif str2bool(request.POST.get('update_commits', 'false')): self._update_commits(pull_request) elif str2bool(request.POST.get('close_pull_request', 'false')): self._reject_close(pull_request) elif str2bool(request.POST.get('edit_pull_request', 'false')): self._edit_pull_request(pull_request) else: raise HTTPBadRequest() return True raise HTTPForbidden()
def test_merge_pull_request_enabled(self, pr_util, csrf_token): # Clear any previous calls to rcextensions rhodecode.EXTENSIONS.calls.clear() pull_request = pr_util.create_pull_request( approved=True, mergeable=True) pull_request_id = pull_request.pull_request_id repo_name = pull_request.target_repo.scm_instance().name, response = self.app.post( url(controller='pullrequests', action='merge', repo_name=str(repo_name[0]), pull_request_id=str(pull_request_id)), params={'csrf_token': csrf_token}).follow() pull_request = PullRequest.get(pull_request_id) assert response.status_int == 200 assert pull_request.is_closed() assert_pull_request_status( pull_request, ChangesetStatus.STATUS_APPROVED) # Check the relevant log entries were added user_logs = UserLog.query().order_by('-user_log_id').limit(4) actions = [log.action for log in user_logs] pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request) expected_actions = [ u'user_closed_pull_request:%d' % pull_request_id, u'user_merged_pull_request:%d' % pull_request_id, # The action below reflect that the post push actions were executed u'user_commented_pull_request:%d' % pull_request_id, u'push:%s' % ','.join(pr_commit_ids), ] assert actions == expected_actions # Check post_push rcextension was really executed push_calls = rhodecode.EXTENSIONS.calls['post_push'] assert len(push_calls) == 1 unused_last_call_args, last_call_kwargs = push_calls[0] assert last_call_kwargs['action'] == 'push' assert last_call_kwargs['pushed_revs'] == pr_commit_ids
def my_account_my_pullrequests(self): c.show_closed = request.GET.get('pr_show_closed') def _filter(pr): s = sorted(pr, key=lambda o: o.created_on, reverse=True) if not c.show_closed: s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s) return s c.my_pull_requests = _filter(PullRequest.query()\ .filter(PullRequest.user_id == self.rhodecode_user.user_id)\ .all()) c.participate_in_pull_requests = _filter([ x.pull_request for x in PullRequestReviewers.query()\ .filter(PullRequestReviewers.user_id == self.rhodecode_user.user_id).all()]) return render('admin/users/user_edit_my_account_pullrequests.html')
def test_reject_and_close_pull_request(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request() pull_request_id = pull_request.pull_request_id response = self.app.post( url(controller='pullrequests', action='update', repo_name=pull_request.target_repo.scm_instance().name, pull_request_id=str(pull_request.pull_request_id)), params={'close_pull_request': 'true', '_method': 'put', 'csrf_token': csrf_token}) pull_request = PullRequest.get(pull_request_id) assert response.json is True assert pull_request.is_closed() # 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 my_account_pullrequests(self): c.active = 'pullrequests' self.__load_data() c.show_closed = request.GET.get('pr_show_closed') def _filter(pr): s = sorted(pr, key=lambda o: o.created_on, reverse=True) if not c.show_closed: s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s) return s c.my_pull_requests = _filter(PullRequest.query().filter( PullRequest.user_id == c.rhodecode_user.user_id).all()) my_prs = [ x.pull_request for x in PullRequestReviewers.query().filter( PullRequestReviewers.user_id == c.rhodecode_user.user_id).all() ] c.participate_in_pull_requests = _filter(my_prs) return render('admin/my_account/my_account.html')
def test_edit_title_description(self, pr_util, csrf_token): pull_request = pr_util.create_pull_request() pull_request_id = pull_request.pull_request_id response = self.app.post( url(controller='pullrequests', action='update', repo_name=pull_request.target_repo.repo_name, pull_request_id=str(pull_request_id)), params={ 'edit_pull_request': 'true', '_method': 'put', 'title': 'New title', 'description': 'New description', 'csrf_token': csrf_token}) assert_session_flash( response, u'Pull request title & description updated.', category='success') pull_request = PullRequest.get(pull_request_id) assert pull_request.title == 'New title' assert pull_request.description == 'New description'
def test_create_pull_request(self, backend, csrf_token): commits = [ {'message': 'ancestor'}, {'message': 'change'}, ] commit_ids = backend.create_master_repo(commits) target = backend.create_repo(heads=['ancestor']) 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'], 'pullrequest_desc': 'Description', 'pullrequest_title': 'Title', 'review_members': '1', '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 we have now both revisions assert pull_request.revisions == [commit_ids['change']] assert pull_request.source_ref == 'branch:default:' + commit_ids['change'] expected_target_ref = 'branch:default:' + commit_ids['ancestor'] assert pull_request.target_ref == expected_target_ref
def create(self, created_by, org_repo, org_ref, other_repo, other_ref, revisions, reviewers, title, description=None): from rhodecode.model.changeset_status import ChangesetStatusModel created_by_user = self._get_user(created_by) org_repo = self._get_repo(org_repo) other_repo = self._get_repo(other_repo) new = PullRequest() new.org_repo = org_repo new.org_ref = org_ref new.other_repo = other_repo new.other_ref = other_ref new.revisions = revisions new.title = title new.description = description new.author = created_by_user Session().add(new) Session().flush() #members for member in set(reviewers): _usr = self._get_user(member) reviewer = PullRequestReviewers(_usr, new) Session().add(reviewer) #reset state to under-review ChangesetStatusModel().set_status( repo=org_repo, status=ChangesetStatus.STATUS_UNDER_REVIEW, user=created_by_user, pull_request=new ) revision_data = [(x.raw_id, x.message) for x in map(org_repo.get_changeset, revisions)] #notification to reviewers pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name, pull_request_id=new.pull_request_id, qualified=True, ) subject = safe_unicode( h.link_to( _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \ {'user': created_by_user.username, 'pr_title': new.title, 'pr_id': new.pull_request_id}, pr_url ) ) body = description kwargs = { 'pr_title': title, 'pr_user_created': h.person(created_by_user.email), 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name, qualified=True,), 'pr_url': pr_url, 'pr_revisions': revision_data } NotificationModel().create(created_by=created_by_user, subject=subject, body=body, recipients=reviewers, type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs) return new
def test_update_of_ancestor_reference(self, backend, csrf_token): commits = [ {'message': 'ancestor'}, {'message': 'change'}, {'message': 'change-2'}, {'message': 'ancestor-new', 'parents': ['ancestor']}, {'message': 'change-rebased'}, ] commit_ids = backend.create_master_repo(commits) target = backend.create_repo(heads=['ancestor']) source = backend.create_repo(heads=['change']) # create pr from a in source to A in target pull_request = PullRequest() pull_request.source_repo = source # TODO: johbo: Make sure that we write the source ref this way! pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( branch=backend.default_branch_name, commit_id=commit_ids['change']) pull_request.target_repo = target # TODO: johbo: Target ref should be branch based, since tip can jump # from branch to branch pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( branch=backend.default_branch_name, commit_id=commit_ids['ancestor']) pull_request.revisions = [commit_ids['change']] pull_request.title = u"Test" pull_request.description = u"Description" pull_request.author = UserModel().get_by_username( TEST_USER_ADMIN_LOGIN) Session().add(pull_request) Session().commit() pull_request_id = pull_request.pull_request_id # target has ancestor - ancestor-new # source has ancestor - ancestor-new - change-rebased backend.pull_heads(target, heads=['ancestor-new']) backend.pull_heads(source, heads=['change-rebased']) # update PR self.app.post( url(controller='pullrequests', action='update', repo_name=target.repo_name, pull_request_id=str(pull_request_id)), params={'update_commits': 'true', '_method': 'put', 'csrf_token': csrf_token}, status=200) # Expect the target reference to be updated correctly pull_request = PullRequest.get(pull_request_id) assert pull_request.revisions == [commit_ids['change-rebased']] expected_target_ref = 'branch:{branch}:{commit_id}'.format( branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new']) assert pull_request.target_ref == expected_target_ref
def get_all(self, repo): repo = self._get_repo(repo) return PullRequest.query().filter(PullRequest.other_repo == repo).all()
def show(self, repo_name, pull_request_id): pull_request_id = safe_int(pull_request_id) c.pull_request = PullRequest.get_or_404(pull_request_id) # pull_requests repo_name we opened it against # ie. target_repo must match if repo_name != c.pull_request.target_repo.repo_name: raise HTTPNotFound c.allowed_to_change_status = PullRequestModel(). \ check_user_change_status(c.pull_request, c.rhodecode_user) c.allowed_to_update = PullRequestModel().check_user_update( c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed() c.allowed_to_merge = PullRequestModel().check_user_merge( c.pull_request, c.rhodecode_user) and not c.pull_request.is_closed() cc_model = ChangesetCommentsModel() c.pull_request_reviewers = c.pull_request.reviewers_statuses() c.pull_request_review_status = c.pull_request.calculated_review_status( ) c.pr_merge_status, c.pr_merge_msg = PullRequestModel().merge_status( c.pull_request) c.approval_msg = None if c.pull_request_review_status != ChangesetStatus.STATUS_APPROVED: c.approval_msg = _('Reviewer approval is pending.') c.pr_merge_status = False # load compare data into template context enable_comments = not c.pull_request.is_closed() self._load_compare_data(c.pull_request, enable_comments=enable_comments) # this is a hack to properly display links, when creating PR, the # compare view and others uses different notation, and # compare_commits.html renders links based on the target_repo. # We need to swap that here to generate it properly on the html side c.target_repo = c.source_repo # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.rhodecode_db_repo.repo_id, pull_request=pull_request_id).items() # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) # outdated comments c.outdated_cnt = 0 if ChangesetCommentsModel.use_outdated_comments(c.pull_request): c.outdated_comments = cc_model.get_outdated_comments( c.rhodecode_db_repo.repo_id, pull_request=c.pull_request) # Count outdated comments and check for deleted files for file_name, lines in c.outdated_comments.iteritems(): for comments in lines.values(): c.outdated_cnt += len(comments) if file_name not in c.included_files: c.deleted_files.append(file_name) else: c.outdated_comments = {} # comments c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) if c.allowed_to_update: force_close = ('forced_closed', _('Close Pull Request')) statuses = ChangesetStatus.STATUSES + [force_close] else: statuses = ChangesetStatus.STATUSES c.commit_statuses = statuses c.ancestor = None # TODO: add ancestor here return render('/pullrequests/pullrequest_show.html')
def comment(self, repo_name, pull_request_id): pull_request_id = safe_int(pull_request_id) pull_request = PullRequest.get_or_404(pull_request_id) if pull_request.is_closed(): raise HTTPForbidden() # TODO: johbo: Re-think this bit, "approved_closed" does not exist # as a changeset status, still we want to send it in one value. status = request.POST.get('changeset_status', None) text = request.POST.get('text') if status and '_closed' in status: close_pr = True status = status.replace('_closed', '') else: close_pr = False forced = (status == 'forced') if forced: status = 'rejected' allowed_to_change_status = PullRequestModel().check_user_change_status( pull_request, c.rhodecode_user) if status and allowed_to_change_status: message = (_('Status change %(transition_icon)s %(status)s') % { 'transition_icon': '>', 'status': ChangesetStatus.get_status_lbl(status) }) if close_pr: message = _('Closing with') + ' ' + message text = text or message comm = ChangesetCommentsModel().create( text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, pull_request=pull_request_id, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), status_change=(ChangesetStatus.get_status_lbl(status) if status and allowed_to_change_status else None), closing_pr=close_pr) if allowed_to_change_status: old_calculated_status = pull_request.calculated_review_status() # get status if set ! if status: ChangesetStatusModel().set_status(c.rhodecode_db_repo.repo_id, status, c.rhodecode_user.user_id, comm, pull_request=pull_request_id) Session().flush() # we now calculate the status of pull request, and based on that # calculation we set the commits status calculated_status = pull_request.calculated_review_status() if old_calculated_status != calculated_status: PullRequestModel()._trigger_pull_request_hook( pull_request, c.rhodecode_user, 'review_status_change') calculated_status_lbl = ChangesetStatus.get_status_lbl( calculated_status) if close_pr: status_completed = (calculated_status in [ ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED ]) if forced or status_completed: PullRequestModel().close_pull_request( pull_request_id, c.rhodecode_user) else: h.flash(_('Closing pull request on other statuses than ' 'rejected or approved is forbidden. ' 'Calculated status from all reviewers ' 'is currently: %s') % calculated_status_lbl, category='warning') Session().commit() if not request.is_xhr: return redirect( h.url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id)) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comm: c.co = comm data.update(comm.get_dict()) data.update({ 'rendered_text': render('changeset/changeset_comment_block.html') }) return data
def show(self, repo_name, pull_request_id): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() c.pull_request = PullRequest.get_or_404(pull_request_id) c.target_repo = c.pull_request.org_repo.repo_name cc_model = ChangesetCommentsModel() cs_model = ChangesetStatusModel() _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo, pull_request=c.pull_request, with_revisions=True) cs_statuses = defaultdict(list) for st in _cs_statuses: cs_statuses[st.author.username] += [st] c.pull_request_reviewers = [] c.pull_request_pending_reviewers = [] for o in c.pull_request.reviewers: st = cs_statuses.get(o.user.username, None) if st: sorter = lambda k: k.version st = [(x, list(y)[0]) for x, y in (groupby(sorted(st, key=sorter), sorter))] else: c.pull_request_pending_reviewers.append(o.user) c.pull_request_reviewers.append([o.user, st]) # pull_requests repo_name we opened it against # ie. other_repo must match if repo_name != c.pull_request.other_repo.repo_name: raise HTTPNotFound # load compare data into template context enable_comments = not c.pull_request.is_closed() self._load_compare_data(c.pull_request, enable_comments=enable_comments) # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) # comments c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id, pull_request=pull_request_id) try: cur_status = c.statuses[c.pull_request.revisions[0]][0] except: log.error(traceback.format_exc()) cur_status = 'undefined' if c.pull_request.is_closed() and 0: c.current_changeset_status = cur_status else: # changeset(pull-request) status calulation based on reviewers c.current_changeset_status = cs_model.calculate_status( c.pull_request_reviewers, ) c.changeset_statuses = ChangesetStatus.STATUSES return render('/pullrequests/pullrequest_show.html')
def comment(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) if pull_request.is_closed(): raise HTTPForbidden() status = request.POST.get('changeset_status') change_status = request.POST.get('change_changeset_status') text = request.POST.get('text') close_pr = request.POST.get('save_close') allowed_to_change_status = self._get_is_allowed_change_status(pull_request) if status and change_status and allowed_to_change_status: _def = (_('Status change -> %s') % ChangesetStatus.get_status_lbl(status)) if close_pr: _def = _('Closing with') + ' ' + _def text = text or _def comm = ChangesetCommentsModel().create( text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, pull_request=pull_request_id, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), status_change=(ChangesetStatus.get_status_lbl(status) if status and change_status and allowed_to_change_status else None), closing_pr=close_pr ) action_logger(self.rhodecode_user, 'user_commented_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) if allowed_to_change_status: # get status if set ! if status and change_status: ChangesetStatusModel().set_status( c.rhodecode_db_repo.repo_id, status, c.rhodecode_user.user_id, comm, pull_request=pull_request_id ) if close_pr: if status in ['rejected', 'approved']: PullRequestModel().close_pull_request(pull_request_id) action_logger(self.rhodecode_user, 'user_closed_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) else: h.flash(_('Closing pull request on other statuses than ' 'rejected or approved forbidden'), category='warning') Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect(h.url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id)) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comm: c.co = comm data.update(comm.get_dict()) data.update({'rendered_text': render('changeset/changeset_comment_block.html')}) return data
def comment(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) if pull_request.is_closed(): raise HTTPForbidden() status = request.POST.get('changeset_status') change_status = request.POST.get('change_changeset_status') text = request.POST.get('text') close_pr = request.POST.get('save_close') allowed_to_change_status = self._get_is_allowed_change_status( pull_request) if status and change_status and allowed_to_change_status: _def = (_('Status change -> %s') % ChangesetStatus.get_status_lbl(status)) if close_pr: _def = _('Closing with') + ' ' + _def text = text or _def comm = ChangesetCommentsModel().create( text=text, repo=c.rhodecode_db_repo.repo_id, user=c.rhodecode_user.user_id, pull_request=pull_request_id, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), status_change=(ChangesetStatus.get_status_lbl(status) if status and change_status and allowed_to_change_status else None), closing_pr=close_pr) action_logger(self.rhodecode_user, 'user_commented_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) if allowed_to_change_status: # get status if set ! if status and change_status: ChangesetStatusModel().set_status(c.rhodecode_db_repo.repo_id, status, c.rhodecode_user.user_id, comm, pull_request=pull_request_id) if close_pr: if status in ['rejected', 'approved']: PullRequestModel().close_pull_request(pull_request_id) action_logger( self.rhodecode_user, 'user_closed_pull_request:%s' % pull_request_id, c.rhodecode_db_repo, self.ip_addr, self.sa) else: h.flash(_('Closing pull request on other statuses than ' 'rejected or approved forbidden'), category='warning') Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect( h.url('pullrequest_show', repo_name=repo_name, pull_request_id=pull_request_id)) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comm: c.co = comm data.update(comm.get_dict()) data.update({ 'rendered_text': render('changeset/changeset_comment_block.html') }) return data
def test_update_source_revision(self, backend, csrf_token): commits = [ {'message': 'ancestor'}, {'message': 'change'}, {'message': 'change-2'}, ] commit_ids = backend.create_master_repo(commits) target = backend.create_repo(heads=['ancestor']) source = backend.create_repo(heads=['change']) # create pr from a in source to A in target pull_request = PullRequest() pull_request.source_repo = source # TODO: johbo: Make sure that we write the source ref this way! pull_request.source_ref = 'branch:{branch}:{commit_id}'.format( branch=backend.default_branch_name, commit_id=commit_ids['change']) pull_request.target_repo = target pull_request.target_ref = 'branch:{branch}:{commit_id}'.format( branch=backend.default_branch_name, commit_id=commit_ids['ancestor']) pull_request.revisions = [commit_ids['change']] pull_request.title = u"Test" pull_request.description = u"Description" pull_request.author = UserModel().get_by_username( TEST_USER_ADMIN_LOGIN) Session().add(pull_request) Session().commit() pull_request_id = pull_request.pull_request_id # source has ancestor - change - change-2 backend.pull_heads(source, heads=['change-2']) # update PR self.app.post( url(controller='pullrequests', action='update', repo_name=target.repo_name, pull_request_id=str(pull_request_id)), params={'update_commits': 'true', '_method': 'put', 'csrf_token': csrf_token}) # check that we have now both revisions pull_request = PullRequest.get(pull_request_id) assert pull_request.revisions == [ commit_ids['change-2'], commit_ids['change']] # TODO: johbo: this should be a test on its own response = self.app.get(url( controller='pullrequests', action='index', repo_name=target.repo_name)) assert response.status_int == 200 assert 'Pull request updated to' in response.body assert 'with 1 added, 0 removed commits.' in response.body
def create(self, created_by, org_repo, org_ref, other_repo, other_ref, revisions, reviewers, title, description=None): from rhodecode.model.changeset_status import ChangesetStatusModel created_by_user = self._get_user(created_by) org_repo = self._get_repo(org_repo) other_repo = self._get_repo(other_repo) new = PullRequest() new.org_repo = org_repo new.org_ref = org_ref new.other_repo = other_repo new.other_ref = other_ref new.revisions = revisions new.title = title new.description = description new.author = created_by_user Session().add(new) Session().flush() #members for member in set(reviewers): _usr = self._get_user(member) reviewer = PullRequestReviewers(_usr, new) Session().add(reviewer) #reset state to under-review ChangesetStatusModel().set_status( repo=org_repo, status=ChangesetStatus.STATUS_UNDER_REVIEW, user=created_by_user, pull_request=new ) revision_data = [(x.raw_id, x.message) for x in map(org_repo.get_changeset, revisions)] #notification to reviewers notif = NotificationModel() pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name, pull_request_id=new.pull_request_id, qualified=True, ) subject = safe_unicode( h.link_to( _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \ {'user': created_by_user.username, 'pr_title': new.title, 'pr_id': new.pull_request_id}, pr_url ) ) body = description kwargs = { 'pr_title': title, 'pr_user_created': h.person(created_by_user.email), 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name, qualified=True,), 'pr_url': pr_url, 'pr_revisions': revision_data } notif.create(created_by=created_by_user, subject=subject, body=body, recipients=reviewers, type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs) return new
def get_all(self, repo): repo = self._get_repo(repo) return PullRequest.query()\ .filter(PullRequest.other_repo == repo)\ .order_by(PullRequest.created_on.desc())\ .all()