pagure-new/pagure/ui/fork.py

1324 lines
45 KiB
Python
Raw Normal View History

2016-09-21 23:21:07 +00:00
# -*- coding: utf-8 -*-
"""
(c) 2014-2016 - 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
# pylint: disable=no-member
import flask
import os
from math import ceil
import pygit2
from sqlalchemy.exc import SQLAlchemyError
import pagure
import pagure.doc_utils
import pagure.exceptions
import pagure.lib
import pagure.lib.git
import pagure.forms
from pagure import APP, SESSION, LOG, login_required, __get_file_in_tree
def _get_parent_repo_path(repo):
""" Return the path of the parent git repository corresponding to the
provided Repository object from the DB.
"""
if repo.parent:
parentpath = os.path.join(APP.config['GIT_FOLDER'], repo.parent.path)
else:
parentpath = os.path.join(APP.config['GIT_FOLDER'], repo.path)
return parentpath
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:
parentpath = os.path.join(
APP.config['REQUESTS_FOLDER'], repo.parent.path)
else:
parentpath = os.path.join(APP.config['REQUESTS_FOLDER'], repo.path)
return parentpath
def _get_pr_info(repo_obj, orig_repo, branch_from, branch_to):
''' Return the info needed to see a diff or make a Pull-Request between
the two specified repo.
'''
frombranch = repo_obj.lookup_branch(branch_from)
if not frombranch and not repo_obj.is_empty:
flask.abort(
400,
'Branch %s does not exist' % branch_from)
branch = orig_repo.lookup_branch(branch_to)
if not branch and not orig_repo.is_empty:
flask.abort(
400,
'Branch %s could not be found in the target repo' % branch_to)
branch = repo_obj.lookup_branch(branch_from)
commitid = None
if branch:
commitid = branch.get_object().hex
diff_commits = []
diff = None
if not repo_obj.is_empty and not orig_repo.is_empty:
orig_commit = orig_repo[
orig_repo.lookup_branch(branch_to).get_object().hex]
master_commits = [
commit.oid.hex
for commit in orig_repo.walk(
orig_commit.oid.hex, pygit2.GIT_SORT_TIME)
]
repo_commit = repo_obj[commitid]
for commit in repo_obj.walk(
repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
if commit.oid.hex in master_commits:
break
diff_commits.append(commit)
if diff_commits:
first_commit = repo_obj[diff_commits[-1].oid.hex]
diff = repo_obj.diff(
repo_obj.revparse_single(first_commit.parents[0].oid.hex),
repo_obj.revparse_single(diff_commits[0].oid.hex)
)
elif orig_repo.is_empty and not repo_obj.is_empty:
orig_commit = None
if 'master' in repo_obj.listall_branches():
repo_commit = repo_obj[repo_obj.head.target]
else:
branch = repo_obj.lookup_branch(branch_from)
repo_commit = branch.get_object()
for commit in repo_obj.walk(
repo_commit.oid.hex, pygit2.GIT_SORT_TIME):
diff_commits.append(commit)
diff = repo_commit.tree.diff_to_tree(swap=True)
else:
raise pagure.exceptions.PagureException(
'Fork is empty, there are no commits to request pulling'
)
return(diff, diff_commits, orig_commit)
@APP.route('/<repo>/pull-requests/')
@APP.route('/<repo>/pull-requests')
@APP.route('/<namespace>/<repo>/pull-requests/')
@APP.route('/<namespace>/<repo>/pull-requests')
@APP.route('/fork/<username>/<repo>/pull-requests/')
@APP.route('/fork/<username>/<repo>/pull-requests')
@APP.route('/fork/<username>/<namespace>/<repo>/pull-requests/')
@APP.route('/fork/<username>/<namespace>/<repo>/pull-requests')
def request_pulls(repo, username=None, namespace=None):
""" Request pulling the changes from the fork into the project.
"""
status = flask.request.args.get('status', 'Open')
assignee = flask.request.args.get('assignee', None)
author = flask.request.args.get('author', None)
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
if str(status).lower() in ['false', '0']:
status = False
elif str(status).lower() in ['all']:
status = None
if str(status).lower() in ['true', '1', 'open']:
requests = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
status=True,
assignee=assignee,
author=author,
offset=flask.g.offset,
limit=flask.g.limit)
requests_cnt = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
status=True,
assignee=assignee,
author=author,
count=True)
oth_requests = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
status=False,
assignee=assignee,
author=author,
count=True)
else:
requests = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
assignee=assignee,
author=author,
status=status,
offset=flask.g.offset,
limit=flask.g.limit)
requests_cnt = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
assignee=assignee,
author=author,
status=status,
count=True)
oth_requests = pagure.lib.search_pull_requests(
SESSION,
project_id=repo.id,
status=True,
assignee=assignee,
author=author,
count=True)
reponame = flask.g.reponame
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 = int(ceil(requests_cnt / float(flask.g.limit)))
return flask.render_template(
'requests.html',
select='requests',
repo=repo,
username=username,
requests=requests,
requests_cnt=requests_cnt,
oth_requests=oth_requests,
status=status,
assignee=assignee,
author=author,
form=pagure.forms.ConfirmationForm(),
head=head,
total_page=total_page,
)
@APP.route('/<repo>/pull-request/<int:requestid>/')
@APP.route('/<repo>/pull-request/<int:requestid>')
@APP.route('/<namespace>/<repo>/pull-request/<int:requestid>/')
@APP.route('/<namespace>/<repo>/pull-request/<int:requestid>')
@APP.route('/fork/<username>/<repo>/pull-request/<int:requestid>/')
@APP.route('/fork/<username>/<repo>/pull-request/<int:requestid>')
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/')
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>')
def request_pull(repo, requestid, username=None, namespace=None):
""" Request pulling the changes from the fork into the project.
"""
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if request.remote:
repopath = pagure.get_remote_repo_path(
request.remote_git, request.branch_from)
parentpath = pagure.get_repo_path(request.project)
else:
repo_from = request.project_from
repopath = pagure.get_repo_path(repo_from)
parentpath = _get_parent_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_TIME):
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:
diff = repo_obj.diff(
repo_obj.revparse_single(diff_commits[-1].parents[0].oid.hex),
repo_obj.revparse_single(diff_commits[0].oid.hex)
)
else:
try:
diff_commits, diff = pagure.lib.git.diff_pull_request(
SESSION, request, repo_obj, orig_repo,
requestfolder=APP.config['REQUESTS_FOLDER'])
except pagure.exceptions.PagureException as err:
flask.flash(err.message, 'error')
return flask.redirect(flask.url_for(
'view_repo', username=username, repo=repo.name,
namespace=namespace))
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(
'Could not update this pull-request in the database',
'error')
if diff:
diff.find_similar()
form = pagure.forms.ConfirmationForm()
return flask.render_template(
'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,
)
@APP.route('/<repo>/pull-request/<int:requestid>.patch')
@APP.route('/<namespace>/<repo>/pull-request/<int:requestid>.patch')
@APP.route('/fork/<username>/<repo>/pull-request/<int:requestid>.patch')
@APP.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.
"""
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if request.remote:
repopath = pagure.get_remote_repo_path(
request.remote_git, request.branch_from)
parentpath = pagure.get_repo_path(request.project)
else:
repo_from = request.project_from
repopath = pagure.get_repo_path(repo_from)
parentpath = _get_parent_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.get_object().hex
diff_commits = []
if request.status != 'Open':
commitid = request.commit_stop
try:
for commit in repo_obj.walk(commitid, pygit2.GIT_SORT_TIME):
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(
SESSION, request, repo_obj, orig_repo,
requestfolder=APP.config['REQUESTS_FOLDER'],
with_diff=False)[0]
except pagure.exceptions.PagureException as err:
flask.flash(err.message, 'error')
return flask.redirect(flask.url_for(
'view_repo', username=username, repo=repo.name,
namespace=namespace))
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.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)
return flask.Response(patch, content_type="text/plain;charset=UTF-8")
@APP.route(
'/<repo>/pull-request/<int:requestid>/edit/', methods=('GET', 'POST'))
@APP.route(
'/<repo>/pull-request/<int:requestid>/edit', methods=('GET', 'POST'))
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/edit/',
methods=('GET', 'POST'))
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/edit',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/edit/',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/edit',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/edit/',
methods=('GET', 'POST'))
@APP.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, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if request.status != 'Open':
flask.abort(400, 'Pull-request is already closed')
if not flask.g.repo_admin \
and flask.g.fas_user.username != request.user.username:
flask.abort(403, '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()
SESSION.add(request)
try:
SESSION.commit()
flask.flash('Request pull edited!')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(
'Could not edit this pull-request in the database',
'error')
return flask.redirect(flask.url_for(
'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,
)
@APP.route('/<repo>/pull-request/<int:requestid>/comment',
methods=['POST'])
@APP.route('/<repo>/pull-request/<int:requestid>/comment/<commit>/'
'<path:filename>/<row>', methods=('GET', 'POST'))
@APP.route('/<namespace>/<repo>/pull-request/<int:requestid>/comment',
methods=['POST'])
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/comment/<commit>/'
'<path:filename>/<row>', methods=('GET', 'POST'))
@APP.route('/fork/<username>/<repo>/pull-request/<int:requestid>/comment',
methods=['POST'])
@APP.route('/fork/<username>/<repo>/pull-request/<int:requestid>/comment/'
'<commit>/<path:filename>/<row>', methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/'
'comment', methods=['POST'])
@APP.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, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, '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:
message = pagure.lib.add_pull_request_comment(
SESSION,
request=request,
commit=commit,
tree_id=tree_id,
filename=filename,
row=row,
comment=comment,
user=flask.g.fas_user.username,
requestfolder=APP.config['REQUESTS_FOLDER'],
)
SESSION.commit()
if not is_js:
flask.flash(message)
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(str(err), 'error')
if is_js:
return 'error'
if is_js:
return 'ok'
return flask.redirect(flask.url_for(
'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,
)
@APP.route('/<repo>/pull-request/<int:requestid>/comment/drop',
methods=['POST'])
@APP.route('/<namespace>/<repo>/pull-request/<int:requestid>/comment/drop',
methods=['POST'])
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/comment/drop',
methods=['POST'])
@APP.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.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, '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.get_request_comment(
SESSION, request.uid, commentid)
if comment is None or comment.pull_request.project != repo:
flask.abort(404, 'Comment not found')
if (flask.g.fas_user.username != comment.user.username
or comment.parent.status is False) \
and not flask.g.repo_admin:
flask.abort(
403,
'You are not allowed to remove this comment from '
'this issue')
SESSION.delete(comment)
try:
SESSION.commit()
flask.flash('Comment removed')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
LOG.error(err)
flask.flash(
'Could not remove the comment: %s' % commentid, 'error')
return flask.redirect(flask.url_for(
'request_pull', username=username, namespace=namespace,
repo=repo.name, requestid=requestid))
@APP.route(
'/<repo>/pull-request/<int:requestid>/comment/<int:commentid>/edit',
methods=('GET', 'POST'))
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/comment/'
'<int:commentid>/edit', methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/comment'
'/<int:commentid>/edit', methods=('GET', 'POST'))
@APP.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, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=project.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
comment = pagure.lib.get_request_comment(
SESSION, request.uid, commentid)
if comment is None or comment.parent.project != project:
flask.abort(404, 'Comment not found')
if (flask.g.fas_user.username != comment.user.username
or comment.parent.status != 'Open') \
and not flask.g.repo_admin:
flask.abort(403, '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.edit_comment(
SESSION,
parent=request,
comment=comment,
user=flask.g.fas_user.username,
updated_comment=updated_comment,
folder=APP.config['REQUESTS_FOLDER'],
)
SESSION.commit()
if not is_js:
flask.flash(message)
except SQLAlchemyError, err: # pragma: no cover
SESSION.rollback()
LOG.error(err)
if is_js:
return 'error'
flask.flash(
'Could not edit the comment: %s' % commentid, 'error')
if is_js:
return 'ok'
return flask.redirect(flask.url_for(
'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,
)
@APP.route('/<repo>/pull-request/<int:requestid>/merge', methods=['POST'])
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/merge',
methods=['POST'])
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/merge',
methods=['POST'])
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/merge',
methods=['POST'])
@login_required
def merge_request_pull(repo, requestid, username=None, namespace=None):
""" Request pulling the changes from the fork into the project.
"""
form = pagure.forms.ConfirmationForm()
if not form.validate_on_submit():
flask.flash('Invalid input submitted', 'error')
return flask.redirect(flask.url_for(
'request_pull', repo=repo, requestid=requestid,
username=username, namespace=namespace))
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if not flask.g.repo_admin:
flask.abort(
403,
'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(
'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(
'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(
'request_pull', username=username, namespace=namespace,
repo=repo.name, requestid=requestid))
try:
message = pagure.lib.git.merge_pull_request(
SESSION, request, flask.g.fas_user.username,
APP.config['REQUESTS_FOLDER'])
flask.flash(message)
except pygit2.GitError as err:
flask.flash(str(err.message), 'error')
return flask.redirect(flask.url_for(
'request_pull', repo=repo.name, requestid=requestid,
username=username, namespace=namespace))
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
return flask.redirect(flask.url_for(
'request_pull', repo=repo.name, requestid=requestid,
username=username, namespace=namespace))
return flask.redirect(flask.url_for(
'view_repo', repo=repo.name, username=username, namespace=namespace))
@APP.route('/<repo>/pull-request/cancel/<int:requestid>',
methods=['POST'])
@APP.route('/<namespace>/<repo>/pull-request/cancel/<int:requestid>',
methods=['POST'])
@APP.route('/fork/<username>/<repo>/pull-request/cancel/<int:requestid>',
methods=['POST'])
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/cancel/<int:requestid>',
methods=['POST'])
@login_required
def cancel_request_pull(repo, requestid, username=None, namespace=None):
""" Cancel request pulling request.
"""
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
if not flask.g.repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-requests found for this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=flask.g.repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if not flask.g.repo_admin \
and not flask.g.fas_user.username == request.user.username:
flask.abort(
403,
'You are not allowed to cancel pull-request for this project')
pagure.lib.close_pull_request(
SESSION, request, flask.g.fas_user.username,
requestfolder=APP.config['REQUESTS_FOLDER'],
merged=False)
try:
SESSION.commit()
flask.flash('Request pull canceled!')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.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(
'view_repo', repo=repo, username=username, namespace=namespace))
@APP.route(
'/<repo>/pull-request/<int:requestid>/assign', methods=['POST'])
@APP.route(
'/<namespace>/<repo>/pull-request/<int:requestid>/assign', methods=['POST'])
@APP.route(
'/fork/<username>/<repo>/pull-request/<int:requestid>/assign',
methods=['POST'])
@APP.route(
'/fork/<username>/<namespace>/<repo>/pull-request/<int:requestid>/assign',
methods=['POST'])
@login_required
def set_assignee_requests(repo, requestid, username=None, namespace=None):
''' Assign a pull-request. '''
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-request allowed on this project')
request = pagure.lib.search_pull_requests(
SESSION, project_id=repo.id, requestid=requestid)
if not request:
flask.abort(404, 'Pull-request not found')
if request.status != 'Open':
flask.abort(403, 'Pull-request closed')
if not flask.g.repo_admin:
flask.abort(403, 'You are not allowed to assign this pull-request')
form = pagure.forms.ConfirmationForm()
if form.validate_on_submit():
try:
# Assign or update assignee of the ticket
message = pagure.lib.add_pull_request_assignee(
SESSION,
request=request,
assignee=flask.request.form.get('user', '').strip() or None,
user=flask.g.fas_user.username,
requestfolder=APP.config['REQUESTS_FOLDER'],)
if message:
SESSION.commit()
flask.flash(message)
except pagure.exceptions.PagureException as err:
SESSION.rollback()
flask.flash(err.message, 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(str(err), 'error')
return flask.redirect(flask.url_for(
'request_pull', username=username, namespace=namespace,
repo=repo.name, requestid=requestid))
# Specific actions
@APP.route('/do_fork/<repo>', methods=['POST'])
@APP.route('/do_fork/<namespace>/<repo>', methods=['POST'])
@APP.route('/do_fork/fork/<username>/<repo>', methods=['POST'])
@APP.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.get_project(
SESSION, repo.name, user=flask.g.fas_user.username,
namespace=namespace):
flask.flash('You had already forked this project')
return flask.redirect(flask.url_for(
'view_repo', repo=repo.name, username=flask.g.fas_user.username,
namespace=namespace))
try:
message = pagure.lib.fork_project(
session=SESSION,
repo=repo,
gitfolder=APP.config['GIT_FOLDER'],
docfolder=APP.config['DOCS_FOLDER'],
ticketfolder=APP.config['TICKETS_FOLDER'],
requestfolder=APP.config['REQUESTS_FOLDER'],
user=flask.g.fas_user.username)
SESSION.commit()
pagure.lib.git.generate_gitolite_acls()
flask.flash(message)
return flask.redirect(
flask.url_for(
'view_repo',
username=flask.g.fas_user.username,
namespace=namespace,
repo=repo.name)
)
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
flask.flash(str(err), 'error')
return flask.redirect(flask.url_for(
'view_repo', repo=repo.name, username=username, namespace=namespace
))
@APP.route('/<repo>/diff/<path:branch_to>..<path:branch_from>/',
methods=('GET', 'POST'))
@APP.route('/<repo>/diff/<path:branch_to>..<path:branch_from>',
methods=('GET', 'POST'))
@APP.route('/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>/',
methods=('GET', 'POST'))
@APP.route('/<namespace>/<repo>/diff/<path:branch_to>..<path:branch_from>',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>/',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/diff/<path:branch_to>..<path:branch_from>',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<namespace>/<repo>/diff/'
'<path:branch_to>..<path:branch_from>/', methods=('GET', 'POST'))
@APP.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):
""" Request pulling the changes from the fork into the project.
"""
branch_to = flask.request.values.get('branch_to', branch_to)
repo = flask.g.repo
parent = repo
if repo.parent:
parent = repo.parent
if not parent.settings.get('pull_requests', True):
flask.abort(404, 'No pull-request allowed on this project')
repopath = flask.g.reponame
repo_obj = flask.g.repo_obj
parentpath = _get_parent_repo_path(repo)
orig_repo = pygit2.Repository(parentpath)
try:
diff, diff_commits, orig_commit = _get_pr_info(
repo_obj, orig_repo, branch_from, branch_to)
except pagure.exceptions.PagureException as err:
flask.flash(err.message, 'error')
return flask.redirect(flask.url_for(
'view_repo', username=username, repo=repo.name,
namespace=namespace))
repo_admin = flask.g.repo_admin
form = pagure.forms.RequestPullForm()
if form.validate_on_submit() and repo_admin:
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
initial_comment = form.initial_comment.data.strip() or None
request = pagure.lib.new_pull_request(
SESSION,
repo_to=parent,
branch_to=branch_to,
branch_from=branch_from,
repo_from=repo,
title=form.title.data,
initial_comment=initial_comment,
user=flask.g.fas_user.username,
requestfolder=APP.config['REQUESTS_FOLDER'],
)
try:
SESSION.commit()
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(
'Could not register this pull-request in the database',
'error')
if not parent.is_fork:
url = flask.url_for(
'request_pull', requestid=request.id,
username=None, repo=parent.name, namespace=namespace)
else:
url = flask.url_for(
'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
SESSION.rollback()
flask.flash(str(err), 'error')
if not repo_admin:
form = None
# 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())
return flask.render_template(
'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,
contributing=contributing,
)
@APP.route('/<repo>/diff/remote/', methods=('GET', 'POST'))
@APP.route('/<repo>/diff/remote', methods=('GET', 'POST'))
@APP.route('/<namespace>/<repo>/diff/remote/', methods=('GET', 'POST'))
@APP.route('/<namespace>/<repo>/diff/remote', methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/diff/remote/', methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<repo>/diff/remote', methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<namespace>/<repo>/diff/remote/',
methods=('GET', 'POST'))
@APP.route(
'/fork/<username>/<namespace>/<repo>/diff/remote',
methods=('GET', 'POST'))
@login_required
def new_remote_request_pull(repo, username=None, namespace=None):
""" Request pulling the changes from a remote fork into the project.
"""
confirm = flask.request.values.get('confirm', False)
repo = flask.g.repo
if not repo.settings.get('pull_requests', True):
flask.abort(404, 'No pull-request allowed on this project')
parentpath = flask.g.reponame
orig_repo = flask.g.repo_obj
repo_admin = flask.g.repo_admin
form = pagure.forms.RemoteRequestPullForm()
if form.validate_on_submit():
branch_from = form.branch_from.data.strip()
branch_to = form.branch_to.data.strip()
remote_git = form.git_repo.data.strip()
repopath = pagure.get_remote_repo_path(remote_git, branch_from)
repo_obj = pygit2.Repository(repopath)
try:
diff, diff_commits, orig_commit = _get_pr_info(
repo_obj, orig_repo, branch_from, branch_to)
except pagure.exceptions.PagureException as err:
flask.flash(err.message, 'error')
return flask.redirect(flask.url_for(
'view_repo', username=username, repo=repo.name,
namespace=namespace))
if not confirm:
flask.g.branches = sorted(orig_repo.listall_branches())
return flask.render_template(
'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,
)
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.new_pull_request(
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,
requestfolder=APP.config['REQUESTS_FOLDER'],
)
if form.initial_comment.data.strip() != '':
pagure.lib.add_pull_request_comment(
SESSION,
request=request,
commit=None,
tree_id=None,
filename=None,
row=None,
comment=form.initial_comment.data.strip(),
user=flask.g.fas_user.username,
requestfolder=APP.config['REQUESTS_FOLDER'],
)
try:
SESSION.commit()
flask.flash('Request created')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
APP.logger.exception(err)
flask.flash(
'Could not register this pull-request in '
'the database', 'error')
if not parent.is_fork:
url = flask.url_for(
'request_pull', requestid=request.id,
username=None, repo=parent.name,
namespace=namespace)
else:
url = flask.url_for(
'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
SESSION.rollback()
flask.flash(str(err), 'error')
flask.g.branches = sorted(orig_repo.listall_branches())
return flask.render_template(
'remote_pull_request.html',
select='requests',
repo=repo,
username=username,
form=form,
branch_to=orig_repo.head.shorthand,
)
@APP.route(
'/fork_edit/<repo>/edit/<path:branchname>/f/<path:filename>',
methods=['POST'])
@APP.route(
'/fork_edit/<namespace>/<repo>/edit/<path:branchname>/f/<path:filename>',
methods=['POST'])
@APP.route(
'/fork_edit/fork/<username>/<repo>/edit/<path:branchname>/'
'f/<path:filename>', methods=['POST'])
@APP.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.get_project(
SESSION, repo.name, user=flask.g.fas_user.username):
flask.flash('You had already forked this project')
return flask.redirect(flask.url_for(
'edit_file',
username=flask.g.fas_user.username,
namespace=namespace,
repo=repo.name,
branchname=branchname,
filename=filename
))
try:
message = pagure.lib.fork_project(
session=SESSION,
repo=repo,
gitfolder=APP.config['GIT_FOLDER'],
docfolder=APP.config['DOCS_FOLDER'],
ticketfolder=APP.config['TICKETS_FOLDER'],
requestfolder=APP.config['REQUESTS_FOLDER'],
user=flask.g.fas_user.username)
SESSION.commit()
pagure.lib.git.generate_gitolite_acls()
flask.flash(message)
return flask.redirect(flask.url_for(
'edit_file',
username=flask.g.fas_user.username,
namespace=repo.namespace,
repo=repo.name,
branchname=branchname,
filename=filename
))
except pagure.exceptions.PagureException as err:
flask.flash(str(err), 'error')
except SQLAlchemyError as err: # pragma: no cover
SESSION.rollback()
flask.flash(str(err), 'error')
return flask.redirect(flask.url_for(
'view_repo', repo=repo.name, username=username, namespace=namespace))