##// END OF EJS Templates
compare: minor refactoring and comments
Mads Kiilerich -
r3443:3ac76dfd beta
parent child Browse files
Show More
@@ -1,192 +1,195 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28
29 29 from webob.exc import HTTPNotFound
30 30 from pylons import request, response, session, tmpl_context as c, url
31 31 from pylons.controllers.util import abort, redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib import diffs
39 39
40 40 from rhodecode.model.db import Repository
41 41 from rhodecode.model.pull_request import PullRequestModel
42 42 from webob.exc import HTTPBadRequest
43 43 from rhodecode.lib.diffs import LimitedDiffContainer
44 44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class CompareController(BaseRepoController):
50 50
51 51 @LoginRequired()
52 52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 53 'repository.admin')
54 54 def __before__(self):
55 55 super(CompareController, self).__before__()
56 56
57 57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 58 partial=False):
59 59 """
60 60 Safe way to get changeset if error occur it redirects to changeset with
61 61 proper message. If partial is set then don't do redirect raise Exception
62 62 instead
63 63
64 64 :param rev: revision to fetch
65 65 :param repo: repo instance
66 66 """
67 67
68 68 try:
69 69 type_, rev = rev
70 70 return repo.scm_instance.get_changeset(rev)
71 71 except EmptyRepositoryError, e:
72 72 if not redirect_after:
73 73 return None
74 74 h.flash(h.literal(_('There are no changesets yet')),
75 75 category='warning')
76 76 redirect(url('summary_home', repo_name=repo.repo_name))
77 77
78 78 except RepositoryError, e:
79 79 log.error(traceback.format_exc())
80 80 h.flash(str(e), category='warning')
81 81 if not partial:
82 82 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 83 raise HTTPBadRequest()
84 84
85 85 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
86
86 # org_ref will be evaluated in org_repo
87 87 org_repo = c.rhodecode_db_repo.repo_name
88 88 org_ref = (org_ref_type, org_ref)
89 # other_ref will be evaluated in other_repo
89 90 other_ref = (other_ref_type, other_ref)
90 91 other_repo = request.GET.get('other_repo', org_repo)
91 c.fulldiff = fulldiff = request.GET.get('fulldiff')
92 # fulldiff disables cut_off_limit
93 c.fulldiff = request.GET.get('fulldiff')
94 # only consider this range of changesets
92 95 rev_start = request.GET.get('rev_start')
93 96 rev_end = request.GET.get('rev_end')
94 97 # partial uses compare_cs.html template directly
95 98 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
96 99 # as_form puts hidden input field with changeset revisions
97 100 c.as_form = partial and request.GET.get('as_form')
98 101 # swap url for compare_diff page - never partial and never as_form
99 102 c.swap_url = h.url('compare_url',
100 103 repo_name=other_repo,
101 104 org_ref_type=other_ref[0], org_ref=other_ref[1],
102 105 other_repo=org_repo,
103 106 other_ref_type=org_ref[0], other_ref=org_ref[1])
104 107
105 108 org_repo = Repository.get_by_repo_name(org_repo)
106 109 other_repo = Repository.get_by_repo_name(other_repo)
107 110
108 111 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
109 112 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
110 113
111 114 if org_repo is None:
112 115 log.error('Could not find org repo %s' % org_repo)
113 116 raise HTTPNotFound
114 117 if other_repo is None:
115 118 log.error('Could not find other repo %s' % other_repo)
116 119 raise HTTPNotFound
117 120
118 121 if org_repo != other_repo and h.is_git(org_repo):
119 122 log.error('compare of two remote repos not available for GIT REPOS')
120 123 raise HTTPNotFound
121 124
122 125 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
123 126 log.error('compare of two different kind of remote repos not available')
124 127 raise HTTPNotFound
125 128
126 129 c.org_repo = org_repo
127 130 c.other_repo = other_repo
128 131 c.org_ref = org_ref[1]
129 132 c.other_ref = other_ref[1]
130 133 c.org_ref_type = org_ref[0]
131 134 c.other_ref_type = other_ref[0]
132 135
133 136 if rev_start and rev_end:
134 137 # swap revs with cherry picked ones, save them for display
135 138 #org_ref = ('rev', rev_start)
136 139 #other_ref = ('rev', rev_end)
137 140 c.org_ref = rev_start[:12]
138 141 c.other_ref = rev_end[:12]
139 142 # get parent of
140 143 # rev start to include it in the diff
141 144 _cs = other_repo.scm_instance.get_changeset(rev_start)
142 145 rev_start = _cs.parents[0].raw_id if _cs.parents else EmptyChangeset().raw_id
143 146 org_ref = ('rev', rev_start)
144 147 other_ref = ('rev', rev_end)
145 148 #if we cherry pick it's not remote, make the other_repo org_repo
146 149 org_repo = other_repo
147 150
148 151 c.cs_ranges, ancestor = PullRequestModel().get_compare_data(
149 152 org_repo, org_ref, other_repo, other_ref)
150 153
151 154 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
152 155 c.cs_ranges])
153 156 if partial:
154 157 return render('compare/compare_cs.html')
155 158
156 159 if ancestor and org_repo != other_repo:
157 160 # case we want a simple diff without incoming changesets,
158 161 # previewing what will be merged.
159 162 # Make the diff on the forked repo, with
160 163 # revision that is common ancestor
161 164 log.debug('Using ancestor %s as org_ref instead of %s'
162 165 % (ancestor, org_ref))
163 166 org_ref = ('rev', ancestor)
164 167 org_repo = other_repo
165 168
166 diff_limit = self.cut_off_limit if not fulldiff else None
169 diff_limit = self.cut_off_limit if not c.fulldiff else None
167 170
168 171 _diff = diffs.differ(org_repo, org_ref, other_repo, other_ref)
169 172
170 173 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
171 174 diff_limit=diff_limit)
172 175 _parsed = diff_processor.prepare()
173 176
174 177 c.limited_diff = False
175 178 if isinstance(_parsed, LimitedDiffContainer):
176 179 c.limited_diff = True
177 180
178 181 c.files = []
179 182 c.changes = {}
180 183 c.lines_added = 0
181 184 c.lines_deleted = 0
182 185 for f in _parsed:
183 186 st = f['stats']
184 187 if st[0] != 'b':
185 188 c.lines_added += st[0]
186 189 c.lines_deleted += st[1]
187 190 fid = h.FID('', f['filename'])
188 191 c.files.append([fid, f['operation'], f['filename'], f['stats']])
189 192 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
190 193 c.changes[fid] = [f['operation'], f['filename'], diff]
191 194
192 195 return render('compare/compare_diff.html')
@@ -1,259 +1,255 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.pull_request
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull request model for RhodeCode
7 7
8 8 :created_on: Jun 6, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import datetime
28 28 import re
29 29
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.lib import helpers as h, unionrepo
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
36 36 ChangesetStatus
37 37 from rhodecode.model.notification import NotificationModel
38 38 from rhodecode.lib.utils2 import safe_unicode
39 39
40 40 from rhodecode.lib.vcs.utils.hgcompat import scmutil
41 41 from rhodecode.lib.vcs.utils import safe_str
42 42 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class PullRequestModel(BaseModel):
48 48
49 49 cls = PullRequest
50 50
51 51 def __get_pull_request(self, pull_request):
52 52 return self._get_instance(PullRequest, pull_request)
53 53
54 54 def get_all(self, repo):
55 55 repo = self._get_repo(repo)
56 56 return PullRequest.query()\
57 57 .filter(PullRequest.other_repo == repo)\
58 58 .order_by(PullRequest.created_on.desc())\
59 59 .all()
60 60
61 61 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
62 62 revisions, reviewers, title, description=None):
63 63 from rhodecode.model.changeset_status import ChangesetStatusModel
64 64
65 65 created_by_user = self._get_user(created_by)
66 66 org_repo = self._get_repo(org_repo)
67 67 other_repo = self._get_repo(other_repo)
68 68
69 69 new = PullRequest()
70 70 new.org_repo = org_repo
71 71 new.org_ref = org_ref
72 72 new.other_repo = other_repo
73 73 new.other_ref = other_ref
74 74 new.revisions = revisions
75 75 new.title = title
76 76 new.description = description
77 77 new.author = created_by_user
78 78 Session().add(new)
79 79 Session().flush()
80 80 #members
81 81 for member in set(reviewers):
82 82 _usr = self._get_user(member)
83 83 reviewer = PullRequestReviewers(_usr, new)
84 84 Session().add(reviewer)
85 85
86 86 #reset state to under-review
87 87 ChangesetStatusModel().set_status(
88 88 repo=org_repo,
89 89 status=ChangesetStatus.STATUS_UNDER_REVIEW,
90 90 user=created_by_user,
91 91 pull_request=new
92 92 )
93 93 revision_data = [(x.raw_id, x.message)
94 94 for x in map(org_repo.get_changeset, revisions)]
95 95 #notification to reviewers
96 96 notif = NotificationModel()
97 97
98 98 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
99 99 pull_request_id=new.pull_request_id,
100 100 qualified=True,
101 101 )
102 102 subject = safe_unicode(
103 103 h.link_to(
104 104 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
105 105 {'user': created_by_user.username,
106 106 'pr_title': new.title,
107 107 'pr_id': new.pull_request_id},
108 108 pr_url
109 109 )
110 110 )
111 111 body = description
112 112 kwargs = {
113 113 'pr_title': title,
114 114 'pr_user_created': h.person(created_by_user.email),
115 115 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
116 116 qualified=True,),
117 117 'pr_url': pr_url,
118 118 'pr_revisions': revision_data
119 119 }
120 120
121 121 notif.create(created_by=created_by_user, subject=subject, body=body,
122 122 recipients=reviewers,
123 123 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
124 124 return new
125 125
126 126 def update_reviewers(self, pull_request, reviewers_ids):
127 127 reviewers_ids = set(reviewers_ids)
128 128 pull_request = self.__get_pull_request(pull_request)
129 129 current_reviewers = PullRequestReviewers.query()\
130 130 .filter(PullRequestReviewers.pull_request==
131 131 pull_request)\
132 132 .all()
133 133 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
134 134
135 135 to_add = reviewers_ids.difference(current_reviewers_ids)
136 136 to_remove = current_reviewers_ids.difference(reviewers_ids)
137 137
138 138 log.debug("Adding %s reviewers" % to_add)
139 139 log.debug("Removing %s reviewers" % to_remove)
140 140
141 141 for uid in to_add:
142 142 _usr = self._get_user(uid)
143 143 reviewer = PullRequestReviewers(_usr, pull_request)
144 144 Session().add(reviewer)
145 145
146 146 for uid in to_remove:
147 147 reviewer = PullRequestReviewers.query()\
148 148 .filter(PullRequestReviewers.user_id==uid,
149 149 PullRequestReviewers.pull_request==pull_request)\
150 150 .scalar()
151 151 if reviewer:
152 152 Session().delete(reviewer)
153 153
154 154 def delete(self, pull_request):
155 155 pull_request = self.__get_pull_request(pull_request)
156 156 Session().delete(pull_request)
157 157
158 158 def close_pull_request(self, pull_request):
159 159 pull_request = self.__get_pull_request(pull_request)
160 160 pull_request.status = PullRequest.STATUS_CLOSED
161 161 pull_request.updated_on = datetime.datetime.now()
162 162 Session().add(pull_request)
163 163
164 164 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref):
165 165 """
166 166 Returns a list of changesets that can be merged from org_repo@org_ref
167 167 to other_repo@other_ref ... and the ancestor that would be used for merge
168 168
169 169 :param org_repo:
170 170 :param org_ref:
171 171 :param other_repo:
172 172 :param other_ref:
173 173 :param tmp:
174 174 """
175 175
176 176 ancestor = None
177 177
178 178 if alias == 'hg':
179 179 # lookup up the exact node id
180 180 _revset_predicates = {
181 181 'branch': 'branch',
182 182 'book': 'bookmark',
183 183 'tag': 'tag',
184 184 'rev': 'id',
185 185 }
186 186
187 187 org_rev_spec = "%s('%s')" % (_revset_predicates[org_ref[0]],
188 188 safe_str(org_ref[1]))
189 189 if org_ref[1] == EmptyChangeset().raw_id:
190 190 org_rev = org_ref[1]
191 191 else:
192 192 org_rev = org_repo._repo[scmutil.revrange(org_repo._repo,
193 193 [org_rev_spec])[-1]]
194 194 other_rev_spec = "%s('%s')" % (_revset_predicates[other_ref[0]],
195 195 safe_str(other_ref[1]))
196 196 if other_ref[1] == EmptyChangeset().raw_id:
197 197 other_rev = other_ref[1]
198 198 else:
199 199 other_rev = other_repo._repo[scmutil.revrange(other_repo._repo,
200 200 [other_rev_spec])[-1]]
201 201
202 202 #case two independent repos
203 203 if org_repo != other_repo:
204 204 hgrepo = unionrepo.unionrepository(other_repo.baseui,
205 205 other_repo.path,
206 206 org_repo.path)
207 207 # all the changesets we are looking for will be in other_repo,
208 208 # so rev numbers from hgrepo can be used in other_repo
209 209
210 210 #no remote compare do it on the same repository
211 211 else:
212 212 hgrepo = other_repo._repo
213 213
214 214 revs = ["ancestors(id('%s')) and not ancestors(id('%s'))" %
215 215 (other_rev, org_rev)]
216 216 changesets = [other_repo.get_changeset(cs)
217 217 for cs in scmutil.revrange(hgrepo, revs)]
218 218
219 219 if org_repo != other_repo:
220 220 ancestors = scmutil.revrange(hgrepo,
221 221 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
222 222 if len(ancestors) == 1:
223 223 ancestor = hgrepo[ancestors[0]].hex()
224 224
225 225 elif alias == 'git':
226 226 assert org_repo == other_repo, (org_repo, other_repo) # no git support for different repos
227 227 so, se = org_repo.run_git_command(
228 228 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
229 229 other_ref[1])
230 230 )
231 231 changesets = [org_repo.get_changeset(cs)
232 232 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
233 233
234 234 return changesets, ancestor
235 235
236 236 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
237 237 """
238 238 Returns incoming changesets for mercurial repositories
239 239
240 240 :param org_repo:
241 241 :param org_ref:
242 242 :param other_repo:
243 243 :param other_ref:
244 244 """
245 245
246 246 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
247 247 raise Exception('org_ref must be a two element list/tuple')
248 248
249 249 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
250 250 raise Exception('other_ref must be a two element list/tuple')
251 251
252 org_repo_scm = org_repo.scm_instance
253 other_repo_scm = other_repo.scm_instance
254
255 alias = org_repo.scm_instance.alias
256 cs_ranges, ancestor = self._get_changesets(alias,
257 org_repo_scm, org_ref,
258 other_repo_scm, other_ref)
252 cs_ranges, ancestor = self._get_changesets(org_repo.scm_instance.alias,
253 org_repo.scm_instance, org_ref,
254 other_repo.scm_instance, other_ref)
259 255 return cs_ranges, ancestor
General Comments 0
You need to be logged in to leave comments. Login now