@@ -0,0 +1,54 | |||||
|
1 | |RCE| 4.21.0 |RNS| | |||
|
2 | ------------------ | |||
|
3 | ||||
|
4 | Release Date | |||
|
5 | ^^^^^^^^^^^^ | |||
|
6 | ||||
|
7 | - 2020-09-28 | |||
|
8 | ||||
|
9 | ||||
|
10 | New Features | |||
|
11 | ^^^^^^^^^^^^ | |||
|
12 | ||||
|
13 | - Pull requests: overhaul of the UX/UI by adding new sidebar | |||
|
14 | - Pull requests: new live reviewer present indicator (requires channelstream enabled) | |||
|
15 | - Pull requests: new live new comments indicator (requires channelstream enabled) | |||
|
16 | - Pull requests: new sidebar with comments/todos/referenced tickets navigation | |||
|
17 | - Commits page: Introduced sidebar for single commits pages | |||
|
18 | ||||
|
19 | ||||
|
20 | General | |||
|
21 | ^^^^^^^ | |||
|
22 | ||||
|
23 | - API: allow repo admins to get/set settings. | |||
|
24 | Previously it was only super-admins that could do that. | |||
|
25 | - Sessions: patch baker to take expire time for redis for auto session cleanup feature. | |||
|
26 | - Git: bumped git version to 2.27.0 | |||
|
27 | - Packages: bumped to channelstream==0.6.14 | |||
|
28 | ||||
|
29 | ||||
|
30 | Security | |||
|
31 | ^^^^^^^^ | |||
|
32 | ||||
|
33 | - Issue trackers: fix XSS with description field. | |||
|
34 | ||||
|
35 | ||||
|
36 | Performance | |||
|
37 | ^^^^^^^^^^^ | |||
|
38 | ||||
|
39 | - Artifacts: speed-up of artifacts download request processing. | |||
|
40 | ||||
|
41 | ||||
|
42 | Fixes | |||
|
43 | ^^^^^ | |||
|
44 | ||||
|
45 | - Pull requests: properly save merge failure metadata. | |||
|
46 | In rare cases merge check reported conflicts which there were none. | |||
|
47 | - Sessions: fixed cleanup with corrupted session data issue. | |||
|
48 | ||||
|
49 | ||||
|
50 | Upgrade notes | |||
|
51 | ^^^^^^^^^^^^^ | |||
|
52 | ||||
|
53 | - Scheduled feature release. | |||
|
54 | - Git version was bumped to 2.27.0 |
@@ -0,0 +1,55 | |||||
|
1 | |RCE| 4.22.0 |RNS| | |||
|
2 | ------------------ | |||
|
3 | ||||
|
4 | Release Date | |||
|
5 | ^^^^^^^^^^^^ | |||
|
6 | ||||
|
7 | - 2020-10-12 | |||
|
8 | ||||
|
9 | ||||
|
10 | New Features | |||
|
11 | ^^^^^^^^^^^^ | |||
|
12 | ||||
|
13 | - Reviewers: added observers as another role for reviewers. | |||
|
14 | Observers is a role that doesn't require voting, but still gets notified about | |||
|
15 | PR and should participate in review process. | |||
|
16 | - Issue trackers: implemented more sophisticated ticket data extraction based on | |||
|
17 | advanced regex module. This allows using ticket references without false positives | |||
|
18 | like catching ticket data in an URL. | |||
|
19 | - Channelstream: Notification about updates and comments now works via API, and both | |||
|
20 | Pull-requests and individual commits. | |||
|
21 | ||||
|
22 | ||||
|
23 | General | |||
|
24 | ^^^^^^^ | |||
|
25 | ||||
|
26 | - Data tables: unified tables look for main pages of rhodecode repo pages. | |||
|
27 | - Users: autocomplete now sorts by matched username to show best matches first. | |||
|
28 | - Pull requests: only allow actual reviewers to leave status/votes in order to not | |||
|
29 | confuse others users about voting from people who aren't actual reviewers. | |||
|
30 | ||||
|
31 | Security | |||
|
32 | ^^^^^^^^ | |||
|
33 | ||||
|
34 | ||||
|
35 | ||||
|
36 | Performance | |||
|
37 | ^^^^^^^^^^^ | |||
|
38 | ||||
|
39 | - Default reviewers: optimize diff data, and creation of PR with advanced default reviewers | |||
|
40 | - default-reviewers: diff data should load more things lazy for better performance. | |||
|
41 | - Pull requests: limit the amount of data saved in default reviewers data for better memory usage | |||
|
42 | - DB: don't use lazy loaders on PR related objects, to optimize memory usage on large | |||
|
43 | Pull requests with lots of comments, and commits. | |||
|
44 | ||||
|
45 | Fixes | |||
|
46 | ^^^^^ | |||
|
47 | ||||
|
48 | - Quick search bar: fixes #5634, crash when search on non-ascii characters. | |||
|
49 | - Sidebar: few fixes for panel rendering of reviewers/observers for both commits and PRS. | |||
|
50 | ||||
|
51 | Upgrade notes | |||
|
52 | ^^^^^^^^^^^^^ | |||
|
53 | ||||
|
54 | - Scheduled feature release. | |||
|
55 |
@@ -0,0 +1,68 | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | from sqlalchemy import * | |||
|
5 | ||||
|
6 | from alembic.migration import MigrationContext | |||
|
7 | from alembic.operations import Operations | |||
|
8 | ||||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
10 | from rhodecode.model import meta, init_model_encryption | |||
|
11 | ||||
|
12 | ||||
|
13 | log = logging.getLogger(__name__) | |||
|
14 | ||||
|
15 | ||||
|
16 | def upgrade(migrate_engine): | |||
|
17 | """ | |||
|
18 | Upgrade operations go here. | |||
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
20 | """ | |||
|
21 | _reset_base(migrate_engine) | |||
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |||
|
23 | ||||
|
24 | init_model_encryption(db) | |||
|
25 | ||||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |||
|
27 | op = Operations(context) | |||
|
28 | ||||
|
29 | table = db.RepoReviewRuleUser.__table__ | |||
|
30 | with op.batch_alter_table(table.name) as batch_op: | |||
|
31 | new_column = Column('role', Unicode(255), nullable=True) | |||
|
32 | batch_op.add_column(new_column) | |||
|
33 | ||||
|
34 | _fill_rule_user_role(op, meta.Session) | |||
|
35 | ||||
|
36 | table = db.RepoReviewRuleUserGroup.__table__ | |||
|
37 | with op.batch_alter_table(table.name) as batch_op: | |||
|
38 | new_column = Column('role', Unicode(255), nullable=True) | |||
|
39 | batch_op.add_column(new_column) | |||
|
40 | ||||
|
41 | _fill_rule_user_group_role(op, meta.Session) | |||
|
42 | ||||
|
43 | ||||
|
44 | def downgrade(migrate_engine): | |||
|
45 | meta = MetaData() | |||
|
46 | meta.bind = migrate_engine | |||
|
47 | ||||
|
48 | ||||
|
49 | def fixups(models, _SESSION): | |||
|
50 | pass | |||
|
51 | ||||
|
52 | ||||
|
53 | def _fill_rule_user_role(op, session): | |||
|
54 | params = {'role': 'reviewer'} | |||
|
55 | query = text( | |||
|
56 | 'UPDATE repo_review_rules_users SET role = :role' | |||
|
57 | ).bindparams(**params) | |||
|
58 | op.execute(query) | |||
|
59 | session().commit() | |||
|
60 | ||||
|
61 | ||||
|
62 | def _fill_rule_user_group_role(op, session): | |||
|
63 | params = {'role': 'reviewer'} | |||
|
64 | query = text( | |||
|
65 | 'UPDATE repo_review_rules_users_groups SET role = :role' | |||
|
66 | ).bindparams(**params) | |||
|
67 | op.execute(query) | |||
|
68 | session().commit() |
@@ -0,0 +1,35 | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2020 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | ||||
|
22 | def html(info): | |||
|
23 | """ | |||
|
24 | Custom string as html content_type renderer for pyramid | |||
|
25 | """ | |||
|
26 | def _render(value, system): | |||
|
27 | request = system.get('request') | |||
|
28 | if request is not None: | |||
|
29 | response = request.response | |||
|
30 | ct = response.content_type | |||
|
31 | if ct == response.default_content_type: | |||
|
32 | response.content_type = 'text/html' | |||
|
33 | return value | |||
|
34 | ||||
|
35 | return _render |
@@ -68,3 +68,5 7ac623a4a2405917e2af660d645ded662011e40d | |||||
68 | ef7ffda65eeb90c3ba88590a6cb816ef9b0bc232 v4.19.3 |
|
68 | ef7ffda65eeb90c3ba88590a6cb816ef9b0bc232 v4.19.3 | |
69 | 3e635489bb7961df93b01e42454ad1a8730ae968 v4.20.0 |
|
69 | 3e635489bb7961df93b01e42454ad1a8730ae968 v4.20.0 | |
70 | 7e2eb896a02ca7cd2cd9f0f853ef3dac3f0039e3 v4.20.1 |
|
70 | 7e2eb896a02ca7cd2cd9f0f853ef3dac3f0039e3 v4.20.1 | |
|
71 | 8bb5fece08ab65986225b184e46f53d2a71729cb v4.21.0 | |||
|
72 | 90734aac31ee4563bbe665a43ff73190cc762275 v4.22.0 |
@@ -90,7 +90,7 comment_pull_request | |||||
90 | create_pull_request |
|
90 | create_pull_request | |
91 | ------------------- |
|
91 | ------------------- | |
92 |
|
92 | |||
93 | .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, owner=<Optional:<OptionalAttr:apiuser>>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>) |
|
93 | .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, owner=<Optional:<OptionalAttr:apiuser>>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, observers=<Optional:None>) | |
94 |
|
94 | |||
95 | Creates a new pull request. |
|
95 | Creates a new pull request. | |
96 |
|
96 | |||
@@ -128,6 +128,13 create_pull_request | |||||
128 | Accepts username strings or objects of the format: |
|
128 | Accepts username strings or objects of the format: | |
129 |
|
129 | |||
130 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
130 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] | |
|
131 | :param observers: Set the new pull request observers list. | |||
|
132 | Reviewer defined by review rules will be added automatically to the | |||
|
133 | defined list. This feature is only available in RhodeCode EE | |||
|
134 | :type observers: Optional(list) | |||
|
135 | Accepts username strings or objects of the format: | |||
|
136 | ||||
|
137 | [{'username': 'nick', 'reasons': ['original author']}] | |||
131 |
|
138 | |||
132 |
|
139 | |||
133 | get_pull_request |
|
140 | get_pull_request | |
@@ -392,7 +399,7 merge_pull_request | |||||
392 | update_pull_request |
|
399 | update_pull_request | |
393 | ------------------- |
|
400 | ------------------- | |
394 |
|
401 | |||
395 | .. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>) |
|
402 | .. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, observers=<Optional:None>, update_commits=<Optional:None>) | |
396 |
|
403 | |||
397 | Updates a pull request. |
|
404 | Updates a pull request. | |
398 |
|
405 | |||
@@ -414,7 +421,11 update_pull_request | |||||
414 | Accepts username strings or objects of the format: |
|
421 | Accepts username strings or objects of the format: | |
415 |
|
422 | |||
416 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
423 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] | |
|
424 | :param observers: Update pull request observers list with new value. | |||
|
425 | :type observers: Optional(list) | |||
|
426 | Accepts username strings or objects of the format: | |||
417 |
|
427 | |||
|
428 | [{'username': 'nick', 'reasons': ['should be aware about this PR']}] | |||
418 | :param update_commits: Trigger update of commits for this pull request |
|
429 | :param update_commits: Trigger update of commits for this pull request | |
419 | :type: update_commits: Optional(bool) |
|
430 | :type: update_commits: Optional(bool) | |
420 |
|
431 | |||
@@ -432,6 +443,12 update_pull_request | |||||
432 | ], |
|
443 | ], | |
433 | "removed": [] |
|
444 | "removed": [] | |
434 | }, |
|
445 | }, | |
|
446 | "updated_observers": { | |||
|
447 | "added": [ | |||
|
448 | "username" | |||
|
449 | ], | |||
|
450 | "removed": [] | |||
|
451 | }, | |||
435 | "updated_commits": { |
|
452 | "updated_commits": { | |
436 | "added": [ |
|
453 | "added": [ | |
437 | "<sha1_hash>" |
|
454 | "<sha1_hash>" |
@@ -9,6 +9,8 Release Notes | |||||
9 | .. toctree:: |
|
9 | .. toctree:: | |
10 | :maxdepth: 1 |
|
10 | :maxdepth: 1 | |
11 |
|
11 | |||
|
12 | release-notes-4.22.0.rst | |||
|
13 | release-notes-4.21.0.rst | |||
12 | release-notes-4.20.1.rst |
|
14 | release-notes-4.20.1.rst | |
13 | release-notes-4.20.0.rst |
|
15 | release-notes-4.20.0.rst | |
14 | release-notes-4.19.3.rst |
|
16 | release-notes-4.19.3.rst |
@@ -1816,6 +1816,17 self: super: { | |||||
1816 | license = [ pkgs.lib.licenses.mit ]; |
|
1816 | license = [ pkgs.lib.licenses.mit ]; | |
1817 | }; |
|
1817 | }; | |
1818 | }; |
|
1818 | }; | |
|
1819 | "regex" = super.buildPythonPackage { | |||
|
1820 | name = "regex-2020.9.27"; | |||
|
1821 | doCheck = false; | |||
|
1822 | src = fetchurl { | |||
|
1823 | url = "https://files.pythonhosted.org/packages/93/8c/17f45cdfb39b13d4b5f909e4b4c2917abcbdef9c0036919a0399769148cf/regex-2020.9.27.tar.gz"; | |||
|
1824 | sha256 = "179ngfzwbsjvn5vhyzdahvmg0f7acahkwwy9bpjy1pv08bm2mwx6"; | |||
|
1825 | }; | |||
|
1826 | meta = { | |||
|
1827 | license = [ pkgs.lib.licenses.psfl ]; | |||
|
1828 | }; | |||
|
1829 | }; | |||
1819 | "redis" = super.buildPythonPackage { |
|
1830 | "redis" = super.buildPythonPackage { | |
1820 | name = "redis-3.4.1"; |
|
1831 | name = "redis-3.4.1"; | |
1821 | doCheck = false; |
|
1832 | doCheck = false; | |
@@ -1872,7 +1883,7 self: super: { | |||||
1872 | }; |
|
1883 | }; | |
1873 | }; |
|
1884 | }; | |
1874 | "rhodecode-enterprise-ce" = super.buildPythonPackage { |
|
1885 | "rhodecode-enterprise-ce" = super.buildPythonPackage { | |
1875 |
name = "rhodecode-enterprise-ce-4.2 |
|
1886 | name = "rhodecode-enterprise-ce-4.22.0"; | |
1876 | buildInputs = [ |
|
1887 | buildInputs = [ | |
1877 | self."pytest" |
|
1888 | self."pytest" | |
1878 | self."py" |
|
1889 | self."py" | |
@@ -1946,6 +1957,7 self: super: { | |||||
1946 | self."tzlocal" |
|
1957 | self."tzlocal" | |
1947 | self."pyzmq" |
|
1958 | self."pyzmq" | |
1948 | self."py-gfm" |
|
1959 | self."py-gfm" | |
|
1960 | self."regex" | |||
1949 | self."redis" |
|
1961 | self."redis" | |
1950 | self."repoze.lru" |
|
1962 | self."repoze.lru" | |
1951 | self."requests" |
|
1963 | self."requests" |
@@ -56,6 +56,7 pytz==2019.3 | |||||
56 | tzlocal==1.5.1 |
|
56 | tzlocal==1.5.1 | |
57 | pyzmq==14.6.0 |
|
57 | pyzmq==14.6.0 | |
58 | py-gfm==0.1.4 |
|
58 | py-gfm==0.1.4 | |
|
59 | regex==2020.9.27 | |||
59 | redis==3.4.1 |
|
60 | redis==3.4.1 | |
60 | repoze.lru==0.7 |
|
61 | repoze.lru==0.7 | |
61 | requests==2.22.0 |
|
62 | requests==2.22.0 |
@@ -48,7 +48,7 PYRAMID_SETTINGS = {} | |||||
48 | EXTENSIONS = {} |
|
48 | EXTENSIONS = {} | |
49 |
|
49 | |||
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) | |
51 |
__dbversion__ = 10 |
|
51 | __dbversion__ = 110 # defines current db version for migrations | |
52 | __platform__ = platform.system() |
|
52 | __platform__ = platform.system() | |
53 | __license__ = 'AGPLv3, and Commercial License' |
|
53 | __license__ = 'AGPLv3, and Commercial License' | |
54 | __author__ = 'RhodeCode GmbH' |
|
54 | __author__ = 'RhodeCode GmbH' |
@@ -320,7 +320,7 class TestCreatePullRequestApi(object): | |||||
320 | id_, params = build_data( |
|
320 | id_, params = build_data( | |
321 | self.apikey_regular, 'create_pull_request', **data) |
|
321 | self.apikey_regular, 'create_pull_request', **data) | |
322 | response = api_call(self.app, params) |
|
322 | response = api_call(self.app, params) | |
323 | expected_message = 'no commits found' |
|
323 | expected_message = 'no commits found for merge between specified references' | |
324 | assert_error(id_, expected_message, given=response.body) |
|
324 | assert_error(id_, expected_message, given=response.body) | |
325 |
|
325 | |||
326 | @pytest.mark.backends("git", "hg") |
|
326 | @pytest.mark.backends("git", "hg") |
@@ -29,6 +29,7 from rhodecode.api.tests.utils import ( | |||||
29 |
|
29 | |||
30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | @pytest.mark.usefixtures("testuser_api", "app") | |
31 | class TestGetPullRequest(object): |
|
31 | class TestGetPullRequest(object): | |
|
32 | ||||
32 | @pytest.mark.backends("git", "hg") |
|
33 | @pytest.mark.backends("git", "hg") | |
33 | def test_api_get_pull_requests(self, pr_util): |
|
34 | def test_api_get_pull_requests(self, pr_util): | |
34 | pull_request = pr_util.create_pull_request() |
|
35 | pull_request = pr_util.create_pull_request() | |
@@ -40,6 +41,7 class TestGetPullRequest(object): | |||||
40 | target_ref=pull_request.target_ref, |
|
41 | target_ref=pull_request.target_ref, | |
41 | revisions=pull_request.revisions, |
|
42 | revisions=pull_request.revisions, | |
42 | reviewers=(), |
|
43 | reviewers=(), | |
|
44 | observers=(), | |||
43 | title=pull_request.title, |
|
45 | title=pull_request.title, | |
44 | description=pull_request.description, |
|
46 | description=pull_request.description, | |
45 | ) |
|
47 | ) |
@@ -51,6 +51,7 class TestUpdatePullRequest(object): | |||||
51 | "pull_request": response.json['result']['pull_request'], |
|
51 | "pull_request": response.json['result']['pull_request'], | |
52 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
52 | "updated_commits": {"added": [], "common": [], "removed": []}, | |
53 | "updated_reviewers": {"added": [], "removed": []}, |
|
53 | "updated_reviewers": {"added": [], "removed": []}, | |
|
54 | "updated_observers": {"added": [], "removed": []}, | |||
54 | } |
|
55 | } | |
55 |
|
56 | |||
56 | response_json = response.json['result'] |
|
57 | response_json = response.json['result'] | |
@@ -111,6 +112,7 class TestUpdatePullRequest(object): | |||||
111 | "total": total_commits, |
|
112 | "total": total_commits, | |
112 | "removed": []}, |
|
113 | "removed": []}, | |
113 | "updated_reviewers": {"added": [], "removed": []}, |
|
114 | "updated_reviewers": {"added": [], "removed": []}, | |
|
115 | "updated_observers": {"added": [], "removed": []}, | |||
114 | } |
|
116 | } | |
115 |
|
117 | |||
116 | assert_ok(id_, expected, response.body) |
|
118 | assert_ok(id_, expected, response.body) | |
@@ -122,7 +124,7 class TestUpdatePullRequest(object): | |||||
122 | b = user_util.create_user() |
|
124 | b = user_util.create_user() | |
123 | c = user_util.create_user() |
|
125 | c = user_util.create_user() | |
124 | new_reviewers = [ |
|
126 | new_reviewers = [ | |
125 | {'username': b.username,'reasons': ['updated via API'], |
|
127 | {'username': b.username, 'reasons': ['updated via API'], | |
126 | 'mandatory':False}, |
|
128 | 'mandatory':False}, | |
127 | {'username': c.username, 'reasons': ['updated via API'], |
|
129 | {'username': c.username, 'reasons': ['updated via API'], | |
128 | 'mandatory':False}, |
|
130 | 'mandatory':False}, | |
@@ -132,7 +134,7 class TestUpdatePullRequest(object): | |||||
132 | removed = [a.username] |
|
134 | removed = [a.username] | |
133 |
|
135 | |||
134 | pull_request = pr_util.create_pull_request( |
|
136 | pull_request = pr_util.create_pull_request( | |
135 | reviewers=[(a.username, ['added via API'], False, [])]) |
|
137 | reviewers=[(a.username, ['added via API'], False, 'reviewer', [])]) | |
136 |
|
138 | |||
137 | id_, params = build_data( |
|
139 | id_, params = build_data( | |
138 | self.apikey, 'update_pull_request', |
|
140 | self.apikey, 'update_pull_request', | |
@@ -146,6 +148,7 class TestUpdatePullRequest(object): | |||||
146 | "pull_request": response.json['result']['pull_request'], |
|
148 | "pull_request": response.json['result']['pull_request'], | |
147 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
149 | "updated_commits": {"added": [], "common": [], "removed": []}, | |
148 | "updated_reviewers": {"added": added, "removed": removed}, |
|
150 | "updated_reviewers": {"added": added, "removed": removed}, | |
|
151 | "updated_observers": {"added": [], "removed": []}, | |||
149 | } |
|
152 | } | |
150 |
|
153 | |||
151 | assert_ok(id_, expected, response.body) |
|
154 | assert_ok(id_, expected, response.body) |
@@ -26,12 +26,15 from rhodecode.api.utils import ( | |||||
26 | has_superadmin_permission, Optional, OAttr, get_repo_or_error, |
|
26 | has_superadmin_permission, Optional, OAttr, get_repo_or_error, | |
27 | get_pull_request_or_error, get_commit_or_error, get_user_or_error, |
|
27 | get_pull_request_or_error, get_commit_or_error, get_user_or_error, | |
28 | validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions) |
|
28 | validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions) | |
|
29 | from rhodecode.lib import channelstream | |||
29 | from rhodecode.lib.auth import (HasRepoPermissionAnyApi) |
|
30 | from rhodecode.lib.auth import (HasRepoPermissionAnyApi) | |
30 | from rhodecode.lib.base import vcs_operation_context |
|
31 | from rhodecode.lib.base import vcs_operation_context | |
31 | from rhodecode.lib.utils2 import str2bool |
|
32 | from rhodecode.lib.utils2 import str2bool | |
|
33 | from rhodecode.lib.vcs.backends.base import unicode_to_reference | |||
32 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
34 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
33 | from rhodecode.model.comment import CommentsModel |
|
35 | from rhodecode.model.comment import CommentsModel | |
34 | from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest |
|
36 | from rhodecode.model.db import ( | |
|
37 | Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers) | |||
35 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
|
38 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck | |
36 | from rhodecode.model.settings import SettingsModel |
|
39 | from rhodecode.model.settings import SettingsModel | |
37 | from rhodecode.model.validation_schema import Invalid |
|
40 | from rhodecode.model.validation_schema import Invalid | |
@@ -502,16 +505,19 def comment_pull_request( | |||||
502 | }, |
|
505 | }, | |
503 | error : null |
|
506 | error : null | |
504 | """ |
|
507 | """ | |
|
508 | _ = request.translate | |||
|
509 | ||||
505 | pull_request = get_pull_request_or_error(pullrequestid) |
|
510 | pull_request = get_pull_request_or_error(pullrequestid) | |
506 | if Optional.extract(repoid): |
|
511 | if Optional.extract(repoid): | |
507 | repo = get_repo_or_error(repoid) |
|
512 | repo = get_repo_or_error(repoid) | |
508 | else: |
|
513 | else: | |
509 | repo = pull_request.target_repo |
|
514 | repo = pull_request.target_repo | |
510 |
|
515 | |||
|
516 | db_repo_name = repo.repo_name | |||
511 | auth_user = apiuser |
|
517 | auth_user = apiuser | |
512 | if not isinstance(userid, Optional): |
|
518 | if not isinstance(userid, Optional): | |
513 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( |
|
519 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( | |
514 |
user=apiuser, repo_name= |
|
520 | user=apiuser, repo_name=db_repo_name) | |
515 | if has_superadmin_permission(apiuser) or is_repo_admin: |
|
521 | if has_superadmin_permission(apiuser) or is_repo_admin: | |
516 | apiuser = get_user_or_error(userid) |
|
522 | apiuser = get_user_or_error(userid) | |
517 | auth_user = apiuser.AuthUser() |
|
523 | auth_user = apiuser.AuthUser() | |
@@ -596,6 +602,7 def comment_pull_request( | |||||
596 | extra_recipients=extra_recipients, |
|
602 | extra_recipients=extra_recipients, | |
597 | send_email=send_email |
|
603 | send_email=send_email | |
598 | ) |
|
604 | ) | |
|
605 | is_inline = comment.is_inline | |||
599 |
|
606 | |||
600 | if allowed_to_change_status and status: |
|
607 | if allowed_to_change_status and status: | |
601 | old_calculated_status = pull_request.calculated_review_status() |
|
608 | old_calculated_status = pull_request.calculated_review_status() | |
@@ -628,14 +635,39 def comment_pull_request( | |||||
628 | 'comment_id': comment.comment_id if comment else None, |
|
635 | 'comment_id': comment.comment_id if comment else None, | |
629 | 'status': {'given': status, 'was_changed': status_change}, |
|
636 | 'status': {'given': status, 'was_changed': status_change}, | |
630 | } |
|
637 | } | |
|
638 | ||||
|
639 | comment_broadcast_channel = channelstream.comment_channel( | |||
|
640 | db_repo_name, pull_request_obj=pull_request) | |||
|
641 | ||||
|
642 | comment_data = data | |||
|
643 | comment_type = 'inline' if is_inline else 'general' | |||
|
644 | channelstream.comment_channelstream_push( | |||
|
645 | request, comment_broadcast_channel, apiuser, | |||
|
646 | _('posted a new {} comment').format(comment_type), | |||
|
647 | comment_data=comment_data) | |||
|
648 | ||||
631 | return data |
|
649 | return data | |
632 |
|
650 | |||
|
651 | def _reviewers_validation(obj_list): | |||
|
652 | schema = ReviewerListSchema() | |||
|
653 | try: | |||
|
654 | reviewer_objects = schema.deserialize(obj_list) | |||
|
655 | except Invalid as err: | |||
|
656 | raise JSONRPCValidationError(colander_exc=err) | |||
|
657 | ||||
|
658 | # validate users | |||
|
659 | for reviewer_object in reviewer_objects: | |||
|
660 | user = get_user_or_error(reviewer_object['username']) | |||
|
661 | reviewer_object['user_id'] = user.user_id | |||
|
662 | return reviewer_objects | |||
|
663 | ||||
633 |
|
664 | |||
634 | @jsonrpc_method() |
|
665 | @jsonrpc_method() | |
635 | def create_pull_request( |
|
666 | def create_pull_request( | |
636 | request, apiuser, source_repo, target_repo, source_ref, target_ref, |
|
667 | request, apiuser, source_repo, target_repo, source_ref, target_ref, | |
637 | owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''), |
|
668 | owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''), | |
638 |
description_renderer=Optional(''), |
|
669 | description_renderer=Optional(''), | |
|
670 | reviewers=Optional(None), observers=Optional(None)): | |||
639 | """ |
|
671 | """ | |
640 | Creates a new pull request. |
|
672 | Creates a new pull request. | |
641 |
|
673 | |||
@@ -673,6 +705,13 def create_pull_request( | |||||
673 | Accepts username strings or objects of the format: |
|
705 | Accepts username strings or objects of the format: | |
674 |
|
706 | |||
675 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
707 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] | |
|
708 | :param observers: Set the new pull request observers list. | |||
|
709 | Reviewer defined by review rules will be added automatically to the | |||
|
710 | defined list. This feature is only available in RhodeCode EE | |||
|
711 | :type observers: Optional(list) | |||
|
712 | Accepts username strings or objects of the format: | |||
|
713 | ||||
|
714 | [{'username': 'nick', 'reasons': ['original author']}] | |||
676 | """ |
|
715 | """ | |
677 |
|
716 | |||
678 | source_db_repo = get_repo_or_error(source_repo) |
|
717 | source_db_repo = get_repo_or_error(source_repo) | |
@@ -686,34 +725,39 def create_pull_request( | |||||
686 | full_source_ref = resolve_ref_or_error(source_ref, source_db_repo) |
|
725 | full_source_ref = resolve_ref_or_error(source_ref, source_db_repo) | |
687 | full_target_ref = resolve_ref_or_error(target_ref, target_db_repo) |
|
726 | full_target_ref = resolve_ref_or_error(target_ref, target_db_repo) | |
688 |
|
727 | |||
689 |
|
|
728 | get_commit_or_error(full_source_ref, source_db_repo) | |
690 |
|
|
729 | get_commit_or_error(full_target_ref, target_db_repo) | |
691 |
|
730 | |||
692 | reviewer_objects = Optional.extract(reviewers) or [] |
|
731 | reviewer_objects = Optional.extract(reviewers) or [] | |
|
732 | observer_objects = Optional.extract(observers) or [] | |||
693 |
|
733 | |||
694 | # serialize and validate passed in given reviewers |
|
734 | # serialize and validate passed in given reviewers | |
695 | if reviewer_objects: |
|
735 | if reviewer_objects: | |
696 | schema = ReviewerListSchema() |
|
736 | reviewer_objects = _reviewers_validation(reviewer_objects) | |
697 | try: |
|
737 | ||
698 | reviewer_objects = schema.deserialize(reviewer_objects) |
|
738 | if observer_objects: | |
699 | except Invalid as err: |
|
739 | observer_objects = _reviewers_validation(reviewer_objects) | |
700 | raise JSONRPCValidationError(colander_exc=err) |
|
|||
701 |
|
740 | |||
702 | # validate users |
|
741 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
703 | for reviewer_object in reviewer_objects: |
|
742 | PullRequestModel().get_reviewer_functions() | |
704 | user = get_user_or_error(reviewer_object['username']) |
|
|||
705 | reviewer_object['user_id'] = user.user_id |
|
|||
706 |
|
743 | |||
707 | get_default_reviewers_data, validate_default_reviewers = \ |
|
744 | source_ref_obj = unicode_to_reference(full_source_ref) | |
708 | PullRequestModel().get_reviewer_functions() |
|
745 | target_ref_obj = unicode_to_reference(full_target_ref) | |
709 |
|
746 | |||
710 | # recalculate reviewers logic, to make sure we can validate this |
|
747 | # recalculate reviewers logic, to make sure we can validate this | |
711 | default_reviewers_data = get_default_reviewers_data( |
|
748 | default_reviewers_data = get_default_reviewers_data( | |
712 |
owner, |
|
749 | owner, | |
713 |
source_ |
|
750 | source_db_repo, | |
|
751 | source_ref_obj, | |||
|
752 | target_db_repo, | |||
|
753 | target_ref_obj, | |||
|
754 | ) | |||
714 |
|
755 | |||
715 | # now MERGE our given with the calculated |
|
756 | # now MERGE our given with the calculated from the default rules | |
716 | reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects |
|
757 | just_reviewers = [ | |
|
758 | x for x in default_reviewers_data['reviewers'] | |||
|
759 | if x['role'] == PullRequestReviewers.ROLE_REVIEWER] | |||
|
760 | reviewer_objects = just_reviewers + reviewer_objects | |||
717 |
|
761 | |||
718 | try: |
|
762 | try: | |
719 | reviewers = validate_default_reviewers( |
|
763 | reviewers = validate_default_reviewers( | |
@@ -721,9 +765,21 def create_pull_request( | |||||
721 | except ValueError as e: |
|
765 | except ValueError as e: | |
722 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
766 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) | |
723 |
|
767 | |||
|
768 | # now MERGE our given with the calculated from the default rules | |||
|
769 | just_observers = [ | |||
|
770 | x for x in default_reviewers_data['reviewers'] | |||
|
771 | if x['role'] == PullRequestReviewers.ROLE_OBSERVER] | |||
|
772 | observer_objects = just_observers + observer_objects | |||
|
773 | ||||
|
774 | try: | |||
|
775 | observers = validate_observers( | |||
|
776 | observer_objects, default_reviewers_data) | |||
|
777 | except ValueError as e: | |||
|
778 | raise JSONRPCError('Observer Validation: {}'.format(e)) | |||
|
779 | ||||
724 | title = Optional.extract(title) |
|
780 | title = Optional.extract(title) | |
725 | if not title: |
|
781 | if not title: | |
726 |
title_source_ref = source_ref. |
|
782 | title_source_ref = source_ref_obj.name | |
727 | title = PullRequestModel().generate_pullrequest_title( |
|
783 | title = PullRequestModel().generate_pullrequest_title( | |
728 | source=source_repo, |
|
784 | source=source_repo, | |
729 | source_ref=title_source_ref, |
|
785 | source_ref=title_source_ref, | |
@@ -732,20 +788,17 def create_pull_request( | |||||
732 |
|
788 | |||
733 | diff_info = default_reviewers_data['diff_info'] |
|
789 | diff_info = default_reviewers_data['diff_info'] | |
734 | common_ancestor_id = diff_info['ancestor'] |
|
790 | common_ancestor_id = diff_info['ancestor'] | |
735 | commits = diff_info['commits'] |
|
791 | # NOTE(marcink): reversed is consistent with how we open it in the WEB interface | |
|
792 | commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])] | |||
736 |
|
793 | |||
737 | if not common_ancestor_id: |
|
794 | if not common_ancestor_id: | |
738 | raise JSONRPCError('no common ancestor found') |
|
795 | raise JSONRPCError('no common ancestor found between specified references') | |
739 |
|
796 | |||
740 | if not commits: |
|
797 | if not commits: | |
741 | raise JSONRPCError('no commits found') |
|
798 | raise JSONRPCError('no commits found for merge between specified references') | |
742 |
|
||||
743 | # NOTE(marcink): reversed is consistent with how we open it in the WEB interface |
|
|||
744 | revisions = [commit.raw_id for commit in reversed(commits)] |
|
|||
745 |
|
799 | |||
746 | # recalculate target ref based on ancestor |
|
800 | # recalculate target ref based on ancestor | |
747 | target_ref_type, target_ref_name, __ = full_target_ref.split(':') |
|
801 | full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id)) | |
748 | full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id)) |
|
|||
749 |
|
802 | |||
750 | # fetch renderer, if set fallback to plain in case of PR |
|
803 | # fetch renderer, if set fallback to plain in case of PR | |
751 | rc_config = SettingsModel().get_all_settings() |
|
804 | rc_config = SettingsModel().get_all_settings() | |
@@ -760,8 +813,9 def create_pull_request( | |||||
760 | target_repo=target_repo, |
|
813 | target_repo=target_repo, | |
761 | target_ref=full_target_ref, |
|
814 | target_ref=full_target_ref, | |
762 | common_ancestor_id=common_ancestor_id, |
|
815 | common_ancestor_id=common_ancestor_id, | |
763 |
revisions= |
|
816 | revisions=commits, | |
764 | reviewers=reviewers, |
|
817 | reviewers=reviewers, | |
|
818 | observers=observers, | |||
765 | title=title, |
|
819 | title=title, | |
766 | description=description, |
|
820 | description=description, | |
767 | description_renderer=description_renderer, |
|
821 | description_renderer=description_renderer, | |
@@ -781,7 +835,7 def create_pull_request( | |||||
781 | def update_pull_request( |
|
835 | def update_pull_request( | |
782 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
836 | request, apiuser, pullrequestid, repoid=Optional(None), | |
783 | title=Optional(''), description=Optional(''), description_renderer=Optional(''), |
|
837 | title=Optional(''), description=Optional(''), description_renderer=Optional(''), | |
784 | reviewers=Optional(None), update_commits=Optional(None)): |
|
838 | reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)): | |
785 | """ |
|
839 | """ | |
786 | Updates a pull request. |
|
840 | Updates a pull request. | |
787 |
|
841 | |||
@@ -803,7 +857,11 def update_pull_request( | |||||
803 | Accepts username strings or objects of the format: |
|
857 | Accepts username strings or objects of the format: | |
804 |
|
858 | |||
805 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
859 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] | |
|
860 | :param observers: Update pull request observers list with new value. | |||
|
861 | :type observers: Optional(list) | |||
|
862 | Accepts username strings or objects of the format: | |||
806 |
|
863 | |||
|
864 | [{'username': 'nick', 'reasons': ['should be aware about this PR']}] | |||
807 | :param update_commits: Trigger update of commits for this pull request |
|
865 | :param update_commits: Trigger update of commits for this pull request | |
808 | :type: update_commits: Optional(bool) |
|
866 | :type: update_commits: Optional(bool) | |
809 |
|
867 | |||
@@ -821,6 +879,12 def update_pull_request( | |||||
821 | ], |
|
879 | ], | |
822 | "removed": [] |
|
880 | "removed": [] | |
823 | }, |
|
881 | }, | |
|
882 | "updated_observers": { | |||
|
883 | "added": [ | |||
|
884 | "username" | |||
|
885 | ], | |||
|
886 | "removed": [] | |||
|
887 | }, | |||
824 | "updated_commits": { |
|
888 | "updated_commits": { | |
825 | "added": [ |
|
889 | "added": [ | |
826 | "<sha1_hash>" |
|
890 | "<sha1_hash>" | |
@@ -852,36 +916,14 def update_pull_request( | |||||
852 | pullrequestid,)) |
|
916 | pullrequestid,)) | |
853 |
|
917 | |||
854 | reviewer_objects = Optional.extract(reviewers) or [] |
|
918 | reviewer_objects = Optional.extract(reviewers) or [] | |
855 |
|
919 | observer_objects = Optional.extract(observers) or [] | ||
856 | if reviewer_objects: |
|
|||
857 | schema = ReviewerListSchema() |
|
|||
858 | try: |
|
|||
859 | reviewer_objects = schema.deserialize(reviewer_objects) |
|
|||
860 | except Invalid as err: |
|
|||
861 | raise JSONRPCValidationError(colander_exc=err) |
|
|||
862 |
|
||||
863 | # validate users |
|
|||
864 | for reviewer_object in reviewer_objects: |
|
|||
865 | user = get_user_or_error(reviewer_object['username']) |
|
|||
866 | reviewer_object['user_id'] = user.user_id |
|
|||
867 |
|
||||
868 | get_default_reviewers_data, get_validated_reviewers = \ |
|
|||
869 | PullRequestModel().get_reviewer_functions() |
|
|||
870 |
|
||||
871 | # re-use stored rules |
|
|||
872 | reviewer_rules = pull_request.reviewer_data |
|
|||
873 | try: |
|
|||
874 | reviewers = get_validated_reviewers( |
|
|||
875 | reviewer_objects, reviewer_rules) |
|
|||
876 | except ValueError as e: |
|
|||
877 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
|||
878 | else: |
|
|||
879 | reviewers = [] |
|
|||
880 |
|
920 | |||
881 | title = Optional.extract(title) |
|
921 | title = Optional.extract(title) | |
882 | description = Optional.extract(description) |
|
922 | description = Optional.extract(description) | |
883 | description_renderer = Optional.extract(description_renderer) |
|
923 | description_renderer = Optional.extract(description_renderer) | |
884 |
|
924 | |||
|
925 | # Update title/description | |||
|
926 | title_changed = False | |||
885 | if title or description: |
|
927 | if title or description: | |
886 | PullRequestModel().edit( |
|
928 | PullRequestModel().edit( | |
887 | pull_request, |
|
929 | pull_request, | |
@@ -890,8 +932,12 def update_pull_request( | |||||
890 | description_renderer or pull_request.description_renderer, |
|
932 | description_renderer or pull_request.description_renderer, | |
891 | apiuser) |
|
933 | apiuser) | |
892 | Session().commit() |
|
934 | Session().commit() | |
|
935 | title_changed = True | |||
893 |
|
936 | |||
894 | commit_changes = {"added": [], "common": [], "removed": []} |
|
937 | commit_changes = {"added": [], "common": [], "removed": []} | |
|
938 | ||||
|
939 | # Update commits | |||
|
940 | commits_changed = False | |||
895 | if str2bool(Optional.extract(update_commits)): |
|
941 | if str2bool(Optional.extract(update_commits)): | |
896 |
|
942 | |||
897 | if pull_request.pull_request_state |