def check_acl(self, session, project, username, refname, pull_request, repotype, is_internal, **info): if is_internal: self.info("Internal push allowed") return True # Check whether a PR is required for this repo or in general global_pr_only = pagure_config.get("PR_ONLY", False) pr_only = project.settings.get("pull_request_access_only", False) if repotype == "main": if (pr_only or (global_pr_only and not project.is_fork)) and not pull_request: self.info("Pull request required") return False if username is None: return False # Determine whether the current user is allowed to push is_committer = is_repo_committer(project, username, session) deploykey = lookup_deploykey(project, username) if deploykey is not None: self.info("Deploykey used. Push access: %s" % deploykey.pushaccess) is_committer = deploykey.pushaccess self.info("Has commit access: %s" % is_committer) return is_committer
def check_acl( self, session, project, username, refname, pull_request, repotype, is_internal, **info ): if is_internal: self.info("Internal push allowed") return True # Check whether a PR is required for this repo or in general global_pr_only = pagure_config.get("PR_ONLY", False) pr_only = project.settings.get("pull_request_access_only", False) if repotype == "main": if ( pr_only or (global_pr_only and not project.is_fork) ) and not pull_request: self.info("Pull request required") return False # Determine whether the current user is allowed to push is_committer = is_repo_committer(project, username, session) deploykey = lookup_deploykey(project, username) if deploykey is not None: self.info("Deploykey used. Push access: %s" % deploykey.pushaccess) is_committer = deploykey.pushaccess self.info("Has commit access: %s" % is_committer) return is_committer
def _check_private_pull_request_access(request): """Check if user can access PR. Must be repo committer or author to see private PR. :param request: PullRequest object :raises pagure.exceptions.APIError: when access denied """ if (request.private and not is_repo_committer(request.project) and (not api_authenticated() or not request.user.user == flask.g.fas_user.username)): raise pagure.exceptions.APIError(403, error_code=APIERROR.EPRNOTALLOWED)
def _check_private_issue_access(issue): """Check if user can access issue. Must be repo committer or author to see private issues. :param issue: issue object :raises pagure.exceptions.APIError: when access denied """ if (issue.private and not is_repo_committer(issue.project) and (not api_authenticated() or not issue.user.user == flask.g.fas_user.username)): raise pagure.exceptions.APIError(403, error_code=APIERROR.EISSUENOTALLOWED)
def _check_private_pull_request_access(request): """Check if user can access PR. Must be repo committer or author to see private PR. :param request: PullRequest object :raises pagure.exceptions.APIError: when access denied """ if ( request.private and not is_repo_committer(request.project) and ( not api_authenticated() or not request.user.user == flask.g.fas_user.username ) ): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EPRNOTALLOWED )
def _check_private_issue_access(issue): """Check if user can access issue. Must be repo committer or author to see private issues. :param issue: issue object :raises pagure.exceptions.APIError: when access denied """ if ( issue.private and not is_repo_committer(issue.project) and ( not api_authenticated() or not issue.user.user == flask.g.fas_user.username ) ): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED )
def api_pull_request_close(repo, requestid, username=None, namespace=None): """ Close a pull-request -------------------- Instruct Pagure to close a pull request. :: POST /api/0/<repo>/pull-request/<request id>/close POST /api/0/<namespace>/<repo>/pull-request/<request id>/close :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/close POST /api/0/fork/<username>/<namespace>/<repo>/pull-request/<request id>/close Sample response ^^^^^^^^^^^^^^^ :: { "message": "Pull-request closed!" } """ # noqa output = {} repo = get_authorized_api_project( flask.g.session, repo, user=username, namespace=namespace ) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get("pull_requests", True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED ) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.query.search_pull_requests( flask.g.session, project_id=repo.id, requestid=requestid ) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) if not is_repo_committer(repo): raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE) try: pagure.lib.query.close_pull_request( flask.g.session, request, flask.g.fas_user.username, merged=False ) flask.g.session.commit() output["message"] = "Pull-request closed!" except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_pull_request_merge(repo, requestid, username=None, namespace=None): """ Merge a pull-request -------------------- Instruct Pagure to merge a pull request. This is an asynchronous call. :: POST /api/0/<repo>/pull-request/<request id>/merge POST /api/0/<namespace>/<repo>/pull-request/<request id>/merge :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/merge POST /api/0/fork/<username>/<namespace>/<repo>/pull-request/<request id>/merge Sample response ^^^^^^^^^^^^^^^ :: wait=False: { "message": "Merging queued", "taskid": "123-abcd" } wait=True: { "message": "Changes merged!" } """ # noqa output = {} repo = get_authorized_api_project( flask.g.session, repo, user=username, namespace=namespace ) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get("pull_requests", True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED ) if flask.g.token.project and repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.query.search_pull_requests( flask.g.session, project_id=repo.id, requestid=requestid ) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) if not is_repo_committer(repo): raise pagure.exceptions.APIError(403, error_code=APIERROR.ENOPRCLOSE) if repo.settings.get("Only_assignee_can_merge_pull-request", False): if not request.assignee: raise pagure.exceptions.APIError( 403, error_code=APIERROR.ENOTASSIGNED ) if request.assignee.username != flask.g.fas_user.username: raise pagure.exceptions.APIError( 403, error_code=APIERROR.ENOTASSIGNEE ) threshold = repo.settings.get("Minimum_score_to_merge_pull-request", -1) if threshold > 0 and int(request.score) < int(threshold): raise pagure.exceptions.APIError(403, error_code=APIERROR.EPRSCORE) task = pagure.lib.tasks.merge_pull_request.delay( repo.name, namespace, username, requestid, flask.g.fas_user.username ) output = {"message": "Merging queued", "taskid": task.id} if get_request_data().get("wait", True): task.get() output = {"message": "Changes merged!"} jsonout = flask.jsonify(output) return jsonout
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+-------------+---------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "asd", "custom_fields": [], "date_created": "1427442217", "depends": [], "id": 4, "last_updated": "1533815358", "milestone": null, "priority": null, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ], 'pagination': { 'first': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'last': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': null }, } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) assignee = flask.request.args.get("assignee", None) author = flask.request.args.get("author", None) milestone = flask.request.args.getlist("milestones", None) no_stones = flask.request.args.get("no_stones", None) if no_stones is not None: no_stones = is_true(no_stones) priority = flask.request.args.get("priority", None) since = flask.request.args.get("since", None) order = flask.request.args.get("order", None) status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] search_id = flask.request.args.get("query_id", None) priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY ) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { "session": flask.g.session, "repo": repo, "tags": tags, "assignee": assignee, "author": author, "private": private, "milestones": milestone, "priority": priority_key, "order": order, "no_milestones": no_stones, "search_id": search_id, } if status is not None: if status.lower() == "all": params.update({"status": None}) elif status.lower() == "closed": params.update({"closed": True}) else: params.update({"status": status}) else: params.update({"status": "Open"}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = arrow.get(int(since)).datetime except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP ) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, "%Y-%m-%d") except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EDATETIME ) params.update({"updated_after": updated_after}) page = get_page() per_page = get_per_page() params["count"] = True issue_cnt = pagure.lib.query.search_issues(**params) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issue_cnt ) query_start = (page - 1) * per_page query_limit = per_page params["count"] = False params["limit"] = query_limit params["offset"] = query_start issues = pagure.lib.query.search_issues(**params) jsonout = flask.jsonify( { "total_issues": len(issues), "issues": [issue.to_json(public=True) for issue in issues], "args": { "assignee": assignee, "author": author, "milestones": milestone, "no_stones": no_stones, "order": order, "priority": priority, "since": since, "status": status, "tags": tags, }, "pagination": pagination_metadata, } ) return jsonout
def format_loc(loc, commit=None, filename=None, tree_id=None, prequest=None, index=None): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = ['<div class="highlight">', '<table class="code_table">'] comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if commit and unicode(com.commit_id) == unicode(commit) \ and unicode(com.filename) == unicode(filename): if com.line in comments: comments[com.line].append(com) else: comments[com.line] = [com] for key in comments: comments[key] = sorted(comments[key], key=lambda obj: obj.date_created) if not index: index = '' cnt = 1 for line in loc.split('\n'): if line == '</pre></div>': break if filename and commit: output.append( '<tr id="c-%(commit)s-%(cnt_lbl)s"><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s"></a></td>' '<td class="prc" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' '<p>' '<span class="oi prc_img" data-glyph="comment-square" ' 'alt="Add comment" title="Add comment"></span>' '</p>' '</td>' % ({ 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, 'filename': filename.decode('UTF-8'), 'commit': commit, 'tree_id': tree_id, })) else: output.append('<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s"></a></td>' % ({ 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, })) cnt += 1 if not line: output.append(line) continue if line.startswith('<div'): line = line.split('<pre style="line-height: 125%">')[1] if prequest and prequest.project_from: rangeline = line.partition('font-weight: bold">@@ ')[2] \ if line.partition('font-weight: bold">@@ ')[1] == \ 'font-weight: bold">@@ ' else None if rangeline: rangeline = rangeline.split(' @@</span>')[0] linenumber = rangeline.split('+')[1].split(',')[0] line = line + ' <a href="%s#_%s" target="_blank" ' % ( flask.url_for( 'ui_ns.view_file', repo=prequest.project_from.name, username=prequest.project_from.user.username if prequest.project_from.is_fork else None, namespace=prequest.project_from.namespace, identifier=prequest.branch_from, filename=filename), linenumber) line = line + 'class="open_changed_file_icon_wrap">' + \ '<span class="oi open_changed_file_icon" ' + \ 'data-glyph="eye" alt="Open changed file" ' + \ 'title="Open changed file"></span></a>' output.append('<td class="cell2"><pre>%s</pre></td>' % line) output.append('</tr>') tpl_edit = '<a href="%(edit_url)s" ' \ 'class="btn btn-secondary btn-sm" data-comment="%(commentid)s" ' \ 'data-objid="%(requestid)s">' \ '<span class="oi" data-glyph="pencil"></span>' \ '</a>' tpl_edited = '<small class="text-muted" title="%(edit_date)s"> ' \ 'Edited %(human_edit_date)s by %(user)s </small>' tpl_delete = '<button class="btn btn-secondary btn-sm" '\ 'title="Remove comment" '\ 'name="drop_comment" value="%(commentid)s" type="submit" ' \ 'onclick="return confirm(\'Do you really want to remove this' \ ' comment?\');" ><span class="oi" data-glyph="trash"></span>' \ '</button>' if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = '' templ_edit = '' templ_edited = '' status = str(comment.parent.status).lower() if authenticated() and ( (status in ['true', 'open'] and comment.user.user == flask.g.fas_user.username) or is_repo_committer(comment.parent.project)): templ_delete = tpl_delete % ({'commentid': comment.id}) templ_edit = tpl_edit % ({ 'edit_url': flask.url_for( 'ui_ns.pull_request_edit_comment', repo=comment.parent.project.name, requestid=comment.parent.id, commentid=comment.id, username=comment.parent.user.user if comment.parent.project.is_fork else None), 'requestid': comment.parent.id, 'commentid': comment.id, }) if comment.edited_on: templ_edited = tpl_edited % ( { 'edit_date': format_ts(comment.edited_on), 'human_edit_date': humanize_date( comment.edited_on), 'user': comment.editor.user, }) output.append( '<tr class="inline-pr-comment"><td></td>' '<td colspan="2">' '<div class="card clearfix m-x-1 ">' '<div class="card-block">' '<small><div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s" title="%(user_html)s">' '%(user)s</a> commented ' '<a class="headerlink" title="Permalink ' 'to this headline" href="#comment-%(commentid)s">' '<span title="%(date)s">%(human_date)s</span>' '</a></div></small>' '<section class="issue_comment">' '<div class="comment_body">' '%(comment)s' '</div>' '</section>' '<div class="issue_actions m-t-2">' '%(templ_edited)s' '<aside class="btn-group issue_action icon ' 'pull-xs-right p-b-1">' '%(templ_edit)s' '%(templ_delete)s' '</aside>' '</div></div></div>' '</td></tr>' % ({ 'url': flask.url_for('ui_ns.view_user', username=comment.user.user), 'templ_delete': templ_delete, 'templ_edit': templ_edit, 'templ_edited': templ_edited, 'user': comment.user.user, 'user_html': comment.user.html_title, 'avatar_url': avatar_url(comment.user.default_email, 16), 'date': format_ts(comment.date_created), 'human_date': humanize_date(comment.date_created), 'comment': markdown_filter(comment.comment), 'commentid': comment.id, })) output.append('</table></div>') return '\n'.join(output)
def format_loc( loc, commit=None, filename=None, tree_id=None, prequest=None, index=None, isprdiff=False, ): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = ['<div class="highlight">', '<table class="code_table">'] commit_hash = commit if hasattr(commit_hash, "hex"): commit_hash = commit_hash.hex comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if (commit and com.commit_id == commit_hash and com.filename == filename): if com.line in comments: comments[com.line].append(com) else: comments[com.line] = [com] for key in comments: comments[key] = sorted(comments[key], key=lambda obj: obj.date_created) if not index: index = "" cnt = 1 for line in loc.split("\n"): if filename and commit: if isinstance(filename, str) and six.PY2: filename = filename.decode("UTF-8") if isprdiff and (line.startswith("@@") or line.startswith("+") or line.startswith("-")): if line.startswith("@@"): output.append('<tr class="stretch-table-column bg-light"\ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) elif line.startswith("+"): output.append( '<tr class="stretch-table-column alert-success" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) elif line.startswith("-"): output.append( '<tr class="stretch-table-column alert-danger" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) else: output.append('<tr id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) output.append( '<td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s" data-file-number=' '"%(line)s"></a></td>' '<td class="prc border-right" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' "<p>" '<span class="fa fa-comment prc_img" style="display: none;"' 'alt="Add comment" title="Add comment"></span>' "</p>" "</td>" % ({ "cnt": "_%s__%s" % (index, cnt), "cnt_lbl": cnt, "line": index, "filename": filename, "commit": commit, "tree_id": tree_id, })) else: output.append('<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s"></a></td>' % ({ "cnt": "%s_%s" % (index, cnt), "cnt_lbl": cnt })) cnt += 1 if not line: output.append(line) continue if line.startswith("@@"): if prequest and prequest.project_from: rangeline = (line.partition("@@ ")[2] if line.partition("@@ ")[1] == "@@ " else None) if rangeline: rangeline = rangeline.split(" @@")[0] linenumber = rangeline.split("+")[1].split(",")[0] line = line + ' <a href="%s#_%s" target="_blank" ' % ( flask.url_for( "ui_ns.view_file", repo=prequest.project_from.name, username=prequest.project_from.user.username if prequest.project_from.is_fork else None, namespace=prequest.project_from.namespace, identifier=prequest.branch_from, filename=filename, ), linenumber, ) line = (line + 'class="open_changed_file_icon_wrap">' + '<span class="fa fa-file-code-o fa-fw" ' + 'alt="Open changed file" ' + 'title="Open changed file"></span></a>') if isprdiff and (line.startswith("@@") or line.startswith("+") or line.startswith("-")): if line.startswith("@@"): output.append('<td class="cell2 stretch-table-column">\ <pre class="text-muted"><code>%s</code></pre></td>' % line) elif line.startswith("+"): output.append('<td class="cell2 stretch-table-column">\ <pre class="alert-success"><code>%s</code></pre></td>' % escape(line)) elif line.startswith("-"): output.append('<td class="cell2 stretch-table-column">\ <pre class="alert-danger"><code>%s</code></pre></td>' % escape(line)) else: output.append('<td class="cell2"><pre><code>%s</code></pre></td>' % (escape(line))) output.append("</tr>") tpl_edit = ('<a href="%(edit_url)s" ' 'class="btn btn-outline-primary border-0" ' 'data-comment="%(commentid)s" ' 'data-objid="%(requestid)s">' '<i class="fa fa-pencil"></i>' "</a>") tpl_edited = ('<small class="text-muted" title="%(edit_date)s"> ' "Edited %(human_edit_date)s by %(user)s </small>") tpl_delete = ( '<button class="btn btn-outline-primary border-0" ' 'title="Remove comment" ' 'name="drop_comment" value="%(commentid)s" type="submit" ' "onclick=\"return confirm('Do you really want to remove this" ' comment?\');" ><i class="fa fa-trash"></i>' "</button>") if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = "" templ_edit = "" templ_edited = "" if authenticated() and ( (is_true(comment.parent.status, ["true", "open"]) and comment.user.user == flask.g.fas_user.username) or is_repo_committer(comment.parent.project)): templ_delete = tpl_delete % ({"commentid": comment.id}) templ_edit = tpl_edit % ({ "edit_url": flask.url_for( "ui_ns.pull_request_edit_comment", repo=comment.parent.project.name, requestid=comment.parent.id, commentid=comment.id, username=comment.parent.user.user if comment.parent.project.is_fork else None, ), "requestid": comment.parent.id, "commentid": comment.id, }) if comment.edited_on: templ_edited = tpl_edited % ( { "edit_date": format_ts(comment.edited_on), "human_edit_date": humanize_date( comment.edited_on), "user": comment.editor.user, }) output.append( '<tr class="inline-pr-comment">' '<td colspan="3" class="p-3 border">' '<div class="card clearfix">' '<div class="card-header bg-light d-flex ' 'align-items-center px-3 py-2">' "<div>" '<div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s" title="%(user_html)s">' "%(user)s</a> commented " '<a class="headerlink" title="Permalink ' 'to this headline" href="#comment-%(commentid)s">' '<span title="%(date)s">%(human_date)s</span>' "</a></div>" "</div>" '<div class="mr-auto">' "%(templ_edit)s" "%(templ_delete)s" "</div>" "</div>" '<div class="card-block">' "<small></small>" '<section class="issue_comment">' '<div class="comment_body">' "%(comment)s" "</div>" "</section>" "</div></div>" "</td></tr>" % ({ "url": flask.url_for("ui_ns.view_user", username=comment.user.user), "templ_delete": templ_delete, "templ_edit": templ_edit, "templ_edited": templ_edited, "user": comment.user.user, "user_html": comment.user.html_title, "avatar_url": avatar_url(comment.user.default_email, 16), "date": format_ts(comment.date_created), "human_date": humanize_date(comment.date_created), "comment": markdown_filter(comment.comment), "commentid": comment.id, })) output.append("</table></div>") return "\n".join(output)
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+-------------+---------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "asd", "custom_fields": [], "date_created": "1427442217", "depends": [], "id": 4, "last_updated": "1533815358", "milestone": null, "priority": null, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ], 'pagination': { 'first': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'last': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': null }, } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) assignee = flask.request.args.get("assignee", None) author = flask.request.args.get("author", None) milestone = flask.request.args.getlist("milestones", None) no_stones = flask.request.args.get("no_stones", None) if no_stones is not None: no_stones = is_true(no_stones) priority = flask.request.args.get("priority", None) since = flask.request.args.get("since", None) order = flask.request.args.get("order", None) status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] search_id = flask.request.args.get("query_id", None) priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { "session": flask.g.session, "repo": repo, "tags": tags, "assignee": assignee, "author": author, "private": private, "milestones": milestone, "priority": priority_key, "order": order, "no_milestones": no_stones, "search_id": search_id, } if status is not None: if status.lower() == "all": params.update({"status": None}) elif status.lower() == "closed": params.update({"closed": True}) else: params.update({"status": status}) else: params.update({"status": "Open"}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = arrow.get(int(since)).datetime except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, "%Y-%m-%d") except ValueError: raise pagure.exceptions.APIError(400, error_code=APIERROR.EDATETIME) params.update({"updated_after": updated_after}) page = get_page() per_page = get_per_page() params["count"] = True issue_cnt = pagure.lib.query.search_issues(**params) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issue_cnt) query_start = (page - 1) * per_page query_limit = per_page params["count"] = False params["limit"] = query_limit params["offset"] = query_start issues = pagure.lib.query.search_issues(**params) jsonout = flask.jsonify({ "total_issues": len(issues), "issues": [issue.to_json(public=True) for issue in issues], "args": { "assignee": assignee, "author": author, "milestones": milestone, "no_stones": no_stones, "order": order, "priority": priority, "since": since, "status": status, "tags": tags, }, "pagination": pagination_metadata, }) return jsonout
def format_loc( loc, commit=None, filename=None, tree_id=None, prequest=None, index=None, isprdiff=False, ): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = ['<div class="highlight">', '<table class="code_table">'] commit_hash = commit if hasattr(commit_hash, "hex"): commit_hash = commit_hash.hex comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if ( commit and com.commit_id == commit_hash and com.filename == filename ): if com.line in comments: comments[com.line].append(com) else: comments[com.line] = [com] for key in comments: comments[key] = sorted(comments[key], key=lambda obj: obj.date_created) if not index: index = "" cnt = 1 for line in loc.split("\n"): if filename and commit: if isinstance(filename, str) and six.PY2: filename = filename.decode("UTF-8") if isprdiff and ( line.startswith("@@") or line.startswith("+") or line.startswith("-") ): if line.startswith("@@"): output.append( '<tr class="stretch-table-column bg-light"\ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) elif line.startswith("+"): output.append( '<tr class="stretch-table-column alert-success" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) elif line.startswith("-"): output.append( '<tr class="stretch-table-column alert-danger" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) else: output.append( '<tr id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) output.append( '<td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s" data-file-number=' '"%(line)s"></a></td>' '<td class="prc border-right" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' "<p>" '<span class="fa fa-comment prc_img" style="display: none;"' 'alt="Add comment" title="Add comment"></span>' "</p>" "</td>" % ( { "cnt": "_%s__%s" % (index, cnt), "cnt_lbl": cnt, "line": index, "filename": filename, "commit": commit, "tree_id": tree_id, } ) ) else: output.append( '<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s"></a></td>' % ({"cnt": "%s_%s" % (index, cnt), "cnt_lbl": cnt}) ) cnt += 1 if not line: output.append(line) continue if line.startswith("@@"): if prequest and prequest.project_from: rangeline = ( line.partition("@@ ")[2] if line.partition("@@ ")[1] == "@@ " else None ) if rangeline: rangeline = rangeline.split(" @@")[0] linenumber = rangeline.split("+")[1].split(",")[0] line = line + ' <a href="%s#_%s" target="_blank" ' % ( flask.url_for( "ui_ns.view_file", repo=prequest.project_from.name, username=prequest.project_from.user.username if prequest.project_from.is_fork else None, namespace=prequest.project_from.namespace, identifier=prequest.branch_from, filename=filename, ), linenumber, ) line = ( line + 'class="open_changed_file_icon_wrap">' + '<span class="fa fa-file-code-o fa-fw" ' + 'alt="Open changed file" ' + 'title="Open changed file"></span></a>' ) if isprdiff and ( line.startswith("@@") or line.startswith("+") or line.startswith("-") ): if line.startswith("@@"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="text-muted"><code>%s</code></pre></td>' % line ) elif line.startswith("+"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="alert-success"><code>%s</code></pre></td>' % escape(line) ) elif line.startswith("-"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="alert-danger"><code>%s</code></pre></td>' % escape(line) ) else: output.append( '<td class="cell2"><pre><code>%s</code></pre></td>' % (escape(line)) ) output.append("</tr>") tpl_edit = ( '<a href="%(edit_url)s" ' 'class="btn btn-outline-primary border-0" ' 'data-comment="%(commentid)s" ' 'data-objid="%(requestid)s">' '<i class="fa fa-pencil"></i>' "</a>" ) tpl_edited = ( '<small class="text-muted" title="%(edit_date)s"> ' "Edited %(human_edit_date)s by %(user)s </small>" ) tpl_delete = ( '<button class="btn btn-outline-primary border-0" ' 'title="Remove comment" ' 'name="drop_comment" value="%(commentid)s" type="submit" ' "onclick=\"return confirm('Do you really want to remove this" ' comment?\');" ><i class="fa fa-trash"></i>' "</button>" ) if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = "" templ_edit = "" templ_edited = "" if authenticated() and ( ( is_true(comment.parent.status, ["true", "open"]) and comment.user.user == flask.g.fas_user.username ) or is_repo_committer(comment.parent.project) ): templ_delete = tpl_delete % ({"commentid": comment.id}) templ_edit = tpl_edit % ( { "edit_url": flask.url_for( "ui_ns.pull_request_edit_comment", repo=comment.parent.project.name, namespace=comment.parent.project.namespace, requestid=comment.parent.id, commentid=comment.id, username=comment.parent.user.user if comment.parent.project.is_fork else None, ), "requestid": comment.parent.id, "commentid": comment.id, } ) if comment.edited_on: templ_edited = tpl_edited % ( { "edit_date": format_ts(comment.edited_on), "human_edit_date": humanize_date( comment.edited_on ), "user": comment.editor.user, } ) output.append( '<tr class="inline-pr-comment">' '<td colspan="3" class="p-3 border">' '<div class="card clearfix">' '<div class="card-header bg-light d-flex ' 'align-items-center px-3 py-2">' "<div>" '<div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s" title="%(user_html)s">' "%(user)s</a> commented " '<a class="headerlink" title="Permalink ' 'to this headline" href="#comment-%(commentid)s">' '<span title="%(date)s">%(human_date)s</span>' "</a></div>" "</div>" '<div class="mr-auto">' "%(templ_edit)s" "%(templ_delete)s" "</div>" "</div>" '<div class="card-block">' "<small></small>" '<section class="issue_comment">' '<div class="comment_body">' "%(comment)s" "</div>" "</section>" "</div></div>" "</td></tr>" % ( { "url": flask.url_for( "ui_ns.view_user", username=comment.user.user ), "templ_delete": templ_delete, "templ_edit": templ_edit, "templ_edited": templ_edited, "user": comment.user.user, "user_html": comment.user.html_title, "avatar_url": avatar_url( comment.user.default_email, 16 ), "date": format_ts(comment.date_created), "human_date": humanize_date(comment.date_created), "comment": markdown_filter(comment.comment), "commentid": comment.id, } ) ) output.append("</table></div>") return "\n".join(output)
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) milestone = flask.request.args.getlist('milestones', None) no_stones = flask.request.args.get('no_stones', None) if no_stones is not None: if str(no_stones).lower() in ['1', 'true', 't']: no_stones = True else: no_stones = False priority = flask.request.args.get('priority', None) since = flask.request.args.get('since', None) order = flask.request.args.get('order', None) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { 'session': flask.g.session, 'repo': repo, 'tags': tags, 'assignee': assignee, 'author': author, 'private': private, 'milestones': milestone, 'priority': priority_key, 'order': order, 'no_milestones': no_stones, } if status is not None: if status.lower() == 'all': params.update({'status': None}) elif status.lower() == 'closed': params.update({'closed': True}) else: params.update({'status': status}) else: params.update({'status': 'Open'}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = arrow.get(int(since)).datetime except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, '%Y-%m-%d') except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EDATETIME) params.update({'updated_after': updated_after}) issues = pagure.lib.search_issues(**params) jsonout = flask.jsonify({ 'total_issues': len(issues), 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'assignee': assignee, 'author': author, 'milestones': milestone, 'no_stones': no_stones, 'order': order, 'priority': priority, 'since': since, 'status': status, 'tags': tags, } }) return jsonout