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