##// END OF EJS Templates
pullrequests: simplify handling of null revisions...
Mads Kiilerich -
r3722:5dcfa630 beta
parent child Browse files
Show More
@@ -1,261 +1,255 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 datetime
27 import datetime
28 import re
28 import re
29
29
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.lib import helpers as h, unionrepo
33 from rhodecode.lib import helpers as h, unionrepo
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
36 ChangesetStatus
36 ChangesetStatus
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
38 from rhodecode.lib.utils2 import safe_unicode
38 from rhodecode.lib.utils2 import safe_unicode
39
39
40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
41 from rhodecode.lib.vcs.utils import safe_str
41 from rhodecode.lib.vcs.utils import safe_str
42 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43
42
44 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
45
44
46
45
47 class PullRequestModel(BaseModel):
46 class PullRequestModel(BaseModel):
48
47
49 cls = PullRequest
48 cls = PullRequest
50
49
51 def __get_pull_request(self, pull_request):
50 def __get_pull_request(self, pull_request):
52 return self._get_instance(PullRequest, pull_request)
51 return self._get_instance(PullRequest, pull_request)
53
52
54 def get_all(self, repo):
53 def get_all(self, repo):
55 repo = self._get_repo(repo)
54 repo = self._get_repo(repo)
56 return PullRequest.query()\
55 return PullRequest.query()\
57 .filter(PullRequest.other_repo == repo)\
56 .filter(PullRequest.other_repo == repo)\
58 .order_by(PullRequest.created_on.desc())\
57 .order_by(PullRequest.created_on.desc())\
59 .all()
58 .all()
60
59
61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
60 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
62 revisions, reviewers, title, description=None):
61 revisions, reviewers, title, description=None):
63 from rhodecode.model.changeset_status import ChangesetStatusModel
62 from rhodecode.model.changeset_status import ChangesetStatusModel
64
63
65 created_by_user = self._get_user(created_by)
64 created_by_user = self._get_user(created_by)
66 org_repo = self._get_repo(org_repo)
65 org_repo = self._get_repo(org_repo)
67 other_repo = self._get_repo(other_repo)
66 other_repo = self._get_repo(other_repo)
68
67
69 new = PullRequest()
68 new = PullRequest()
70 new.org_repo = org_repo
69 new.org_repo = org_repo
71 new.org_ref = org_ref
70 new.org_ref = org_ref
72 new.other_repo = other_repo
71 new.other_repo = other_repo
73 new.other_ref = other_ref
72 new.other_ref = other_ref
74 new.revisions = revisions
73 new.revisions = revisions
75 new.title = title
74 new.title = title
76 new.description = description
75 new.description = description
77 new.author = created_by_user
76 new.author = created_by_user
78 Session().add(new)
77 Session().add(new)
79 Session().flush()
78 Session().flush()
80 #members
79 #members
81 for member in set(reviewers):
80 for member in set(reviewers):
82 _usr = self._get_user(member)
81 _usr = self._get_user(member)
83 reviewer = PullRequestReviewers(_usr, new)
82 reviewer = PullRequestReviewers(_usr, new)
84 Session().add(reviewer)
83 Session().add(reviewer)
85
84
86 #reset state to under-review
85 #reset state to under-review
87 ChangesetStatusModel().set_status(
86 ChangesetStatusModel().set_status(
88 repo=org_repo,
87 repo=org_repo,
89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
88 status=ChangesetStatus.STATUS_UNDER_REVIEW,
90 user=created_by_user,
89 user=created_by_user,
91 pull_request=new
90 pull_request=new
92 )
91 )
93 revision_data = [(x.raw_id, x.message)
92 revision_data = [(x.raw_id, x.message)
94 for x in map(org_repo.get_changeset, revisions)]
93 for x in map(org_repo.get_changeset, revisions)]
95 #notification to reviewers
94 #notification to reviewers
96 notif = NotificationModel()
95 notif = NotificationModel()
97
96
98 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
97 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
99 pull_request_id=new.pull_request_id,
98 pull_request_id=new.pull_request_id,
100 qualified=True,
99 qualified=True,
101 )
100 )
102 subject = safe_unicode(
101 subject = safe_unicode(
103 h.link_to(
102 h.link_to(
104 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
103 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
105 {'user': created_by_user.username,
104 {'user': created_by_user.username,
106 'pr_title': new.title,
105 'pr_title': new.title,
107 'pr_id': new.pull_request_id},
106 'pr_id': new.pull_request_id},
108 pr_url
107 pr_url
109 )
108 )
110 )
109 )
111 body = description
110 body = description
112 kwargs = {
111 kwargs = {
113 'pr_title': title,
112 'pr_title': title,
114 'pr_user_created': h.person(created_by_user.email),
113 'pr_user_created': h.person(created_by_user.email),
115 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
114 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
116 qualified=True,),
115 qualified=True,),
117 'pr_url': pr_url,
116 'pr_url': pr_url,
118 'pr_revisions': revision_data
117 'pr_revisions': revision_data
119 }
118 }
120
119
121 notif.create(created_by=created_by_user, subject=subject, body=body,
120 notif.create(created_by=created_by_user, subject=subject, body=body,
122 recipients=reviewers,
121 recipients=reviewers,
123 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
122 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
124 return new
123 return new
125
124
126 def update_reviewers(self, pull_request, reviewers_ids):
125 def update_reviewers(self, pull_request, reviewers_ids):
127 reviewers_ids = set(reviewers_ids)
126 reviewers_ids = set(reviewers_ids)
128 pull_request = self.__get_pull_request(pull_request)
127 pull_request = self.__get_pull_request(pull_request)
129 current_reviewers = PullRequestReviewers.query()\
128 current_reviewers = PullRequestReviewers.query()\
130 .filter(PullRequestReviewers.pull_request==
129 .filter(PullRequestReviewers.pull_request==
131 pull_request)\
130 pull_request)\
132 .all()
131 .all()
133 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
132 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
134
133
135 to_add = reviewers_ids.difference(current_reviewers_ids)
134 to_add = reviewers_ids.difference(current_reviewers_ids)
136 to_remove = current_reviewers_ids.difference(reviewers_ids)
135 to_remove = current_reviewers_ids.difference(reviewers_ids)
137
136
138 log.debug("Adding %s reviewers" % to_add)
137 log.debug("Adding %s reviewers" % to_add)
139 log.debug("Removing %s reviewers" % to_remove)
138 log.debug("Removing %s reviewers" % to_remove)
140
139
141 for uid in to_add:
140 for uid in to_add:
142 _usr = self._get_user(uid)
141 _usr = self._get_user(uid)
143 reviewer = PullRequestReviewers(_usr, pull_request)
142 reviewer = PullRequestReviewers(_usr, pull_request)
144 Session().add(reviewer)
143 Session().add(reviewer)
145
144
146 for uid in to_remove:
145 for uid in to_remove:
147 reviewer = PullRequestReviewers.query()\
146 reviewer = PullRequestReviewers.query()\
148 .filter(PullRequestReviewers.user_id==uid,
147 .filter(PullRequestReviewers.user_id==uid,
149 PullRequestReviewers.pull_request==pull_request)\
148 PullRequestReviewers.pull_request==pull_request)\
150 .scalar()
149 .scalar()
151 if reviewer:
150 if reviewer:
152 Session().delete(reviewer)
151 Session().delete(reviewer)
153
152
154 def delete(self, pull_request):
153 def delete(self, pull_request):
155 pull_request = self.__get_pull_request(pull_request)
154 pull_request = self.__get_pull_request(pull_request)
156 Session().delete(pull_request)
155 Session().delete(pull_request)
157
156
158 def close_pull_request(self, pull_request):
157 def close_pull_request(self, pull_request):
159 pull_request = self.__get_pull_request(pull_request)
158 pull_request = self.__get_pull_request(pull_request)
160 pull_request.status = PullRequest.STATUS_CLOSED
159 pull_request.status = PullRequest.STATUS_CLOSED
161 pull_request.updated_on = datetime.datetime.now()
160 pull_request.updated_on = datetime.datetime.now()
162 Session().add(pull_request)
161 Session().add(pull_request)
163
162
164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
163 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
165 """
164 """
166 Returns a list of changesets that can be merged from org_repo@org_ref
165 Returns a list of changesets that can be merged from org_repo@org_ref
167 to other_repo@other_ref ... and the ancestor that would be used for merge
166 to other_repo@other_ref ... and the ancestor that would be used for merge
168
167
169 :param org_repo:
168 :param org_repo:
170 :param org_ref:
169 :param org_ref:
171 :param other_repo:
170 :param other_repo:
172 :param other_ref:
171 :param other_ref:
173 :param tmp:
172 :param tmp:
174 """
173 """
175
174
176 ancestor = None
175 ancestor = None
177
176
178 if alias == 'hg':
177 if alias == 'hg':
179 # lookup up the exact node id
178 # lookup up the exact node id
180 _revset_predicates = {
179 _revset_predicates = {
181 'branch': 'branch',
180 'branch': 'branch',
182 'book': 'bookmark',
181 'book': 'bookmark',
183 'tag': 'tag',
182 'tag': 'tag',
184 'rev': 'id',
183 'rev': 'id',
185 }
184 }
186
185
187 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
186 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
188 safe_str(org_ref[1]))
187 safe_str(org_ref[1]))
189 if org_ref[1] == EmptyChangeset().raw_id:
188 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
190 org_rev = org_ref[1]
189 [org_rev_spec])[-1]]
191 else:
190
192 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
193 [org_rev_spec])[-1]]
194 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
191 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
195 safe_str(other_ref[1]))
192 safe_str(other_ref[1]))
196 if other_ref[1] == EmptyChangeset().raw_id:
193 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
197 other_rev = other_ref[1]
194 [other_rev_spec])[-1]]
198 else:
199 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
200 [other_rev_spec])[-1]]
201
195
202 #case two independent repos
196 #case two independent repos
203 if org_repo != other_repo:
197 if org_repo != other_repo:
204 hgrepo = unionrepo.unionrepository(other_repo.baseui,
198 hgrepo = unionrepo.unionrepository(other_repo.baseui,
205 other_repo.path,
199 other_repo.path,
206 org_repo.path)
200 org_repo.path)
207 # all the changesets we are looking for will be in other_repo,
201 # all the changesets we are looking for will be in other_repo,
208 # so rev numbers from hgrepo can be used in other_repo
202 # so rev numbers from hgrepo can be used in other_repo
209
203
210 #no remote compare do it on the same repository
204 #no remote compare do it on the same repository
211 else:
205 else:
212 hgrepo = other_repo._repo
206 hgrepo = other_repo._repo
213
207
214 if merge:
208 if merge:
215 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
209 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
216 (other_rev, org_rev, org_rev)]
210 (other_rev, org_rev, org_rev)]
217
211
218 ancestors = scmutil.revrange(hgrepo,
212 ancestors = scmutil.revrange(hgrepo,
219 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
213 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
220 if len(ancestors) == 1:
214 if len(ancestors) == 1:
221 ancestor = hgrepo[ancestors[0]].hex()
215 ancestor = hgrepo[ancestors[0]].hex()
222 else:
216 else:
223 # TODO: have both + and - changesets
217 # TODO: have both + and - changesets
224 revs = ["id('%s') :: id('%s') - id('%s')" %
218 revs = ["id('%s') :: id('%s') - id('%s')" %
225 (org_rev, other_rev, org_rev)]
219 (org_rev, other_rev, org_rev)]
226
220
227 changesets = [other_repo.get_changeset(cs)
221 changesets = [other_repo.get_changeset(cs)
228 for cs in scmutil.revrange(hgrepo, revs)]
222 for cs in scmutil.revrange(hgrepo, revs)]
229
223
230 elif alias == 'git':
224 elif alias == 'git':
231 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
225 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
232 so, se = org_repo.run_git_command(
226 so, se = org_repo.run_git_command(
233 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
227 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
234 other_ref[1])
228 other_ref[1])
235 )
229 )
236 changesets = [org_repo.get_changeset(cs)
230 changesets = [org_repo.get_changeset(cs)
237 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
231 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
238
232
239 return changesets, ancestor
233 return changesets, ancestor
240
234
241 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
235 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref, merge):
242 """
236 """
243 Returns incoming changesets for mercurial repositories
237 Returns incoming changesets for mercurial repositories
244
238
245 :param org_repo:
239 :param org_repo:
246 :param org_ref:
240 :param org_ref:
247 :param other_repo:
241 :param other_repo:
248 :param other_ref:
242 :param other_ref:
249 """
243 """
250
244
251 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
245 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
252 raise Exception('org_ref must be a two element list/tuple')
246 raise Exception('org_ref must be a two element list/tuple')
253
247
254 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
248 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
255 raise Exception('other_ref must be a two element list/tuple')
249 raise Exception('other_ref must be a two element list/tuple')
256
250
257 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
251 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
258 org_repo.scm_instance, org_ref,
252 org_repo.scm_instance, org_ref,
259 other_repo.scm_instance, other_ref,
253 other_repo.scm_instance, other_ref,
260 merge)
254 merge)
261 return cs_ranges, ancestor
255 return cs_ranges, ancestor
General Comments 0
You need to be logged in to leave comments. Login now