From 130d0e7d40117523309f368dfba29bd6fa7be4cd Mon Sep 17 00:00:00 2001 From: Karsten Hopp Date: Jun 23 2018 17:02:32 +0000 Subject: Allow to reopen pull requests Fixes: https://pagure.io/pagure/issue/2643 Merges https://pagure.io/pagure/pull-request/3349 Signed-off-by: Karsten Hopp Signed-off-by: Pierre-Yves Chibon --- 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) %} +
+ {{ mergeform.csrf_token }} + +
+ {% 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.