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