##// END OF EJS Templates
compare: make __get_cs_or_redirect more exact
Mads Kiilerich -
r4038:ed50319a default
parent child Browse files
Show More
@@ -1,269 +1,283 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, unionrepo
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil, unionrepo
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
42 from rhodecode.lib import diffs
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, ref, 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 rev = ref[1] # default and used for git
69 if repo.scm_instance.alias == 'hg':
70 # lookup up the exact node id
71 _revset_predicates = {
72 'branch': 'branch',
73 'book': 'bookmark',
74 'tag': 'tag',
75 'rev': 'id',
76 }
77 rev_spec = "max(%s(%%s))" % _revset_predicates[ref[0]]
78 revs = repo.scm_instance._repo.revs(rev_spec, safe_str(ref[1]))
79 if revs:
80 rev = revs[-1]
81 # else: TODO: just report 'not found'
82
68 try:
83 try:
69 type_, rev = rev
70 return repo.scm_instance.get_changeset(rev)
84 return repo.scm_instance.get_changeset(rev)
71 except EmptyRepositoryError, e:
85 except EmptyRepositoryError, e:
72 if not redirect_after:
86 if not redirect_after:
73 return None
87 return None
74 h.flash(h.literal(_('There are no changesets yet')),
88 h.flash(h.literal(_('There are no changesets yet')),
75 category='warning')
89 category='warning')
76 redirect(url('summary_home', repo_name=repo.repo_name))
90 redirect(url('summary_home', repo_name=repo.repo_name))
77
91
78 except RepositoryError, e:
92 except RepositoryError, e:
79 log.error(traceback.format_exc())
93 log.error(traceback.format_exc())
80 h.flash(str(e), category='warning')
94 h.flash(str(e), category='warning')
81 if not partial:
95 if not partial:
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
96 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 raise HTTPBadRequest()
97 raise HTTPBadRequest()
84
98
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
99 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
86 """
100 """
87 Returns a list of changesets that can be merged from org_repo@org_ref
101 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
102 to other_repo@other_ref ... and the ancestor that would be used for merge
89
103
90 :param org_repo:
104 :param org_repo:
91 :param org_ref:
105 :param org_ref:
92 :param other_repo:
106 :param other_repo:
93 :param other_ref:
107 :param other_ref:
94 :param tmp:
108 :param tmp:
95 """
109 """
96
110
97 ancestor = None
111 ancestor = None
98
112
99 if alias == 'hg':
113 if alias == 'hg':
100 # lookup up the exact node id
114 # lookup up the exact node id
101 _revset_predicates = {
115 _revset_predicates = {
102 'branch': 'branch',
116 'branch': 'branch',
103 'book': 'bookmark',
117 'book': 'bookmark',
104 'tag': 'tag',
118 'tag': 'tag',
105 'rev': 'id',
119 'rev': 'id',
106 }
120 }
107
121
108 org_rev_spec = "max(%s(%%s))" % _revset_predicates[org_ref[0]]
122 org_rev_spec = "max(%s(%%s))" % _revset_predicates[org_ref[0]]
109 org_revs = org_repo._repo.revs(org_rev_spec, safe_str(org_ref[1]))
123 org_revs = org_repo._repo.revs(org_rev_spec, safe_str(org_ref[1]))
110 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
124 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
111
125
112 other_revs_spec = "max(%s(%%s))" % _revset_predicates[other_ref[0]]
126 other_revs_spec = "max(%s(%%s))" % _revset_predicates[other_ref[0]]
113 other_revs = other_repo._repo.revs(other_revs_spec, safe_str(other_ref[1]))
127 other_revs = other_repo._repo.revs(other_revs_spec, safe_str(other_ref[1]))
114 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
128 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
115
129
116 #case two independent repos
130 #case two independent repos
117 if org_repo != other_repo:
131 if org_repo != other_repo:
118 hgrepo = unionrepo.unionrepository(other_repo.baseui,
132 hgrepo = unionrepo.unionrepository(other_repo.baseui,
119 other_repo.path,
133 other_repo.path,
120 org_repo.path)
134 org_repo.path)
121 # all the changesets we are looking for will be in other_repo,
135 # all the changesets we are looking for will be in other_repo,
122 # so rev numbers from hgrepo can be used in other_repo
136 # so rev numbers from hgrepo can be used in other_repo
123
137
124 #no remote compare do it on the same repository
138 #no remote compare do it on the same repository
125 else:
139 else:
126 hgrepo = other_repo._repo
140 hgrepo = other_repo._repo
127
141
128 if merge:
142 if merge:
129 revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
143 revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
130 other_rev, org_rev, org_rev)
144 other_rev, org_rev, org_rev)
131
145
132 ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
146 ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
133 if ancestors:
147 if ancestors:
134 # pick arbitrary ancestor - but there is usually only one
148 # pick arbitrary ancestor - but there is usually only one
135 ancestor = hgrepo[ancestors[0]].hex()
149 ancestor = hgrepo[ancestors[0]].hex()
136 else:
150 else:
137 # TODO: have both + and - changesets
151 # TODO: have both + and - changesets
138 revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)",
152 revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)",
139 org_rev, other_rev, org_rev)
153 org_rev, other_rev, org_rev)
140
154
141 changesets = [other_repo.get_changeset(rev) for rev in revs]
155 changesets = [other_repo.get_changeset(rev) for rev in revs]
142
156
143 elif alias == 'git':
157 elif alias == 'git':
144 if org_repo != other_repo:
158 if org_repo != other_repo:
145 raise Exception('Comparing of different GIT repositories is not'
159 raise Exception('Comparing of different GIT repositories is not'
146 'allowed. Got %s != %s' % (org_repo, other_repo))
160 'allowed. Got %s != %s' % (org_repo, other_repo))
147
161
148 so, se = org_repo.run_git_command(
162 so, se = org_repo.run_git_command(
149 'log --reverse --pretty="format: %%H" -s -p %s..%s'
163 'log --reverse --pretty="format: %%H" -s -p %s..%s'
150 % (org_ref[1], other_ref[1])
164 % (org_ref[1], other_ref[1])
151 )
165 )
152 changesets = [org_repo.get_changeset(cs)
166 changesets = [org_repo.get_changeset(cs)
153 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
167 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
154
168
155 return changesets, ancestor
169 return changesets, ancestor
156
170
157 @LoginRequired()
171 @LoginRequired()
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
172 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
159 'repository.admin')
173 'repository.admin')
160 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
174 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
161 # org_ref will be evaluated in org_repo
175 # org_ref will be evaluated in org_repo
162 org_repo = c.rhodecode_db_repo.repo_name
176 org_repo = c.rhodecode_db_repo.repo_name
163 org_ref = (org_ref_type, org_ref)
177 org_ref = (org_ref_type, org_ref)
164 # other_ref will be evaluated in other_repo
178 # other_ref will be evaluated in other_repo
165 other_ref = (other_ref_type, other_ref)
179 other_ref = (other_ref_type, other_ref)
166 other_repo = request.GET.get('other_repo', org_repo)
180 other_repo = request.GET.get('other_repo', org_repo)
167 # If merge is True:
181 # If merge is True:
168 # Show what org would get if merged with other:
182 # Show what org would get if merged with other:
169 # List changesets that are ancestors of other but not of org.
183 # List changesets that are ancestors of other but not of org.
170 # New changesets in org is thus ignored.
184 # New changesets in org is thus ignored.
171 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
185 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
172 # If merge is False:
186 # If merge is False:
173 # Make a raw diff from org to other, no matter if related or not.
187 # Make a raw diff from org to other, no matter if related or not.
174 # Changesets in one and not in the other will be ignored
188 # Changesets in one and not in the other will be ignored
175 merge = bool(request.GET.get('merge'))
189 merge = bool(request.GET.get('merge'))
176 # fulldiff disables cut_off_limit
190 # fulldiff disables cut_off_limit
177 c.fulldiff = request.GET.get('fulldiff')
191 c.fulldiff = request.GET.get('fulldiff')
178 # partial uses compare_cs.html template directly
192 # partial uses compare_cs.html template directly
179 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
193 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
180 # as_form puts hidden input field with changeset revisions
194 # as_form puts hidden input field with changeset revisions
181 c.as_form = partial and request.GET.get('as_form')
195 c.as_form = partial and request.GET.get('as_form')
182 # swap url for compare_diff page - never partial and never as_form
196 # swap url for compare_diff page - never partial and never as_form
183 c.swap_url = h.url('compare_url',
197 c.swap_url = h.url('compare_url',
184 repo_name=other_repo,
198 repo_name=other_repo,
185 org_ref_type=other_ref[0], org_ref=other_ref[1],
199 org_ref_type=other_ref[0], org_ref=other_ref[1],
186 other_repo=org_repo,
200 other_repo=org_repo,
187 other_ref_type=org_ref[0], other_ref=org_ref[1],
201 other_ref_type=org_ref[0], other_ref=org_ref[1],
188 merge=merge or '')
202 merge=merge or '')
189
203
190 org_repo = Repository.get_by_repo_name(org_repo)
204 org_repo = Repository.get_by_repo_name(org_repo)
191 other_repo = Repository.get_by_repo_name(other_repo)
205 other_repo = Repository.get_by_repo_name(other_repo)
192
206
193 if org_repo is None:
207 if org_repo is None:
194 log.error('Could not find org repo %s' % org_repo)
208 log.error('Could not find org repo %s' % org_repo)
195 raise HTTPNotFound
209 raise HTTPNotFound
196 if other_repo is None:
210 if other_repo is None:
197 log.error('Could not find other repo %s' % other_repo)
211 log.error('Could not find other repo %s' % other_repo)
198 raise HTTPNotFound
212 raise HTTPNotFound
199
213
200 if org_repo != other_repo and h.is_git(org_repo):
214 if org_repo != other_repo and h.is_git(org_repo):
201 log.error('compare of two remote repos not available for GIT REPOS')
215 log.error('compare of two remote repos not available for GIT REPOS')
202 raise HTTPNotFound
216 raise HTTPNotFound
203
217
204 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
218 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
205 log.error('compare of two different kind of remote repos not available')
219 log.error('compare of two different kind of remote repos not available')
206 raise HTTPNotFound
220 raise HTTPNotFound
207
221
208 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
222 self.__get_cs_or_redirect(ref=org_ref, repo=org_repo, partial=partial)
209 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
223 self.__get_cs_or_redirect(ref=other_ref, repo=other_repo, partial=partial)
210
224
211 c.org_repo = org_repo
225 c.org_repo = org_repo
212 c.other_repo = other_repo
226 c.other_repo = other_repo
213 c.org_ref = org_ref[1]
227 c.org_ref = org_ref[1]
214 c.other_ref = other_ref[1]
228 c.other_ref = other_ref[1]
215 c.org_ref_type = org_ref[0]
229 c.org_ref_type = org_ref[0]
216 c.other_ref_type = other_ref[0]
230 c.other_ref_type = other_ref[0]
217
231
218 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
232 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
219 org_repo.scm_instance, org_ref,
233 org_repo.scm_instance, org_ref,
220 other_repo.scm_instance, other_ref,
234 other_repo.scm_instance, other_ref,
221 merge)
235 merge)
222
236
223 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
237 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
224 c.cs_ranges])
238 c.cs_ranges])
225 if merge and not c.ancestor:
239 if merge and not c.ancestor:
226 log.error('Unable to find ancestor revision')
240 log.error('Unable to find ancestor revision')
227
241
228 if partial:
242 if partial:
229 return render('compare/compare_cs.html')
243 return render('compare/compare_cs.html')
230
244
231 if c.ancestor:
245 if c.ancestor:
232 assert merge
246 assert merge
233 # case we want a simple diff without incoming changesets,
247 # case we want a simple diff without incoming changesets,
234 # previewing what will be merged.
248 # previewing what will be merged.
235 # Make the diff on the other repo (which is known to have other_ref)
249 # Make the diff on the other repo (which is known to have other_ref)
236 log.debug('Using ancestor %s as org_ref instead of %s'
250 log.debug('Using ancestor %s as org_ref instead of %s'
237 % (c.ancestor, org_ref))
251 % (c.ancestor, org_ref))
238 org_ref = ('rev', c.ancestor)
252 org_ref = ('rev', c.ancestor)
239 org_repo = other_repo
253 org_repo = other_repo
240
254
241 diff_limit = self.cut_off_limit if not c.fulldiff else None
255 diff_limit = self.cut_off_limit if not c.fulldiff else None
242
256
243 log.debug('running diff between %s and %s in %s'
257 log.debug('running diff between %s and %s in %s'
244 % (org_ref, other_ref, org_repo.scm_instance.path))
258 % (org_ref, other_ref, org_repo.scm_instance.path))
245 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
259 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
246
260
247 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
261 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
248 diff_limit=diff_limit)
262 diff_limit=diff_limit)
249 _parsed = diff_processor.prepare()
263 _parsed = diff_processor.prepare()
250
264
251 c.limited_diff = False
265 c.limited_diff = False
252 if isinstance(_parsed, LimitedDiffContainer):
266 if isinstance(_parsed, LimitedDiffContainer):
253 c.limited_diff = True
267 c.limited_diff = True
254
268
255 c.files = []
269 c.files = []
256 c.changes = {}
270 c.changes = {}
257 c.lines_added = 0
271 c.lines_added = 0
258 c.lines_deleted = 0
272 c.lines_deleted = 0
259 for f in _parsed:
273 for f in _parsed:
260 st = f['stats']
274 st = f['stats']
261 if not st['binary']:
275 if not st['binary']:
262 c.lines_added += st['added']
276 c.lines_added += st['added']
263 c.lines_deleted += st['deleted']
277 c.lines_deleted += st['deleted']
264 fid = h.FID('', f['filename'])
278 fid = h.FID('', f['filename'])
265 c.files.append([fid, f['operation'], f['filename'], f['stats']])
279 c.files.append([fid, f['operation'], f['filename'], f['stats']])
266 htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
280 htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
267 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
281 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
268
282
269 return render('compare/compare_diff.html')
283 return render('compare/compare_diff.html')
General Comments 0
You need to be logged in to leave comments. Login now