##// END OF EJS Templates
pull request: mention title of pull request in notifications
Mads Kiilerich -
r3251:e7685996 beta
parent child Browse files
Show More
@@ -1,251 +1,252 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_request
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import binascii
27 import binascii
28 import datetime
28 import datetime
29 import re
29 import re
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
36 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
37 ChangesetStatus
37 ChangesetStatus
38 from rhodecode.model.notification import NotificationModel
38 from rhodecode.model.notification import NotificationModel
39 from rhodecode.lib.utils2 import safe_unicode
39 from rhodecode.lib.utils2 import safe_unicode
40
40
41 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
41 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
42 findcommonoutgoing
42 findcommonoutgoing
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class PullRequestModel(BaseModel):
47 class PullRequestModel(BaseModel):
48
48
49 cls = PullRequest
49 cls = PullRequest
50
50
51 def __get_pull_request(self, pull_request):
51 def __get_pull_request(self, pull_request):
52 return self._get_instance(PullRequest, pull_request)
52 return self._get_instance(PullRequest, pull_request)
53
53
54 def get_all(self, repo):
54 def get_all(self, repo):
55 repo = self._get_repo(repo)
55 repo = self._get_repo(repo)
56 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
56 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
57
57
58 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
58 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
59 revisions, reviewers, title, description=None):
59 revisions, reviewers, title, description=None):
60 from rhodecode.model.changeset_status import ChangesetStatusModel
60 from rhodecode.model.changeset_status import ChangesetStatusModel
61
61
62 created_by_user = self._get_user(created_by)
62 created_by_user = self._get_user(created_by)
63 org_repo = self._get_repo(org_repo)
63 org_repo = self._get_repo(org_repo)
64 other_repo = self._get_repo(other_repo)
64 other_repo = self._get_repo(other_repo)
65
65
66 new = PullRequest()
66 new = PullRequest()
67 new.org_repo = org_repo
67 new.org_repo = org_repo
68 new.org_ref = org_ref
68 new.org_ref = org_ref
69 new.other_repo = other_repo
69 new.other_repo = other_repo
70 new.other_ref = other_ref
70 new.other_ref = other_ref
71 new.revisions = revisions
71 new.revisions = revisions
72 new.title = title
72 new.title = title
73 new.description = description
73 new.description = description
74 new.author = created_by_user
74 new.author = created_by_user
75 self.sa.add(new)
75 self.sa.add(new)
76 Session().flush()
76 Session().flush()
77 #members
77 #members
78 for member in reviewers:
78 for member in reviewers:
79 _usr = self._get_user(member)
79 _usr = self._get_user(member)
80 reviewer = PullRequestReviewers(_usr, new)
80 reviewer = PullRequestReviewers(_usr, new)
81 self.sa.add(reviewer)
81 self.sa.add(reviewer)
82
82
83 #reset state to under-review
83 #reset state to under-review
84 ChangesetStatusModel().set_status(
84 ChangesetStatusModel().set_status(
85 repo=org_repo,
85 repo=org_repo,
86 status=ChangesetStatus.STATUS_UNDER_REVIEW,
86 status=ChangesetStatus.STATUS_UNDER_REVIEW,
87 user=created_by_user,
87 user=created_by_user,
88 pull_request=new
88 pull_request=new
89 )
89 )
90
90
91 #notification to reviewers
91 #notification to reviewers
92 notif = NotificationModel()
92 notif = NotificationModel()
93
93
94 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
94 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
95 pull_request_id=new.pull_request_id,
95 pull_request_id=new.pull_request_id,
96 qualified=True,
96 qualified=True,
97 )
97 )
98 subject = safe_unicode(
98 subject = safe_unicode(
99 h.link_to(
99 h.link_to(
100 _('%(user)s wants you to review pull request #%(pr_id)s') % \
100 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
101 {'user': created_by_user.username,
101 {'user': created_by_user.username,
102 'pr_title': new.title,
102 'pr_id': new.pull_request_id},
103 'pr_id': new.pull_request_id},
103 pr_url
104 pr_url
104 )
105 )
105 )
106 )
106 body = description
107 body = description
107 kwargs = {
108 kwargs = {
108 'pr_title': title,
109 'pr_title': title,
109 'pr_user_created': h.person(created_by_user.email),
110 'pr_user_created': h.person(created_by_user.email),
110 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
111 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
111 qualified=True,),
112 qualified=True,),
112 'pr_url': pr_url,
113 'pr_url': pr_url,
113 'pr_revisions': revisions
114 'pr_revisions': revisions
114 }
115 }
115 notif.create(created_by=created_by_user, subject=subject, body=body,
116 notif.create(created_by=created_by_user, subject=subject, body=body,
116 recipients=reviewers,
117 recipients=reviewers,
117 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
118 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
118 return new
119 return new
119
120
120 def update_reviewers(self, pull_request, reviewers_ids):
121 def update_reviewers(self, pull_request, reviewers_ids):
121 reviewers_ids = set(reviewers_ids)
122 reviewers_ids = set(reviewers_ids)
122 pull_request = self.__get_pull_request(pull_request)
123 pull_request = self.__get_pull_request(pull_request)
123 current_reviewers = PullRequestReviewers.query()\
124 current_reviewers = PullRequestReviewers.query()\
124 .filter(PullRequestReviewers.pull_request==
125 .filter(PullRequestReviewers.pull_request==
125 pull_request)\
126 pull_request)\
126 .all()
127 .all()
127 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
128 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
128
129
129 to_add = reviewers_ids.difference(current_reviewers_ids)
130 to_add = reviewers_ids.difference(current_reviewers_ids)
130 to_remove = current_reviewers_ids.difference(reviewers_ids)
131 to_remove = current_reviewers_ids.difference(reviewers_ids)
131
132
132 log.debug("Adding %s reviewers" % to_add)
133 log.debug("Adding %s reviewers" % to_add)
133 log.debug("Removing %s reviewers" % to_remove)
134 log.debug("Removing %s reviewers" % to_remove)
134
135
135 for uid in to_add:
136 for uid in to_add:
136 _usr = self._get_user(uid)
137 _usr = self._get_user(uid)
137 reviewer = PullRequestReviewers(_usr, pull_request)
138 reviewer = PullRequestReviewers(_usr, pull_request)
138 self.sa.add(reviewer)
139 self.sa.add(reviewer)
139
140
140 for uid in to_remove:
141 for uid in to_remove:
141 reviewer = PullRequestReviewers.query()\
142 reviewer = PullRequestReviewers.query()\
142 .filter(PullRequestReviewers.user_id==uid,
143 .filter(PullRequestReviewers.user_id==uid,
143 PullRequestReviewers.pull_request==pull_request)\
144 PullRequestReviewers.pull_request==pull_request)\
144 .scalar()
145 .scalar()
145 if reviewer:
146 if reviewer:
146 self.sa.delete(reviewer)
147 self.sa.delete(reviewer)
147
148
148 def delete(self, pull_request):
149 def delete(self, pull_request):
149 pull_request = self.__get_pull_request(pull_request)
150 pull_request = self.__get_pull_request(pull_request)
150 Session().delete(pull_request)
151 Session().delete(pull_request)
151
152
152 def close_pull_request(self, pull_request):
153 def close_pull_request(self, pull_request):
153 pull_request = self.__get_pull_request(pull_request)
154 pull_request = self.__get_pull_request(pull_request)
154 pull_request.status = PullRequest.STATUS_CLOSED
155 pull_request.status = PullRequest.STATUS_CLOSED
155 pull_request.updated_on = datetime.datetime.now()
156 pull_request.updated_on = datetime.datetime.now()
156 self.sa.add(pull_request)
157 self.sa.add(pull_request)
157
158
158 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
159 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
159 """
160 """
160 Returns a list of changesets that are incoming from org_repo@org_ref
161 Returns a list of changesets that are incoming from org_repo@org_ref
161 to other_repo@other_ref
162 to other_repo@other_ref
162
163
163 :param org_repo:
164 :param org_repo:
164 :param org_ref:
165 :param org_ref:
165 :param other_repo:
166 :param other_repo:
166 :param other_ref:
167 :param other_ref:
167 :param tmp:
168 :param tmp:
168 """
169 """
169
170
170 changesets = []
171 changesets = []
171 #case two independent repos
172 #case two independent repos
172 if org_repo != other_repo:
173 if org_repo != other_repo:
173 revs = [
174 revs = [
174 org_repo._repo.lookup(org_ref[1]),
175 org_repo._repo.lookup(org_ref[1]),
175 org_repo._repo.lookup(other_ref[1]),
176 org_repo._repo.lookup(other_ref[1]),
176 ]
177 ]
177
178
178 obj = findcommonoutgoing(org_repo._repo,
179 obj = findcommonoutgoing(org_repo._repo,
179 localrepo.locallegacypeer(other_repo._repo.local()),
180 localrepo.locallegacypeer(other_repo._repo.local()),
180 revs,
181 revs,
181 force=True)
182 force=True)
182 revs = obj.missing
183 revs = obj.missing
183
184
184 for cs in map(binascii.hexlify, revs):
185 for cs in map(binascii.hexlify, revs):
185 _cs = org_repo.get_changeset(cs)
186 _cs = org_repo.get_changeset(cs)
186 changesets.append(_cs)
187 changesets.append(_cs)
187 # in case we have revisions filter out the ones not in given range
188 # in case we have revisions filter out the ones not in given range
188 if org_ref[0] == 'rev' and other_ref[0] == 'rev':
189 if org_ref[0] == 'rev' and other_ref[0] == 'rev':
189 revs = [x.raw_id for x in changesets]
190 revs = [x.raw_id for x in changesets]
190 start = org_ref[1]
191 start = org_ref[1]
191 stop = other_ref[1]
192 stop = other_ref[1]
192 changesets = changesets[revs.index(start):revs.index(stop) + 1]
193 changesets = changesets[revs.index(start):revs.index(stop) + 1]
193 else:
194 else:
194 #no remote compare do it on the same repository
195 #no remote compare do it on the same repository
195 if alias == 'hg':
196 if alias == 'hg':
196 _revset_predicates = {
197 _revset_predicates = {
197 'branch': 'branch',
198 'branch': 'branch',
198 'book': 'bookmark',
199 'book': 'bookmark',
199 'tag': 'tag',
200 'tag': 'tag',
200 'rev': 'id',
201 'rev': 'id',
201 }
202 }
202
203
203 revs = [
204 revs = [
204 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
205 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
205 _revset_predicates[other_ref[0]], other_ref[1],
206 _revset_predicates[other_ref[0]], other_ref[1],
206 _revset_predicates[org_ref[0]], org_ref[1],
207 _revset_predicates[org_ref[0]], org_ref[1],
207 )
208 )
208 ]
209 ]
209
210
210 out = scmutil.revrange(org_repo._repo, revs)
211 out = scmutil.revrange(org_repo._repo, revs)
211 for cs in (out):
212 for cs in (out):
212 changesets.append(org_repo.get_changeset(cs))
213 changesets.append(org_repo.get_changeset(cs))
213 elif alias == 'git':
214 elif alias == 'git':
214 so, se = org_repo.run_git_command(
215 so, se = org_repo.run_git_command(
215 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
216 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
216 other_ref[1])
217 other_ref[1])
217 )
218 )
218 ids = re.findall(r'[0-9a-fA-F]{40}', so)
219 ids = re.findall(r'[0-9a-fA-F]{40}', so)
219 for cs in (ids):
220 for cs in (ids):
220 changesets.append(org_repo.get_changeset(cs))
221 changesets.append(org_repo.get_changeset(cs))
221
222
222 return changesets
223 return changesets
223
224
224 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
225 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
225 """
226 """
226 Returns incomming changesets for mercurial repositories
227 Returns incomming changesets for mercurial repositories
227
228
228 :param org_repo:
229 :param org_repo:
229 :type org_repo:
230 :type org_repo:
230 :param org_ref:
231 :param org_ref:
231 :type org_ref:
232 :type org_ref:
232 :param other_repo:
233 :param other_repo:
233 :type other_repo:
234 :type other_repo:
234 :param other_ref:
235 :param other_ref:
235 :type other_ref:
236 :type other_ref:
236 """
237 """
237
238
238 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
239 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
239 raise Exception('org_ref must be a two element list/tuple')
240 raise Exception('org_ref must be a two element list/tuple')
240
241
241 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
242 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
242 raise Exception('other_ref must be a two element list/tuple')
243 raise Exception('other_ref must be a two element list/tuple')
243
244
244 org_repo_scm = org_repo.scm_instance
245 org_repo_scm = org_repo.scm_instance
245 other_repo_scm = other_repo.scm_instance
246 other_repo_scm = other_repo.scm_instance
246
247
247 alias = org_repo.scm_instance.alias
248 alias = org_repo.scm_instance.alias
248 cs_ranges = self._get_changesets(alias,
249 cs_ranges = self._get_changesets(alias,
249 org_repo_scm, org_ref,
250 org_repo_scm, org_ref,
250 other_repo_scm, other_ref)
251 other_repo_scm, other_ref)
251 return cs_ranges
252 return cs_ranges
General Comments 0
You need to be logged in to leave comments. Login now