diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py
index 5d0d3da..088204f 100644
--- a/pagure/lib/__init__.py
+++ b/pagure/lib/__init__.py
@@ -2923,6 +2923,41 @@ def search_pull_requests(
return output
+def reopen_pull_request(session, request, user, requestfolder):
+ ''' Re-Open the provided pull request
+ '''
+ if request.status != 'Closed':
+ raise pagure.exceptions.PagureException(
+ 'Trying to reopen a pull request that is not closed'
+ )
+ user_obj = get_user(session, user)
+ request.status = 'Open'
+ session.add(request)
+ session.flush()
+ log_action(session, request.status.lower(), request, user_obj)
+ pagure.lib.notify.notify_reopen_pull_request(request, user_obj)
+ pagure.lib.git.update_git(
+ request, repo=request.project, repofolder=requestfolder)
+ pagure.lib.add_pull_request_comment(
+ session, request,
+ commit=None, tree_id=None, filename=None, row=None,
+ comment='Pull-Request has been reopened by %s' % (
+ user),
+ user=user,
+ requestfolder=requestfolder,
+ notify=False, notification=True
+ )
+ pagure.lib.notify.log(
+ request.project,
+ topic='pull-request.reopened',
+ msg=dict(
+ pullrequest=request.to_json(public=True),
+ agent=user_obj.username,
+ ),
+ redis=REDIS,
+ )
+
+
def close_pull_request(session, request, user, requestfolder, merged=True):
''' Close the provided pull-request.
'''
diff --git a/pagure/lib/notify.py b/pagure/lib/notify.py
index 0e2dc9c..c9b1fe3 100644
--- a/pagure/lib/notify.py
+++ b/pagure/lib/notify.py
@@ -660,6 +660,42 @@ Merged pull-request:
)
+def notify_reopen_pull_request(request, user):
+ ''' Notify the people following a project that a closed pull-request
+ has been reopened.
+ '''
+ text = """
+%s reopened a pull-request against the project: `%s` that you are following.
+
+Reopened pull-request:
+
+``
+%s
+``
+
+%s
+""" % (user.username,
+ request.project.name,
+ request.title,
+ _build_url(
+ pagure_config['APP_URL'],
+ _fullname_to_url(request.project.fullname),
+ 'pull-request',
+ request.id))
+ mail_to = _get_emails_for_obj(request)
+
+ uid = time.mktime(datetime.datetime.now().timetuple())
+ send_email(
+ text,
+ 'PR #%s: %s' % (request.id, request.title),
+ ','.join(mail_to),
+ mail_id='%s/close/%s' % (request.mail_id, uid),
+ in_reply_to=request.mail_id,
+ project_name=request.project.fullname,
+ user_from=user.fullname or user.user,
+ )
+
+
def notify_cancelled_pull_request(request, user):
''' Notify the people following a project that a pull-request was
cancelled in it.
diff --git a/pagure/templates/pull_request.html b/pagure/templates/pull_request.html
index 69de47c..2f74375 100644
--- a/pagure/templates/pull_request.html
+++ b/pagure/templates/pull_request.html
@@ -30,7 +30,7 @@
{% if pull_request %}
PR#{{requestid}}
- {% if pull_request.status != 'Open'%}
+ {% if pull_request.status != 'Open' and pull_request.status != 'Closed' %}
{{pull_request.status}}
{% endif %}
{{ pull_request.title | noJS(ignore="img") | safe}}
@@ -705,6 +705,19 @@
{{ pull_request.closed_by.user if pull_request.closed_by else ''}}
{{pull_request.closed_at|humanize}}
+ {% if pull_request.status == 'Closed' and g.authenticated and
+ (g.repo_committer or g.fas_user.username == pull_request.user.username) %}
+
+ {% endif %}
{% endif %}
@@ -1190,6 +1203,10 @@ $(document).ready(function() {
return window.confirm("Are you sure you want to close this requested pull?");
});
+ $('#reopen_pr').click(function(){
+ return window.confirm("Are you sure you want to reopen this requested pull?");
+ });
+
$( ".code_table tr" ).hover(
function() {
$( this ).find( ".prc_img" ).show().width(13);
diff --git a/pagure/ui/fork.py b/pagure/ui/fork.py
index e4217fe..1343808 100644
--- a/pagure/ui/fork.py
+++ b/pagure/ui/fork.py
@@ -745,6 +745,64 @@ def pull_request_edit_comment(
@UI_NS.route(
+ '//pull-request//reopen', methods=['POST'])
+@UI_NS.route(
+ '///pull-request//reopen',
+ methods=['POST'])
+@UI_NS.route(
+ '/fork///pull-request//reopen',
+ methods=['POST'])
+@UI_NS.route(
+ '/fork////pull-request//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, 'No pull-requests found for this project')
+
+ request = pagure.lib.search_pull_requests(
+ flask.g.session, project_id=flask.g.repo.id, requestid=requestid)
+
+ if not request:
+ flask.abort(404, 'Pull-request not found')
+
+ if not flask.g.repo_committer \
+ and not flask.g.fas_user.username == request.user.username:
+ flask.abort(
+ 403,
+ 'You are not allowed to reopen pull-request for this project')
+
+ try:
+ pagure.lib.reopen_pull_request(
+ flask.g.session, request, flask.g.fas_user.username,
+ requestfolder=pagure_config['REQUESTS_FOLDER'])
+ 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(
'//pull-request//merge', methods=['POST'])
@UI_NS.route(
'///pull-request//merge',
diff --git a/tests/test_pagure_flask_ui_fork.py b/tests/test_pagure_flask_ui_fork.py
index 8669474..2434831 100644
--- a/tests/test_pagure_flask_ui_fork.py
+++ b/tests/test_pagure_flask_ui_fork.py
@@ -1478,6 +1478,98 @@ index 0000000..2a552bb
'\n Pull request canceled!',
output_text)
+ @patch('pagure.lib.notify.send_email')
+ def test_reopen_request_pull(self, send_email):
+ """ Test the reopen_request_pull endpoint. """
+ send_email.return_value = True
+
+ tests.create_projects(self.session)
+ tests.create_projects_git(
+ os.path.join(self.path, 'requests'), bare=True)
+ self.set_up_git_repo(
+ new_project=None, branch_from='feature', mtype='merge')
+
+ user = tests.FakeUser()
+ with tests.user_set(self.app.application, user):
+ output = self.app.post('/test/pull-request/1/reopen')
+ self.assertEqual(output.status_code, 302)
+
+ output = self.app.post(
+ '/test/pull-request/1/reopen', follow_redirects=True)
+ self.assertEqual(output.status_code, 200)
+ output_text = output.get_data(as_text=True)
+ self.assertIn(
+ 'PR#1: PR from the feature branch - test\n - Pagure', output_text)
+ self.assertIn(
+ #'\n Pull request reopened!',
+ 'return window.confirm("Are you sure you want to reopen this requested pull?")',
+ output_text)
+
+ output = self.app.get('/test/pull-request/1')
+ self.assertEqual(output.status_code, 200)
+
+ csrf_token = self.get_csrf(output=output)
+
+ data = {
+ 'csrf_token': csrf_token,
+ }
+
+ # Invalid project
+ output = self.app.post(
+ '/foo/pull-request/1/reopen', data=data,
+ follow_redirects=True)
+ self.assertEqual(output.status_code, 404)
+
+ # Invalid PR id
+ output = self.app.post(
+ '/test/pull-request/100/reopen', data=data,
+ follow_redirects=True)
+ self.assertEqual(output.status_code, 404)
+
+ # Invalid user for this project
+ output = self.app.post(
+ '/test/pull-request/1/reopen', data=data,
+ follow_redirects=True)
+ self.assertEqual(output.status_code, 403)
+
+ user.username = 'pingou'
+ with tests.user_set(self.app.application, user):
+ # Project w/o pull-request
+ repo = pagure.lib.get_authorized_project(self.session, 'test')
+ settings = repo.settings
+ settings['pull_requests'] = False
+ repo.settings = settings
+ self.session.add(repo)
+ self.session.commit()
+
+ output = self.app.post(
+ '/test/pull-request/1/reopen', data=data,
+ follow_redirects=True)
+ self.assertEqual(output.status_code, 404)
+
+ # Project w/ pull-request
+ repo = pagure.lib.get_authorized_project(self.session, 'test')
+ settings = repo.settings
+ settings['pull_requests'] = True
+ repo.settings = settings
+ self.session.add(repo)
+ self.session.commit()
+
+ output = self.app.post(
+ '/test/pull-request/cancel/1', data=data,
+ follow_redirects=True)
+ output = self.app.post(
+ '/test/pull-request/1/reopen', data=data,
+ follow_redirects=True)
+ self.assertEqual(output.status_code, 200)
+ output_text = output.get_data(as_text=True)
+ self.assertIn(
+ 'PR#1: PR from the feature branch - test\n - '
+ 'Pagure', output_text)
+ self.assertIn(
+ 'return window.confirm("Are you sure you want to reopen this requested pull?")',
+ output_text)
+
@patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
def test_update_pull_requests_assign(self):
""" Test the update_pull_requests endpoint when assigning a PR.