# -*- coding: utf-8 -*-
"""
(c) 2014-2017 - Copyright Red Hat Inc
Authors:
Pierre-Yves Chibon <pingou@pingoured.fr>
"""
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-arguments
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=too-many-lines
from __future__ import unicode_literals, absolute_import
import logging
import os
from math import ceil
import flask
import pygit2
from sqlalchemy.exc import SQLAlchemyError
import pagure
import pagure.doc_utils
import pagure.exceptions
import pagure.lib.git
import pagure.lib.plugins
import pagure.lib.query
import pagure.lib.tasks
import pagure.forms
from pagure.config import config as pagure_config
from pagure.ui import UI_NS
from pagure.utils import (
login_required,
__get_file_in_tree,
get_parent_repo_path,
is_true,
)
_log = logging.getLogger(__name__)
def _get_parent_request_repo_path(repo):
""" Return the path of the parent git repository corresponding to the
provided Repository object from the DB.
"""
if repo.parent:
return repo.parent.repopath("requests")
else:
return repo.repopath("requests")
@UI_NS.route("/<repo>/pull-requests/")
@UI_NS.route("/<repo>/pull-requests")
@UI_NS.route("/<namespace>/<repo>/pull-requests/")
@UI_NS.route("/<namespace>/<repo>/pull-requests")
@UI_NS.route("/fork/<username>/<repo>/pull-requests/")
@UI_NS.route("/fork/<username>/<repo>/pull-requests")
@UI_NS.route("/fork/<username>/<namespace>/<repo>/pull-requests/")
@UI_NS.route("/fork/<username>/<namespace>/<repo>/pull-requests")
def request_pulls(repo, username=None, namespace=None):
""" List all Pull-requests associated to a repo
"""
status = flask.request.args.get("status", "Open")
assignee = flask.request.args.get("assignee", None)
search_pattern = flask.request.args.get("search_pattern", None)
author = flask.request.args.get("author", None)
order = flask.request.args.get("order", "desc")
order_key = flask.request.args.get("order_key", "date_created")
repo = flask.g.repo
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
total_open = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, status=True, count=True
)
total_merged = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, status="Merged", count=True
)
if status.lower() == "merged" or is_true(status, ["false", "0"]):
status_filter = "Merged"
requests = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Merged",
order=order,
order_key=order_key,
assignee=assignee,
author=author,
search_pattern=search_pattern,
offset=flask.g.offset,
limit=flask.g.limit,
)
elif is_true(status, ["true", "1", "open"]):
status_filter = "Open"
requests = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Open",
order=order,
order_key=order_key,
assignee=assignee,
author=author,
search_pattern=search_pattern,
offset=flask.g.offset,
limit=flask.g.limit,
)
elif status.lower() == "closed":
status_filter = "Closed"
requests = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Closed",
order=order,
order_key=order_key,
assignee=assignee,
author=author,
search_pattern=search_pattern,
offset=flask.g.offset,
limit=flask.g.limit,
)
else:
status_filter = None
requests = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status=None,
order=order,
order_key=order_key,
assignee=assignee,
author=author,
search_pattern=search_pattern,
offset=flask.g.offset,
limit=flask.g.limit,
)
open_cnt = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Open",
assignee=assignee,
author=author,
search_pattern=search_pattern,
count=True,
)
merged_cnt = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Merged",
assignee=assignee,
author=author,
search_pattern=search_pattern,
count=True,
)
closed_cnt = pagure.lib.query.search_pull_requests(
flask.g.session,
project_id=repo.id,
status="Closed",
assignee=assignee,
author=author,
search_pattern=search_pattern,
count=True,
)
repo_obj = flask.g.repo_obj
if not repo_obj.is_empty and not repo_obj.head_is_unborn:
head = repo_obj.head.shorthand
else:
head = "master"
total_page = 1
if len(requests):
if status_filter == "Closed":
total_requests = closed_cnt
elif status_filter == "Merged":
total_requests = merged_cnt
elif status_filter == "Open":
total_requests = open_cnt
else:
total_requests = closed_cnt + merged_cnt + open_cnt
total_page = int(ceil(total_requests / float(flask.g.limit)))
return flask.render_template(
"requests.html",
select="requests",
repo=repo,
username=username,
requests=requests,
open_cnt=open_cnt,
merged_cnt=merged_cnt,
closed_cnt=closed_cnt,
order=order,
order_key=order_key,
status=status,
status_filter=status_filter,
assignee=assignee,
author=author,
search_pattern=search_pattern,
head=head,
total_page=total_page,
total_open=total_open,
total_merged=total_merged,
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>/")
@UI_NS.route("/<repo>/pull-request/<int:requestid>")
@UI_NS.route("/<namespace>/<repo>/pull-request/<int:requestid>/")
@UI_NS.route("/<namespace>/<repo>/pull-request/<int:requestid>")
@UI_NS.route("/fork/<username>/<repo>/pull-request/<int:requestid>/")
@UI_NS.route("/fork/<username>/<repo>/pull-request/<int:requestid>")
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/"
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>"
)
def request_pull(repo, requestid, username=None, namespace=None):
""" Create a pull request with the changes from the fork into the project.
"""
repo = flask.g.repo
_log.info("Viewing pull Request #%s repo: %s", requestid, repo.fullname)
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if request.remote:
repopath = pagure.utils.get_remote_repo_path(
request.remote_git, request.branch_from
)
parentpath = pagure.utils.get_repo_path(request.project)
else:
repo_from = request.project_from
parentpath = pagure.utils.get_repo_path(request.project)
repopath = parentpath
if repo_from:
repopath = pagure.utils.get_repo_path(repo_from)
repo_obj = pygit2.Repository(repopath)
orig_repo = pygit2.Repository(parentpath)
diff_commits = []
diff = None
# Closed pull-request
if request.status != "Open":
commitid = request.commit_stop
try:
for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_NONE):
diff_commits.append(commit)
if commit.oid.hex == request.commit_start:
break
except KeyError:
# This happens when repo.walk() cannot find commitid
pass
if diff_commits:
# Ensure the first commit in the PR as a parent, otherwise
# point to it
start = diff_commits[-1].oid.hex
if diff_commits[-1].parents:
start = diff_commits[-1].parents[0].oid.hex
# If the start and the end commits are the same, it means we are,
# dealing with one commit that has no parent, so just diff that
# one commit
if start == diff_commits[0].oid.hex:
diff = diff_commits[0].tree.diff_to_tree(swap=True)
else:
diff = repo_obj.diff(
repo_obj.revparse_single(start),
repo_obj.revparse_single(diff_commits[0].oid.hex),
)
else:
try:
diff_commits, diff = pagure.lib.git.diff_pull_request(
flask.g.session, request, repo_obj, orig_repo
)
except pagure.exceptions.PagureException as err:
flask.flash("%s" % err, "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not update this pull-request in the database", "error"
)
if diff:
diff.find_similar()
form = pagure.forms.MergePRForm()
trigger_ci_pr_form = pagure.forms.TriggerCIPRForm()
# we need to leave out all members of trigger_ci_conf that have
# "meta" set to False or meta["requires_project_hook_attr"] condition
# defined and it's not met
trigger_ci_conf = pagure_config["TRIGGER_CI"]
if not isinstance(trigger_ci_conf, dict):
trigger_ci_conf = {}
trigger_ci = {}
# make sure all the backrefs are set properly on repo
pagure.lib.plugins.get_enabled_plugins(repo)
for comment, meta in trigger_ci_conf.items():
if not meta:
continue
cond = meta.get("requires_project_hook_attr", ())
if cond and not pagure.utils.project_has_hook_attr_value(repo, *cond):
continue
trigger_ci[comment] = meta
can_delete_branch = (
pagure_config.get("ALLOW_DELETE_BRANCH", True)
and not request.remote_git
and pagure.utils.is_repo_committer(request.project_from)
)
return flask.render_template(
"repo_pull_request.html",
select="requests",
requestid=requestid,
repo=repo,
username=username,
repo_obj=repo_obj,
pull_request=request,
diff_commits=diff_commits,
diff=diff,
mergeform=form,
subscribers=pagure.lib.query.get_watch_list(flask.g.session, request),
tag_list=pagure.lib.query.get_tags_of_project(flask.g.session, repo),
can_delete_branch=can_delete_branch,
trigger_ci=trigger_ci,
trigger_ci_pr_form=trigger_ci_pr_form,
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>.patch")
@UI_NS.route("/<namespace>/<repo>/pull-request/<int:requestid>.patch")
@UI_NS.route("/fork/<username>/<repo>/pull-request/<int:requestid>.patch")
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.patch"
)
def request_pull_patch(repo, requestid, username=None, namespace=None):
""" Returns the commits from the specified pull-request as patches.
"""
return request_pull_to_diff_or_patch(
repo, requestid, username, namespace, diff=False
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>.diff")
@UI_NS.route("/<namespace>/<repo>/pull-request/<int:requestid>.diff")
@UI_NS.route("/fork/<username>/<repo>/pull-request/<int:requestid>.diff")
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>.diff"
)
def request_pull_diff(repo, requestid, username=None, namespace=None):
""" Returns the commits from the specified pull-request as patches.
"""
return request_pull_to_diff_or_patch(
repo, requestid, username, namespace, diff=True
)
def request_pull_to_diff_or_patch(
repo, requestid, username=None, namespace=None, diff=False
):
""" Returns the commits from the specified pull-request as patches.
:arg repo: the `pagure.lib.model.Project` object of the current pagure
project browsed
:type repo: `pagure.lib.model.Project`
:arg requestid: the identifier of the pull-request to convert to patch
or diff
:type requestid: int
:kwarg username: the username of the user who forked then project when
the project viewed is a fork
:type username: str or None
:kwarg namespace: the namespace of the project if it has one
:type namespace: str or None
:kwarg diff: a boolean whether the data returned is a patch or a diff
:type diff: boolean
:return: the patch or diff representation of the specified pull-request
:rtype: str
"""
repo = flask.g.repo
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if request.remote:
repopath = pagure.utils.get_remote_repo_path(
request.remote_git, request.branch_from
)
parentpath = pagure.utils.get_repo_path(request.project)
else:
repo_from = request.project_from
parentpath = pagure.utils.get_repo_path(request.project)
repopath = parentpath
if repo_from:
repopath = pagure.utils.get_repo_path(repo_from)
repo_obj = pygit2.Repository(repopath)
orig_repo = pygit2.Repository(parentpath)
branch = repo_obj.lookup_branch(request.branch_from)
commitid = None
if branch:
commitid = branch.peel().hex
diff_commits = []
if request.status != "Open":
commitid = request.commit_stop
try:
for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_NONE):
diff_commits.append(commit)
if commit.oid.hex == request.commit_start:
break
except KeyError:
# This happens when repo.walk() cannot find commitid
pass
else:
try:
diff_commits = pagure.lib.git.diff_pull_request(
flask.g.session, request, repo_obj, orig_repo, with_diff=False
)
except pagure.exceptions.PagureException as err:
flask.flash("%s" % err, "error")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
username=username,
repo=repo.name,
namespace=namespace,
)
)
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not update this pull-request in the database", "error"
)
diff_commits.reverse()
patch = pagure.lib.git.commit_to_patch(
repo_obj, diff_commits, diff_view=diff
)
return flask.Response(patch, content_type="text/plain;charset=UTF-8")
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/edit/", methods=("GET", "POST")
)
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/edit", methods=("GET", "POST")
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/edit/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/edit",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/edit/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/edit",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/edit/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/edit",
methods=("GET", "POST"),
)
@login_required
def request_pull_edit(repo, requestid, username=None, namespace=None):
""" Edit the title of a pull-request.
"""
repo = flask.g.repo
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if request.status != "Open":
flask.abort(400, description="Pull-request is already closed")
if (
not flask.g.repo_committer
and flask.g.fas_user.username != request.user.username
):
flask.abort(
403, description="You are not allowed to edit this pull-request"
)
form = pagure.forms.RequestPullForm()
if form.validate_on_submit():
request.title = form.title.data.strip()
request.initial_comment = form.initial_comment.data.strip()
flask.g.session.add(request)
if not request.private and not request.project.private:
pagure.lib.notify.log(
request.project,
topic="pull-request.initial_comment.edited",
msg={
"pullrequest": request.to_json(
public=True, with_comments=False
),
"project": request.project.to_json(public=True),
"agent": flask.g.fas_user.username,
},
)
try:
# Link the PR to issue(s) if there is such link
pagure.lib.query.link_pr_to_issue_on_description(
flask.g.session, request
)
flask.g.session.commit()
flask.flash("Pull request edited!")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not edit this pull-request in the database", "error"
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
elif flask.request.method == "GET":
form.title.data = request.title
form.initial_comment.data = request.initial_comment
return flask.render_template(
"pull_request_title.html",
select="requests",
request=request,
repo=repo,
username=username,
form=form,
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>/comment", methods=["POST"])
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/comment/<commit>/"
"<path:filename>/<row>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/comment",
methods=["POST"],
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/comment/<commit>/"
"<path:filename>/<row>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/comment",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/comment/"
"<commit>/<path:filename>/<row>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/"
"comment",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/"
"comment/<commit>/<path:filename>/<row>",
methods=("GET", "POST"),
)
@login_required
def pull_request_add_comment(
repo,
requestid,
commit=None,
filename=None,
row=None,
username=None,
namespace=None,
):
""" Add a comment to a commit in a pull-request.
"""
repo = flask.g.repo
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
is_js = flask.request.args.get("js", False)
tree_id = flask.request.args.get("tree_id") or None
form = pagure.forms.AddPullRequestCommentForm()
form.commit.data = commit
form.filename.data = filename
form.requestid.data = requestid
form.row.data = row
form.tree_id.data = tree_id
if form.validate_on_submit():
comment = form.comment.data
try:
trigger_ci = pagure_config["TRIGGER_CI"]
if isinstance(trigger_ci, dict):
trigger_ci = list(trigger_ci.keys())
message = pagure.lib.query.add_pull_request_comment(
flask.g.session,
request=request,
commit=commit,
tree_id=tree_id,
filename=filename,
row=row,
comment=comment,
user=flask.g.fas_user.username,
trigger_ci=trigger_ci,
)
flask.g.session.commit()
if not is_js:
flask.flash(message)
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
if is_js:
return "error"
else:
flask.flash(str(err), "error")
if is_js:
return "ok"
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
if is_js and flask.request.method == "POST":
return "failed"
return flask.render_template(
"pull_request_comment.html",
select="requests",
requestid=requestid,
repo=repo,
username=username,
commit=commit,
tree_id=tree_id,
filename=filename,
row=row,
form=form,
)
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/comment/drop", methods=["POST"]
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/comment/drop",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/comment/drop",
methods=["POST"],
)
@UI_NS.route(
"/fork/<namespace>/<username>/<repo>/pull-request/<int:requestid>/"
"comment/drop",
methods=["POST"],
)
@login_required
def pull_request_drop_comment(repo, requestid, username=None, namespace=None):
""" Delete a comment of a pull-request.
"""
repo = flask.g.repo
if not repo:
flask.abort(404, description="Project not found")
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if flask.request.form.get("edit_comment"):
commentid = flask.request.form.get("edit_comment")
form = pagure.forms.EditCommentForm()
if form.validate_on_submit():
return pull_request_edit_comment(
repo.name, requestid, commentid, username=username
)
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
if flask.request.form.get("drop_comment"):
commentid = flask.request.form.get("drop_comment")
comment = pagure.lib.query.get_request_comment(
flask.g.session, request.uid, commentid
)
if comment is None or comment.pull_request.project != repo:
flask.abort(404, description="Comment not found")
if (
flask.g.fas_user.username != comment.user.username
or comment.parent.status is False
) and not flask.g.repo_committer:
flask.abort(
403,
description="You are not allowed to remove this comment "
"from this issue",
)
flask.g.session.delete(comment)
try:
flask.g.session.commit()
flask.flash("Comment removed")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.error(err)
flask.flash(
"Could not remove the comment: %s" % commentid, "error"
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/comment/<int:commentid>/edit",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/comment/"
"<int:commentid>/edit",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/comment"
"/<int:commentid>/edit",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/"
"<int:requestid>/comment/<int:commentid>/edit",
methods=("GET", "POST"),
)
@login_required
def pull_request_edit_comment(
repo, requestid, commentid, username=None, namespace=None
):
"""Edit comment of a pull request
"""
is_js = flask.request.args.get("js", False)
project = flask.g.repo
if not project.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=project.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
comment = pagure.lib.query.get_request_comment(
flask.g.session, request.uid, commentid
)
if comment is None or comment.parent.project != project:
flask.abort(404, description="Comment not found")
if (
flask.g.fas_user.username != comment.user.username
or comment.parent.status != "Open"
) and not flask.g.repo_committer:
flask.abort(403, description="You are not allowed to edit the comment")
form = pagure.forms.EditCommentForm()
if form.validate_on_submit():
updated_comment = form.update_comment.data
try:
message = pagure.lib.query.edit_comment(
flask.g.session,
parent=request,
comment=comment,
user=flask.g.fas_user.username,
updated_comment=updated_comment,
)
flask.g.session.commit()
if not is_js:
flask.flash(message)
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.error(err)
if is_js:
return "error"
else:
flask.flash(
"Could not edit the comment: %s" % commentid, "error"
)
if is_js:
return "ok"
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=project.name,
requestid=requestid,
)
)
if is_js and flask.request.method == "POST":
return "failed"
return flask.render_template(
"comment_update.html",
select="requests",
requestid=requestid,
repo=project,
username=username,
form=form,
comment=comment,
is_js=is_js,
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>/reopen", methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/reopen", methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/reopen",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/reopen",
methods=["POST"],
)
@login_required
def reopen_request_pull(repo, requestid, username=None, namespace=None):
""" Re-Open a pull request.
"""
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
if not flask.g.repo.settings.get("pull_requests", True):
flask.abort(
404, description="No pull-requests found for this project"
)
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=flask.g.repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if (
not flask.g.repo_committer
and not flask.g.fas_user.username == request.user.username
):
flask.abort(
403,
description="You are not allowed to reopen pull-request "
"for this project",
)
try:
pagure.lib.query.reopen_pull_request(
flask.g.session, request, flask.g.fas_user.username
)
except pagure.exceptions.PagureException as err:
flask.flash(str(err), "error")
try:
flask.g.session.commit()
flask.flash("Pull request reopened!")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not update this pull-request in the database", "error"
)
else:
flask.flash("Invalid input submitted", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo,
username=username,
namespace=namespace,
requestid=requestid,
)
)
@UI_NS.route(
"/<repo>/pull-request/<int:requestid>/trigger-ci", methods=["POST"]
)
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/trigger-ci",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/trigger-ci",
methods=["POST"],
)
@UI_NS.route(
(
"/fork/<username>/<namespace>/<repo>/pull-request/"
"<int:requestid>/trigger-ci"
),
methods=["POST"],
)
@login_required
def ci_trigger_request_pull(repo, requestid, username=None, namespace=None):
""" Trigger CI testing for a PR.
"""
form = pagure.forms.TriggerCIPRForm()
if not form.validate_on_submit():
flask.flash("Invalid input submitted", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo,
requestid=requestid,
username=username,
namespace=namespace,
)
)
repo_obj = flask.g.repo
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo_obj.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
trigger_ci = pagure_config["TRIGGER_CI"]
if isinstance(trigger_ci, dict):
trigger_ci = list(trigger_ci.keys())
pagure.lib.query.add_pull_request_comment(
flask.g.session,
request,
commit=None,
tree_id=None,
filename=None,
row=None,
comment=form.comment.data,
user=flask.g.fas_user.username,
notify=True,
notification=True,
trigger_ci=trigger_ci,
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo,
username=username,
namespace=namespace,
requestid=requestid,
)
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>/merge", methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/merge", methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/merge",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/merge",
methods=["POST"],
)
@login_required
def merge_request_pull(repo, requestid, username=None, namespace=None):
""" Create a pull request with the changes from the fork into the project.
"""
form = pagure.forms.MergePRForm()
if not form.validate_on_submit():
flask.flash("Invalid input submitted", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo,
requestid=requestid,
username=username,
namespace=namespace,
)
)
repo = flask.g.repo
_log.info(
"called merge_request_pull for repo: %s - requestid: %s",
repo.fullname,
requestid,
)
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-requests found for this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if not flask.g.repo_committer:
flask.abort(
403,
description="You are not allowed to merge pull-request "
"for this project",
)
if repo.settings.get("Only_assignee_can_merge_pull-request", False):
if not request.assignee:
flask.flash("This request must be assigned to be merged", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
if request.assignee.username != flask.g.fas_user.username:
flask.flash("Only the assignee can merge this review", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
threshold = repo.settings.get("Minimum_score_to_merge_pull-request", -1)
if threshold > 0 and int(request.score) < int(threshold):
flask.flash(
"This request does not have the minimum review score necessary "
"to be merged",
"error",
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
if form.delete_branch.data:
if not pagure_config.get("ALLOW_DELETE_BRANCH", True):
flask.flash(
"This pagure instance does not allow branch deletion", "error"
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
if not pagure.utils.is_repo_committer(request.project_from):
flask.flash(
"You do not have permissions to delete the branch in the "
"source repo",
"error",
)
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
if request.remote_git:
flask.flash("You can not delete branch in remote repo", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
_log.info("All checks in the controller passed")
try:
if flask.request.form.get("comment"):
trigger_ci = pagure_config["TRIGGER_CI"]
if isinstance(trigger_ci, dict):
trigger_ci = list(trigger_ci.keys())
message = pagure.lib.query.add_pull_request_comment(
flask.g.session,
request=request,
commit=None,
tree_id=None,
filename=None,
row=None,
comment=flask.request.form.get("comment"),
user=flask.g.fas_user.username,
trigger_ci=trigger_ci,
)
flask.g.session.commit()
flask.flash(message)
task = pagure.lib.tasks.merge_pull_request.delay(
repo.name,
namespace,
username,
requestid,
flask.g.fas_user.username,
delete_branch_after=form.delete_branch.data,
)
return pagure.utils.wait_for_task(
task,
prev=flask.url_for(
"ui_ns.request_pull",
repo=repo.name,
namespace=namespace,
username=username,
requestid=requestid,
),
)
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(str(err), "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo.name,
requestid=requestid,
username=username,
namespace=namespace,
)
)
except pygit2.GitError as err:
_log.info("GitError exception raised")
flask.flash("%s" % err, "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo.name,
requestid=requestid,
username=username,
namespace=namespace,
)
)
except pagure.exceptions.PagureException as err:
_log.info("PagureException exception raised")
flask.flash(str(err), "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
repo=repo.name,
requestid=requestid,
username=username,
namespace=namespace,
)
)
_log.info("All fine, returning")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
repo=repo.name,
username=username,
namespace=namespace,
)
)
@UI_NS.route("/<repo>/pull-request/close/<int:requestid>", methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/pull-request/close/<int:requestid>", methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/close/<int:requestid>",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/close/<int:requestid>",
methods=["POST"],
)
@login_required
def close_request_pull(repo, requestid, username=None, namespace=None):
""" Close a pull request without merging it.
"""
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
if not flask.g.repo.settings.get("pull_requests", True):
flask.abort(
404, description="No pull-requests found for this project"
)
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=flask.g.repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if (
not flask.g.repo_committer
and not flask.g.fas_user.username == request.user.username
):
flask.abort(
403,
description="You are not allowed to close pull-request "
"for this project",
)
pagure.lib.query.close_pull_request(
flask.g.session, request, flask.g.fas_user.username, merged=False
)
try:
flask.g.session.commit()
flask.flash("Pull request closed!")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not update this pull-request in the database", "error"
)
else:
flask.flash("Invalid input submitted", "error")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
repo=repo,
username=username,
namespace=namespace,
)
)
@UI_NS.route("/<repo>/pull-request/refresh/<int:requestid>", methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/pull-request/refresh/<int:requestid>",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/refresh/<int:requestid>",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/refresh/<int:requestid>",
methods=["POST"],
)
@login_required
def refresh_request_pull(repo, requestid, username=None, namespace=None):
""" Refresh a remote pull request.
"""
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
if not flask.g.repo.settings.get("pull_requests", True):
flask.abort(
404, description="No pull-requests found for this project"
)
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=flask.g.repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if (
not flask.g.repo_committer
and not flask.g.fas_user.username == request.user.username
):
flask.abort(
403,
description="You are not allowed to refresh this pull request",
)
task = pagure.lib.tasks.refresh_remote_pr.delay(
flask.g.repo.name, namespace, username, requestid
)
return pagure.utils.wait_for_task(
task,
prev=flask.url_for(
"ui_ns.request_pull",
repo=flask.g.repo.name,
namespace=namespace,
username=username,
requestid=requestid,
),
)
else:
flask.flash("Invalid input submitted", "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=flask.g.repo.name,
requestid=requestid,
)
)
@UI_NS.route("/<repo>/pull-request/<int:requestid>/update", methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/pull-request/<int:requestid>/update", methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/pull-request/<int:requestid>/update",
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/update",
methods=["POST"],
)
@login_required
def update_pull_requests(repo, requestid, username=None, namespace=None):
""" Update the metadata of a pull-request. """
repo = flask.g.repo
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-request allowed on this project")
request = pagure.lib.query.search_pull_requests(
flask.g.session, project_id=repo.id, requestid=requestid
)
if not request:
flask.abort(404, description="Pull-request not found")
if (
not flask.g.repo_user
and flask.g.fas_user.username != request.user.username
):
flask.abort(
403, description="You are not allowed to update this pull-request"
)
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
tags = [
tag.strip()
for tag in flask.request.form.get("tag", "").strip().split(",")
if tag.strip()
]
messages = set()
try:
# Adjust (add/remove) tags
msgs = pagure.lib.query.update_tags(
flask.g.session,
obj=request,
tags=tags,
username=flask.g.fas_user.username,
)
messages = messages.union(set(msgs))
if flask.g.repo_user:
# Assign or update assignee of the ticket
msg = pagure.lib.query.add_pull_request_assignee(
flask.g.session,
request=request,
assignee=flask.request.form.get("user", "").strip()
or None,
user=flask.g.fas_user.username,
)
if msg:
messages.add(msg)
if messages:
# Add the comment for field updates:
not_needed = set(["Comment added", "Updated comment"])
pagure.lib.query.add_metadata_update_notif(
session=flask.g.session,
obj=request,
messages=messages - not_needed,
user=flask.g.fas_user.username,
)
messages.add("Metadata fields updated")
flask.g.session.commit()
for message in messages:
flask.flash(message)
except pagure.exceptions.PagureException as err:
flask.g.session.rollback()
flask.flash("%s" % err, "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(str(err), "error")
return flask.redirect(
flask.url_for(
"ui_ns.request_pull",
username=username,
namespace=namespace,
repo=repo.name,
requestid=requestid,
)
)
# Specific actions
@UI_NS.route("/do_fork/<repo>", methods=["POST"])
@UI_NS.route("/do_fork/<namespace>/<repo>", methods=["POST"])
@UI_NS.route("/do_fork/fork/<username>/<repo>", methods=["POST"])
@UI_NS.route("/do_fork/fork/<username>/<namespace>/<repo>", methods=["POST"])
@login_required
def fork_project(repo, username=None, namespace=None):
""" Fork the project specified into the user's namespace
"""
repo = flask.g.repo
form = pagure.forms.ConfirmationForm()
if not form.validate_on_submit():
flask.abort(400)
if pagure.lib.query._get_project(
flask.g.session,
repo.name,
user=flask.g.fas_user.username,
namespace=namespace,
):
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
repo=repo.name,
username=flask.g.fas_user.username,
namespace=namespace,
)
)
try:
task = pagure.lib.query.fork_project(
session=flask.g.session, repo=repo, user=flask.g.fas_user.username
)
flask.g.session.commit()
return pagure.utils.wait_for_task(
task,
prev=flask.url_for(
"ui_ns.view_repo",
repo=repo.name,
username=username,
namespace=namespace,
_external=True,
),
)
except pagure.exceptions.PagureException as err:
flask.flash(str(err), "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
flask.flash(str(err), "error")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
repo=repo.name,
username=username,
namespace=namespace,
)
)
@UI_NS.route(
"/<repo>/diff/<path:branch_to>..<path:branch_from>/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<repo>/diff/<path:branch_to>..<path:branch_from>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/diff/"
"<path:branch_to>..<path:branch_from>/",
methods=("GET", "POST"),
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/diff/"
"<path:branch_to>..<path:branch_from>",
methods=("GET", "POST"),
)
def new_request_pull(
repo, branch_to, branch_from, username=None, namespace=None
):
""" Create a pull request with the changes from the fork into the project.
"""
branch_to = flask.request.values.get("branch_to", branch_to)
project_to = flask.request.values.get("project_to")
repo = flask.g.repo
parent = repo
if repo.parent:
parent = repo.parent
repo_obj = flask.g.repo_obj
if not project_to:
parentpath = get_parent_repo_path(repo)
orig_repo = pygit2.Repository(parentpath)
else:
p_namespace = None
p_username = None
p_name = None
project_to = project_to.rstrip("/")
if project_to.startswith("fork/"):
tmp = project_to.split("fork/")[1]
p_username, left = tmp.split("/", 1)
else:
left = project_to
if "/" in left:
p_namespace, p_name = left.split("/", 1)
else:
p_name = left
parent = pagure.lib.query.get_authorized_project(
flask.g.session, p_name, user=p_username, namespace=p_namespace
)
if parent:
family = [
p.url_path
for p in pagure.lib.query.get_project_family(
flask.g.session, repo
)
]
if parent.url_path not in family:
flask.abort(
400,
description="%s is not part of %s's family"
% (project_to, repo.url_path),
)
orig_repo = pygit2.Repository(parent.repopath("main"))
else:
flask.abort(
404, description="No project found for %s" % project_to
)
if not parent.settings.get("pull_requests", True):
flask.abort(404, description="No pull-request allowed on this project")
if parent.settings.get(
"Enforce_signed-off_commits_in_pull-request", False
):
flask.flash(
"This project enforces the Signed-off-by statement on all "
"commits"
)
try:
diff, diff_commits, orig_commit = pagure.lib.git.get_diff_info(
repo_obj, orig_repo, branch_from, branch_to
)
except pagure.exceptions.PagureException as err:
flask.abort(400, description=str(err))
repo_committer = flask.g.repo_committer
form = pagure.forms.RequestPullForm()
if form.validate_on_submit() and repo_committer:
try:
if parent.settings.get(
"Enforce_signed-off_commits_in_pull-request", False
):
for commit in diff_commits:
if "signed-off-by" not in commit.message.lower():
raise pagure.exceptions.PagureException(
"This repo enforces that all commits are "
"signed off by their author. "
)
if orig_commit:
orig_commit = orig_commit.oid.hex
initial_comment = form.initial_comment.data.strip() or None
commit_start = commit_stop = None
if diff_commits:
commit_stop = diff_commits[0].oid.hex
commit_start = diff_commits[-1].oid.hex
request = pagure.lib.query.new_pull_request(
flask.g.session,
repo_to=parent,
branch_to=branch_to,
branch_from=branch_from,
repo_from=repo,
title=form.title.data,
initial_comment=initial_comment,
allow_rebase=form.allow_rebase.data,
user=flask.g.fas_user.username,
commit_start=commit_start,
commit_stop=commit_stop,
)
try:
flask.g.session.commit()
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not register this pull-request in the database",
"error",
)
if not parent.is_fork:
url = flask.url_for(
"ui_ns.request_pull",
requestid=request.id,
username=None,
repo=parent.name,
namespace=namespace,
)
else:
url = flask.url_for(
"ui_ns.request_pull",
requestid=request.id,
username=parent.user.user,
repo=parent.name,
namespace=namespace,
)
return flask.redirect(url)
except pagure.exceptions.PagureException as err: # pragma: no cover
# There could be a PagureException thrown if the flask.g.fas_user
# wasn't in the DB but then it shouldn't be recognized as a
# repo admin and thus, if we ever are here, we are in trouble.
flask.flash(str(err), "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
flask.flash(str(err), "error")
if not flask.g.repo_committer:
form = None
elif flask.request.method == "GET":
form.allow_rebase.data = True
# if the pull request we are creating only has one commit,
# we automatically fill out the form fields for the PR with
# the commit title and bodytext
if len(diff_commits) == 1 and form:
form.title.data = diff_commits[0].message.strip().split("\n")[0]
form.initial_comment.data = diff_commits[0].message.partition("\n")[2]
# Get the contributing templates from the requests git repo
contributing = None
requestrepopath = _get_parent_request_repo_path(repo)
if os.path.exists(requestrepopath):
requestrepo = pygit2.Repository(requestrepopath)
if not requestrepo.is_empty and not requestrepo.head_is_unborn:
commit = requestrepo[requestrepo.head.target]
contributing = __get_file_in_tree(
requestrepo,
commit.tree,
["templates", "contributing.md"],
bail_on_tree=True,
)
if contributing:
contributing, _ = pagure.doc_utils.convert_readme(
contributing.data, "md"
)
flask.g.branches = sorted(orig_repo.listall_branches())
if diff:
diff.find_similar()
return flask.render_template(
"repo_new_pull_request.html",
select="requests",
repo=repo,
username=username,
orig_repo=orig_repo,
parent_branches=sorted(flask.g.repo_obj.listall_branches()),
diff_commits=diff_commits,
diff=diff,
form=form,
branch_to=branch_to,
branch_from=branch_from,
contributing=contributing,
parent=parent,
project_to=project_to,
)
@UI_NS.route("/<repo>/diff/remote/", methods=("GET", "POST"))
@UI_NS.route("/<repo>/diff/remote", methods=("GET", "POST"))
@UI_NS.route("/<namespace>/<repo>/diff/remote/", methods=("GET", "POST"))
@UI_NS.route("/<namespace>/<repo>/diff/remote", methods=("GET", "POST"))
@UI_NS.route("/fork/<username>/<repo>/diff/remote/", methods=("GET", "POST"))
@UI_NS.route("/fork/<username>/<repo>/diff/remote", methods=("GET", "POST"))
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/diff/remote/", methods=("GET", "POST")
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/diff/remote", methods=("GET", "POST")
)
@login_required
def new_remote_request_pull(repo, username=None, namespace=None):
""" Create a pull request with the changes from a remote fork into the
project.
"""
confirm = flask.request.values.get("confirm", False)
repo = flask.g.repo
if pagure_config.get("DISABLE_REMOTE_PR", True):
flask.abort(
404, description="Remote pull-requests disabled on this server"
)
if not repo.settings.get("pull_requests", True):
flask.abort(404, description="No pull-request allowed on this project")
if repo.settings.get("Enforce_signed-off_commits_in_pull-request", False):
flask.flash(
"This project enforces the Signed-off-by statement on all "
"commits"
)
orig_repo = flask.g.repo_obj
form = pagure.forms.RemoteRequestPullForm()
if form.validate_on_submit():
taskid = flask.request.values.get("taskid")
if taskid:
result = pagure.lib.tasks.get_result(taskid)
if not result.ready:
return pagure.utils.wait_for_task_post(
taskid,
form,
"ui_ns.new_remote_request_pull",
repo=repo.name,
username=username,
namespace=namespace,
)
# Make sure to collect any exceptions resulting from the task
try:
result.get(timeout=0)
except Exception as err:
flask.abort(500, description=err)
branch_from = form.branch_from.data.strip()
branch_to = form.branch_to.data.strip()
remote_git = form.git_repo.data.strip()
repopath = pagure.utils.get_remote_repo_path(remote_git, branch_from)
if not repopath:
taskid = pagure.lib.tasks.pull_remote_repo.delay(
remote_git, branch_from
)
return pagure.utils.wait_for_task_post(
taskid,
form,
"ui_ns.new_remote_request_pull",
repo=repo.name,
username=username,
namespace=namespace,
initial=True,
)
repo_obj = pygit2.Repository(repopath)
try:
diff, diff_commits, orig_commit = pagure.lib.git.get_diff_info(
repo_obj, orig_repo, branch_from, branch_to
)
except pagure.exceptions.PagureException as err:
flask.flash("%s" % err, "error")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
username=username,
repo=repo.name,
namespace=namespace,
)
)
if not confirm:
flask.g.branches = sorted(orig_repo.listall_branches())
return flask.render_template(
"repo_new_pull_request.html",
select="requests",
repo=repo,
username=username,
orig_repo=orig_repo,
diff_commits=diff_commits,
diff=diff,
form=form,
branch_to=branch_to,
branch_from=branch_from,
remote_git=remote_git,
parent=repo,
)
try:
if repo.settings.get(
"Enforce_signed-off_commits_in_pull-request", False
):
for commit in diff_commits:
if "signed-off-by" not in commit.message.lower():
raise pagure.exceptions.PagureException(
"This repo enforces that all commits are "
"signed off by their author. "
)
if orig_commit:
orig_commit = orig_commit.oid.hex
parent = repo
if repo.parent:
parent = repo.parent
request = pagure.lib.query.new_pull_request(
flask.g.session,
repo_to=parent,
branch_to=branch_to,
branch_from=branch_from,
repo_from=None,
remote_git=remote_git,
title=form.title.data,
user=flask.g.fas_user.username,
)
if form.initial_comment.data.strip() != "":
pagure.lib.query.add_pull_request_comment(
flask.g.session,
request=request,
commit=None,
tree_id=None,
filename=None,
row=None,
comment=form.initial_comment.data.strip(),
user=flask.g.fas_user.username,
)
try:
flask.g.session.commit()
flask.flash("Request created")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.exception(err)
flask.flash(
"Could not register this pull-request in " "the database",
"error",
)
if not parent.is_fork:
url = flask.url_for(
"ui_ns.request_pull",
requestid=request.id,
username=None,
repo=parent.name,
namespace=namespace,
)
else:
url = flask.url_for(
"ui_ns.request_pull",
requestid=request.id,
username=parent.user,
repo=parent.name,
namespace=namespace,
)
return flask.redirect(url)
except pagure.exceptions.PagureException as err: # pragma: no cover
# There could be a PagureException thrown if the
# flask.g.fas_user wasn't in the DB but then it shouldn't
# be recognized as a repo admin and thus, if we ever are
# here, we are in trouble.
flask.flash(str(err), "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
flask.flash(str(err), "error")
flask.g.branches = sorted(orig_repo.listall_branches())
if flask.request.method == "GET":
try:
branch_to = orig_repo.head.shorthand
except pygit2.GitError:
branch_to = "master"
else:
branch_to = form.branch_to.data.strip()
return flask.render_template(
"remote_pull_request.html",
select="requests",
repo=repo,
username=username,
form=form,
branch_to=branch_to,
)
@UI_NS.route(
"/fork_edit/<repo>/edit/<path:branchname>/f/<path:filename>",
methods=["POST"],
)
@UI_NS.route(
"/fork_edit/<namespace>/<repo>/edit/<path:branchname>/f/<path:filename>",
methods=["POST"],
)
@UI_NS.route(
"/fork_edit/fork/<username>/<repo>/edit/<path:branchname>/"
"f/<path:filename>",
methods=["POST"],
)
@UI_NS.route(
"/fork_edit/fork/<username>/<namespace>/<repo>/edit/<path:branchname>/"
"f/<path:filename>",
methods=["POST"],
)
@login_required
def fork_edit_file(repo, branchname, filename, username=None, namespace=None):
""" Fork the project specified and open the specific file to edit
"""
repo = flask.g.repo
form = pagure.forms.ConfirmationForm()
if not form.validate_on_submit():
flask.abort(400)
if pagure.lib.query._get_project(
flask.g.session,
repo.name,
namespace=repo.namespace,
user=flask.g.fas_user.username,
):
flask.flash("You had already forked this project")
return flask.redirect(
flask.url_for(
"ui_ns.edit_file",
username=flask.g.fas_user.username,
namespace=namespace,
repo=repo.name,
branchname=branchname,
filename=filename,
)
)
try:
task = pagure.lib.query.fork_project(
session=flask.g.session,
repo=repo,
user=flask.g.fas_user.username,
editbranch=branchname,
editfile=filename,
)
flask.g.session.commit()
return pagure.utils.wait_for_task(task)
except pagure.exceptions.PagureException as err:
flask.flash(str(err), "error")
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
flask.flash(str(err), "error")
return flask.redirect(
flask.url_for(
"ui_ns.view_repo",
repo=repo.name,
username=username,
namespace=namespace,
)
)
_REACTION_URL_SNIPPET = (
"pull-request/<int:requestid>/comment/<int:commentid>/react"
)
@UI_NS.route("/<repo>/%s/" % _REACTION_URL_SNIPPET, methods=["POST"])
@UI_NS.route("/<repo>/%s" % _REACTION_URL_SNIPPET, methods=["POST"])
@UI_NS.route(
"/<namespace>/<repo>/%s/" % _REACTION_URL_SNIPPET, methods=["POST"]
)
@UI_NS.route(
"/<namespace>/<repo>/%s" % _REACTION_URL_SNIPPET, methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/%s/" % _REACTION_URL_SNIPPET, methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<repo>/%s" % _REACTION_URL_SNIPPET, methods=["POST"]
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/%s/" % _REACTION_URL_SNIPPET,
methods=["POST"],
)
@UI_NS.route(
"/fork/<username>/<namespace>/<repo>/%s" % _REACTION_URL_SNIPPET,
methods=["POST"],
)
@login_required
def pull_request_comment_add_reaction(
repo, requestid, commentid, username=None, namespace=None
):
repo = flask.g.repo
form = pagure.forms.ConfirmationForm()
if not form.validate_on_submit():
flask.abort(400, description="CSRF token not valid")
request = pagure.lib.query.search_pull_requests(
flask.g.session, requestid=requestid, project_id=repo.id
)
if not request:
flask.abort(404, description="Comment not found")
comment = pagure.lib.query.get_request_comment(
flask.g.session, request.uid, commentid
)
if "reaction" not in flask.request.form:
flask.abort(400, description="Reaction not found")
reactions = comment.reactions
r = flask.request.form["reaction"]
if not r:
flask.abort(400, description="Empty reaction is not acceptable")
if flask.g.fas_user.username in reactions.get(r, []):
flask.abort(409, description="Already posted this one")
reactions.setdefault(r, []).append(flask.g.fas_user.username)
comment.reactions = reactions
flask.g.session.add(comment)
try:
flask.g.session.commit()
except SQLAlchemyError as err: # pragma: no cover
flask.g.session.rollback()
_log.error(err)
return "error"
return "ok"