##// END OF EJS Templates
removed assertion about ancestor revision....
marcink -
r3771:d23f2bea beta
parent child Browse files
Show More
@@ -1,269 +1,273 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, (org_repo, other_repo) # no git support for different repos
148 assert org_repo == other_repo, ('no support for compare for two '
149 'different repositories in git')
149 so, se = org_repo.run_git_command(
150 so, se = org_repo.run_git_command(
150 'log --reverse --pretty="format: %%H" -s -p %s..%s' % (org_ref[1],
151 'log --reverse --pretty="format: %%H" -s -p %s..%s'
151 other_ref[1])
152 % (org_ref[1], other_ref[1])
152 )
153 )
153 changesets = [org_repo.get_changeset(cs)
154 changesets = [org_repo.get_changeset(cs)
154 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
155 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
155
156
156 return changesets, ancestor
157 return changesets, ancestor
157
158
158 @LoginRequired()
159 @LoginRequired()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
161 'repository.admin')
161 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
162 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
162 # org_ref will be evaluated in org_repo
163 # org_ref will be evaluated in org_repo
163 org_repo = c.rhodecode_db_repo.repo_name
164 org_repo = c.rhodecode_db_repo.repo_name
164 org_ref = (org_ref_type, org_ref)
165 org_ref = (org_ref_type, org_ref)
165 # other_ref will be evaluated in other_repo
166 # other_ref will be evaluated in other_repo
166 other_ref = (other_ref_type, other_ref)
167 other_ref = (other_ref_type, other_ref)
167 other_repo = request.GET.get('other_repo', org_repo)
168 other_repo = request.GET.get('other_repo', org_repo)
168 # If merge is True:
169 # If merge is True:
169 # Show what org would get if merged with other:
170 # Show what org would get if merged with other:
170 # List changesets that are ancestors of other but not of org.
171 # List changesets that are ancestors of other but not of org.
171 # New changesets in org is thus ignored.
172 # New changesets in org is thus ignored.
172 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
173 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
173 # If merge is False:
174 # If merge is False:
174 # Make a raw diff from org to other, no matter if related or not.
175 # Make a raw diff from org to other, no matter if related or not.
175 # Changesets in one and not in the other will be ignored
176 # Changesets in one and not in the other will be ignored
176 merge = bool(request.GET.get('merge'))
177 merge = bool(request.GET.get('merge'))
177 # fulldiff disables cut_off_limit
178 # fulldiff disables cut_off_limit
178 c.fulldiff = request.GET.get('fulldiff')
179 c.fulldiff = request.GET.get('fulldiff')
179 # partial uses compare_cs.html template directly
180 # partial uses compare_cs.html template directly
180 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
181 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
181 # as_form puts hidden input field with changeset revisions
182 # as_form puts hidden input field with changeset revisions
182 c.as_form = partial and request.GET.get('as_form')
183 c.as_form = partial and request.GET.get('as_form')
183 # swap url for compare_diff page - never partial and never as_form
184 # swap url for compare_diff page - never partial and never as_form
184 c.swap_url = h.url('compare_url',
185 c.swap_url = h.url('compare_url',
185 repo_name=other_repo,
186 repo_name=other_repo,
186 org_ref_type=other_ref[0], org_ref=other_ref[1],
187 org_ref_type=other_ref[0], org_ref=other_ref[1],
187 other_repo=org_repo,
188 other_repo=org_repo,
188 other_ref_type=org_ref[0], other_ref=org_ref[1],
189 other_ref_type=org_ref[0], other_ref=org_ref[1],
189 merge=merge or '')
190 merge=merge or '')
190
191
191 org_repo = Repository.get_by_repo_name(org_repo)
192 org_repo = Repository.get_by_repo_name(org_repo)
192 other_repo = Repository.get_by_repo_name(other_repo)
193 other_repo = Repository.get_by_repo_name(other_repo)
193
194
194 if org_repo is None:
195 if org_repo is None:
195 log.error('Could not find org repo %s' % org_repo)
196 log.error('Could not find org repo %s' % org_repo)
196 raise HTTPNotFound
197 raise HTTPNotFound
197 if other_repo is None:
198 if other_repo is None:
198 log.error('Could not find other repo %s' % other_repo)
199 log.error('Could not find other repo %s' % other_repo)
199 raise HTTPNotFound
200 raise HTTPNotFound
200
201
201 if org_repo != other_repo and h.is_git(org_repo):
202 if org_repo != other_repo and h.is_git(org_repo):
202 log.error('compare of two remote repos not available for GIT REPOS')
203 log.error('compare of two remote repos not available for GIT REPOS')
203 raise HTTPNotFound
204 raise HTTPNotFound
204
205
205 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
206 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
206 log.error('compare of two different kind of remote repos not available')
207 log.error('compare of two different kind of remote repos not available')
207 raise HTTPNotFound
208 raise HTTPNotFound
208
209
209 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
210 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
210 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
211 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
211
212
212 c.org_repo = org_repo
213 c.org_repo = org_repo
213 c.other_repo = other_repo
214 c.other_repo = other_repo
214 c.org_ref = org_ref[1]
215 c.org_ref = org_ref[1]
215 c.other_ref = other_ref[1]
216 c.other_ref = other_ref[1]
216 c.org_ref_type = org_ref[0]
217 c.org_ref_type = org_ref[0]
217 c.other_ref_type = other_ref[0]
218 c.other_ref_type = other_ref[0]
218
219
219 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
220 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
220 org_repo.scm_instance, org_ref,
221 org_repo.scm_instance, org_ref,
221 other_repo.scm_instance, other_ref,
222 other_repo.scm_instance, other_ref,
222 merge)
223 merge)
223
224
224 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
225 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
225 c.cs_ranges])
226 c.cs_ranges])
227 if not c.ancestor:
228 log.warning('Unable to find ancestor revision')
229
226 if partial:
230 if partial:
227 assert c.ancestor
228 return render('compare/compare_cs.html')
231 return render('compare/compare_cs.html')
229
232
230 if c.ancestor:
233 if c.ancestor:
231 assert merge
234 assert merge
232 # case we want a simple diff without incoming changesets,
235 # case we want a simple diff without incoming changesets,
233 # previewing what will be merged.
236 # previewing what will be merged.
234 # Make the diff on the other repo (which is known to have other_ref)
237 # Make the diff on the other repo (which is known to have other_ref)
235 log.debug('Using ancestor %s as org_ref instead of %s'
238 log.debug('Using ancestor %s as org_ref instead of %s'
236 % (c.ancestor, org_ref))
239 % (c.ancestor, org_ref))
237 org_ref = ('rev', c.ancestor)
240 org_ref = ('rev', c.ancestor)
238 org_repo = other_repo
241 org_repo = other_repo
239
242
240 diff_limit = self.cut_off_limit if not c.fulldiff else None
243 diff_limit = self.cut_off_limit if not c.fulldiff else None
241
244
242 log.debug('running diff between %s@%s and %s@%s'
245 log.debug('running diff between %s@%s and %s@%s'
243 % (org_repo.scm_instance.path, org_ref,
246 % (org_repo.scm_instance.path, org_ref,
244 other_repo.scm_instance.path, other_ref))
247 other_repo.scm_instance.path, other_ref))
245 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]), rev2=safe_str(other_ref[1]))
248 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]),
249 rev2=safe_str(other_ref[1]))
246
250
247 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
251 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
248 diff_limit=diff_limit)
252 diff_limit=diff_limit)
249 _parsed = diff_processor.prepare()
253 _parsed = diff_processor.prepare()
250
254
251 c.limited_diff = False
255 c.limited_diff = False
252 if isinstance(_parsed, LimitedDiffContainer):
256 if isinstance(_parsed, LimitedDiffContainer):
253 c.limited_diff = True
257 c.limited_diff = True
254
258
255 c.files = []
259 c.files = []
256 c.changes = {}
260 c.changes = {}
257 c.lines_added = 0
261 c.lines_added = 0
258 c.lines_deleted = 0
262 c.lines_deleted = 0
259 for f in _parsed:
263 for f in _parsed:
260 st = f['stats']
264 st = f['stats']
261 if st[0] != 'b':
265 if st[0] != 'b':
262 c.lines_added += st[0]
266 c.lines_added += st[0]
263 c.lines_deleted += st[1]
267 c.lines_deleted += st[1]
264 fid = h.FID('', f['filename'])
268 fid = h.FID('', f['filename'])
265 c.files.append([fid, f['operation'], f['filename'], f['stats']])
269 c.files.append([fid, f['operation'], f['filename'], f['stats']])
266 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
270 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
267 c.changes[fid] = [f['operation'], f['filename'], diff]
271 c.changes[fid] = [f['operation'], f['filename'], diff]
268
272
269 return render('compare/compare_diff.html')
273 return render('compare/compare_diff.html')
General Comments 0
You need to be logged in to leave comments. Login now