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