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