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