##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r4528:5055a30b merge tip default
parent child
Show More
@@ -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.20.0";
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__ = 109 # defines current db version for migrations
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=repo.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(''), reviewers=Optional(None)):
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 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
728 get_commit_or_error(full_source_ref, source_db_repo)
690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
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, source_db_repo,
749 owner,
713 source_commit, target_db_repo, target_commit)
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.split(':', 2)[1]
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=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