def test_create_status_change(self): self.log_user() rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc' text = 'general comment on changeset' params = { 'text': text, 'changeset_status': 'rejected', '_session_csrf_secret_token': self.session_csrf_secret_token() } response = self.app.post(base.url(controller='changeset', action='comment', repo_name=base.HG_REPO, revision=rev), params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'}) # Test response... assert response.status == '200 OK' response = self.app.get( base.url(controller='changeset', action='index', repo_name=base.HG_REPO, revision=rev)) response.mustcontain('''<div class="comments-number">''' ''' 1 comment (0 inline, 1 general)''') response.mustcontain(text) # test DB assert ChangesetComment.query().count() == 1 # check status status = ChangesetStatusModel().get_status(repo=base.HG_REPO, revision=rev) assert status == 'rejected'
class TestChangesetStatusCalculation(BaseTestCase): def setUp(self): self.m = ChangesetStatusModel() @parameterized.expand([ ('empty list', CS.STATUS_UNDER_REVIEW, []), ('approve', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED)]), ('approve2', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED)]), ('approve_reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED)]), ('approve_underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW)]), ('approve_notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED)]), ('underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW)]), ('reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED)]), ('reject_underreview', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW)]), ('reject_notreviewed', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED)]), ('notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED)]), ('approve_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), None]), ('approve2_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED), None]), ('approve_reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED), None]), ('approve_underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW), None]), ('approve_notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED), None]), ('underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW), None]), ('reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), None]), ('reject_underreview_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW), None]), ('reject_notreviewed_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED), None]), ('notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED), None]), ]) def test_result(self, name, expected_result, statuses): result = self.m._calculate_status(statuses) self.assertEqual(result, expected_result)
class TestChangesetStatusCalculation(TestController): def setup_method(self, method): self.m = ChangesetStatusModel() @parametrize('name,expected_result,statuses', [ ('empty list', CS.STATUS_UNDER_REVIEW, []), ('approve', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED)]), ('approve2', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED)]), ('approve_reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED)]), ('approve_underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW)]), ('approve_notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED)]), ('underreview', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW)]), ('reject', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED)]), ('reject_underreview', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW)]), ('reject_notreviewed', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED)]), ('notreviewed', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED)]), ('approve_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), None]), ('approve2_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED), None]), ('approve_reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_REJECTED), None]), ('approve_underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_UNDER_REVIEW), None]), ('approve_notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_NOT_REVIEWED), None]), ('underreview_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_UNDER_REVIEW), CSM(CS.STATUS_UNDER_REVIEW), None]), ('reject_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), None]), ('reject_underreview_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_UNDER_REVIEW), None]), ('reject_notreviewed_none', CS.STATUS_REJECTED, [CSM(CS.STATUS_REJECTED), CSM(CS.STATUS_NOT_REVIEWED), None]), ('notreviewed_none', CS.STATUS_UNDER_REVIEW, [CSM(CS.STATUS_NOT_REVIEWED), None]), ]) def test_result(self, name, expected_result, statuses): result = self.m._calculate_status(statuses) assert result == expected_result
def comment(self, repo_name, revision): status = request.POST.get('changeset_status') text = request.POST.get('text', '').strip() or _('No comments.') c.co = comm = ChangesetCommentsModel().create( text=text, repo=c.db_repo.repo_id, user=c.authuser.user_id, revision=revision, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), status_change=(ChangesetStatus.get_status_lbl(status) if status else None)) # get status if set ! if status: # if latest status was from pull request and it's closed # disallow changing status ! # dont_allow_on_closed_pull_request = True ! try: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, c.authuser.user_id, comm, revision=revision, dont_allow_on_closed_pull_request=True) except StatusChangeOnClosedPullRequestError: log.error(traceback.format_exc()) msg = _('Changing status on a changeset associated with ' 'a closed pull request is not allowed') h.flash(msg, category='warning') return redirect( h.url('changeset_home', repo_name=repo_name, revision=revision)) action_logger(self.authuser, 'user_commented_revision:%s' % revision, c.db_repo, self.ip_addr, self.sa) Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect( h.url('changeset_home', repo_name=repo_name, revision=revision)) #only ajax below data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comm: data.update(comm.get_dict()) data.update({ 'rendered_text': render('changeset/changeset_comment_block.html') }) return data
def test_NotReviewedRevisions(self): repo_id = Repository.get_by_repo_name(HG_REPO).repo_id validator = v.NotReviewedRevisions(repo_id) rev = '0' * 40 # add status for a rev, that should throw an error because it is already # reviewed new_status = ChangesetStatus() new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN) new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO) new_status.status = ChangesetStatus.STATUS_APPROVED new_status.comment = None new_status.revision = rev Session().add(new_status) Session().commit() try: self.assertRaises(formencode.Invalid, validator.to_python, [rev]) finally: Session().delete(new_status) Session().commit()
def comment(self, repo_name, revision): assert request.environ.get('HTTP_X_PARTIAL_XHR') status = request.POST.get('changeset_status') text = request.POST.get('text', '').strip() c.comment = create_comment( text, status, revision=revision, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), ) # get status if set ! if status: # if latest status was from pull request and it's closed # disallow changing status ! RLY? try: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, request.authuser.user_id, c.comment, revision=revision, dont_allow_on_closed_pull_request=True, ) except StatusChangeOnClosedPullRequestError: log.debug('cannot change status on %s with closed pull request', revision) raise HTTPBadRequest() action_logger(request.authuser, 'user_commented_revision:%s' % revision, c.db_repo, request.ip_addr) Session().commit() data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if c.comment is not None: data.update(c.comment.get_dict()) data.update({'rendered_text': render('changeset/changeset_comment_block.html')}) return data
def review_changeset(self, repo, revision, status, author=TEST_USER_ADMIN_LOGIN): comment = ChangesetCommentsModel().create("review comment", repo, author, revision=revision, send_email=False) csm = ChangesetStatusModel().set_status( repo, ChangesetStatus.STATUS_APPROVED, author, comment, revision=revision) Session().commit() return csm
def execute(self): created_by = User.get(request.authuser.user_id) pr = PullRequest() pr.org_repo = self.org_repo pr.org_ref = self.org_ref pr.other_repo = self.other_repo pr.other_ref = self.other_ref pr.revisions = self.revisions pr.title = self.title pr.description = self.description pr.owner = self.owner Session().add(pr) Session().flush() # make database assign pull_request_id if self.org_repo.scm_instance.alias == 'git': # create a ref under refs/pull/ so that commits don't get garbage-collected self.org_repo.scm_instance._repo["refs/pull/%d/head" % pr.pull_request_id] = safe_str(self.org_rev) #reset state to under-review from kallithea.model.changeset_status import ChangesetStatusModel from kallithea.model.comment import ChangesetCommentsModel comment = ChangesetCommentsModel().create( text=u'', repo=self.org_repo, author=created_by, pull_request=pr, send_email=False, status_change=ChangesetStatus.STATUS_UNDER_REVIEW, ) ChangesetStatusModel().set_status( self.org_repo, ChangesetStatus.STATUS_UNDER_REVIEW, created_by, comment, pull_request=pr, ) mention_recipients = extract_mentioned_users(self.description) PullRequestModel().add_reviewers(created_by, pr, self.reviewers, mention_recipients) return pr
def create(self, created_by, org_repo, org_ref, other_repo, other_ref, revisions, reviewers, title, description=None): from kallithea.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() #reset state to under-review from kallithea.model.comment import ChangesetCommentsModel comment = ChangesetCommentsModel().create( text=u'Auto status change to %s' % (ChangesetStatus.get_status_lbl(ChangesetStatus.STATUS_UNDER_REVIEW)), repo=org_repo, user=new.author, pull_request=new, send_email=False ) ChangesetStatusModel().set_status( org_repo, ChangesetStatus.STATUS_UNDER_REVIEW, new.author, comment, pull_request=new ) mention_recipients = set(User.get_by_username(username, case_insensitive=True) for username in extract_mentioned_users(new.description)) self.__add_reviewers(new, reviewers, mention_recipients) return new
def test_create_status_change(self): self.log_user() pr_id = self._create_pr() text = 'general comment on pullrequest' params = { 'text': text, 'changeset_status': 'rejected', '_session_csrf_secret_token': self.session_csrf_secret_token() } response = self.app.post(base.url(controller='pullrequests', action='comment', repo_name=base.HG_REPO, pull_request_id=pr_id), params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'}) # Test response... assert response.status == '200 OK' response = self.app.get( base.url(controller='pullrequests', action='show', repo_name=base.HG_REPO, pull_request_id=pr_id, extra='')) # PRs currently always have an initial 'Under Review' status change # that counts as a general comment, hence '2' in the test below. That # could be counted as a misfeature, to be reworked later. response.mustcontain('''<div class="comments-number">''' ''' 2 comments (0 inline, 2 general)''') response.mustcontain(text) # test DB assert ChangesetComment.query().count() == 2 # check status status = ChangesetStatusModel().get_status(repo=base.HG_REPO, pull_request=pr_id) assert status == 'rejected'
def comment(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) status = 0 close_pr = False allowed_to_change_status = self._get_is_allowed_change_status( pull_request) if allowed_to_change_status: status = request.POST.get('changeset_status') close_pr = request.POST.get('save_close') text = request.POST.get('text', '').strip() or _('No comments.') if close_pr: text = _('Closing.') + '\n' + text comm = ChangesetCommentsModel().create( text=text, repo=c.db_repo.repo_id, user=c.authuser.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) action_logger(self.authuser, 'user_commented_pull_request:%s' % pull_request_id, c.db_repo, self.ip_addr, self.sa) if allowed_to_change_status: # get status if set ! if status: ChangesetStatusModel().set_status(c.db_repo.repo_id, status, c.authuser.user_id, comm, pull_request=pull_request_id) if close_pr: PullRequestModel().close_pull_request(pull_request_id) action_logger(self.authuser, 'user_closed_pull_request:%s' % pull_request_id, c.db_repo, self.ip_addr, self.sa) Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect(pull_request.url()) 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
class ChangesetController(BaseRepoController): def __before__(self): super(ChangesetController, self).__before__() c.affected_files_cut_off = 60 def __load_data(self): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.user_groups_array = repo_model.get_user_groups_js() def _index(self, revision, method): c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = fulldiff = request.GET.get('fulldiff') #get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True c.cs_repo = c.db_repo try: if len(rev_range) == 2: enable_comments = False rev_start = rev_range[0] rev_end = rev_range[1] rev_ranges = c.db_repo_scm_instance.get_changesets( start=rev_start, end=rev_end) else: rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] c.cs_ranges = list(rev_ranges) if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') except (ChangesetDoesNotExistError, ), e: log.error(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() c.changes = OrderedDict() c.lines_added = 0 # count of lines added c.lines_deleted = 0 # count of lines removes c.changeset_statuses = ChangesetStatus.STATUSES comments = dict() c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: inlines = [] if method == 'show': c.statuses.extend([ ChangesetStatusModel().get_status(c.db_repo.repo_id, changeset.raw_id) ]) # Changeset comments comments.update( (com.comment_id, com) for com in ChangesetCommentsModel().get_comments( c.db_repo.repo_id, revision=changeset.raw_id)) # Status change comments - mostly from pull requests comments.update(( st.changeset_comment_id, st.comment ) for st in ChangesetStatusModel().get_statuses( c.db_repo.repo_id, changeset.raw_id, with_revisions=True)) inlines = ChangesetCommentsModel()\ .get_inline_comments(c.db_repo.repo_id, revision=changeset.raw_id) c.inline_comments.extend(inlines) c.changes[changeset.raw_id] = [] cs2 = changeset.raw_id cs1 = changeset.parents[ 0].raw_id if changeset.parents else EmptyChangeset().raw_id context_lcl = get_line_ctx('', request.GET) ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws( '', request.GET) _diff = c.db_repo_scm_instance.get_diff( cs1, cs2, ignore_whitespace=ign_whitespace_lcl, context=context_lcl) diff_limit = self.cut_off_limit if not fulldiff else None diff_processor = diffs.DiffProcessor( _diff, vcs=c.db_repo_scm_instance.alias, format='gitdiff', diff_limit=diff_limit) cs_changes = OrderedDict() if method == 'show': _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] fid = h.FID(changeset.raw_id, f['filename']) diff = diff_processor.as_html( enable_comments=enable_comments, parsed_lines=[f]) cs_changes[fid] = [ cs1, cs2, f['operation'], f['filename'], diff, st ] else: # downloads/raw we only need RAW diff nothing else diff = diff_processor.as_raw() cs_changes[''] = [None, None, None, None, diff, None] c.changes[changeset.raw_id] = cs_changes #sort comments in creation order c.comments = [com for com_id, com in sorted(comments.items())] # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) if len(c.cs_ranges) == 1: c.changeset = c.cs_ranges[0] c.parent_tmpl = ''.join( ['# Parent %s\n' % x.raw_id for x in c.changeset.parents]) if method == 'download': response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s.diff' \ % revision[:12] return diff elif method == 'patch': response.content_type = 'text/plain' c.diff = safe_unicode(diff) return render('changeset/patch_changeset.html') elif method == 'raw': response.content_type = 'text/plain' return diff elif method == 'show': self.__load_data() if len(c.cs_ranges) == 1: return render('changeset/changeset.html') else: c.cs_ranges_org = None c.cs_comments = {} revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs)) return render('changeset/changeset_range.html')
def show(self, repo_name, pull_request_id, extra=None): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.user_groups_array = repo_model.get_user_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() # 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 c.cs_repo = c.pull_request.org_repo (c.cs_ref_type, c.cs_ref_name, c.cs_rev) = c.pull_request.org_ref.split(':') c.a_repo = c.pull_request.other_repo (c.a_ref_type, c.a_ref_name, c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!! try: c.cs_ranges = [org_scm_instance.get_changeset(x) for x in c.pull_request.revisions] except ChangesetDoesNotExistError: c.cs_ranges = [] h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name), 'error') c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = graph_data(org_scm_instance, revs) c.is_range = False try: if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor cs_a = org_scm_instance.get_changeset(c.a_rev) root_parents = c.cs_ranges[0].parents c.is_range = cs_a in root_parents #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning except ChangesetDoesNotExistError: # probably because c.a_rev not found pass except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing pass avail_revs = set() avail_show = [] c.cs_branch_name = c.cs_ref_name c.a_branch_name = None other_scm_instance = c.a_repo.scm_instance c.update_msg = "" c.update_msg_other = "" try: if not c.cs_ranges: c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor': if c.cs_ref_type != 'branch': c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ? c.a_branch_name = c.a_ref_name if c.a_ref_type != 'branch': try: c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ? except EmptyRepositoryError: c.a_branch_name = 'null' # not a branch name ... but close enough # candidates: descendants of old head that are on the right branch # and not are the old head itself ... # and nothing at all if old head is a descendant of target ref name if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name): c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name elif c.pull_request.is_closed(): c.update_msg = _('This pull request has been closed and can not be updated.') else: # look for descendants of PR head on source branch in org repo avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)', revs[0], c.cs_branch_name) if len(avail_revs) > 1: # more than just revs[0] # also show changesets that not are descendants but would be merged in targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id if org_scm_instance.path != other_scm_instance.path: # Note: org_scm_instance.path must come first so all # valid revision numbers are 100% org_scm compatible # - both for avail_revs and for revset results hgrepo = unionrepo.unionrepository(org_scm_instance.baseui, org_scm_instance.path, other_scm_instance.path) else: hgrepo = org_scm_instance._repo show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s', avail_revs, revs[0], targethead)) c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name else: show = set() avail_revs = set() # drop revs[0] c.update_msg = _('No additional changesets found for iterating on this pull request.') # TODO: handle branch heads that not are tip-most brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0]) if brevs: # also show changesets that are on branch but neither ancestors nor descendants show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name)) show.add(revs[0]) # make sure graph shows this so we can see how they relate c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name, h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id)) avail_show = sorted(show, reverse=True) elif org_scm_instance.alias == 'git': c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not c.update_msg = _("Git pull requests don't support iterating yet.") except ChangesetDoesNotExistError: c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev c.avail_revs = avail_revs c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show] c.avail_jsdata = graph_data(org_scm_instance, avail_show) raw_ids = [x.raw_id for x in c.cs_ranges] c.cs_comments = c.cs_repo.get_comments(raw_ids) c.statuses = c.cs_repo.statuses(raw_ids) ignore_whitespace = request.GET.get('ignorews') == '1' line_context = safe_int(request.GET.get('context'), 3) c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = request.GET.get('fulldiff') diff_limit = self.cut_off_limit if not c.fulldiff else None # we swap org/other ref since we run a simple diff on one repo log.debug('running diff between %s and %s in %s', c.a_rev, c.cs_rev, org_scm_instance.path) try: txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev), ignore_whitespace=ignore_whitespace, context=line_context) except ChangesetDoesNotExistError: txtdiff = _("The diff can't be shown - the PR revisions could not be found.") diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.file_diff_data = [] c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] filename = f['filename'] fid = h.FID('', filename) diff = diff_processor.as_html(enable_comments=True, parsed_lines=[f]) c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st)) # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.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.db_repo.repo_id, pull_request=pull_request_id) # (badly named) pull-request status calculation based on reviewer votes (c.pull_request_reviewers, c.pull_request_pending_reviewers, c.current_voting_result, ) = cs_model.calculate_pull_request_result(c.pull_request) c.changeset_statuses = ChangesetStatus.STATUSES c.is_ajax_preview = False c.ancestors = None # [c.a_rev] ... but that is shown in an other way return render('/pullrequests/pullrequest_show.html')
def setup_method(self, method): self.m = ChangesetStatusModel()
def comment(self, repo_name, pull_request_id): pull_request = PullRequest.get_or_404(pull_request_id) status = request.POST.get('changeset_status') close_pr = request.POST.get('save_close') delete = request.POST.get('save_delete') f_path = request.POST.get('f_path') line_no = request.POST.get('line') if (status or close_pr or delete) and (f_path or line_no): # status votes and closing is only possible in general comments raise HTTPBadRequest() allowed_to_change_status = self._get_is_allowed_change_status(pull_request) if not allowed_to_change_status: if status or close_pr: h.flash(_('No permission to change pull request status'), 'error') raise HTTPForbidden() if delete == "delete": if (pull_request.owner_id == request.authuser.user_id or h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name) ) and not pull_request.is_closed(): PullRequestModel().delete(pull_request) Session().commit() h.flash(_('Successfully deleted pull request %s') % pull_request_id, category='success') return { 'location': url('my_pullrequests'), # or repo pr list? } raise HTTPFound(location=url('my_pullrequests')) # or repo pr list? raise HTTPForbidden() text = request.POST.get('text', '').strip() comment = create_comment( text, status, pull_request_id=pull_request_id, f_path=f_path, line_no=line_no, closing_pr=close_pr, ) action_logger(request.authuser, 'user_commented_pull_request:%s' % pull_request_id, c.db_repo, request.ip_addr) if status: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, request.authuser.user_id, comment, pull_request=pull_request_id ) if close_pr: PullRequestModel().close_pull_request(pull_request_id) action_logger(request.authuser, 'user_closed_pull_request:%s' % pull_request_id, c.db_repo, request.ip_addr) Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): raise HTTPFound(location=pull_request.url()) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } if comment is not None: c.comment = comment data.update(comment.get_dict()) data.update({'rendered_text': render('changeset/changeset_comment_block.html')}) return data
def changeset_status(repo, revision): from kallithea.model.changeset_status import ChangesetStatusModel return ChangesetStatusModel().get_status(repo, revision)
def show(self, repo_name, pull_request_id, extra=None): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.user_groups_array = repo_model.get_user_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() # 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 c.cs_repo = c.pull_request.org_repo (c.cs_ref_type, c.cs_ref_name, c.cs_rev) = c.pull_request.org_ref.split(':') c.a_repo = c.pull_request.other_repo (c.a_ref_type, c.a_ref_name, c.a_rev) = c.pull_request.other_ref.split(':') # other_rev is ancestor org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!! c.cs_repo = c.cs_repo try: c.cs_ranges = [org_scm_instance.get_changeset(x) for x in c.pull_request.revisions] except ChangesetDoesNotExistError: c.cs_ranges = [] c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = json.dumps(graph_data(org_scm_instance, revs)) c.is_range = False if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor cs_a = org_scm_instance.get_changeset(c.a_rev) root_parents = c.cs_ranges[0].parents c.is_range = cs_a in root_parents #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning avail_revs = set() avail_show = [] c.cs_branch_name = c.cs_ref_name other_scm_instance = c.a_repo.scm_instance c.update_msg = "" c.update_msg_other = "" try: if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor': if c.cs_ref_type != 'branch': c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ? c.a_branch_name = c.a_ref_name if c.a_ref_type != 'branch': try: c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ? except EmptyRepositoryError: c.a_branch_name = 'null' # not a branch name ... but close enough # candidates: descendants of old head that are on the right branch # and not are the old head itself ... # and nothing at all if old head is a descendant of target ref name if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name): c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name elif c.pull_request.is_closed(): c.update_msg = _('This pull request has been closed and can not be updated.') else: # look for descendants of PR head on source branch in org repo avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)', revs[0], c.cs_branch_name) if len(avail_revs) > 1: # more than just revs[0] # also show changesets that not are descendants but would be merged in targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id if org_scm_instance.path != other_scm_instance.path: # Note: org_scm_instance.path must come first so all # valid revision numbers are 100% org_scm compatible # - both for avail_revs and for revset results hgrepo = unionrepo.unionrepository(org_scm_instance.baseui, org_scm_instance.path, other_scm_instance.path) else: hgrepo = org_scm_instance._repo show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s', avail_revs, revs[0], targethead)) c.update_msg = _('The following changes are available on %s:') % c.cs_branch_name else: show = set() avail_revs = set() # drop revs[0] c.update_msg = _('No changesets found for updating this pull request.') # TODO: handle branch heads that not are tip-most brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0]) if brevs: # also show changesets that are on branch but neither ancestors nor descendants show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name)) show.add(revs[0]) # make sure graph shows this so we can see how they relate c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name, h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id)) avail_show = sorted(show, reverse=True) elif org_scm_instance.alias == 'git': c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not c.update_msg = _("Git pull requests don't support updates yet.") except ChangesetDoesNotExistError: c.update_msg = _('Error: revision %s was not found. Please create a new pull request!') % c.cs_rev c.avail_revs = avail_revs c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show] c.avail_jsdata = json.dumps(graph_data(org_scm_instance, avail_show)) raw_ids = [x.raw_id for x in c.cs_ranges] c.cs_comments = c.cs_repo.get_comments(raw_ids) c.statuses = c.cs_repo.statuses(raw_ids) ignore_whitespace = request.GET.get('ignorews') == '1' line_context = request.GET.get('context', 3) c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = request.GET.get('fulldiff') diff_limit = self.cut_off_limit if not c.fulldiff else None # we swap org/other ref since we run a simple diff on one repo log.debug('running diff between %s and %s in %s', c.a_rev, c.cs_rev, org_scm_instance.path) try: txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev), ignore_whitespace=ignore_whitespace, context=line_context) except ChangesetDoesNotExistError: txtdiff = _("The diff can't be shown - the PR revisions could not be found.") diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() c.limited_diff = False if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True c.files = [] c.changes = {} c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] fid = h.FID('', f['filename']) c.files.append([fid, f['operation'], f['filename'], f['stats']]) htmldiff = diff_processor.as_html(enable_comments=True, parsed_lines=[f]) c.changes[fid] = [f['operation'], f['filename'], htmldiff] # inline comments c.inline_cnt = 0 c.inline_comments = cc_model.get_inline_comments( c.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.db_repo.repo_id, pull_request=pull_request_id) # (badly named) pull-request status calculation based on reviewer votes (c.pull_request_reviewers, c.pull_request_pending_reviewers, c.current_voting_result, ) = cs_model.calculate_pull_request_result(c.pull_request) 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 setUp(self): self.m = ChangesetStatusModel()
def create_cs_pr_comment(repo_name, revision=None, pull_request=None, allowed_to_change_status=True): """ Add a comment to the specified changeset or pull request, using POST values from the request. Comments can be inline (when a file path and line number is specified in POST) or general comments. A comment can be accompanied by a review status change (accepted, rejected, etc.). Pull requests can be closed or deleted. Parameter 'allowed_to_change_status' is used for both status changes and closing of pull requests. For deleting of pull requests, more specific checks are done. """ assert request.environ.get('HTTP_X_PARTIAL_XHR') if pull_request: pull_request_id = pull_request.pull_request_id else: pull_request_id = None status = request.POST.get('changeset_status') close_pr = request.POST.get('save_close') delete = request.POST.get('save_delete') f_path = request.POST.get('f_path') line_no = request.POST.get('line') if (status or close_pr or delete) and (f_path or line_no): # status votes and closing is only possible in general comments raise HTTPBadRequest() if not allowed_to_change_status: if status or close_pr: h.flash(_('No permission to change status'), 'error') raise HTTPForbidden() if pull_request and delete == "delete": if (pull_request.owner_id == request.authuser.user_id or h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionLevel('admin')( pull_request.org_repo.repo_name) or h.HasRepoPermissionLevel('admin')( pull_request.other_repo.repo_name) ) and not pull_request.is_closed(): PullRequestModel().delete(pull_request) Session().commit() h.flash(_('Successfully deleted pull request %s') % pull_request_id, category='success') return { 'location': h.url('my_pullrequests'), # or repo pr list? } raise HTTPForbidden() text = request.POST.get('text', '').strip() comment = ChangesetCommentsModel().create( text=text, repo=c.db_repo.repo_id, author=request.authuser.user_id, revision=revision, pull_request=pull_request_id, f_path=f_path or None, line_no=line_no or None, status_change=ChangesetStatus.get_status_lbl(status) if status else None, closing_pr=close_pr, ) if status: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, request.authuser.user_id, comment, revision=revision, pull_request=pull_request_id, ) if pull_request: action = 'user_commented_pull_request:%s' % pull_request_id else: action = 'user_commented_revision:%s' % revision action_logger(request.authuser, action, c.db_repo, request.ip_addr) if pull_request and close_pr: PullRequestModel().close_pull_request(pull_request_id) action_logger(request.authuser, 'user_closed_pull_request:%s' % pull_request_id, c.db_repo, request.ip_addr) Session().commit() data = { 'target_id': h.safeid(request.POST.get('f_path')), } if comment is not None: c.comment = comment data.update(comment.get_dict()) data.update({ 'rendered_text': render('changeset/changeset_comment_block.html') }) return data
def _index(self, revision, method): c.pull_request = None c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = request.GET.get( 'fulldiff') # for reporting number of changed files # get ranges of revisions if preset rev_range = revision.split('...')[:2] enable_comments = True c.cs_repo = c.db_repo try: if len(rev_range) == 2: enable_comments = False rev_start = rev_range[0] rev_end = rev_range[1] rev_ranges = c.db_repo_scm_instance.get_changesets( start=rev_start, end=rev_end) else: rev_ranges = [c.db_repo_scm_instance.get_changeset(revision)] c.cs_ranges = list(rev_ranges) if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') except (ChangesetDoesNotExistError, EmptyRepositoryError): log.debug(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() c.changes = OrderedDict() c.lines_added = 0 # count of lines added c.lines_deleted = 0 # count of lines removes c.changeset_statuses = ChangesetStatus.STATUSES comments = dict() c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: if method == 'show': c.statuses.extend([ ChangesetStatusModel().get_status(c.db_repo.repo_id, changeset.raw_id) ]) # Changeset comments comments.update( (com.comment_id, com) for com in ChangesetCommentsModel().get_comments( c.db_repo.repo_id, revision=changeset.raw_id)) # Status change comments - mostly from pull requests comments.update(( st.comment_id, st.comment ) for st in ChangesetStatusModel().get_statuses( c.db_repo.repo_id, changeset.raw_id, with_revisions=True) if st.comment_id is not None) inlines = ChangesetCommentsModel() \ .get_inline_comments(c.db_repo.repo_id, revision=changeset.raw_id) c.inline_comments.extend(inlines) cs2 = changeset.raw_id cs1 = changeset.parents[ 0].raw_id if changeset.parents else EmptyChangeset().raw_id context_lcl = get_line_ctx('', request.GET) ign_whitespace_lcl = get_ignore_ws('', request.GET) raw_diff = diffs.get_diff(c.db_repo_scm_instance, cs1, cs2, ignore_whitespace=ign_whitespace_lcl, context=context_lcl) diff_limit = None if c.fulldiff else self.cut_off_limit file_diff_data = [] if method == 'show': diff_processor = diffs.DiffProcessor( raw_diff, vcs=c.db_repo_scm_instance.alias, diff_limit=diff_limit) c.limited_diff = diff_processor.limited_diff for f in diff_processor.parsed: st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] filename = f['filename'] fid = h.FID(changeset.raw_id, filename) url_fid = h.FID('', filename) html_diff = diffs.as_html(enable_comments=enable_comments, parsed_lines=[f]) file_diff_data.append( (fid, url_fid, f['operation'], f['old_filename'], filename, html_diff, st)) else: # downloads/raw we only need RAW diff nothing else file_diff_data.append(('', None, None, None, raw_diff, None)) c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) # sort comments in creation order c.comments = [com for com_id, com in sorted(comments.items())] # count inline comments for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) if len(c.cs_ranges) == 1: c.changeset = c.cs_ranges[0] c.parent_tmpl = ''.join( ['# Parent %s\n' % x.raw_id for x in c.changeset.parents]) c.changeset_graft_source_hash = ascii_str( c.changeset.extra.get(b'source', b'')) c.changeset_transplant_source_hash = ascii_str( binascii.hexlify( c.changeset.extra.get(b'transplant_source', b''))) if method == 'download': response.content_type = 'text/plain' response.content_disposition = 'attachment; filename=%s.diff' \ % revision[:12] return raw_diff elif method == 'patch': response.content_type = 'text/plain' c.diff = safe_str(raw_diff) return render('changeset/patch_changeset.html') elif method == 'raw': response.content_type = 'text/plain' return raw_diff elif method == 'show': if len(c.cs_ranges) == 1: return render('changeset/changeset.html') else: c.cs_ranges_org = None c.cs_comments = {} revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = graph_data(c.db_repo_scm_instance, revs) return render('changeset/changeset_range.html')