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