##// END OF EJS Templates
It's better to use Exception here than assertion. It plays better with the exception handling software like sentry or errormator
marcink -
r3783:f533c054 beta
parent child Browse files
Show More
@@ -1,273 +1,275 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showing differences between two
6 compare controller for pylons showing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import re
29 import re
30
30
31 from webob.exc import HTTPNotFound
31 from webob.exc import HTTPNotFound
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
37 from rhodecode.lib.vcs.utils import safe_str
37 from rhodecode.lib.vcs.utils import safe_str
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib import diffs, unionrepo
42 from rhodecode.lib import diffs, unionrepo
43
43
44 from rhodecode.model.db import Repository
44 from rhodecode.model.db import Repository
45 from webob.exc import HTTPBadRequest
45 from webob.exc import HTTPBadRequest
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class CompareController(BaseRepoController):
52 class CompareController(BaseRepoController):
53
53
54 def __before__(self):
54 def __before__(self):
55 super(CompareController, self).__before__()
55 super(CompareController, self).__before__()
56
56
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 partial=False):
58 partial=False):
59 """
59 """
60 Safe way to get changeset if error occur it redirects to changeset with
60 Safe way to get changeset if error occur it redirects to changeset with
61 proper message. If partial is set then don't do redirect raise Exception
61 proper message. If partial is set then don't do redirect raise Exception
62 instead
62 instead
63
63
64 :param rev: revision to fetch
64 :param rev: revision to fetch
65 :param repo: repo instance
65 :param repo: repo instance
66 """
66 """
67
67
68 try:
68 try:
69 type_, rev = rev
69 type_, rev = rev
70 return repo.scm_instance.get_changeset(rev)
70 return repo.scm_instance.get_changeset(rev)
71 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
72 if not redirect_after:
72 if not redirect_after:
73 return None
73 return None
74 h.flash(h.literal(_('There are no changesets yet')),
74 h.flash(h.literal(_('There are no changesets yet')),
75 category='warning')
75 category='warning')
76 redirect(url('summary_home', repo_name=repo.repo_name))
76 redirect(url('summary_home', repo_name=repo.repo_name))
77
77
78 except RepositoryError, e:
78 except RepositoryError, e:
79 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
80 h.flash(str(e), category='warning')
80 h.flash(str(e), category='warning')
81 if not partial:
81 if not partial:
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 raise HTTPBadRequest()
83 raise HTTPBadRequest()
84
84
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
86 """
86 """
87 Returns a list of changesets that can be merged from org_repo@org_ref
87 Returns a list of changesets that can be merged from org_repo@org_ref
88 to other_repo@other_ref ... and the ancestor that would be used for merge
88 to other_repo@other_ref ... and the ancestor that would be used for merge
89
89
90 :param org_repo:
90 :param org_repo:
91 :param org_ref:
91 :param org_ref:
92 :param other_repo:
92 :param other_repo:
93 :param other_ref:
93 :param other_ref:
94 :param tmp:
94 :param tmp:
95 """
95 """
96
96
97 ancestor = None
97 ancestor = None
98
98
99 if alias == 'hg':
99 if alias == 'hg':
100 # lookup up the exact node id
100 # lookup up the exact node id
101 _revset_predicates = {
101 _revset_predicates = {
102 'branch': 'branch',
102 'branch': 'branch',
103 'book': 'bookmark',
103 'book': 'bookmark',
104 'tag': 'tag',
104 'tag': 'tag',
105 'rev': 'id',
105 'rev': 'id',
106 }
106 }
107
107
108 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
108 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
109 safe_str(org_ref[1]))
109 safe_str(org_ref[1]))
110 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
110 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
111 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
111 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
112
112
113 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
113 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
114 safe_str(other_ref[1]))
114 safe_str(other_ref[1]))
115 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
115 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
116 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
116 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
117
117
118 #case two independent repos
118 #case two independent repos
119 if org_repo != other_repo:
119 if org_repo != other_repo:
120 hgrepo = unionrepo.unionrepository(other_repo.baseui,
120 hgrepo = unionrepo.unionrepository(other_repo.baseui,
121 other_repo.path,
121 other_repo.path,
122 org_repo.path)
122 org_repo.path)
123 # all the changesets we are looking for will be in other_repo,
123 # all the changesets we are looking for will be in other_repo,
124 # so rev numbers from hgrepo can be used in other_repo
124 # so rev numbers from hgrepo can be used in other_repo
125
125
126 #no remote compare do it on the same repository
126 #no remote compare do it on the same repository
127 else:
127 else:
128 hgrepo = other_repo._repo
128 hgrepo = other_repo._repo
129
129
130 if merge:
130 if merge:
131 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
131 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
132 (other_rev, org_rev, org_rev)]
132 (other_rev, org_rev, org_rev)]
133
133
134 ancestors = scmutil.revrange(hgrepo,
134 ancestors = scmutil.revrange(hgrepo,
135 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
135 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
136 if ancestors:
136 if ancestors:
137 # pick arbitrary ancestor - but there is usually only one
137 # pick arbitrary ancestor - but there is usually only one
138 ancestor = hgrepo[ancestors[0]].hex()
138 ancestor = hgrepo[ancestors[0]].hex()
139 else:
139 else:
140 # TODO: have both + and - changesets
140 # TODO: have both + and - changesets
141 revs = ["id('%s') :: id('%s') - id('%s')" %
141 revs = ["id('%s') :: id('%s') - id('%s')" %
142 (org_rev, other_rev, org_rev)]
142 (org_rev, other_rev, org_rev)]
143
143
144 changesets = [other_repo.get_changeset(cs)
144 changesets = [other_repo.get_changeset(cs)
145 for cs in scmutil.revrange(hgrepo, revs)]
145 for cs in scmutil.revrange(hgrepo, revs)]
146
146
147 elif alias == 'git':
147 elif alias == 'git':
148 assert org_repo == other_repo, ('no support for compare for two '
148 if org_repo != other_repo:
149 'different repositories in git')
149 raise Exception('Comparing of different GIT repositories is not'
150 'allowed. Got %s != %s' % (org_repo, other_repo))
151
150 so, se = org_repo.run_git_command(
152 so, se = org_repo.run_git_command(
151 'log --reverse --pretty="format: %%H" -s -p %s..%s'
153 'log --reverse --pretty="format: %%H" -s -p %s..%s'
152 % (org_ref[1], other_ref[1])
154 % (org_ref[1], other_ref[1])
153 )
155 )
154 changesets = [org_repo.get_changeset(cs)
156 changesets = [org_repo.get_changeset(cs)
155 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
157 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
156
158
157 return changesets, ancestor
159 return changesets, ancestor
158
160
159 @LoginRequired()
161 @LoginRequired()
160 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
162 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
161 'repository.admin')
163 'repository.admin')
162 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
164 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
163 # org_ref will be evaluated in org_repo
165 # org_ref will be evaluated in org_repo
164 org_repo = c.rhodecode_db_repo.repo_name
166 org_repo = c.rhodecode_db_repo.repo_name
165 org_ref = (org_ref_type, org_ref)
167 org_ref = (org_ref_type, org_ref)
166 # other_ref will be evaluated in other_repo
168 # other_ref will be evaluated in other_repo
167 other_ref = (other_ref_type, other_ref)
169 other_ref = (other_ref_type, other_ref)
168 other_repo = request.GET.get('other_repo', org_repo)
170 other_repo = request.GET.get('other_repo', org_repo)
169 # If merge is True:
171 # If merge is True:
170 # Show what org would get if merged with other:
172 # Show what org would get if merged with other:
171 # List changesets that are ancestors of other but not of org.
173 # List changesets that are ancestors of other but not of org.
172 # New changesets in org is thus ignored.
174 # New changesets in org is thus ignored.
173 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
175 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
174 # If merge is False:
176 # If merge is False:
175 # Make a raw diff from org to other, no matter if related or not.
177 # Make a raw diff from org to other, no matter if related or not.
176 # Changesets in one and not in the other will be ignored
178 # Changesets in one and not in the other will be ignored
177 merge = bool(request.GET.get('merge'))
179 merge = bool(request.GET.get('merge'))
178 # fulldiff disables cut_off_limit
180 # fulldiff disables cut_off_limit
179 c.fulldiff = request.GET.get('fulldiff')
181 c.fulldiff = request.GET.get('fulldiff')
180 # partial uses compare_cs.html template directly
182 # partial uses compare_cs.html template directly
181 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
183 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
182 # as_form puts hidden input field with changeset revisions
184 # as_form puts hidden input field with changeset revisions
183 c.as_form = partial and request.GET.get('as_form')
185 c.as_form = partial and request.GET.get('as_form')
184 # swap url for compare_diff page - never partial and never as_form
186 # swap url for compare_diff page - never partial and never as_form
185 c.swap_url = h.url('compare_url',
187 c.swap_url = h.url('compare_url',
186 repo_name=other_repo,
188 repo_name=other_repo,
187 org_ref_type=other_ref[0], org_ref=other_ref[1],
189 org_ref_type=other_ref[0], org_ref=other_ref[1],
188 other_repo=org_repo,
190 other_repo=org_repo,
189 other_ref_type=org_ref[0], other_ref=org_ref[1],
191 other_ref_type=org_ref[0], other_ref=org_ref[1],
190 merge=merge or '')
192 merge=merge or '')
191
193
192 org_repo = Repository.get_by_repo_name(org_repo)
194 org_repo = Repository.get_by_repo_name(org_repo)
193 other_repo = Repository.get_by_repo_name(other_repo)
195 other_repo = Repository.get_by_repo_name(other_repo)
194
196
195 if org_repo is None:
197 if org_repo is None:
196 log.error('Could not find org repo %s' % org_repo)
198 log.error('Could not find org repo %s' % org_repo)
197 raise HTTPNotFound
199 raise HTTPNotFound
198 if other_repo is None:
200 if other_repo is None:
199 log.error('Could not find other repo %s' % other_repo)
201 log.error('Could not find other repo %s' % other_repo)
200 raise HTTPNotFound
202 raise HTTPNotFound
201
203
202 if org_repo != other_repo and h.is_git(org_repo):
204 if org_repo != other_repo and h.is_git(org_repo):
203 log.error('compare of two remote repos not available for GIT REPOS')
205 log.error('compare of two remote repos not available for GIT REPOS')
204 raise HTTPNotFound
206 raise HTTPNotFound
205
207
206 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
208 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
207 log.error('compare of two different kind of remote repos not available')
209 log.error('compare of two different kind of remote repos not available')
208 raise HTTPNotFound
210 raise HTTPNotFound
209
211
210 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
212 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
211 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
213 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
212
214
213 c.org_repo = org_repo
215 c.org_repo = org_repo
214 c.other_repo = other_repo
216 c.other_repo = other_repo
215 c.org_ref = org_ref[1]
217 c.org_ref = org_ref[1]
216 c.other_ref = other_ref[1]
218 c.other_ref = other_ref[1]
217 c.org_ref_type = org_ref[0]
219 c.org_ref_type = org_ref[0]
218 c.other_ref_type = other_ref[0]
220 c.other_ref_type = other_ref[0]
219
221
220 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
222 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
221 org_repo.scm_instance, org_ref,
223 org_repo.scm_instance, org_ref,
222 other_repo.scm_instance, other_ref,
224 other_repo.scm_instance, other_ref,
223 merge)
225 merge)
224
226
225 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
227 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
226 c.cs_ranges])
228 c.cs_ranges])
227 if not c.ancestor:
229 if not c.ancestor:
228 log.warning('Unable to find ancestor revision')
230 log.warning('Unable to find ancestor revision')
229
231
230 if partial:
232 if partial:
231 return render('compare/compare_cs.html')
233 return render('compare/compare_cs.html')
232
234
233 if c.ancestor:
235 if c.ancestor:
234 assert merge
236 assert merge
235 # case we want a simple diff without incoming changesets,
237 # case we want a simple diff without incoming changesets,
236 # previewing what will be merged.
238 # previewing what will be merged.
237 # Make the diff on the other repo (which is known to have other_ref)
239 # Make the diff on the other repo (which is known to have other_ref)
238 log.debug('Using ancestor %s as org_ref instead of %s'
240 log.debug('Using ancestor %s as org_ref instead of %s'
239 % (c.ancestor, org_ref))
241 % (c.ancestor, org_ref))
240 org_ref = ('rev', c.ancestor)
242 org_ref = ('rev', c.ancestor)
241 org_repo = other_repo
243 org_repo = other_repo
242
244
243 diff_limit = self.cut_off_limit if not c.fulldiff else None
245 diff_limit = self.cut_off_limit if not c.fulldiff else None
244
246
245 log.debug('running diff between %s@%s and %s@%s'
247 log.debug('running diff between %s@%s and %s@%s'
246 % (org_repo.scm_instance.path, org_ref,
248 % (org_repo.scm_instance.path, org_ref,
247 other_repo.scm_instance.path, other_ref))
249 other_repo.scm_instance.path, other_ref))
248 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]),
250 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]),
249 rev2=safe_str(other_ref[1]))
251 rev2=safe_str(other_ref[1]))
250
252
251 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
253 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
252 diff_limit=diff_limit)
254 diff_limit=diff_limit)
253 _parsed = diff_processor.prepare()
255 _parsed = diff_processor.prepare()
254
256
255 c.limited_diff = False
257 c.limited_diff = False
256 if isinstance(_parsed, LimitedDiffContainer):
258 if isinstance(_parsed, LimitedDiffContainer):
257 c.limited_diff = True
259 c.limited_diff = True
258
260
259 c.files = []
261 c.files = []
260 c.changes = {}
262 c.changes = {}
261 c.lines_added = 0
263 c.lines_added = 0
262 c.lines_deleted = 0
264 c.lines_deleted = 0
263 for f in _parsed:
265 for f in _parsed:
264 st = f['stats']
266 st = f['stats']
265 if st[0] != 'b':
267 if st[0] != 'b':
266 c.lines_added += st[0]
268 c.lines_added += st[0]
267 c.lines_deleted += st[1]
269 c.lines_deleted += st[1]
268 fid = h.FID('', f['filename'])
270 fid = h.FID('', f['filename'])
269 c.files.append([fid, f['operation'], f['filename'], f['stats']])
271 c.files.append([fid, f['operation'], f['filename'], f['stats']])
270 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
272 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
271 c.changes[fid] = [f['operation'], f['filename'], diff]
273 c.changes[fid] = [f['operation'], f['filename'], diff]
272
274
273 return render('compare/compare_diff.html')
275 return render('compare/compare_diff.html')
General Comments 0
You need to be logged in to leave comments. Login now