Show More
@@ -1,213 +1,223 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 | kallithea.model.pull_request |
|
15 | kallithea.model.pull_request | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | pull request model for Kallithea |
|
18 | pull request model for Kallithea | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: Jun 6, 2012 |
|
22 | :created_on: Jun 6, 2012 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | import logging |
|
28 | import logging | |
29 | import datetime |
|
29 | import datetime | |
30 |
|
30 | |||
31 | from pylons.i18n.translation import _ |
|
31 | from pylons.i18n.translation import _ | |
32 |
|
32 | |||
33 | from kallithea.model.meta import Session |
|
33 | from kallithea.model.meta import Session | |
34 | from kallithea.lib import helpers as h |
|
34 | from kallithea.lib import helpers as h | |
35 | from kallithea.lib.exceptions import UserInvalidException |
|
35 | from kallithea.lib.exceptions import UserInvalidException | |
36 | from kallithea.model import BaseModel |
|
36 | from kallithea.model import BaseModel | |
37 | from kallithea.model.db import PullRequest, PullRequestReviewers, Notification, \ |
|
37 | from kallithea.model.db import PullRequest, PullRequestReviewers, Notification, \ | |
38 | ChangesetStatus |
|
38 | ChangesetStatus, User | |
39 | from kallithea.model.notification import NotificationModel |
|
39 | from kallithea.model.notification import NotificationModel | |
40 | from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode |
|
40 | from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | log = logging.getLogger(__name__) |
|
43 | log = logging.getLogger(__name__) | |
44 |
|
44 | |||
45 |
|
45 | |||
46 | class PullRequestModel(BaseModel): |
|
46 | class PullRequestModel(BaseModel): | |
47 |
|
47 | |||
48 | cls = PullRequest |
|
48 | cls = PullRequest | |
49 |
|
49 | |||
50 | def __get_pull_request(self, pull_request): |
|
50 | def __get_pull_request(self, pull_request): | |
51 | return self._get_instance(PullRequest, pull_request) |
|
51 | return self._get_instance(PullRequest, pull_request) | |
52 |
|
52 | |||
53 | def get_pullrequest_cnt_for_user(self, user): |
|
53 | def get_pullrequest_cnt_for_user(self, user): | |
54 | return PullRequest.query() \ |
|
54 | return PullRequest.query() \ | |
55 | .join(PullRequestReviewers) \ |
|
55 | .join(PullRequestReviewers) \ | |
56 | .filter(PullRequestReviewers.user_id == user) \ |
|
56 | .filter(PullRequestReviewers.user_id == user) \ | |
57 | .filter(PullRequest.status != PullRequest.STATUS_CLOSED) \ |
|
57 | .filter(PullRequest.status != PullRequest.STATUS_CLOSED) \ | |
58 | .count() |
|
58 | .count() | |
59 |
|
59 | |||
60 | def get_all(self, repo_name, from_=False, closed=False): |
|
60 | def get_all(self, repo_name, from_=False, closed=False): | |
61 | """Get all PRs for repo. |
|
61 | """Get all PRs for repo. | |
62 | Default is all PRs to the repo, PRs from the repo if from_. |
|
62 | Default is all PRs to the repo, PRs from the repo if from_. | |
63 | Closed PRs are only included if closed is true.""" |
|
63 | Closed PRs are only included if closed is true.""" | |
64 | repo = self._get_repo(repo_name) |
|
64 | repo = self._get_repo(repo_name) | |
65 | q = PullRequest.query() |
|
65 | q = PullRequest.query() | |
66 | if from_: |
|
66 | if from_: | |
67 | q = q.filter(PullRequest.org_repo == repo) |
|
67 | q = q.filter(PullRequest.org_repo == repo) | |
68 | else: |
|
68 | else: | |
69 | q = q.filter(PullRequest.other_repo == repo) |
|
69 | q = q.filter(PullRequest.other_repo == repo) | |
70 | if not closed: |
|
70 | if not closed: | |
71 | q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED) |
|
71 | q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED) | |
72 | return q.order_by(PullRequest.created_on.desc()).all() |
|
72 | return q.order_by(PullRequest.created_on.desc()).all() | |
73 |
|
73 | |||
|
74 | def _get_valid_reviewers(self, seq): | |||
|
75 | """ Generate User objects from a sequence of user IDs, usernames or | |||
|
76 | User objects. Raises UserInvalidException if the DEFAULT user is | |||
|
77 | specified, or if a given ID or username does not match any user. | |||
|
78 | """ | |||
|
79 | for user_spec in seq: | |||
|
80 | user = self._get_user(user_spec) | |||
|
81 | if user is None or user.username == User.DEFAULT_USER: | |||
|
82 | raise UserInvalidException(user_spec) | |||
|
83 | yield user | |||
|
84 | ||||
74 | def create(self, created_by, org_repo, org_ref, other_repo, other_ref, |
|
85 | def create(self, created_by, org_repo, org_ref, other_repo, other_ref, | |
75 | revisions, reviewers, title, description=None): |
|
86 | revisions, reviewers, title, description=None): | |
76 | from kallithea.model.changeset_status import ChangesetStatusModel |
|
87 | from kallithea.model.changeset_status import ChangesetStatusModel | |
77 |
|
88 | |||
78 | created_by_user = self._get_user(created_by) |
|
89 | created_by_user = self._get_user(created_by) | |
79 | org_repo = self._get_repo(org_repo) |
|
90 | org_repo = self._get_repo(org_repo) | |
80 | other_repo = self._get_repo(other_repo) |
|
91 | other_repo = self._get_repo(other_repo) | |
81 |
|
92 | |||
82 | new = PullRequest() |
|
93 | new = PullRequest() | |
83 | new.org_repo = org_repo |
|
94 | new.org_repo = org_repo | |
84 | new.org_ref = org_ref |
|
95 | new.org_ref = org_ref | |
85 | new.other_repo = other_repo |
|
96 | new.other_repo = other_repo | |
86 | new.other_ref = other_ref |
|
97 | new.other_ref = other_ref | |
87 | new.revisions = revisions |
|
98 | new.revisions = revisions | |
88 | new.title = title |
|
99 | new.title = title | |
89 | new.description = description |
|
100 | new.description = description | |
90 | new.owner = created_by_user |
|
101 | new.owner = created_by_user | |
91 | Session().add(new) |
|
102 | Session().add(new) | |
92 | Session().flush() |
|
103 | Session().flush() | |
93 |
|
104 | |||
94 | #reset state to under-review |
|
105 | #reset state to under-review | |
95 | from kallithea.model.comment import ChangesetCommentsModel |
|
106 | from kallithea.model.comment import ChangesetCommentsModel | |
96 | comment = ChangesetCommentsModel().create( |
|
107 | comment = ChangesetCommentsModel().create( | |
97 | text=u'', |
|
108 | text=u'', | |
98 | repo=org_repo, |
|
109 | repo=org_repo, | |
99 | user=new.owner, |
|
110 | user=new.owner, | |
100 | pull_request=new, |
|
111 | pull_request=new, | |
101 | send_email=False, |
|
112 | send_email=False, | |
102 | status_change=ChangesetStatus.STATUS_UNDER_REVIEW, |
|
113 | status_change=ChangesetStatus.STATUS_UNDER_REVIEW, | |
103 | ) |
|
114 | ) | |
104 | ChangesetStatusModel().set_status( |
|
115 | ChangesetStatusModel().set_status( | |
105 | org_repo, |
|
116 | org_repo, | |
106 | ChangesetStatus.STATUS_UNDER_REVIEW, |
|
117 | ChangesetStatus.STATUS_UNDER_REVIEW, | |
107 | new.owner, |
|
118 | new.owner, | |
108 | comment, |
|
119 | comment, | |
109 | pull_request=new |
|
120 | pull_request=new | |
110 | ) |
|
121 | ) | |
111 |
|
122 | |||
|
123 | reviewers = set(self._get_valid_reviewers(reviewers)) | |||
112 | mention_recipients = extract_mentioned_users(new.description) |
|
124 | mention_recipients = extract_mentioned_users(new.description) | |
113 | self.__add_reviewers(created_by_user, new, reviewers, mention_recipients) |
|
125 | self.__add_reviewers(created_by_user, new, reviewers, mention_recipients) | |
114 |
|
126 | |||
115 | return new |
|
127 | return new | |
116 |
|
128 | |||
117 |
def __add_reviewers(self, user, pr, reviewers, mention_recipients |
|
129 | def __add_reviewers(self, user, pr, reviewers, mention_recipients): | |
|
130 | # reviewers and mention_recipients should be sets of User objects. | |||
118 | #members |
|
131 | #members | |
119 |
for |
|
132 | for reviewer in reviewers: | |
120 | _usr = self._get_user(member) |
|
133 | reviewer = PullRequestReviewers(reviewer, pr) | |
121 | if _usr is None: |
|
|||
122 | raise UserInvalidException(member) |
|
|||
123 | reviewer = PullRequestReviewers(_usr, pr) |
|
|||
124 | Session().add(reviewer) |
|
134 | Session().add(reviewer) | |
125 |
|
135 | |||
126 | revision_data = [(x.raw_id, x.message) |
|
136 | revision_data = [(x.raw_id, x.message) | |
127 | for x in map(pr.org_repo.get_changeset, pr.revisions)] |
|
137 | for x in map(pr.org_repo.get_changeset, pr.revisions)] | |
128 |
|
138 | |||
129 | #notification to reviewers |
|
139 | #notification to reviewers | |
130 | pr_url = pr.url(canonical=True) |
|
140 | pr_url = pr.url(canonical=True) | |
131 | threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name, |
|
141 | threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name, | |
132 | pr.pull_request_id, |
|
142 | pr.pull_request_id, | |
133 | h.canonical_hostname())] |
|
143 | h.canonical_hostname())] | |
134 | subject = safe_unicode( |
|
144 | subject = safe_unicode( | |
135 | h.link_to( |
|
145 | h.link_to( | |
136 | _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \ |
|
146 | _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \ | |
137 | {'user': user.username, |
|
147 | {'user': user.username, | |
138 | 'pr_title': pr.title, |
|
148 | 'pr_title': pr.title, | |
139 | 'pr_nice_id': pr.nice_id()}, |
|
149 | 'pr_nice_id': pr.nice_id()}, | |
140 | pr_url) |
|
150 | pr_url) | |
141 | ) |
|
151 | ) | |
142 | body = pr.description |
|
152 | body = pr.description | |
143 | _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':') |
|
153 | _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':') | |
144 | email_kwargs = { |
|
154 | email_kwargs = { | |
145 | 'pr_title': pr.title, |
|
155 | 'pr_title': pr.title, | |
146 | 'pr_user_created': user.full_name_and_username, |
|
156 | 'pr_user_created': user.full_name_and_username, | |
147 | 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name), |
|
157 | 'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name), | |
148 | 'pr_url': pr_url, |
|
158 | 'pr_url': pr_url, | |
149 | 'pr_revisions': revision_data, |
|
159 | 'pr_revisions': revision_data, | |
150 | 'repo_name': pr.other_repo.repo_name, |
|
160 | 'repo_name': pr.other_repo.repo_name, | |
151 | 'org_repo_name': pr.org_repo.repo_name, |
|
161 | 'org_repo_name': pr.org_repo.repo_name, | |
152 | 'pr_nice_id': pr.nice_id(), |
|
162 | 'pr_nice_id': pr.nice_id(), | |
153 | 'ref': org_ref_name, |
|
163 | 'ref': org_ref_name, | |
154 | 'pr_username': user.username, |
|
164 | 'pr_username': user.username, | |
155 | 'threading': threading, |
|
165 | 'threading': threading, | |
156 | 'is_mention': False, |
|
166 | 'is_mention': False, | |
157 | } |
|
167 | } | |
158 | if reviewers: |
|
168 | if reviewers: | |
159 | NotificationModel().create(created_by=user, subject=subject, body=body, |
|
169 | NotificationModel().create(created_by=user, subject=subject, body=body, | |
160 | recipients=reviewers, |
|
170 | recipients=reviewers, | |
161 | type_=Notification.TYPE_PULL_REQUEST, |
|
171 | type_=Notification.TYPE_PULL_REQUEST, | |
162 | email_kwargs=email_kwargs) |
|
172 | email_kwargs=email_kwargs) | |
163 |
|
173 | |||
164 | if mention_recipients: |
|
174 | if mention_recipients: | |
165 | mention_recipients.difference_update(reviewers) |
|
175 | mention_recipients.difference_update(reviewers) | |
166 | if mention_recipients: |
|
176 | if mention_recipients: | |
167 | email_kwargs['is_mention'] = True |
|
177 | email_kwargs['is_mention'] = True | |
168 | subject = _('[Mention]') + ' ' + subject |
|
178 | subject = _('[Mention]') + ' ' + subject | |
169 | NotificationModel().create(created_by=user, subject=subject, body=body, |
|
179 | NotificationModel().create(created_by=user, subject=subject, body=body, | |
170 | recipients=mention_recipients, |
|
180 | recipients=mention_recipients, | |
171 | type_=Notification.TYPE_PULL_REQUEST, |
|
181 | type_=Notification.TYPE_PULL_REQUEST, | |
172 | email_kwargs=email_kwargs) |
|
182 | email_kwargs=email_kwargs) | |
173 |
|
183 | |||
174 | def mention_from_description(self, user, pr, old_description=''): |
|
184 | def mention_from_description(self, user, pr, old_description=''): | |
175 | mention_recipients = (extract_mentioned_users(pr.description) - |
|
185 | mention_recipients = (extract_mentioned_users(pr.description) - | |
176 | extract_mentioned_users(old_description)) |
|
186 | extract_mentioned_users(old_description)) | |
177 |
|
187 | |||
178 | log.debug("Mentioning %s", mention_recipients) |
|
188 | log.debug("Mentioning %s", mention_recipients) | |
179 |
self.__add_reviewers(user, pr, |
|
189 | self.__add_reviewers(user, pr, set(), mention_recipients) | |
180 |
|
190 | |||
181 | def update_reviewers(self, user, pull_request, reviewers_ids): |
|
191 | def update_reviewers(self, user, pull_request, reviewers_ids): | |
182 | reviewers_ids = set(reviewers_ids) |
|
192 | reviewers_ids = set(reviewers_ids) | |
183 | pull_request = self.__get_pull_request(pull_request) |
|
193 | pull_request = self.__get_pull_request(pull_request) | |
184 | current_reviewers = PullRequestReviewers.query() \ |
|
194 | current_reviewers = PullRequestReviewers.query() \ | |
185 | .filter(PullRequestReviewers.pull_request== |
|
195 | .filter(PullRequestReviewers.pull_request== | |
186 | pull_request) \ |
|
196 | pull_request) \ | |
187 | .all() |
|
197 | .all() | |
188 | current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) |
|
198 | current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) | |
189 |
|
199 | |||
190 | to_add = reviewers_ids.difference(current_reviewers_ids) |
|
200 | to_add = reviewers_ids.difference(current_reviewers_ids) | |
191 | to_remove = current_reviewers_ids.difference(reviewers_ids) |
|
201 | to_remove = current_reviewers_ids.difference(reviewers_ids) | |
192 |
|
202 | |||
193 | log.debug("Adding %s reviewers", to_add) |
|
203 | log.debug("Adding %s reviewers", to_add) | |
194 | self.__add_reviewers(user, pull_request, to_add) |
|
204 | self.__add_reviewers(user, pull_request, set(self._get_valid_reviewers(to_add)), set()) | |
195 |
|
205 | |||
196 | log.debug("Removing %s reviewers", to_remove) |
|
206 | log.debug("Removing %s reviewers", to_remove) | |
197 | for uid in to_remove: |
|
207 | for uid in to_remove: | |
198 | reviewer = PullRequestReviewers.query() \ |
|
208 | reviewer = PullRequestReviewers.query() \ | |
199 | .filter(PullRequestReviewers.user_id==uid, |
|
209 | .filter(PullRequestReviewers.user_id==uid, | |
200 | PullRequestReviewers.pull_request==pull_request) \ |
|
210 | PullRequestReviewers.pull_request==pull_request) \ | |
201 | .scalar() |
|
211 | .scalar() | |
202 | if reviewer: |
|
212 | if reviewer: | |
203 | Session().delete(reviewer) |
|
213 | Session().delete(reviewer) | |
204 |
|
214 | |||
205 | def delete(self, pull_request): |
|
215 | def delete(self, pull_request): | |
206 | pull_request = self.__get_pull_request(pull_request) |
|
216 | pull_request = self.__get_pull_request(pull_request) | |
207 | Session().delete(pull_request) |
|
217 | Session().delete(pull_request) | |
208 |
|
218 | |||
209 | def close_pull_request(self, pull_request): |
|
219 | def close_pull_request(self, pull_request): | |
210 | pull_request = self.__get_pull_request(pull_request) |
|
220 | pull_request = self.__get_pull_request(pull_request) | |
211 | pull_request.status = PullRequest.STATUS_CLOSED |
|
221 | pull_request.status = PullRequest.STATUS_CLOSED | |
212 | pull_request.updated_on = datetime.datetime.now() |
|
222 | pull_request.updated_on = datetime.datetime.now() | |
213 | Session().add(pull_request) |
|
223 | Session().add(pull_request) |
General Comments 0
You need to be logged in to leave comments.
Login now