##// END OF EJS Templates
pull-requests: migrated code from pylons to pyramid
marcink -
r1974:cb4db595 default
parent child Browse files
Show More
@@ -21,10 +21,10 b''
21 21
22 22 import pytest
23 23 import urlobject
24 from pylons import url
25 24
26 25 from rhodecode.api.tests.utils import (
27 26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
@@ -46,10 +46,10 b' class TestGetPullRequest(object):'
46 46 assert response.status == '200 OK'
47 47
48 48 url_obj = urlobject.URLObject(
49 url(
49 h.route_url(
50 50 'pullrequest_show',
51 51 repo_name=pull_request.target_repo.repo_name,
52 pull_request_id=pull_request.pull_request_id, qualified=True))
52 pull_request_id=pull_request.pull_request_id))
53 53
54 54 pr_url = safe_unicode(
55 55 url_obj.with_netloc(http_host_only_stub))
@@ -217,7 +217,7 b' def includeme(config):'
217 217 # Pull Requests
218 218 config.add_route(
219 219 name='pullrequest_show',
220 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id}',
220 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}',
221 221 repo_route=True)
222 222
223 223 config.add_route(
@@ -230,6 +230,51 b' def includeme(config):'
230 230 pattern='/{repo_name:.*?[^/]}/pull-request-data',
231 231 repo_route=True, repo_accepted_types=['hg', 'git'])
232 232
233 config.add_route(
234 name='pullrequest_repo_refs',
235 pattern='/{repo_name:.*?[^/]}/pull-request/refs/{target_repo_name:.*?[^/]}',
236 repo_route=True)
237
238 config.add_route(
239 name='pullrequest_repo_destinations',
240 pattern='/{repo_name:.*?[^/]}/pull-request/repo-destinations',
241 repo_route=True)
242
243 config.add_route(
244 name='pullrequest_new',
245 pattern='/{repo_name:.*?[^/]}/pull-request/new',
246 repo_route=True, repo_accepted_types=['hg', 'git'])
247
248 config.add_route(
249 name='pullrequest_create',
250 pattern='/{repo_name:.*?[^/]}/pull-request/create',
251 repo_route=True, repo_accepted_types=['hg', 'git'])
252
253 config.add_route(
254 name='pullrequest_update',
255 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/update',
256 repo_route=True)
257
258 config.add_route(
259 name='pullrequest_merge',
260 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/merge',
261 repo_route=True)
262
263 config.add_route(
264 name='pullrequest_delete',
265 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/delete',
266 repo_route=True)
267
268 config.add_route(
269 name='pullrequest_comment_create',
270 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment',
271 repo_route=True)
272
273 config.add_route(
274 name='pullrequest_comment_delete',
275 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
276 repo_route=True, repo_accepted_types=['hg', 'git'])
277
233 278 # Settings
234 279 config.add_route(
235 280 name='edit_repo',
@@ -17,12 +17,11 b''
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 20 import mock
22 21 import pytest
23 from webob.exc import HTTPNotFound
24 22
25 23 import rhodecode
24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
26 25 from rhodecode.lib.vcs.nodes import FileNode
27 26 from rhodecode.lib import helpers as h
28 27 from rhodecode.model.changeset_status import ChangesetStatusModel
@@ -32,7 +31,7 b' from rhodecode.model.meta import Session'
32 31 from rhodecode.model.pull_request import PullRequestModel
33 32 from rhodecode.model.user import UserModel
34 33 from rhodecode.tests import (
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
34 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 35 from rhodecode.tests.utils import AssertResponse
37 36
38 37
@@ -42,6 +41,18 b' def route_path(name, params=None, **kwar'
42 41 base_url = {
43 42 'repo_changelog':'/{repo_name}/changelog',
44 43 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}',
44 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
45 'pullrequest_show_all': '/{repo_name}/pull-request',
46 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
47 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
48 'pullrequest_repo_destinations': '/{repo_name}/pull-request/repo-destinations',
49 'pullrequest_new': '/{repo_name}/pull-request/new',
50 'pullrequest_create': '/{repo_name}/pull-request/create',
51 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
52 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
53 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
54 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
55 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
45 56 }[name].format(**kwargs)
46 57
47 58 if params:
@@ -51,26 +62,26 b' def route_path(name, params=None, **kwar'
51 62
52 63 @pytest.mark.usefixtures('app', 'autologin_user')
53 64 @pytest.mark.backends("git", "hg")
54 class TestPullrequestsController(object):
65 class TestPullrequestsView(object):
55 66
56 67 def test_index(self, backend):
57 self.app.get(url(
58 controller='pullrequests', action='index',
68 self.app.get(route_path(
69 'pullrequest_new',
59 70 repo_name=backend.repo_name))
60 71
61 72 def test_option_menu_create_pull_request_exists(self, backend):
62 73 repo_name = backend.repo_name
63 74 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
64 75
65 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
66 'pullrequest', repo_name=repo_name)
76 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
77 'pullrequest_new', repo_name=repo_name)
67 78 response.mustcontain(create_pr_link)
68 79
69 80 def test_create_pr_form_with_raw_commit_id(self, backend):
70 81 repo = backend.repo
71 82
72 83 self.app.get(
73 url(controller='pullrequests', action='index',
84 route_path('pullrequest_new',
74 85 repo_name=repo.repo_name,
75 86 commit=repo.get_commit().raw_id),
76 87 status=200)
@@ -80,10 +91,10 b' class TestPullrequestsController(object)'
80 91 pull_request = pr_util.create_pull_request(
81 92 mergeable=pr_merge_enabled, enable_notifications=False)
82 93
83 response = self.app.get(url(
84 controller='pullrequests', action='show',
94 response = self.app.get(route_path(
95 'pullrequest_show',
85 96 repo_name=pull_request.target_repo.scm_instance().name,
86 pull_request_id=str(pull_request.pull_request_id)))
97 pull_request_id=pull_request.pull_request_id))
87 98
88 99 for commit_id in pull_request.revisions:
89 100 response.mustcontain(commit_id)
@@ -111,10 +122,10 b' class TestPullrequestsController(object)'
111 122 pull_request = pr_util.create_pull_request(
112 123 author=TEST_USER_REGULAR_LOGIN)
113 124
114 response = self.app.get(url(
115 controller='pullrequests', action='show',
125 response = self.app.get(route_path(
126 'pullrequest_show',
116 127 repo_name=pull_request.target_repo.scm_instance().name,
117 pull_request_id=str(pull_request.pull_request_id)))
128 pull_request_id=pull_request.pull_request_id))
118 129
119 130 response.mustcontain('Server-side pull request merging is disabled.')
120 131
@@ -126,10 +137,10 b' class TestPullrequestsController(object)'
126 137 pull_request.target_repo,
127 138 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
128 139 'repository.write')
129 response = self.app.get(url(
130 controller='pullrequests', action='show',
140 response = self.app.get(route_path(
141 'pullrequest_show',
131 142 repo_name=pull_request.target_repo.scm_instance().name,
132 pull_request_id=str(pull_request.pull_request_id)))
143 pull_request_id=pull_request.pull_request_id))
133 144
134 145 response.mustcontain('Server-side pull request merging is disabled.')
135 146
@@ -144,10 +155,10 b' class TestPullrequestsController(object)'
144 155 Session().add(pull_request)
145 156 Session().commit()
146 157
147 response = self.app.get(url(
148 controller='pullrequests', action='show',
158 response = self.app.get(route_path(
159 'pullrequest_show',
149 160 repo_name=pull_request.target_repo.scm_instance().name,
150 pull_request_id=str(pull_request.pull_request_id)))
161 pull_request_id=pull_request.pull_request_id))
151 162
152 163 for commit_id in pull_request.revisions:
153 164 response.mustcontain(commit_id)
@@ -158,22 +169,21 b' class TestPullrequestsController(object)'
158 169 Session().add(pull_request)
159 170 Session().commit()
160 171
161 self.app.get(url(
162 controller='pullrequests', action='show',
172 self.app.get(route_path(
173 'pullrequest_show',
163 174 repo_name=pull_request.target_repo.scm_instance().name,
164 pull_request_id=str(pull_request.pull_request_id)))
175 pull_request_id=pull_request.pull_request_id))
165 176
166 177 def test_edit_title_description(self, pr_util, csrf_token):
167 178 pull_request = pr_util.create_pull_request()
168 179 pull_request_id = pull_request.pull_request_id
169 180
170 181 response = self.app.post(
171 url(controller='pullrequests', action='update',
182 route_path('pullrequest_update',
172 183 repo_name=pull_request.target_repo.repo_name,
173 pull_request_id=str(pull_request_id)),
184 pull_request_id=pull_request_id),
174 185 params={
175 186 'edit_pull_request': 'true',
176 '_method': 'put',
177 187 'title': 'New title',
178 188 'description': 'New description',
179 189 'csrf_token': csrf_token})
@@ -192,12 +202,11 b' class TestPullrequestsController(object)'
192 202 pr_util.close()
193 203
194 204 response = self.app.post(
195 url(controller='pullrequests', action='update',
205 route_path('pullrequest_update',
196 206 repo_name=pull_request.target_repo.repo_name,
197 pull_request_id=str(pull_request_id)),
207 pull_request_id=pull_request_id),
198 208 params={
199 209 'edit_pull_request': 'true',
200 '_method': 'put',
201 210 'title': 'New title',
202 211 'description': 'New description',
203 212 'csrf_token': csrf_token})
@@ -217,10 +226,10 b' class TestPullrequestsController(object)'
217 226 pull_request_id = pull_request.pull_request_id
218 227
219 228 response = self.app.post(
220 url(controller='pullrequests', action='update',
229 route_path('pullrequest_update',
221 230 repo_name=pull_request.target_repo.repo_name,
222 pull_request_id=str(pull_request_id)),
223 params={'update_commits': 'true', '_method': 'put',
231 pull_request_id=pull_request_id),
232 params={'update_commits': 'true',
224 233 'csrf_token': csrf_token})
225 234
226 235 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
@@ -236,10 +245,10 b' class TestPullrequestsController(object)'
236 245 Session().commit()
237 246
238 247 pull_request_id = pull_request.pull_request_id
239 pull_request_url = url(
240 controller='pullrequests', action='show',
248 pull_request_url = route_path(
249 'pullrequest_show',
241 250 repo_name=pull_request.target_repo.repo_name,
242 pull_request_id=str(pull_request_id))
251 pull_request_id=pull_request_id)
243 252
244 253 response = self.app.get(pull_request_url)
245 254
@@ -258,10 +267,9 b' class TestPullrequestsController(object)'
258 267 repo = pull_request.target_repo.repo_id
259 268
260 269 self.app.post(
261 url(controller='pullrequests',
262 action='comment',
270 route_path('pullrequest_comment_create',
263 271 repo_name=pull_request.target_repo.scm_instance().name,
264 pull_request_id=str(pull_request_id)),
272 pull_request_id=pull_request_id),
265 273 params={
266 274 'close_pull_request': '1',
267 275 'text': 'Closing a PR',
@@ -298,10 +306,9 b' class TestPullrequestsController(object)'
298 306 repo = pull_request.target_repo.repo_id
299 307
300 308 self.app.post(
301 url(controller='pullrequests',
302 action='comment',
309 route_path('pullrequest_comment_create',
303 310 repo_name=pull_request.target_repo.scm_instance().name,
304 pull_request_id=str(pull_request_id)),
311 pull_request_id=pull_request_id),
305 312 params={
306 313 'close_pull_request': '1',
307 314 'csrf_token': csrf_token},
@@ -326,10 +333,9 b' class TestPullrequestsController(object)'
326 333 pull_request_id = pull_request.pull_request_id
327 334
328 335 response = self.app.post(
329 url(controller='pullrequests',
330 action='comment',
336 route_path('pullrequest_comment_create',
331 337 repo_name=pull_request.target_repo.scm_instance().name,
332 pull_request_id=str(pull_request.pull_request_id)),
338 pull_request_id=pull_request.pull_request_id),
333 339 params={
334 340 'close_pull_request': 'true',
335 341 'csrf_token': csrf_token},
@@ -356,11 +362,7 b' class TestPullrequestsController(object)'
356 362 source = backend.create_repo(heads=['change2'])
357 363
358 364 response = self.app.post(
359 url(
360 controller='pullrequests',
361 action='create',
362 repo_name=source.repo_name
363 ),
365 route_path('pullrequest_create', repo_name=source.repo_name),
364 366 [
365 367 ('source_repo', source.repo_name),
366 368 ('source_ref', 'branch:default:' + commit_ids['change2']),
@@ -417,11 +419,7 b' class TestPullrequestsController(object)'
417 419 source = backend.create_repo(heads=['change'])
418 420
419 421 response = self.app.post(
420 url(
421 controller='pullrequests',
422 action='create',
423 repo_name=source.repo_name
424 ),
422 route_path('pullrequest_create', repo_name=source.repo_name),
425 423 [
426 424 ('source_repo', source.repo_name),
427 425 ('source_ref', 'branch:default:' + commit_ids['change']),
@@ -485,11 +483,7 b' class TestPullrequestsController(object)'
485 483 source = backend.create_repo(heads=['change'])
486 484
487 485 response = self.app.post(
488 url(
489 controller='pullrequests',
490 action='create',
491 repo_name=source.repo_name
492 ),
486 route_path('pullrequest_create', repo_name=source.repo_name),
493 487 [
494 488 ('source_repo', source.repo_name),
495 489 ('source_ref', 'branch:default:' + commit_ids['change']),
@@ -542,10 +536,9 b' class TestPullrequestsController(object)'
542 536 repo_name = pull_request.target_repo.scm_instance().name,
543 537
544 538 response = self.app.post(
545 url(controller='pullrequests',
546 action='merge',
539 route_path('pullrequest_merge',
547 540 repo_name=str(repo_name[0]),
548 pull_request_id=str(pull_request_id)),
541 pull_request_id=pull_request_id),
549 542 params={'csrf_token': csrf_token}).follow()
550 543
551 544 pull_request = PullRequest.get(pull_request_id)
@@ -584,10 +577,9 b' class TestPullrequestsController(object)'
584 577 pull_request = PullRequest.get(pull_request_id)
585 578
586 579 response = self.app.post(
587 url(controller='pullrequests',
588 action='merge',
580 route_path('pullrequest_merge',
589 581 repo_name=pull_request.target_repo.scm_instance().name,
590 pull_request_id=str(pull_request.pull_request_id)),
582 pull_request_id=pull_request.pull_request_id),
591 583 params={'csrf_token': csrf_token}).follow()
592 584
593 585 assert response.status_int == 200
@@ -599,13 +591,12 b' class TestPullrequestsController(object)'
599 591 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
600 592 pull_request = pr_util.create_pull_request(mergeable=True)
601 593 pull_request_id = pull_request.pull_request_id
602 repo_name = pull_request.target_repo.scm_instance().name,
594 repo_name = pull_request.target_repo.scm_instance().name
603 595
604 596 response = self.app.post(
605 url(controller='pullrequests',
606 action='merge',
607 repo_name=str(repo_name[0]),
608 pull_request_id=str(pull_request_id)),
597 route_path('pullrequest_merge',
598 repo_name=repo_name,
599 pull_request_id=pull_request_id),
609 600 params={'csrf_token': csrf_token}).follow()
610 601
611 602 assert response.status_int == 200
@@ -614,6 +605,28 b' class TestPullrequestsController(object)'
614 605 'Merge is not currently possible because of below failed checks.')
615 606 response.mustcontain('Pull request reviewer approval is pending.')
616 607
608 def test_merge_pull_request_renders_failure_reason(
609 self, user_regular, csrf_token, pr_util):
610 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
611 pull_request_id = pull_request.pull_request_id
612 repo_name = pull_request.target_repo.scm_instance().name
613
614 model_patcher = mock.patch.multiple(
615 PullRequestModel,
616 merge=mock.Mock(return_value=MergeResponse(
617 True, False, 'STUB_COMMIT_ID', MergeFailureReason.PUSH_FAILED)),
618 merge_status=mock.Mock(return_value=(True, 'WRONG_MESSAGE')))
619
620 with model_patcher:
621 response = self.app.post(
622 route_path('pullrequest_merge',
623 repo_name=repo_name,
624 pull_request_id=pull_request_id),
625 params={'csrf_token': csrf_token}, status=302)
626
627 assert_session_flash(response, PullRequestModel.MERGE_STATUS_MESSAGES[
628 MergeFailureReason.PUSH_FAILED])
629
617 630 def test_update_source_revision(self, backend, csrf_token):
618 631 commits = [
619 632 {'message': 'ancestor'},
@@ -649,10 +662,10 b' class TestPullrequestsController(object)'
649 662
650 663 # update PR
651 664 self.app.post(
652 url(controller='pullrequests', action='update',
665 route_path('pullrequest_update',
653 666 repo_name=target.repo_name,
654 pull_request_id=str(pull_request_id)),
655 params={'update_commits': 'true', '_method': 'put',
667 pull_request_id=pull_request_id),
668 params={'update_commits': 'true',
656 669 'csrf_token': csrf_token})
657 670
658 671 # check that we have now both revisions
@@ -661,8 +674,8 b' class TestPullrequestsController(object)'
661 674 commit_ids['change-2'], commit_ids['change']]
662 675
663 676 # TODO: johbo: this should be a test on its own
664 response = self.app.get(url(
665 controller='pullrequests', action='index',
677 response = self.app.get(route_path(
678 'pullrequest_new',
666 679 repo_name=target.repo_name))
667 680 assert response.status_int == 200
668 681 assert 'Pull request updated to' in response.body
@@ -707,10 +720,10 b' class TestPullrequestsController(object)'
707 720
708 721 # update PR
709 722 self.app.post(
710 url(controller='pullrequests', action='update',
723 route_path('pullrequest_update',
711 724 repo_name=target.repo_name,
712 pull_request_id=str(pull_request_id)),
713 params={'update_commits': 'true', '_method': 'put',
725 pull_request_id=pull_request_id),
726 params={'update_commits': 'true',
714 727 'csrf_token': csrf_token},
715 728 status=200)
716 729
@@ -722,8 +735,8 b' class TestPullrequestsController(object)'
722 735 commit_id=commit_ids['ancestor-new'])
723 736
724 737 # TODO: johbo: This should be a test on its own
725 response = self.app.get(url(
726 controller='pullrequests', action='index',
738 response = self.app.get(route_path(
739 'pullrequest_new',
727 740 repo_name=target.repo_name))
728 741 assert response.status_int == 200
729 742 assert 'Pull request updated to' in response.body
@@ -770,10 +783,10 b' class TestPullrequestsController(object)'
770 783
771 784 # update PR
772 785 self.app.post(
773 url(controller='pullrequests', action='update',
786 route_path('pullrequest_update',
774 787 repo_name=target.repo_name,
775 pull_request_id=str(pull_request_id)),
776 params={'update_commits': 'true', '_method': 'put',
788 pull_request_id=pull_request_id),
789 params={'update_commits': 'true',
777 790 'csrf_token': csrf_token},
778 791 status=200)
779 792
@@ -814,10 +827,10 b' class TestPullrequestsController(object)'
814 827 vcs = repo.scm_instance()
815 828 vcs.remove_ref('refs/heads/{}'.format(branch_name))
816 829
817 response = self.app.get(url(
818 controller='pullrequests', action='show',
830 response = self.app.get(route_path(
831 'pullrequest_show',
819 832 repo_name=repo.repo_name,
820 pull_request_id=str(pull_request.pull_request_id)))
833 pull_request_id=pull_request.pull_request_id))
821 834
822 835 assert response.status_int == 200
823 836 assert_response = AssertResponse(response)
@@ -846,10 +859,10 b' class TestPullrequestsController(object)'
846 859 else:
847 860 vcs.strip(pr_util.commit_ids['new-feature'])
848 861
849 response = self.app.get(url(
850 controller='pullrequests', action='show',
862 response = self.app.get(route_path(
863 'pullrequest_show',
851 864 repo_name=pr_util.target_repository.repo_name,
852 pull_request_id=str(pull_request.pull_request_id)))
865 pull_request_id=pull_request.pull_request_id))
853 866
854 867 assert response.status_int == 200
855 868 assert_response = AssertResponse(response)
@@ -882,20 +895,20 b' class TestPullrequestsController(object)'
882 895 vcs.strip(pr_util.commit_ids['new-feature'])
883 896
884 897 response = self.app.post(
885 url(controller='pullrequests', action='update',
898 route_path('pullrequest_update',
886 899 repo_name=pull_request.target_repo.repo_name,
887 pull_request_id=str(pull_request.pull_request_id)),
888 params={'update_commits': 'true', '_method': 'put',
900 pull_request_id=pull_request.pull_request_id),
901 params={'update_commits': 'true',
889 902 'csrf_token': csrf_token})
890 903
891 904 assert response.status_int == 200
892 905 assert response.body == 'true'
893 906
894 907 # Make sure that after update, it won't raise 500 errors
895 response = self.app.get(url(
896 controller='pullrequests', action='show',
908 response = self.app.get(route_path(
909 'pullrequest_show',
897 910 repo_name=pr_util.target_repository.repo_name,
898 pull_request_id=str(pull_request.pull_request_id)))
911 pull_request_id=pull_request.pull_request_id))
899 912
900 913 assert response.status_int == 200
901 914 assert_response = AssertResponse(response)
@@ -910,10 +923,10 b' class TestPullrequestsController(object)'
910 923 Session().add(pull_request)
911 924 Session().commit()
912 925
913 response = self.app.get(url(
914 controller='pullrequests', action='show',
926 response = self.app.get(route_path(
927 'pullrequest_show',
915 928 repo_name=pull_request.target_repo.scm_instance().name,
916 pull_request_id=str(pull_request.pull_request_id)))
929 pull_request_id=pull_request.pull_request_id))
917 930 assert response.status_int == 200
918 931 assert_response = AssertResponse(response)
919 932
@@ -944,10 +957,10 b' class TestPullrequestsController(object)'
944 957 Session().add(pull_request)
945 958 Session().commit()
946 959
947 response = self.app.get(url(
948 controller='pullrequests', action='show',
960 response = self.app.get(route_path(
961 'pullrequest_show',
949 962 repo_name=pull_request.target_repo.scm_instance().name,
950 pull_request_id=str(pull_request.pull_request_id)))
963 pull_request_id=pull_request.pull_request_id))
951 964 assert response.status_int == 200
952 965 assert_response = AssertResponse(response)
953 966
@@ -966,10 +979,10 b' class TestPullrequestsController(object)'
966 979 Session().add(pull_request)
967 980 Session().commit()
968 981
969 response = self.app.get(url(
970 controller='pullrequests', action='show',
982 response = self.app.get(route_path(
983 'pullrequest_show',
971 984 repo_name=pull_request.target_repo.scm_instance().name,
972 pull_request_id=str(pull_request.pull_request_id)))
985 pull_request_id=pull_request.pull_request_id))
973 986 assert response.status_int == 200
974 987 assert_response = AssertResponse(response)
975 988
@@ -996,10 +1009,10 b' class TestPullrequestsController(object)'
996 1009 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
997 1010 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
998 1011
999 response = self.app.get(url(
1000 controller='pullrequests', action='show',
1012 response = self.app.get(route_path(
1013 'pullrequest_show',
1001 1014 repo_name=target_repo.name,
1002 pull_request_id=str(pr_id)))
1015 pull_request_id=pr_id))
1003 1016
1004 1017 assertr = AssertResponse(response)
1005 1018 if mergeable:
@@ -1019,10 +1032,10 b' class TestPullrequestsControllerDelete(o'
1019 1032 pull_request = pr_util.create_pull_request(
1020 1033 author=user_admin.username, enable_notifications=False)
1021 1034
1022 response = self.app.get(url(
1023 controller='pullrequests', action='show',
1035 response = self.app.get(route_path(
1036 'pullrequest_show',
1024 1037 repo_name=pull_request.target_repo.scm_instance().name,
1025 pull_request_id=str(pull_request.pull_request_id)))
1038 pull_request_id=pull_request.pull_request_id))
1026 1039
1027 1040 response.mustcontain('id="delete_pullrequest"')
1028 1041 response.mustcontain('Confirm to delete this pull request')
@@ -1032,10 +1045,10 b' class TestPullrequestsControllerDelete(o'
1032 1045 pull_request = pr_util.create_pull_request(
1033 1046 author=user_regular.username, enable_notifications=False)
1034 1047
1035 response = self.app.get(url(
1036 controller='pullrequests', action='show',
1048 response = self.app.get(route_path(
1049 'pullrequest_show',
1037 1050 repo_name=pull_request.target_repo.scm_instance().name,
1038 pull_request_id=str(pull_request.pull_request_id)))
1051 pull_request_id=pull_request.pull_request_id))
1039 1052
1040 1053 response.mustcontain('id="delete_pullrequest"')
1041 1054 response.mustcontain('Confirm to delete this pull request')
@@ -1045,10 +1058,10 b' class TestPullrequestsControllerDelete(o'
1045 1058 pull_request = pr_util.create_pull_request(
1046 1059 author=user_admin.username, enable_notifications=False)
1047 1060
1048 response = self.app.get(url(
1049 controller='pullrequests', action='show',
1061 response = self.app.get(route_path(
1062 'pullrequest_show',
1050 1063 repo_name=pull_request.target_repo.scm_instance().name,
1051 pull_request_id=str(pull_request.pull_request_id)))
1064 pull_request_id=pull_request.pull_request_id))
1052 1065 response.mustcontain(no=['id="delete_pullrequest"'])
1053 1066 response.mustcontain(no=['Confirm to delete this pull request'])
1054 1067
@@ -1063,10 +1076,10 b' class TestPullrequestsControllerDelete(o'
1063 1076 pull_request.target_repo, user_regular,
1064 1077 'repository.write')
1065 1078
1066 response = self.app.get(url(
1067 controller='pullrequests', action='show',
1079 response = self.app.get(route_path(
1080 'pullrequest_show',
1068 1081 repo_name=pull_request.target_repo.scm_instance().name,
1069 pull_request_id=str(pull_request.pull_request_id)))
1082 pull_request_id=pull_request.pull_request_id))
1070 1083
1071 1084 response.mustcontain('id="open_edit_pullrequest"')
1072 1085 response.mustcontain('id="delete_pullrequest"')
@@ -1078,9 +1091,10 b' class TestPullrequestsControllerDelete(o'
1078 1091 pull_request = pr_util.create_pull_request(
1079 1092 author=user_admin.username, enable_notifications=False)
1080 1093
1081 self.app.get(url(
1082 controller='pullrequests', action='delete_comment',
1094 self.app.get(route_path(
1095 'pullrequest_comment_delete',
1083 1096 repo_name=pull_request.target_repo.scm_instance().name,
1097 pull_request_id=pull_request.pull_request_id,
1084 1098 comment_id=1024404), status=404)
1085 1099
1086 1100
@@ -1090,17 +1104,9 b' def assert_pull_request_status(pull_requ'
1090 1104 assert status == expected_status
1091 1105
1092 1106
1093 @pytest.mark.parametrize('action', ['index', 'create'])
1107 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1094 1108 @pytest.mark.usefixtures("autologin_user")
1095 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1096 response = app.get(url(
1097 controller='pullrequests', action=action,
1098 repo_name=backend_svn.repo_name))
1099 assert response.status_int == 302
1109 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1110 response = app.get(
1111 route_path(route, repo_name=backend_svn.repo_name), status=404)
1100 1112
1101 # Not allowed, redirect to the summary
1102 redirected = response.follow()
1103 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1104
1105 # URL adds leading slash and path doesn't have it
1106 assert redirected.request.path == summary_url
This diff has been collapsed as it changes many lines, (630 lines changed) Show them Hide them
@@ -19,23 +19,35 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 import collections
22 23
23 import collections
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 import formencode
25 import peppercorn
26 from pyramid.httpexceptions import (
27 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
25 28 from pyramid.view import view_config
29 from pyramid.renderers import render
26 30
31 from rhodecode import events
27 32 from rhodecode.apps._base import RepoAppView, DataGridAppView
28 from rhodecode.lib import helpers as h, diffs, codeblocks
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.ext_json import json
29 37 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator)
31 from rhodecode.lib.utils2 import str2bool, safe_int, safe_str
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, \
34 RepositoryRequirementError, NodeDoesNotExistError
38 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
39 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
40 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
41 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
42 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
43 from rhodecode.model.changeset_status import ChangesetStatusModel
35 44 from rhodecode.model.comment import CommentsModel
36 from rhodecode.model.db import PullRequest, PullRequestVersion, \
37 ChangesetComment, ChangesetStatus
45 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
46 ChangesetComment, ChangesetStatus, Repository)
47 from rhodecode.model.forms import PullRequestForm
48 from rhodecode.model.meta import Session
38 49 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
50 from rhodecode.model.scm import ScmModel
39 51
40 52 log = logging.getLogger(__name__)
41 53
@@ -189,7 +201,6 b' class RepoPullRequestsView(RepoAppView, '
189 201 return data
190 202
191 203 def _get_pr_version(self, pull_request_id, version=None):
192 pull_request_id = safe_int(pull_request_id)
193 204 at_version = None
194 205
195 206 if version and version == 'latest':
@@ -250,12 +261,12 b' class RepoPullRequestsView(RepoAppView, '
250 261 @LoginRequired()
251 262 @HasRepoPermissionAnyDecorator(
252 263 'repository.read', 'repository.write', 'repository.admin')
253 # @view_config(
254 # route_name='pullrequest_show', request_method='GET',
255 # renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
264 @view_config(
265 route_name='pullrequest_show', request_method='GET',
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
256 267 def pull_request_show(self):
257 pull_request_id = safe_int(
258 self.request.matchdict.get('pull_request_id'))
268 pull_request_id = self.request.matchdict.get('pull_request_id')
269
259 270 c = self.load_default_context()
260 271
261 272 version = self.request.GET.get('version')
@@ -582,3 +593,590 b' class RepoPullRequestsView(RepoAppView, '
582 593 c.review_versions[_ver_pr] = status[0]
583 594
584 595 return self._get_template_context(c)
596
597 def assure_not_empty_repo(self):
598 _ = self.request.translate
599
600 try:
601 self.db_repo.scm_instance().get_commit()
602 except EmptyRepositoryError:
603 h.flash(h.literal(_('There are no commits yet')),
604 category='warning')
605 raise HTTPFound(
606 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
607
608 @LoginRequired()
609 @NotAnonymous()
610 @HasRepoPermissionAnyDecorator(
611 'repository.read', 'repository.write', 'repository.admin')
612 @view_config(
613 route_name='pullrequest_new', request_method='GET',
614 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
615 def pull_request_new(self):
616 _ = self.request.translate
617 c = self.load_default_context()
618
619 self.assure_not_empty_repo()
620 source_repo = self.db_repo
621
622 commit_id = self.request.GET.get('commit')
623 branch_ref = self.request.GET.get('branch')
624 bookmark_ref = self.request.GET.get('bookmark')
625
626 try:
627 source_repo_data = PullRequestModel().generate_repo_data(
628 source_repo, commit_id=commit_id,
629 branch=branch_ref, bookmark=bookmark_ref)
630 except CommitDoesNotExistError as e:
631 log.exception(e)
632 h.flash(_('Commit does not exist'), 'error')
633 raise HTTPFound(
634 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
635
636 default_target_repo = source_repo
637
638 if source_repo.parent:
639 parent_vcs_obj = source_repo.parent.scm_instance()
640 if parent_vcs_obj and not parent_vcs_obj.is_empty():
641 # change default if we have a parent repo
642 default_target_repo = source_repo.parent
643
644 target_repo_data = PullRequestModel().generate_repo_data(
645 default_target_repo)
646
647 selected_source_ref = source_repo_data['refs']['selected_ref']
648
649 title_source_ref = selected_source_ref.split(':', 2)[1]
650 c.default_title = PullRequestModel().generate_pullrequest_title(
651 source=source_repo.repo_name,
652 source_ref=title_source_ref,
653 target=default_target_repo.repo_name
654 )
655
656 c.default_repo_data = {
657 'source_repo_name': source_repo.repo_name,
658 'source_refs_json': json.dumps(source_repo_data),
659 'target_repo_name': default_target_repo.repo_name,
660 'target_refs_json': json.dumps(target_repo_data),
661 }
662 c.default_source_ref = selected_source_ref
663
664 return self._get_template_context(c)
665
666 @LoginRequired()
667 @NotAnonymous()
668 @HasRepoPermissionAnyDecorator(
669 'repository.read', 'repository.write', 'repository.admin')
670 @view_config(
671 route_name='pullrequest_repo_refs', request_method='GET',
672 renderer='json_ext', xhr=True)
673 def pull_request_repo_refs(self):
674 target_repo_name = self.request.matchdict['target_repo_name']
675 repo = Repository.get_by_repo_name(target_repo_name)
676 if not repo:
677 raise HTTPNotFound()
678 return PullRequestModel().generate_repo_data(repo)
679
680 @LoginRequired()
681 @NotAnonymous()
682 @HasRepoPermissionAnyDecorator(
683 'repository.read', 'repository.write', 'repository.admin')
684 @view_config(
685 route_name='pullrequest_repo_destinations', request_method='GET',
686 renderer='json_ext', xhr=True)
687 def pull_request_repo_destinations(self):
688 _ = self.request.translate
689 filter_query = self.request.GET.get('query')
690
691 query = Repository.query() \
692 .order_by(func.length(Repository.repo_name)) \
693 .filter(
694 or_(Repository.repo_name == self.db_repo.repo_name,
695 Repository.fork_id == self.db_repo.repo_id))
696
697 if filter_query:
698 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
699 query = query.filter(
700 Repository.repo_name.ilike(ilike_expression))
701
702 add_parent = False
703 if self.db_repo.parent:
704 if filter_query in self.db_repo.parent.repo_name:
705 parent_vcs_obj = self.db_repo.parent.scm_instance()
706 if parent_vcs_obj and not parent_vcs_obj.is_empty():
707 add_parent = True
708
709 limit = 20 - 1 if add_parent else 20
710 all_repos = query.limit(limit).all()
711 if add_parent:
712 all_repos += [self.db_repo.parent]
713
714 repos = []
715 for obj in ScmModel().get_repos(all_repos):
716 repos.append({
717 'id': obj['name'],
718 'text': obj['name'],
719 'type': 'repo',
720 'obj': obj['dbrepo']
721 })
722
723 data = {
724 'more': False,
725 'results': [{
726 'text': _('Repositories'),
727 'children': repos
728 }] if repos else []
729 }
730 return data
731
732 @LoginRequired()
733 @NotAnonymous()
734 @HasRepoPermissionAnyDecorator(
735 'repository.read', 'repository.write', 'repository.admin')
736 @CSRFRequired()
737 @view_config(
738 route_name='pullrequest_create', request_method='POST',
739 renderer=None)
740 def pull_request_create(self):
741 _ = self.request.translate
742 self.assure_not_empty_repo()
743
744 controls = peppercorn.parse(self.request.POST.items())
745
746 try:
747 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
748 except formencode.Invalid as errors:
749 if errors.error_dict.get('revisions'):
750 msg = 'Revisions: %s' % errors.error_dict['revisions']
751 elif errors.error_dict.get('pullrequest_title'):
752 msg = _('Pull request requires a title with min. 3 chars')
753 else:
754 msg = _('Error creating pull request: {}').format(errors)
755 log.exception(msg)
756 h.flash(msg, 'error')
757
758 # would rather just go back to form ...
759 raise HTTPFound(
760 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
761
762 source_repo = _form['source_repo']
763 source_ref = _form['source_ref']
764 target_repo = _form['target_repo']
765 target_ref = _form['target_ref']
766 commit_ids = _form['revisions'][::-1]
767
768 # find the ancestor for this pr
769 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
770 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
771
772 source_scm = source_db_repo.scm_instance()
773 target_scm = target_db_repo.scm_instance()
774
775 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
776 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
777
778 ancestor = source_scm.get_common_ancestor(
779 source_commit.raw_id, target_commit.raw_id, target_scm)
780
781 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
782 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
783
784 pullrequest_title = _form['pullrequest_title']
785 title_source_ref = source_ref.split(':', 2)[1]
786 if not pullrequest_title:
787 pullrequest_title = PullRequestModel().generate_pullrequest_title(
788 source=source_repo,
789 source_ref=title_source_ref,
790 target=target_repo
791 )
792
793 description = _form['pullrequest_desc']
794
795 get_default_reviewers_data, validate_default_reviewers = \
796 PullRequestModel().get_reviewer_functions()
797
798 # recalculate reviewers logic, to make sure we can validate this
799 reviewer_rules = get_default_reviewers_data(
800 self._rhodecode_db_user, source_db_repo,
801 source_commit, target_db_repo, target_commit)
802
803 given_reviewers = _form['review_members']
804 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
805
806 try:
807 pull_request = PullRequestModel().create(
808 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
809 target_ref, commit_ids, reviewers, pullrequest_title,
810 description, reviewer_rules
811 )
812 Session().commit()
813 h.flash(_('Successfully opened new pull request'),
814 category='success')
815 except Exception as e:
816 msg = _('Error occurred during creation of this pull request.')
817 log.exception(msg)
818 h.flash(msg, category='error')
819 raise HTTPFound(
820 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
821
822 raise HTTPFound(
823 h.route_path('pullrequest_show', repo_name=target_repo,
824 pull_request_id=pull_request.pull_request_id))
825
826 @LoginRequired()
827 @NotAnonymous()
828 @HasRepoPermissionAnyDecorator(
829 'repository.read', 'repository.write', 'repository.admin')
830 @CSRFRequired()
831 @view_config(
832 route_name='pullrequest_update', request_method='POST',
833 renderer='json_ext')
834 def pull_request_update(self):
835 pull_request_id = self.request.matchdict['pull_request_id']
836 pull_request = PullRequest.get_or_404(pull_request_id)
837
838 # only owner or admin can update it
839 allowed_to_update = PullRequestModel().check_user_update(
840 pull_request, self._rhodecode_user)
841 if allowed_to_update:
842 controls = peppercorn.parse(self.request.POST.items())
843
844 if 'review_members' in controls:
845 self._update_reviewers(
846 pull_request_id, controls['review_members'],
847 pull_request.reviewer_data)
848 elif str2bool(self.request.POST.get('update_commits', 'false')):
849 self._update_commits(pull_request)
850 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
851 self._edit_pull_request(pull_request)
852 else:
853 raise HTTPBadRequest()
854 return True
855 raise HTTPForbidden()
856
857 def _edit_pull_request(self, pull_request):
858 _ = self.request.translate
859 try:
860 PullRequestModel().edit(
861 pull_request, self.request.POST.get('title'),
862 self.request.POST.get('description'), self._rhodecode_user)
863 except ValueError:
864 msg = _(u'Cannot update closed pull requests.')
865 h.flash(msg, category='error')
866 return
867 else:
868 Session().commit()
869
870 msg = _(u'Pull request title & description updated.')
871 h.flash(msg, category='success')
872 return
873
874 def _update_commits(self, pull_request):
875 _ = self.request.translate
876 resp = PullRequestModel().update_commits(pull_request)
877
878 if resp.executed:
879
880 if resp.target_changed and resp.source_changed:
881 changed = 'target and source repositories'
882 elif resp.target_changed and not resp.source_changed:
883 changed = 'target repository'
884 elif not resp.target_changed and resp.source_changed:
885 changed = 'source repository'
886 else:
887 changed = 'nothing'
888
889 msg = _(
890 u'Pull request updated to "{source_commit_id}" with '
891 u'{count_added} added, {count_removed} removed commits. '
892 u'Source of changes: {change_source}')
893 msg = msg.format(
894 source_commit_id=pull_request.source_ref_parts.commit_id,
895 count_added=len(resp.changes.added),
896 count_removed=len(resp.changes.removed),
897 change_source=changed)
898 h.flash(msg, category='success')
899
900 channel = '/repo${}$/pr/{}'.format(
901 pull_request.target_repo.repo_name,
902 pull_request.pull_request_id)
903 message = msg + (
904 ' - <a onclick="window.location.reload()">'
905 '<strong>{}</strong></a>'.format(_('Reload page')))
906 channelstream.post_message(
907 channel, message, self._rhodecode_user.username,
908 registry=self.request.registry)
909 else:
910 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
911 warning_reasons = [
912 UpdateFailureReason.NO_CHANGE,
913 UpdateFailureReason.WRONG_REF_TYPE,
914 ]
915 category = 'warning' if resp.reason in warning_reasons else 'error'
916 h.flash(msg, category=category)
917
918 @LoginRequired()
919 @NotAnonymous()
920 @HasRepoPermissionAnyDecorator(
921 'repository.read', 'repository.write', 'repository.admin')
922 @CSRFRequired()
923 @view_config(
924 route_name='pullrequest_merge', request_method='POST',
925 renderer='json_ext')
926 def pull_request_merge(self):
927 """
928 Merge will perform a server-side merge of the specified
929 pull request, if the pull request is approved and mergeable.
930 After successful merging, the pull request is automatically
931 closed, with a relevant comment.
932 """
933 pull_request_id = self.request.matchdict['pull_request_id']
934 pull_request = PullRequest.get_or_404(pull_request_id)
935
936 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
937 merge_possible = not check.failed
938
939 for err_type, error_msg in check.errors:
940 h.flash(error_msg, category=err_type)
941
942 if merge_possible:
943 log.debug("Pre-conditions checked, trying to merge.")
944 extras = vcs_operation_context(
945 self.request.environ, repo_name=pull_request.target_repo.repo_name,
946 username=self._rhodecode_db_user.username, action='push',
947 scm=pull_request.target_repo.repo_type)
948 self._merge_pull_request(
949 pull_request, self._rhodecode_db_user, extras)
950 else:
951 log.debug("Pre-conditions failed, NOT merging.")
952
953 raise HTTPFound(
954 h.route_path('pullrequest_show',
955 repo_name=pull_request.target_repo.repo_name,
956 pull_request_id=pull_request.pull_request_id))
957
958 def _merge_pull_request(self, pull_request, user, extras):
959 _ = self.request.translate
960 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
961
962 if merge_resp.executed:
963 log.debug("The merge was successful, closing the pull request.")
964 PullRequestModel().close_pull_request(
965 pull_request.pull_request_id, user)
966 Session().commit()
967 msg = _('Pull request was successfully merged and closed.')
968 h.flash(msg, category='success')
969 else:
970 log.debug(
971 "The merge was not successful. Merge response: %s",
972 merge_resp)
973 msg = PullRequestModel().merge_status_message(
974 merge_resp.failure_reason)
975 h.flash(msg, category='error')
976
977 def _update_reviewers(self, pull_request_id, review_members, reviewer_rules):
978 _ = self.request.translate
979 get_default_reviewers_data, validate_default_reviewers = \
980 PullRequestModel().get_reviewer_functions()
981
982 try:
983 reviewers = validate_default_reviewers(review_members, reviewer_rules)
984 except ValueError as e:
985 log.error('Reviewers Validation: {}'.format(e))
986 h.flash(e, category='error')
987 return
988
989 PullRequestModel().update_reviewers(
990 pull_request_id, reviewers, self._rhodecode_user)
991 h.flash(_('Pull request reviewers updated.'), category='success')
992 Session().commit()
993
994 @LoginRequired()
995 @NotAnonymous()
996 @HasRepoPermissionAnyDecorator(
997 'repository.read', 'repository.write', 'repository.admin')
998 @CSRFRequired()
999 @view_config(
1000 route_name='pullrequest_delete', request_method='POST',
1001 renderer='json_ext')
1002 def pull_request_delete(self):
1003 _ = self.request.translate
1004
1005 pull_request_id = self.request.matchdict['pull_request_id']
1006 pull_request = PullRequest.get_or_404(pull_request_id)
1007
1008 pr_closed = pull_request.is_closed()
1009 allowed_to_delete = PullRequestModel().check_user_delete(
1010 pull_request, self._rhodecode_user) and not pr_closed
1011
1012 # only owner can delete it !
1013 if allowed_to_delete:
1014 PullRequestModel().delete(pull_request, self._rhodecode_user)
1015 Session().commit()
1016 h.flash(_('Successfully deleted pull request'),
1017 category='success')
1018 raise HTTPFound(h.route_path('my_account_pullrequests'))
1019
1020 log.warning('user %s tried to delete pull request without access',
1021 self._rhodecode_user)
1022 raise HTTPNotFound()
1023
1024 @LoginRequired()
1025 @NotAnonymous()
1026 @HasRepoPermissionAnyDecorator(
1027 'repository.read', 'repository.write', 'repository.admin')
1028 @CSRFRequired()
1029 @view_config(
1030 route_name='pullrequest_comment_create', request_method='POST',
1031 renderer='json_ext')
1032 def pull_request_comment_create(self):
1033 _ = self.request.translate
1034 pull_request_id = self.request.matchdict['pull_request_id']
1035 pull_request = PullRequest.get_or_404(pull_request_id)
1036 if pull_request.is_closed():
1037 log.debug('comment: forbidden because pull request is closed')
1038 raise HTTPForbidden()
1039
1040 c = self.load_default_context()
1041
1042 status = self.request.POST.get('changeset_status', None)
1043 text = self.request.POST.get('text')
1044 comment_type = self.request.POST.get('comment_type')
1045 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1046 close_pull_request = self.request.POST.get('close_pull_request')
1047
1048 # the logic here should work like following, if we submit close
1049 # pr comment, use `close_pull_request_with_comment` function
1050 # else handle regular comment logic
1051
1052 if close_pull_request:
1053 # only owner or admin or person with write permissions
1054 allowed_to_close = PullRequestModel().check_user_update(
1055 pull_request, self._rhodecode_user)
1056 if not allowed_to_close:
1057 log.debug('comment: forbidden because not allowed to close '
1058 'pull request %s', pull_request_id)
1059 raise HTTPForbidden()
1060 comment, status = PullRequestModel().close_pull_request_with_comment(
1061 pull_request, self._rhodecode_user, self.db_repo, message=text)
1062 Session().flush()
1063 events.trigger(
1064 events.PullRequestCommentEvent(pull_request, comment))
1065
1066 else:
1067 # regular comment case, could be inline, or one with status.
1068 # for that one we check also permissions
1069
1070 allowed_to_change_status = PullRequestModel().check_user_change_status(
1071 pull_request, self._rhodecode_user)
1072
1073 if status and allowed_to_change_status:
1074 message = (_('Status change %(transition_icon)s %(status)s')
1075 % {'transition_icon': '>',
1076 'status': ChangesetStatus.get_status_lbl(status)})
1077 text = text or message
1078
1079 comment = CommentsModel().create(
1080 text=text,
1081 repo=self.db_repo.repo_id,
1082 user=self._rhodecode_user.user_id,
1083 pull_request=pull_request_id,
1084 f_path=self.request.POST.get('f_path'),
1085 line_no=self.request.POST.get('line'),
1086 status_change=(ChangesetStatus.get_status_lbl(status)
1087 if status and allowed_to_change_status else None),
1088 status_change_type=(status
1089 if status and allowed_to_change_status else None),
1090 comment_type=comment_type,
1091 resolves_comment_id=resolves_comment_id
1092 )
1093
1094 if allowed_to_change_status:
1095 # calculate old status before we change it
1096 old_calculated_status = pull_request.calculated_review_status()
1097
1098 # get status if set !
1099 if status:
1100 ChangesetStatusModel().set_status(
1101 self.db_repo.repo_id,
1102 status,
1103 self._rhodecode_user.user_id,
1104 comment,
1105 pull_request=pull_request_id
1106 )
1107
1108 Session().flush()
1109 events.trigger(
1110 events.PullRequestCommentEvent(pull_request, comment))
1111
1112 # we now calculate the status of pull request, and based on that
1113 # calculation we set the commits status
1114 calculated_status = pull_request.calculated_review_status()
1115 if old_calculated_status != calculated_status:
1116 PullRequestModel()._trigger_pull_request_hook(
1117 pull_request, self._rhodecode_user, 'review_status_change')
1118
1119 Session().commit()
1120
1121 data = {
1122 'target_id': h.safeid(h.safe_unicode(
1123 self.request.POST.get('f_path'))),
1124 }
1125 if comment:
1126 c.co = comment
1127 rendered_comment = render(
1128 'rhodecode:templates/changeset/changeset_comment_block.mako',
1129 self._get_template_context(c), self.request)
1130
1131 data.update(comment.get_dict())
1132 data.update({'rendered_text': rendered_comment})
1133
1134 return data
1135
1136 @LoginRequired()
1137 @NotAnonymous()
1138 @HasRepoPermissionAnyDecorator(
1139 'repository.read', 'repository.write', 'repository.admin')
1140 @CSRFRequired()
1141 @view_config(
1142 route_name='pullrequest_comment_delete', request_method='POST',
1143 renderer='json_ext')
1144 def pull_request_comment_delete(self):
1145 commit_id = self.request.matchdict['commit_id']
1146 comment_id = self.request.matchdict['comment_id']
1147 pull_request_id = self.request.matchdict['pull_request_id']
1148
1149 pull_request = PullRequest.get_or_404(pull_request_id)
1150 if pull_request.is_closed():
1151 log.debug('comment: forbidden because pull request is closed')
1152 raise HTTPForbidden()
1153
1154 comment = ChangesetComment.get_or_404(comment_id)
1155 if not comment:
1156 log.debug('Comment with id:%s not found, skipping', comment_id)
1157 # comment already deleted in another call probably
1158 return True
1159
1160 if comment.pull_request.is_closed():
1161 # don't allow deleting comments on closed pull request
1162 raise HTTPForbidden()
1163
1164 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1165 super_admin = h.HasPermissionAny('hg.admin')()
1166 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1167 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1168 comment_repo_admin = is_repo_admin and is_repo_comment
1169
1170 if super_admin or comment_owner or comment_repo_admin:
1171 old_calculated_status = comment.pull_request.calculated_review_status()
1172 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1173 Session().commit()
1174 calculated_status = comment.pull_request.calculated_review_status()
1175 if old_calculated_status != calculated_status:
1176 PullRequestModel()._trigger_pull_request_hook(
1177 comment.pull_request, self._rhodecode_user, 'review_status_change')
1178 return True
1179 else:
1180 log.warning('No permissions for user %s to delete comment_id: %s',
1181 self._rhodecode_db_user, comment_id)
1182 raise HTTPNotFound()
@@ -503,73 +503,6 b' def make_map(config):'
503 503 requirements=URL_NAME_REQUIREMENTS)
504 504
505 505
506 rmap.connect('pullrequest_home',
507 '/{repo_name}/pull-request/new', controller='pullrequests',
508 action='index', conditions={'function': check_repo,
509 'method': ['GET']},
510 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
511
512 rmap.connect('pullrequest',
513 '/{repo_name}/pull-request/new', controller='pullrequests',
514 action='create', conditions={'function': check_repo,
515 'method': ['POST']},
516 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
517
518 rmap.connect('pullrequest_repo_refs',
519 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
520 controller='pullrequests',
521 action='get_repo_refs',
522 conditions={'function': check_repo, 'method': ['GET']},
523 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
524
525 rmap.connect('pullrequest_repo_destinations',
526 '/{repo_name}/pull-request/repo-destinations',
527 controller='pullrequests',
528 action='get_repo_destinations',
529 conditions={'function': check_repo, 'method': ['GET']},
530 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
531
532 rmap.connect('pullrequest_show',
533 '/{repo_name}/pull-request/{pull_request_id}',
534 controller='pullrequests',
535 action='show', conditions={'function': check_repo,
536 'method': ['GET']},
537 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
538
539 rmap.connect('pullrequest_update',
540 '/{repo_name}/pull-request/{pull_request_id}',
541 controller='pullrequests',
542 action='update', conditions={'function': check_repo,
543 'method': ['PUT']},
544 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
545
546 rmap.connect('pullrequest_merge',
547 '/{repo_name}/pull-request/{pull_request_id}',
548 controller='pullrequests',
549 action='merge', conditions={'function': check_repo,
550 'method': ['POST']},
551 requirements=URL_NAME_REQUIREMENTS)
552
553 rmap.connect('pullrequest_delete',
554 '/{repo_name}/pull-request/{pull_request_id}',
555 controller='pullrequests',
556 action='delete', conditions={'function': check_repo,
557 'method': ['DELETE']},
558 requirements=URL_NAME_REQUIREMENTS)
559
560 rmap.connect('pullrequest_comment',
561 '/{repo_name}/pull-request-comment/{pull_request_id}',
562 controller='pullrequests',
563 action='comment', conditions={'function': check_repo,
564 'method': ['POST']},
565 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
566
567 rmap.connect('pullrequest_comment_delete',
568 '/{repo_name}/pull-request-comment/{comment_id}/delete',
569 controller='pullrequests', action='delete_comment',
570 conditions={'function': check_repo, 'method': ['DELETE']},
571 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
572
573 506 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
574 507 controller='forks', action='fork_create',
575 508 conditions={'function': check_repo, 'method': ['POST']},
@@ -298,12 +298,11 b' class CommentsModel(BaseModel):'
298 298 pr_target_repo = pull_request_obj.target_repo
299 299 pr_source_repo = pull_request_obj.source_repo
300 300
301 pr_comment_url = h.url(
301 pr_comment_url = h.route_url(
302 302 'pullrequest_show',
303 303 repo_name=pr_target_repo.repo_name,
304 304 pull_request_id=pull_request_obj.pull_request_id,
305 anchor='comment-%s' % comment.comment_id,
306 qualified=True,)
305 anchor='comment-%s' % comment.comment_id)
307 306
308 307 # set some variables for email notification
309 308 pr_target_repo_url = h.route_url(
@@ -1541,6 +1541,7 b' class MergeCheck(object):'
1541 1541 if fail_early:
1542 1542 return merge_check
1543 1543
1544 log.debug('MergeCheck: is failed: %s', merge_check.failed)
1544 1545 return merge_check
1545 1546
1546 1547
@@ -15,14 +15,6 b' function registerRCRoutes() {'
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
19 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
20 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
21 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
22 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
23 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
24 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
25 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
26 18 pyroutes.register('favicon', '/favicon.ico', []);
27 19 pyroutes.register('robots', '/robots.txt', []);
28 20 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
@@ -152,6 +144,15 b' function registerRCRoutes() {'
152 144 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
153 145 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
154 146 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
147 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
148 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
149 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
150 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
151 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
152 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
153 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
154 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
155 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
155 156 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
156 157 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
157 158 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
@@ -138,7 +138,7 b' var bindToggleButtons = function() {'
138 138 'commit_id': this.commitId});
139 139
140 140 } else if (this.pullRequestId) {
141 this.submitUrl = pyroutes.url('pullrequest_comment',
141 this.submitUrl = pyroutes.url('pullrequest_comment_create',
142 142 {'repo_name': templateContext.repo_name,
143 143 'pull_request_id': this.pullRequestId});
144 144 this.selfUrl = pyroutes.url('pullrequest_show',
@@ -387,7 +387,6 b' var editPullRequest = function(repo_name'
387 387 {"repo_name": repo_name, "pull_request_id": pull_request_id});
388 388
389 389 var postData = {
390 '_method': 'put',
391 390 'title': title,
392 391 'description': description,
393 392 'edit_pull_request': true,
@@ -276,7 +276,7 b''
276 276 %if c.rhodecode_user.username != h.DEFAULT_USER:
277 277 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
278 278 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
279 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
279 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
280 280 %endif
281 281 %endif
282 282 </ul>
@@ -55,7 +55,7 b''
55 55 ## pr open link
56 56 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
57 57 <span>
58 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
58 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
59 59 ${_('Open new pull request')}
60 60 </a>
61 61 </span>
@@ -176,7 +176,7 b''
176 176 .show();
177 177
178 178 $commitRangeClear.show();
179 var _url = pyroutes.url('pullrequest_home',
179 var _url = pyroutes.url('pullrequest_new',
180 180 {'repo_name': '${c.repo_name}',
181 181 'commit': revEnd});
182 182 open_new_pull_request.attr('href', _url);
@@ -186,12 +186,12 b''
186 186 $commitRangeClear.hide();
187 187
188 188 %if c.branch_name:
189 var _url = pyroutes.url('pullrequest_home',
189 var _url = pyroutes.url('pullrequest_new',
190 190 {'repo_name': '${c.repo_name}',
191 191 'branch':'${c.branch_name}'});
192 192 open_new_pull_request.attr('href', _url);
193 193 %else:
194 var _url = pyroutes.url('pullrequest_home',
194 var _url = pyroutes.url('pullrequest_new',
195 195 {'repo_name': '${c.repo_name}'});
196 196 open_new_pull_request.attr('href', _url);
197 197 %endif
@@ -27,7 +27,7 b''
27 27 <div class="edit-file-title">
28 28 ${self.breadcrumbs()}
29 29 </div>
30 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', class_="form-horizontal")}
30 ${h.secure_form(h.route_path('repo_files_delete_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', class_="form-horizontal", request=request)}
31 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
@@ -42,7 +42,7 b''
42 42 </div>
43 43
44 44 <div class="table">
45 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST')}
45 ${h.secure_form(h.route_path('repo_files_update_file', repo_name=c.repo_name, commit_id=c.commit.raw_id, f_path=c.f_path), id='eform', method='POST', request=request)}
46 46 <div id="codeblock" class="codeblock" >
47 47 <div class="code-header">
48 48 <div class="stats">
@@ -22,7 +22,7 b''
22 22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 23 </div>
24 24
25 ${h.secure_form(h.url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
25 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name), id='pull_request_form', method='POST', request=request)}
26 26
27 27 ${self.breadcrumbs()}
28 28
@@ -32,7 +32,7 b''
32 32 <div class="pull-request-merge-actions">
33 33 % if c.allowed_to_merge:
34 34 <div class="pull-right">
35 ${h.secure_form(h.url('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form')}
35 ${h.secure_form(h.route_path('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form', method='POST', request=request)}
36 36 <% merge_disabled = ' disabled' if c.pr_merge_possible is False else '' %>
37 37 <a class="btn" href="#" onclick="refreshMergeChecks(); return false;">${_('refresh checks')}</a>
38 38 <input type="submit" id="merge_pull_request" value="${_('Merge Pull Request')}" class="btn${merge_disabled}"${merge_disabled}>
@@ -32,7 +32,7 b''
32 32
33 33 <script type="text/javascript">
34 34 // TODO: marcink switch this to pyroutes
35 AJAX_COMMENT_DELETE_URL = "${h.url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
35 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 37 </script>
38 38 <div class="box">
@@ -52,7 +52,7 b''
52 52 %if c.allowed_to_update:
53 53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 54 % if c.allowed_to_delete:
55 ${h.secure_form(h.url('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id),method='delete')}
55 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), method='POST', request=request)}
56 56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 58 ${h.end_form()}
@@ -200,7 +200,7 b''
200 200 <tr class="version-pr" style="display: ${display_row}">
201 201 <td>
202 202 <code>
203 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
203 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
204 204 </code>
205 205 </td>
206 206 <td>
@@ -616,7 +616,7 b''
616 616 </div>
617 617
618 618 ## main comment form and it status
619 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
619 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
620 620 pull_request_id=c.pull_request.pull_request_id),
621 621 c.pull_request_review_status,
622 622 is_pull_request=True, change_status=c.allowed_to_change_status)}
@@ -730,7 +730,7 b''
730 730 };
731 731
732 732 refreshMergeChecks = function(){
733 var loadUrl = "${h.url.current(merge_checks=1)}";
733 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
734 734 $('.pull-request-merge').css('opacity', 0.3);
735 735 $('.action-buttons-extra').css('opacity', 0.3);
736 736
@@ -30,7 +30,7 b''
30 30 <li>
31 31 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 32 <span>
33 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
33 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
34 34 ${_('Open new Pull Request')}
35 35 </a>
36 36 </span>
@@ -200,13 +200,14 b' class TestAdminUsersGroupsController(Tes'
200 200 assert response.body == '{"members": []}'
201 201 fixture.destroy_user_group(TEST_USER_GROUP)
202 202
203 def test_usergroup_escape(self):
204 user = User.get_by_username('test_admin')
205 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
206 user.lastname = (
207 '<img src="/image2" onload="alert(\'Hello, World!\');">')
208 Session().add(user)
209 Session().commit()
203 def test_usergroup_escape(self, user_util):
204 user = user_util.create_user(
205 username='escape_user',
206 firstname='<img src="/image2" onload="alert(\'Hello, World!\');">',
207 lastname='<img src="/image2" onload="alert(\'Hello, World!\');">'
208 )
209
210 user_util.create_user_group(owner=user.username)
210 211
211 212 self.log_user()
212 213 users_group_name = 'samplegroup'
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (1018 lines changed) Show them Hide them
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now