1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203 |
- # -*- 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 absolute_import, unicode_literals
- import json
- 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.forms
- import pagure.lib.git
- import pagure.lib.plugins
- import pagure.lib.query
- import pagure.lib.tasks
- from pagure.config import config as pagure_config
- from pagure.ui import UI_NS
- from pagure.utils import (
- __get_file_in_tree,
- get_parent_repo_path,
- is_true,
- login_required,
- )
- _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")
- tags = flask.request.args.getlist("tags")
- tags = [tag.strip() for tag in tags if tag.strip()]
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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,
- tags=tags,
- 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)))
- tag_list = pagure.lib.query.get_tags_of_project(flask.g.session, repo)
- return flask.render_template(
- "requests.html",
- select="requests",
- repo=repo,
- username=username,
- tag_list=tag_list,
- tags=tags,
- 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):
- """View 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()
- warning_characters = pagure.lib.query.find_warning_characters(
- repo_obj, diff_commits
- )
- 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
- committer = False
- if request.project_from:
- committer = pagure.utils.is_repo_committer(request.project_from)
- else:
- committer = pagure.utils.is_repo_committer(request.project)
- can_rebase_branch = not request.remote_git and committer
- can_delete_branch = (
- pagure_config.get("ALLOW_DELETE_BRANCH", True) and can_rebase_branch
- )
- 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_rebase_branch=can_rebase_branch,
- can_delete_branch=can_delete_branch,
- trigger_ci=trigger_ci,
- trigger_ci_pr_form=trigger_ci_pr_form,
- flag_statuses_labels=json.dumps(pagure_config["FLAG_STATUSES_LABELS"]),
- warning_characters=warning_characters,
- )
- @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.RequestPullEditForm(branches=flask.g.branches)
- if form.validate_on_submit():
- request.title = form.title.data.strip() if form.title.data else None
- request.initial_comment = (
- form.initial_comment.data.strip()
- if form.initial_comment.data
- else None
- )
- request.branch = (
- form.branch_to.data.strip() if form.branch_to.data else None
- )
- if flask.g.fas_user.username == request.user.username:
- request.allow_rebase = form.allow_rebase.data
- 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
- form.branch_to.data = request.branch
- 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 request", "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,
- )
- )
- committer = False
- if request.project_from:
- committer = pagure.utils.is_repo_committer(request.project_from)
- else:
- committer = pagure.utils.is_repo_committer(request.project)
- if not committer:
- 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()
- if form.initial_comment.data
- else 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() if form.branch_from.data else None
- )
- branch_to = (
- form.branch_to.data.strip() if form.branch_to.data else None
- )
- remote_git = form.git_repo.data.strip() if form.git_repo.data else None
- 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
- and 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() if form.branch_to.data else None
- )
- 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"
|